#if XRPL_ROCKSDB_AVAILABLE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl::NodeStore { class RocksDBEnv : public rocksdb::EnvWrapper { public: RocksDBEnv() : EnvWrapper(rocksdb::Env::Default()) { } struct ThreadParams { ThreadParams(void (*f)(void*), void* a) : f(f), a(a) { } void (*f)(void*); void* a; }; static void threadEntry(void* ptr) { ThreadParams const* const p(reinterpret_cast(ptr)); auto const f = p->f; void* a(p->a); delete p; static std::atomic kN; std::size_t const id(++kN); beast::setCurrentThreadName("rocksdb #" + std::to_string(id)); f(a); } void StartThread(void (*f)(void*), void* a) override { auto* const p = new ThreadParams(f, a); EnvWrapper::StartThread(&RocksDBEnv::threadEntry, p); } }; //------------------------------------------------------------------------------ class RocksDBBackend : public Backend, public BatchWriter::Callback { private: std::atomic deletePath_; public: beast::Journal journal; size_t const keyBytes; BatchWriter batch; std::string name; std::unique_ptr db; int fdMinRequired = 2048; rocksdb::Options options; RocksDBBackend( int keyBytes, Section const& keyValues, Scheduler& scheduler, beast::Journal journal, RocksDBEnv* env) : deletePath_(false), journal(journal), keyBytes(keyBytes), batch(*this, scheduler) { if (!getIfExists(keyValues, Keys::kPath, name)) Throw("Missing path in RocksDBFactory backend"); rocksdb::BlockBasedTableOptions tableOptions; options.env = env; bool const hardSet = keyValues.exists(Keys::kHardSet) && get(keyValues, Keys::kHardSet); if (keyValues.exists(Keys::kCacheMb)) { auto size = get(keyValues, Keys::kCacheMb); if (!hardSet && size == 256) size = 1024; tableOptions.block_cache = rocksdb::NewLRUCache(megabytes(size)); } if (auto const v = get(keyValues, Keys::kFilterBits)) { bool const filterBlocks = !keyValues.exists(Keys::kFilterFull) || (get(keyValues, Keys::kFilterFull) == 0); tableOptions.filter_policy.reset(rocksdb::NewBloomFilterPolicy(v, filterBlocks)); } if (getIfExists(keyValues, Keys::kOpenFiles, options.max_open_files)) { if (!hardSet && options.max_open_files == 2000) options.max_open_files = 8000; fdMinRequired = options.max_open_files + 128; } if (keyValues.exists(Keys::kFileSizeMb)) { auto fileSizeMb = get(keyValues, Keys::kFileSizeMb); if (!hardSet && fileSizeMb == 8) fileSizeMb = 256; options.target_file_size_base = megabytes(fileSizeMb); options.max_bytes_for_level_base = 5 * options.target_file_size_base; options.write_buffer_size = 2 * options.target_file_size_base; } getIfExists(keyValues, Keys::kFileSizeMult, options.target_file_size_multiplier); if (keyValues.exists(Keys::kBgThreads)) { options.env->SetBackgroundThreads( get(keyValues, Keys::kBgThreads), rocksdb::Env::LOW); } if (keyValues.exists(Keys::kHighThreads)) { auto const highThreads = get(keyValues, Keys::kHighThreads); options.env->SetBackgroundThreads(highThreads, rocksdb::Env::HIGH); // If we have high-priority threads, presumably we want to // use them for background flushes if (highThreads > 0) options.max_background_flushes = highThreads; } options.compression = rocksdb::kSnappyCompression; getIfExists(keyValues, Keys::kBlockSize, tableOptions.block_size); if (keyValues.exists(Keys::kUniversalCompaction) && (get(keyValues, Keys::kUniversalCompaction) != 0)) { options.compaction_style = rocksdb::kCompactionStyleUniversal; options.min_write_buffer_number_to_merge = 2; options.max_write_buffer_number = 6; options.write_buffer_size = 6 * options.target_file_size_base; } if (keyValues.exists(Keys::kBbtOptions)) { rocksdb::ConfigOptions const configOptions; auto const s = rocksdb::GetBlockBasedTableOptionsFromString( configOptions, tableOptions, get(keyValues, Keys::kBbtOptions), &tableOptions); if (!s.ok()) { Throw( std::string("Unable to set RocksDB bbt_options: ") + s.ToString()); } } options.table_factory.reset(NewBlockBasedTableFactory(tableOptions)); if (keyValues.exists(Keys::kOptions)) { auto const s = rocksdb::GetOptionsFromString(options, get(keyValues, Keys::kOptions), &options); if (!s.ok()) { Throw( std::string("Unable to set RocksDB options: ") + s.ToString()); } } std::string s1, s2; rocksdb::GetStringFromDBOptions(&s1, options, "; "); rocksdb::GetStringFromColumnFamilyOptions(&s2, options, "; "); JLOG(journal.debug()) << "RocksDB DBOptions: " << s1; JLOG(journal.debug()) << "RocksDB CFOptions: " << s2; } ~RocksDBBackend() override { close(); } void open(bool createIfMissing) override { if (db) { // LCOV_EXCL_START UNREACHABLE( "xrpl::NodeStore::RocksDBBackend::open : database is already " "open"); JLOG(journal.error()) << "database is already open"; return; // LCOV_EXCL_STOP } rocksdb::DB* localDb = nullptr; options.create_if_missing = createIfMissing; rocksdb::Status const status = rocksdb::DB::Open(options, name, &localDb); if (!status.ok() || (localDb == nullptr)) { Throw( std::string("Unable to open/create RocksDB: ") + status.ToString()); } db.reset(localDb); } bool isOpen() override { return static_cast(db); } void close() override { if (db) { db.reset(); if (deletePath_) { boost::filesystem::path const dir = name; boost::filesystem::remove_all(dir); } } } std::string getName() override { return name; } //-------------------------------------------------------------------------- Status fetch(uint256 const& hash, std::shared_ptr* pObject) override { XRPL_ASSERT(db, "xrpl::NodeStore::RocksDBBackend::fetch : non-null database"); pObject->reset(); Status status = Status::Ok; rocksdb::ReadOptions const options; rocksdb::Slice const slice(reinterpret_cast(hash.data()), keyBytes); std::string string; rocksdb::Status const getStatus = db->Get(options, slice, &string); if (getStatus.ok()) { DecodedBlob decoded(hash.data(), string.data(), string.size()); if (decoded.wasOk()) { *pObject = decoded.createObject(); } else { // Decoding failed, probably corrupted! // status = Status::DataCorrupt; } } else { if (getStatus.IsCorruption()) { status = Status::DataCorrupt; } else if (getStatus.IsNotFound()) { status = Status::NotFound; } else { status = static_cast( static_cast(Status::CustomCode) + unsafeCast(getStatus.code())); JLOG(journal.error()) << getStatus.ToString(); } } return status; } void store(std::shared_ptr const& object) override { batch.store(object); } void storeBatch(Batch const& batch) override { XRPL_ASSERT( db, "xrpl::NodeStore::RocksDBBackend::storeBatch : non-null " "database"); rocksdb::WriteBatch wb; for (auto const& e : batch) { EncodedBlob const encoded(e); wb.Put( rocksdb::Slice(reinterpret_cast(encoded.getKey()), keyBytes), rocksdb::Slice( reinterpret_cast(encoded.getData()), encoded.getSize())); } rocksdb::WriteOptions const options; auto ret = db->Write(options, &wb); if (!ret.ok()) Throw("storeBatch failed: " + ret.ToString()); } void sync() override { } void forEach(std::function)> f) override { XRPL_ASSERT(db, "xrpl::NodeStore::RocksDBBackend::forEach : non-null database"); rocksdb::ReadOptions const options; std::unique_ptr it(db->NewIterator(options)); for (it->SeekToFirst(); it->Valid(); it->Next()) { if (it->key().size() == keyBytes) { DecodedBlob decoded(it->key().data(), it->value().data(), it->value().size()); if (decoded.wasOk()) { f(decoded.createObject()); } else { // Uh oh, corrupted data! JLOG(journal.fatal()) << "Corrupt NodeObject #" << it->key().ToString(true); } } else { // VFALCO NOTE What does it mean to find an // incorrectly sized key? Corruption? JLOG(journal.fatal()) << "Bad key size = " << it->key().size(); } } } int getWriteLoad() override { return batch.getWriteLoad(); } void setDeletePath() override { deletePath_ = true; } //-------------------------------------------------------------------------- void writeBatch(Batch const& batch) override { storeBatch(batch); } /** Returns the number of file descriptors the backend expects to need */ [[nodiscard]] int fdRequired() const override { return fdMinRequired; } }; //------------------------------------------------------------------------------ class RocksDBFactory : public Factory { private: Manager& manager_; public: RocksDBEnv env; RocksDBFactory(Manager& manager) : manager_(manager) { manager_.insert(*this); } [[nodiscard]] std::string getName() const override { return "RocksDB"; } std::unique_ptr createInstance( size_t keyBytes, Section const& keyValues, std::size_t, Scheduler& scheduler, beast::Journal journal) override { return std::make_unique(keyBytes, keyValues, scheduler, journal, &env); } }; void registerRocksDBFactory(Manager& manager) { static RocksDBFactory const kInstance{manager}; } } // namespace xrpl::NodeStore #endif