From b7ba50961834b55469495e928a8f992eadf3374f Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 6 Feb 2015 11:32:11 -0800 Subject: [PATCH] NuDB: Use nodeobject codec in Backend (RIPD-793): This adds codecs for snappy and lz4, and a new nodeobject codec. The nodeobject codec provides a highly efficient custom compression scheme for inner nodes, which make up the majority of nodestore databases. Non inner node objects are compressed using lz4. The NuDB backend is modified to use the nodeobject codec. This change is not backward compatible - older NuDB databases cannot be opened or imported. --- Builds/VisualStudio2013/RippleD.vcxproj | 2 + .../VisualStudio2013/RippleD.vcxproj.filters | 3 + src/ripple/nodestore/backend/NuDBFactory.cpp | 205 +------ src/ripple/nodestore/impl/DecodedBlob.cpp | 2 +- src/ripple/nodestore/impl/codec.h | 512 ++++++++++++++++++ 5 files changed, 530 insertions(+), 194 deletions(-) create mode 100644 src/ripple/nodestore/impl/codec.h diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj index c098808a3..93af249fc 100755 --- a/Builds/VisualStudio2013/RippleD.vcxproj +++ b/Builds/VisualStudio2013/RippleD.vcxproj @@ -2112,6 +2112,8 @@ + + diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters index 51784ab98..db5af27b8 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters @@ -3072,6 +3072,9 @@ ripple\nodestore\impl + + ripple\nodestore\impl + ripple\nodestore\impl diff --git a/src/ripple/nodestore/backend/NuDBFactory.cpp b/src/ripple/nodestore/backend/NuDBFactory.cpp index 6c37593d8..3d7c07117 100644 --- a/src/ripple/nodestore/backend/NuDBFactory.cpp +++ b/src/ripple/nodestore/backend/NuDBFactory.cpp @@ -21,14 +21,14 @@ #include #include +#include #include #include #include -#include // remove asap +#include #include #include #include -#include #include #include #include @@ -50,22 +50,11 @@ public: // distribution of data sizes. arena_alloc_size = 16 * 1024 * 1024, - // Version 1 - // No compression - // - typeOne = 1, - - // Version 2 - // Snappy compression - typeTwo = 2, - - - - currentType = typeTwo + currentType = 1 }; using api = beast::nudb::api< - beast::xxhasher, beast::nudb::identity_codec>; + beast::xxhasher, nodeobject_codec>; beast::Journal journal_; size_t const keyBytes_; @@ -137,74 +126,8 @@ public: } } - //-------------------------------------------------------------------------- - - class Buffer - { - private: - std::size_t size_ = 0; - std::size_t capacity_ = 0; - std::unique_ptr buf_; - - public: - Buffer() = default; - Buffer (Buffer const&) = delete; - Buffer& operator= (Buffer const&) = delete; - - explicit - Buffer (std::size_t n) - { - resize (n); - } - - std::size_t - size() const - { - return size_; - } - - std::size_t - capacity() const - { - return capacity_; - } - - void* - get() - { - return buf_.get(); - } - - void - resize (std::size_t n) - { - if (capacity_ < n) - { - capacity_ = beast::nudb::detail::ceil_pow2(n); - buf_.reset (new std::uint8_t[capacity_]); - } - size_ = n; - } - - // Meet the requirements of BufferFactory - void* - operator() (std::size_t n) - { - resize(n); - return get(); - } - }; - - //-------------------------------------------------------------------------- - // - // Version 1 Database - // - // Uncompressed - // - Status - fetch1 (void const* key, - std::shared_ptr * pno) + fetch (void const* key, NodeObject::Ptr* pno) { Status status; pno->reset(); @@ -226,94 +149,13 @@ public: return status; } - void - insert1 (void const* key, void const* data, - std::size_t size) - { - db_.insert (key, data, size); - } - - //-------------------------------------------------------------------------- - // - // Version 2 Database - // - // Snappy compression - // - - Status - fetch2 (void const* key, - std::shared_ptr * pno) - { - Status status; - pno->reset(); - if (! db_.fetch (key, - [&](void const* data, std::size_t size) - { - std::size_t actual; - if (! snappy::GetUncompressedLength( - (char const*)data, size, &actual)) - { - status = dataCorrupt; - return; - } - std::unique_ptr buf (new char[actual]); - snappy::RawUncompress ( - (char const*)data, size, buf.get()); - DecodedBlob decoded (key, buf.get(), actual); - if (! decoded.wasOk ()) - { - status = dataCorrupt; - return; - } - *pno = decoded.createObject(); - status = ok; - })) - { - return notFound; - } - - return status; - } - - void - insert2 (void const* key, void const* data, - std::size_t size) - { - std::unique_ptr buf ( - new char[snappy::MaxCompressedLength(size)]); - std::size_t actual; - snappy::RawCompress ((char const*)data, size, - buf.get(), &actual); - db_.insert (key, buf.get(), actual); - } - - //-------------------------------------------------------------------------- - - Status - fetch (void const* key, NodeObject::Ptr* pno) - { - switch (db_.appnum()) - { - case typeOne: return fetch1 (key, pno); - case typeTwo: return fetch2 (key, pno); - } - throw std::runtime_error( - "nodestore: unknown appnum"); - return notFound; - } - void do_insert (std::shared_ptr const& no) { EncodedBlob e; e.prepare (no); - switch (db_.appnum()) - { - case typeOne: return insert1 (e.getKey(), e.getData(), e.getSize()); - case typeTwo: return insert2 (e.getKey(), e.getData(), e.getSize()); - } - throw std::runtime_error( - "nodestore: unknown appnum"); + db_.insert (e.getKey(), + e.getData(), e.getSize()); } void @@ -352,40 +194,17 @@ public: auto const dp = db_.dat_path(); auto const kp = db_.key_path(); auto const lp = db_.log_path(); - auto const appnum = db_.appnum(); + //auto const appnum = db_.appnum(); db_.close(); api::visit (dp, [&]( void const* key, std::size_t key_bytes, void const* data, std::size_t size) { - switch (appnum) - { - case typeOne: - { - DecodedBlob decoded (key, data, size); - if (! decoded.wasOk ()) - return false; - f (decoded.createObject()); - break; - } - case typeTwo: - { - std::size_t actual; - if (! snappy::GetUncompressedLength( - (char const*)data, size, &actual)) - return false; - std::unique_ptr buf (new char[actual]); - if (! snappy::RawUncompress ((char const*)data, - size, buf.get())) - return false; - DecodedBlob decoded (key, buf.get(), actual); - if (! decoded.wasOk ()) - return false; - f (decoded.createObject()); - break; - } - } + DecodedBlob decoded (key, data, size); + if (! decoded.wasOk ()) + return false; + f (decoded.createObject()); return true; }); db_.open (dp, kp, lp, diff --git a/src/ripple/nodestore/impl/DecodedBlob.cpp b/src/ripple/nodestore/impl/DecodedBlob.cpp index e16268ef0..8fbb914c8 100644 --- a/src/ripple/nodestore/impl/DecodedBlob.cpp +++ b/src/ripple/nodestore/impl/DecodedBlob.cpp @@ -58,10 +58,10 @@ DecodedBlob::DecodedBlob (void const* key, void const* value, int valueBytes) switch (m_objectType) { - case hotUNKNOWN: default: break; + case hotUNKNOWN: case hotLEDGER: case hotTRANSACTION: case hotACCOUNT_NODE: diff --git a/src/ripple/nodestore/impl/codec.h b/src/ripple/nodestore/impl/codec.h new file mode 100644 index 000000000..102942a50 --- /dev/null +++ b/src/ripple/nodestore/impl/codec.h @@ -0,0 +1,512 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_NODESTORE_CODEC_H_INCLUDED +#define RIPPLE_NODESTORE_CODEC_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace NodeStore { + +namespace detail { + +template +std::pair +snappy_compress (void const* in, + std::size_t in_size, BufferFactory&& bf) +{ + std::pair result; + auto const out_max = + snappy::MaxCompressedLength(in_size); + void* const out = bf(out_max); + result.first = out; + snappy::RawCompress( + reinterpret_cast(in), + in_size, reinterpret_cast(out), + &result.second); + return result; +} + +template +std::pair +snappy_decompress (void const* in, + std::size_t in_size, BufferFactory&& bf) +{ + std::pair result; + if (! snappy::GetUncompressedLength( + reinterpret_cast(in), + in_size, &result.second)) + throw beast::nudb::codec_error( + "snappy decompress"); + void* const out = bf(result.second); + result.first = out; + if (! snappy::RawUncompress( + reinterpret_cast(in), in_size, + reinterpret_cast(out))) + throw beast::nudb::codec_error( + "snappy decompress"); + return result; +} + +template +std::pair +lz4_decompress (void const* in, + std::size_t in_size, BufferFactory&& bf) +{ + using beast::nudb::codec_error; + using namespace beast::nudb::detail; + std::pair result; + std::uint8_t const* p = reinterpret_cast< + std::uint8_t const*>(in); + auto const n = read_varint( + p, in_size, result.second); + if (n == 0) + throw codec_error( + "lz4 decompress"); + void* const out = bf(result.second); + result.first = out; + if (LZ4_decompress_fast( + reinterpret_cast(in) + n, + reinterpret_cast(out), + result.second) + n != in_size) + throw codec_error( + "lz4 decompress"); + return result; +} + +template +std::pair +lz4_compress (void const* in, + std::size_t in_size, BufferFactory&& bf) +{ + using beast::nudb::codec_error; + using namespace beast::nudb::detail; + std::pair result; + std::array::max> vi; + auto const n = write_varint( + vi.data(), in_size); + auto const out_max = + LZ4_compressBound(in_size); + std::uint8_t* out = reinterpret_cast< + std::uint8_t*>(bf(n + out_max)); + result.first = out; + std::memcpy(out, vi.data(), n); + auto const out_size = LZ4_compress( + reinterpret_cast(in), + reinterpret_cast(out + n), + in_size); + if (out_size == 0) + throw codec_error( + "lz4 compress"); + result.second = n + out_size; + return result; +} + +//------------------------------------------------------------------------------ + +/* + object types: + + 0 = Uncompressed + 1 = lz4 compressed + 2 = inner node compressed + 3 = full inner node +*/ + +template +std::pair +nodeobject_decompress (void const* in, + std::size_t in_size, BufferFactory&& bf) +{ + using beast::nudb::codec_error; + using namespace beast::nudb::detail; + + std::uint8_t const* p = reinterpret_cast< + std::uint8_t const*>(in); + std::size_t type; + auto const vn = read_varint( + p, in_size, type); + if (vn == 0) + throw codec_error( + "nodeobject decompress"); + p += vn; + in_size -= vn; + + std::pair result; + switch(type) + { + case 0: // uncompressed + { + result.first = p; + result.second = in_size; + break; + } + case 1: // lz4 + { + result = lz4_decompress( + p, in_size, bf); + break; + } + case 2: // inner node + { + auto const hs = + field::size; // Mask + if (in_size < hs + 32) + throw codec_error( + "nodeobject codec: short inner node"); + istream is(p, in_size); + std::uint16_t mask; + read(is, mask); // Mask + in_size -= hs; + result.second = 525; + void* const out = bf(result.second); + result.first = out; + ostream os(out, result.second); + write(os, 0); + write(os, 0); + write (os, hotUNKNOWN); + write(os, HashPrefix::innerNode); + if (mask == 0) + throw codec_error( + "nodeobject codec: empty inner node"); + std::uint16_t bit = 0x8000; + for (int i = 16; i--; bit >>= 1) + { + if (mask & bit) + { + if (in_size < 32) + throw codec_error( + "nodeobject codec: short inner node"); + std::memcpy(os.data(32), is(32), 32); + in_size -= 32; + } + else + { + std::memset(os.data(32), 0, 32); + } + } + if (in_size > 0) + throw codec_error( + "nodeobject codec: long inner node"); + break; + } + case 3: // full inner node + { + if (in_size != 16 * 32) // hashes + throw codec_error( + "nodeobject codec: short full inner node"); + istream is(p, in_size); + result.second = 525; + void* const out = bf(result.second); + result.first = out; + ostream os(out, result.second); + write(os, 0); + write(os, 0); + write (os, hotUNKNOWN); + write(os, HashPrefix::innerNode); + write(os, is(512), 512); + break; + } + default: + throw codec_error( + "nodeobject codec: bad type=" + + std::to_string(type)); + }; + return result; +} + +template +void const* +zero32() +{ + static std::array v = + [] + { + std::array v; + v.fill(0); + return v; + }(); + return v.data(); +} + +template +std::pair +nodeobject_compress (void const* in, + std::size_t in_size, BufferFactory&& bf) +{ + using beast::nudb::codec_error; + using namespace beast::nudb::detail; + + std::size_t type = 1; + // Check for inner node + if (in_size == 525) + { + istream is(in, in_size); + std::uint32_t index; + std::uint32_t unused; + std::uint8_t kind; + std::uint32_t prefix; + read(is, index); + read(is, unused); + read (is, kind); + read(is, prefix); + if (prefix == HashPrefix::innerNode) + { + std::size_t n = 0; + std::uint16_t mask = 0; + std::array< + std::uint8_t, 512> vh; + for (unsigned bit = 0x8000; + bit; bit >>= 1) + { + void const* const h = is(32); + if (std::memcmp( + h, zero32(), 32) == 0) + continue; + std::memcpy( + vh.data() + 32 * n, h, 32); + mask |= bit; + ++n; + } + std::pair result; + if (n < 16) + { + // 2 = inner node compressed + auto const type = 2U; + auto const vs = size_varint(type); + result.second = + vs + + field::size + // mask + n * 32; // hashes + std::uint8_t* out = reinterpret_cast< + std::uint8_t*>(bf(result.second)); + result.first = out; + ostream os(out, result.second); + write(os, type); + write(os, mask); + write(os, vh.data(), n * 32); + return result; + } + // 3 = full inner node + auto const type = 3U; + auto const vs = size_varint(type); + result.second = + vs + + n * 32; // hashes + std::uint8_t* out = reinterpret_cast< + std::uint8_t*>(bf(result.second)); + result.first = out; + ostream os(out, result.second); + write(os, type); + write(os, vh.data(), n * 32); + return result; + } + } + + std::array::max> vi; + auto const vn = write_varint( + vi.data(), type); + std::pair result; + switch(type) + { + case 0: // uncompressed + { + result.second = vn + in_size; + std::uint8_t* p = reinterpret_cast< + std::uint8_t*>(bf(result.second)); + result.first = p; + std::memcpy(p, vi.data(), vn); + std::memcpy(p + vn, in, in_size); + break; + } + case 1: // lz4 + { + std::uint8_t* p; + auto const lzr = lz4_compress( + in, in_size, [&p, &vn, &bf] + (std::size_t n) + { + p = reinterpret_cast< + std::uint8_t*>( + bf(vn + n)); + return p + vn; + }); + std::memcpy(p, vi.data(), vn); + result.first = p; + result.second = vn + lzr.second; + break; + } + default: + throw std::logic_error( + "nodeobject codec: unknown=" + + std::to_string(type)); + }; + return result; +} + +} // detail + +// Modifies an inner node to erase the ledger +// sequence and type information so the codec +// verification can pass. +// +template +void +filter_inner (void* in, std::size_t in_size) +{ + using beast::nudb::codec_error; + using namespace beast::nudb::detail; + + // Check for inner node + if (in_size == 525) + { + istream is(in, in_size); + std::uint32_t index; + std::uint32_t unused; + std::uint8_t kind; + std::uint32_t prefix; + read(is, index); + read(is, unused); + read (is, kind); + read(is, prefix); + if (prefix == HashPrefix::innerNode) + { + ostream os(in, 9); + write(os, 0); + write(os, 0); + write (os, hotUNKNOWN); + } + } +} + +//------------------------------------------------------------------------------ + +class snappy_codec +{ +public: + template + explicit + snappy_codec(Args&&... args) + { + } + + char const* + name() const + { + return "snappy"; + } + + template + std::pair + compress (void const* in, + std::size_t in_size, BufferFactory&& bf) const + { + return snappy_compress(in, in_size, bf); + } + + template + std::pair + decompress (void const* in, + std::size_t in_size, BufferFactory&& bf) const + { + return snappy_decompress(in, in_size, bf); + } +}; + +class lz4_codec +{ +public: + template + explicit + lz4_codec(Args&&... args) + { + } + + char const* + name() const + { + return "lz4"; + } + + template + std::pair + decompress (void const* in, + std::size_t in_size, BufferFactory&& bf) const + { + return lz4_compress(in, in_size, bf); + } + + template + std::pair + compress (void const* in, + std::size_t in_size, BufferFactory&& bf) const + { + return lz4_compress(in, in_size, bf); + } +}; + +class nodeobject_codec +{ +public: + template + explicit + nodeobject_codec(Args&&... args) + { + } + + char const* + name() const + { + return "nodeobject"; + } + + template + std::pair + decompress (void const* in, + std::size_t in_size, BufferFactory&& bf) const + { + return detail::nodeobject_decompress( + in, in_size, bf); + } + + template + std::pair + compress (void const* in, + std::size_t in_size, BufferFactory&& bf) const + { + return detail::nodeobject_compress( + in, in_size, bf); + } +}; + +} // NodeStore +} // ripple + +#endif