mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-12 13:42:10 +02:00
6d0ea4cf5bdoc: add release notes (Andrew Toth)a2b1c86903txospenderindex: disable bloom filters to optimize disk usage (Andrew Toth) Pull request description: LevelDB bloom filters are only consulted on `Get` point reads. This can be verified in https://github.com/bitcoin/bitcoin/blob/master/src/leveldb/table/table.cc#L224-L228. `InternalGet` is the only place that consults the filter, and it is only reached via a `Get` or `Exists` point read. The filters are never consulted for iterator seeks with an iterator created via `NewIterator`. txospenderindex only reads via iterator seeks, so building them is wasted effort and space. For a db as large as txospenderindex, this results in measurable performance and disk usage. On master, a full sync took 4h37m, and the resulting db was 85.0 GiB. On this branch, a full sync took 3h57m, and the resulting db was 80.9 GiB. So this is a sync speedup of 39 minutes (1.17x), and a disk space reduction of 4.2 GiB. ACKs for top commit: l0rinc: ACK6d0ea4cf5bsedited: Re-ACK6d0ea4cf5bfjahr: Code review ACK6d0ea4cf5bTree-SHA512: fb88b9f9a16ff31562d388e3fd9fd9590c7864dbe6093cd9430ecbce9cdc3f2a8d3fc612aade743d26ad4c6eca1e5dc9b3f1ca28d75caea1209e5c784895405d
295 lines
8.1 KiB
C++
295 lines
8.1 KiB
C++
// Copyright (c) 2012-present The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#ifndef BITCOIN_DBWRAPPER_H
|
|
#define BITCOIN_DBWRAPPER_H
|
|
|
|
#include <attributes.h>
|
|
#include <serialize.h>
|
|
#include <span.h>
|
|
#include <streams.h>
|
|
#include <util/byte_units.h>
|
|
#include <util/check.h>
|
|
#include <util/fs.h>
|
|
#include <util/obfuscation.h>
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <exception>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <span>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
namespace leveldb {
|
|
class Env;
|
|
} // namespace leveldb
|
|
|
|
static const size_t DBWRAPPER_PREALLOC_KEY_SIZE = 64;
|
|
static const size_t DBWRAPPER_PREALLOC_VALUE_SIZE = 1024;
|
|
static const size_t DBWRAPPER_MAX_FILE_SIZE{32_MiB};
|
|
|
|
//! User-controlled performance and debug options.
|
|
struct DBOptions {
|
|
//! Compact database on startup.
|
|
bool force_compact = false;
|
|
};
|
|
|
|
//! Application-specific storage settings.
|
|
struct DBParams {
|
|
//! Location in the filesystem where leveldb data will be stored.
|
|
fs::path path;
|
|
//! Configures various leveldb cache settings.
|
|
uint64_t cache_bytes;
|
|
//! If true, use leveldb's memory environment.
|
|
bool memory_only = false;
|
|
//! If true, remove all existing data.
|
|
bool wipe_data = false;
|
|
//! If true, store data obfuscated via simple XOR. If false, XOR with a
|
|
//! zero'd byte array.
|
|
bool obfuscate = false;
|
|
//! If true, build a LevelDB bloom filter to accelerate point lookups.
|
|
bool bloom_filter = true;
|
|
//! Passed-through options.
|
|
DBOptions options{};
|
|
//! If non-null, use this as the leveldb::Env instead of the default.
|
|
//! Caller retains ownership.
|
|
leveldb::Env* testing_env = nullptr;
|
|
//! Maximum LevelDB SST file size. Larger values reduce the frequency
|
|
//! of compactions but increase their duration.
|
|
size_t max_file_size = DBWRAPPER_MAX_FILE_SIZE;
|
|
};
|
|
|
|
class dbwrapper_error : public std::runtime_error
|
|
{
|
|
public:
|
|
explicit dbwrapper_error(const std::string& msg) : std::runtime_error(msg) {}
|
|
};
|
|
|
|
class CDBWrapper;
|
|
|
|
/** These should be considered an implementation detail of the specific database.
|
|
*/
|
|
namespace dbwrapper_private {
|
|
|
|
/** Work around circular dependency, as well as for testing in dbwrapper_tests.
|
|
* Database obfuscation should be considered an implementation detail of the
|
|
* specific database.
|
|
*/
|
|
const Obfuscation& GetObfuscation(const CDBWrapper&);
|
|
}; // namespace dbwrapper_private
|
|
|
|
bool DestroyDB(const std::string& path_str);
|
|
|
|
/** Batch of changes queued to be written to a CDBWrapper */
|
|
class CDBBatch
|
|
{
|
|
friend class CDBWrapper;
|
|
|
|
private:
|
|
const CDBWrapper &parent;
|
|
|
|
struct WriteBatchImpl;
|
|
const std::unique_ptr<WriteBatchImpl> m_impl_batch;
|
|
|
|
DataStream m_key_scratch{};
|
|
DataStream m_value_scratch{};
|
|
|
|
void WriteImpl(std::span<const std::byte> key, DataStream& value);
|
|
void EraseImpl(std::span<const std::byte> key);
|
|
|
|
public:
|
|
/**
|
|
* @param[in] _parent CDBWrapper that this batch is to be submitted to
|
|
*/
|
|
explicit CDBBatch(const CDBWrapper& _parent);
|
|
~CDBBatch();
|
|
void Clear();
|
|
|
|
template <typename K, typename V>
|
|
void Write(const K& key, const V& value)
|
|
{
|
|
ScopedDataStreamUsage scoped_key{m_key_scratch}, scoped_value{m_value_scratch};
|
|
m_key_scratch << key;
|
|
m_value_scratch << value;
|
|
WriteImpl(m_key_scratch, m_value_scratch);
|
|
}
|
|
|
|
template <typename K>
|
|
void Erase(const K& key)
|
|
{
|
|
ScopedDataStreamUsage scoped_key{m_key_scratch};
|
|
m_key_scratch << key;
|
|
EraseImpl(m_key_scratch);
|
|
}
|
|
|
|
size_t ApproximateSize() const;
|
|
};
|
|
|
|
class CDBIterator
|
|
{
|
|
public:
|
|
struct IteratorImpl;
|
|
|
|
private:
|
|
const CDBWrapper &parent;
|
|
const std::unique_ptr<IteratorImpl> m_impl_iter;
|
|
DataStream m_scratch{};
|
|
|
|
void SeekImpl(std::span<const std::byte> key);
|
|
std::span<const std::byte> GetKeyImpl() const;
|
|
std::span<const std::byte> GetValueImpl() const;
|
|
|
|
public:
|
|
|
|
/**
|
|
* @param[in] _parent Parent CDBWrapper instance.
|
|
* @param[in] _piter The original leveldb iterator.
|
|
*/
|
|
CDBIterator(const CDBWrapper& _parent, std::unique_ptr<IteratorImpl> _piter);
|
|
~CDBIterator();
|
|
|
|
bool Valid() const;
|
|
|
|
void SeekToFirst();
|
|
|
|
template<typename K> void Seek(const K& key) {
|
|
ScopedDataStreamUsage scoped_scratch{m_scratch};
|
|
m_scratch << key;
|
|
SeekImpl(m_scratch);
|
|
}
|
|
|
|
void Next();
|
|
|
|
template<typename K> bool GetKey(K& key) {
|
|
try {
|
|
SpanReader ssKey{GetKeyImpl()};
|
|
ssKey >> key;
|
|
} catch (const std::exception&) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template<typename V> bool GetValue(V& value) {
|
|
try {
|
|
ScopedDataStreamUsage scoped_scratch{m_scratch};
|
|
m_scratch.write(GetValueImpl());
|
|
dbwrapper_private::GetObfuscation(parent)(m_scratch);
|
|
m_scratch >> value;
|
|
} catch (const std::exception&) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
struct LevelDBContext;
|
|
|
|
class CDBWrapper
|
|
{
|
|
friend const Obfuscation& dbwrapper_private::GetObfuscation(const CDBWrapper&);
|
|
private:
|
|
//! holds all leveldb-specific fields of this class
|
|
std::unique_ptr<LevelDBContext> m_db_context;
|
|
|
|
//! the name of this database
|
|
std::string m_name;
|
|
|
|
//! optional XOR-obfuscation of the database
|
|
Obfuscation m_obfuscation;
|
|
|
|
//! obfuscation key storage key, null-prefixed to avoid collisions
|
|
inline static const std::string OBFUSCATION_KEY{"\000obfuscate_key", 14}; // explicit size to avoid truncation at leading \0
|
|
|
|
std::optional<std::string> ReadImpl(std::span<const std::byte> key) const;
|
|
bool ExistsImpl(std::span<const std::byte> key) const;
|
|
size_t EstimateSizeImpl(std::span<const std::byte> key1, std::span<const std::byte> key2) const;
|
|
auto& DBContext() const LIFETIMEBOUND { return *Assert(m_db_context); }
|
|
|
|
public:
|
|
CDBWrapper(const DBParams& params);
|
|
~CDBWrapper();
|
|
|
|
CDBWrapper(const CDBWrapper&) = delete;
|
|
CDBWrapper& operator=(const CDBWrapper&) = delete;
|
|
|
|
template <typename K, typename V>
|
|
bool Read(const K& key, V& value) const
|
|
{
|
|
DataStream ssKey{};
|
|
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
|
ssKey << key;
|
|
std::optional<std::string> strValue{ReadImpl(ssKey)};
|
|
if (!strValue) {
|
|
return false;
|
|
}
|
|
try {
|
|
std::span ssValue{MakeWritableByteSpan(*strValue)};
|
|
m_obfuscation(ssValue);
|
|
SpanReader{ssValue} >> value;
|
|
} catch (const std::exception&) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <typename K, typename V>
|
|
void Write(const K& key, const V& value, bool fSync = false)
|
|
{
|
|
CDBBatch batch(*this);
|
|
batch.Write(key, value);
|
|
WriteBatch(batch, fSync);
|
|
}
|
|
|
|
template <typename K>
|
|
bool Exists(const K& key) const
|
|
{
|
|
DataStream ssKey{};
|
|
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
|
ssKey << key;
|
|
return ExistsImpl(ssKey);
|
|
}
|
|
|
|
template <typename K>
|
|
void Erase(const K& key, bool fSync = false)
|
|
{
|
|
CDBBatch batch(*this);
|
|
batch.Erase(key);
|
|
WriteBatch(batch, fSync);
|
|
}
|
|
|
|
void WriteBatch(CDBBatch& batch, bool fSync = false);
|
|
|
|
//! Perform a blocking full compaction of the underlying LevelDB.
|
|
void CompactFull();
|
|
|
|
//! Return a LevelDB property value, if available.
|
|
std::optional<std::string> GetProperty(const std::string& property) const;
|
|
|
|
// Get an estimate of LevelDB memory usage (in bytes).
|
|
size_t DynamicMemoryUsage() const;
|
|
|
|
CDBIterator* NewIterator();
|
|
|
|
/**
|
|
* Return true if the database managed by this class contains no entries.
|
|
*/
|
|
bool IsEmpty();
|
|
|
|
template<typename K>
|
|
size_t EstimateSize(const K& key_begin, const K& key_end) const
|
|
{
|
|
DataStream ssKey1{}, ssKey2{};
|
|
ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
|
ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
|
ssKey1 << key_begin;
|
|
ssKey2 << key_end;
|
|
return EstimateSizeImpl(ssKey1, ssKey2);
|
|
}
|
|
};
|
|
|
|
#endif // BITCOIN_DBWRAPPER_H
|