Compare commits

...

4 Commits

Author SHA1 Message Date
Andi Skrgat
337e065598 BloomBitsReader v1 2023-08-01 12:03:31 +02:00
Andi Skrgat
d5c8f1a3f6 Add RibbonFilter policy 2023-07-31 13:42:45 +02:00
Andi Skrgat
365b12f59e Added vertical lie prefix transform 2023-07-28 18:56:56 +02:00
Andi Skrgat
b393f0d6c0 Initial PR 2023-07-28 14:00:44 +02:00
5 changed files with 235 additions and 28 deletions

View File

@@ -10,7 +10,11 @@
// licenses/APL.txt.
#include "rocksdb_storage.hpp"
#include <rocksdb/slice.h>
#include <string_view>
#include "utils/algorithm.hpp"
#include "utils/disk_utils.hpp"
#include "utils/exceptions.hpp"
#include "utils/rocksdb_serialization.hpp"
namespace memgraph::storage {
@@ -18,6 +22,7 @@ namespace memgraph::storage {
namespace {
inline rocksdb::Slice StripTimestampFromUserKey(const rocksdb::Slice &user_key, size_t ts_sz) {
// spdlog::debug("StripTimestampFromUserKey: {}", user_key.ToString());
rocksdb::Slice ret = user_key;
ret.remove_suffix(ts_sz);
return ret;
@@ -30,10 +35,14 @@ inline rocksdb::Slice ExtractTimestampFromUserKey(const rocksdb::Slice &user_key
}
// Extracts global id from user key. User key must be without timestamp.
std::string_view ExtractGidFromUserKey(const rocksdb::Slice &key) {
assert(key.size() >= 2);
auto keyStrView = key.ToStringView();
return keyStrView.substr(keyStrView.find_last_of('|') + 1);
inline std::string_view ExtractGidFromUserKey(const rocksdb::Slice &key) {
std::string_view keyStrView = key.ToStringView();
// spdlog::debug("ExtractGidFromKey: {}", keyStrView);
if (utils::Contains(keyStrView, '|')) {
assert(key.size() >= 2);
return keyStrView.substr(keyStrView.find_last_of('|') + 1);
}
return keyStrView;
}
} // namespace
@@ -43,26 +52,66 @@ ComparatorWithU64TsImpl::ComparatorWithU64TsImpl()
assert(cmp_without_ts_->timestamp_size() == 0);
}
/// TODO: try to write somehow unit test for this
int ComparatorWithU64TsImpl::Compare(const rocksdb::Slice &a, const rocksdb::Slice &b) const {
int ret = CompareWithoutTimestamp(a, b);
if (ret != 0) {
return ret;
std::string a_str = a.ToString();
std::string b_str = b.ToString();
// spdlog::debug("Received a: {} b: {} in compare method", a_str, b_str);
uint32_t num_separators_a = std::count(a_str.begin(), a_str.end(), '|');
uint32_t num_separators_b = std::count(b_str.begin(), b_str.end(), '|');
utils::RocksDBType a_key_type = utils::GetRocksDBKeyType(num_separators_a);
utils::RocksDBType b_key_type = utils::GetRocksDBKeyType(num_separators_b);
if (utils::ComparingVertexWithVertex(a_key_type, b_key_type) ||
utils::ComparingEdgeWithEdge(a_key_type, b_key_type)) {
int ret = CompareWithoutTimestamp(a, b);
if (ret != 0) {
return ret;
}
// Compare timestamp.
// For the same user key with different timestamps, larger (newer) timestamp
// comes first.
return CompareTimestamp(ExtractTimestampFromUserKey(b), ExtractTimestampFromUserKey(a));
}
// Compare timestamp.
// For the same user key with different timestamps, larger (newer) timestamp
// comes first.
return CompareTimestamp(ExtractTimestampFromUserKey(b), ExtractTimestampFromUserKey(a));
if (utils::ComparingEdgeWithGID(a_key_type, b_key_type)) {
return CompareEdgeWithGidForPrefixSearch(a_str, b_str);
}
throw utils::BasicException(
"Cannot handle specific compare use-case when one of the keys are neither vertex nor edge.");
}
int ComparatorWithU64TsImpl::CompareEdgeWithGidForPrefixSearch(const std::string_view edge,
const std::string_view source_vertex_gid) const {
// spdlog::debug("Compare edge with gid for prefix search START");
rocksdb::Slice stripped_src_vertex_gid = StripTimestampFromUserKey(source_vertex_gid, timestamp_size());
// rocksdb::Slice stripped_src_vertex_gid = source_vertex_gid;
std::string_view edge_source_vertex_gid = edge.substr(0, edge.find('|'));
// spdlog::debug("Edge: {} Edge source vertex gid: {} Size: {} Source vertex gid: {} Size: {}", edge,
// edge_source_vertex_gid, edge_source_vertex_gid.size(), stripped_src_vertex_gid.ToString(),
// stripped_src_vertex_gid.ToString().size());
int cmp_res = cmp_without_ts_->Compare(edge_source_vertex_gid, stripped_src_vertex_gid);
// spdlog::debug("Compare result: {}", cmp_res);
return cmp_res;
}
/// TODO: entered CompareWithoutTimestamp with GID. Handle something like in a compare function.
int ComparatorWithU64TsImpl::CompareWithoutTimestamp(const rocksdb::Slice &a, bool a_has_ts, const rocksdb::Slice &b,
bool b_has_ts) const {
const size_t ts_sz = timestamp_size();
// spdlog::debug("Timestamp size: {}", ts_sz);
// spdlog::debug("a_has_ts: {} b_has_ts: {}", a_has_ts, b_has_ts);
// spdlog::debug("a size: {} b size: {}", a.size(), b.size());
assert(!a_has_ts || a.size() >= ts_sz);
assert(!b_has_ts || b.size() >= ts_sz);
// spdlog::debug("a key: {}", a.ToString());
// spdlog::debug("b key: {}", b.ToString());
rocksdb::Slice lhsUserKey = a_has_ts ? StripTimestampFromUserKey(a, ts_sz) : a;
rocksdb::Slice rhsUserKey = b_has_ts ? StripTimestampFromUserKey(b, ts_sz) : b;
// spdlog::debug("lhsUserKey: {}", lhsUserKey.ToString());
// spdlog::debug("rhsUserKey: {}", rhsUserKey.ToString());
rocksdb::Slice lhsGid = ExtractGidFromUserKey(lhsUserKey);
rocksdb::Slice rhsGid = ExtractGidFromUserKey(rhsUserKey);
// spdlog::debug("lhsGid: {}", lhsGid.ToString());
// spdlog::debug("rhsGid: {}", rhsGid.ToString());
return cmp_without_ts_->Compare(lhsGid, rhsGid);
}

View File

@@ -13,14 +13,23 @@
#include <rocksdb/comparator.h>
#include <rocksdb/db.h>
#include <rocksdb/filter_policy.h>
#include <rocksdb/iterator.h>
#include <rocksdb/options.h>
#include <rocksdb/slice.h>
#include <rocksdb/slice_transform.h>
#include <rocksdb/status.h>
#include <rocksdb/table/block_based/filter_policy_internal.h>
#include <rocksdb/util/bloom_impl.h>
#include <rocksdb/util/hash.h>
#include <rocksdb/utilities/transaction_db.h>
#include <string_view>
#include "storage/v2/id_types.hpp"
#include "storage/v2/property_store.hpp"
#include "utils/algorithm.hpp"
#include "utils/logging.hpp"
#include "utils/string.hpp"
namespace memgraph::storage {
@@ -84,6 +93,97 @@ class ComparatorWithU64TsImpl : public rocksdb::Comparator {
private:
const Comparator *cmp_without_ts_{nullptr};
int CompareEdgeWithGidForPrefixSearch(std::string_view edge, std::string_view source_vertex_gid) const;
};
class VerticalLinePrefixTransform : public rocksdb::SliceTransform {
private:
std::string id_{"vertical_line_separator_prefix_transform"};
public:
explicit VerticalLinePrefixTransform() {}
static const char *kClassName() { return "memgraph.VerticalLine"; }
static const char *kNickName() { return "vertical_line"; }
const char *Name() const override { return kClassName(); }
const char *NickName() const override { return kNickName(); }
bool IsInstanceOf(const std::string &name) const override {
if (name == id_) {
return true;
}
return rocksdb::SliceTransform::IsInstanceOf(name);
}
std::string GetId() const override { return id_; }
rocksdb::Slice Transform(const rocksdb::Slice &src) const override {
// spdlog::debug("Received request for transform: {} {}", src.ToString(), src.ToString().size());
const std::string src_str = src.ToString();
if (utils::Contains(src.ToString(), '|')) {
assert(InDomain(src));
auto res = rocksdb::Slice(src.data(), src_str.find('|'));
// spdlog::debug("Transform result: {} from: {}", res.ToString(), src_str);
return res;
}
return src;
}
bool InDomain(const rocksdb::Slice &src) const override { return (utils::Contains(src.ToString(), '|')); }
// deprecated and implemented here just for backwards compatibility
bool InRange(const rocksdb::Slice & /*dst*/) const override { return true; }
bool FullLengthEnabled(size_t * /*len*/) const override { return false; }
bool SameResultWhenAppended(const rocksdb::Slice &prefix) const override { return InDomain(prefix); }
};
class MemgraphBloomBitsReader : public rocksdb::BuiltinFilterBitsReader {
public:
MemgraphBloomBitsReader(const char *data, int num_probes, uint32_t len_bytes)
: data_(data), num_probes_(num_probes), len_bytes_(len_bytes) {}
// No Copy allowed
MemgraphBloomBitsReader(const MemgraphBloomBitsReader &) = delete;
void operator=(const MemgraphBloomBitsReader &) = delete;
MemgraphBloomBitsReader(const MemgraphBloomBitsReader &&) = delete;
void operator=(const MemgraphBloomBitsReader &&) = delete;
~MemgraphBloomBitsReader() override {}
bool MayMatch(const rocksdb::Slice &key) override {
uint64_t h = rocksdb::GetSliceHash64(key);
uint32_t byte_offset;
rocksdb::FastLocalBloomImpl::PrepareHash(rocksdb::Lower32of64(h), len_bytes_, data_,
/*out*/ &byte_offset);
return rocksdb::FastLocalBloomImpl::HashMayMatchPrepared(rocksdb::Upper32of64(h), num_probes_, data_ + byte_offset);
}
void MayMatch(int num_keys, rocksdb::Slice **keys, bool *may_match) override {
std::array<uint32_t, 32> hashes;
std::array<uint32_t, 32> byte_offsets;
for (int i = 0; i < num_keys; ++i) {
uint64_t h = rocksdb::GetSliceHash64(*keys[i]);
rocksdb::FastLocalBloomImpl::PrepareHash(rocksdb::Lower32of64(h), len_bytes_, data_,
/*out*/ &byte_offsets[i]);
hashes[i] = rocksdb::Upper32of64(h);
}
for (int i = 0; i < num_keys; ++i) {
may_match[i] = rocksdb::FastLocalBloomImpl::HashMayMatchPrepared(hashes[i], num_probes_, data_ + byte_offsets[i]);
}
}
bool HashMayMatch(const uint64_t h) override {
return rocksdb::FastLocalBloomImpl::HashMayMatch(rocksdb::Lower32of64(h), rocksdb::Upper32of64(h), len_bytes_,
num_probes_, data_);
}
private:
const char *data_;
const int num_probes_;
const uint32_t len_bytes_;
};
} // namespace memgraph::storage

View File

@@ -16,7 +16,11 @@
#include <rocksdb/comparator.h>
#include <rocksdb/db.h>
#include <rocksdb/filter_policy.h>
#include <rocksdb/memtablerep.h>
#include <rocksdb/slice.h>
#include <rocksdb/slice_transform.h>
#include <rocksdb/table.h>
#include <rocksdb/options.h>
#include <rocksdb/utilities/transaction.h>
@@ -241,6 +245,12 @@ DiskStorage::DiskStorage(Config config)
LoadIndexInfoIfExists();
LoadConstraintsInfoIfExists();
kvstore_->options_.create_if_missing = true;
rocksdb::BlockBasedTableOptions table_options;
// TODO: have to implement our own FilterPolicy
// table_options.filter_policy.reset(rocksdb::NewBloomFilterPolicy(10, false));
table_options.filter_policy.reset(rocksdb::NewRibbonFilterPolicy(10, false));
kvstore_->options_.table_factory.reset(rocksdb::NewBlockBasedTableFactory(table_options));
kvstore_->options_.prefix_extractor.reset(new VerticalLinePrefixTransform());
kvstore_->options_.comparator = new ComparatorWithU64TsImpl();
kvstore_->options_.compression = rocksdb::kNoCompression;
kvstore_->options_.wal_recovery_mode = rocksdb::WALRecoveryMode::kPointInTimeRecovery;
@@ -382,6 +392,7 @@ std::optional<EdgeAccessor> DiskStorage::DiskAccessor::DeserializeEdge(const roc
}
VerticesIterable DiskStorage::DiskAccessor::Vertices(View view) {
// spdlog::debug("Scanning all vertices");
auto *disk_storage = static_cast<DiskStorage *>(storage_);
rocksdb::ReadOptions ro;
std::string strTs = utils::StringTimestamp(transaction_.start_timestamp);
@@ -886,6 +897,7 @@ void DiskStorage::DiskAccessor::PrefetchEdges(const auto &prefetch_edge_filter)
}
void DiskStorage::DiskAccessor::PrefetchInEdges(const VertexAccessor &vertex_acc) {
spdlog::debug("Prefetch input edges");
PrefetchEdges([&vertex_acc](const std::vector<std::string> &disk_edge_parts) -> bool {
auto disk_vertex_in_edge_gid = disk_edge_parts[1];
auto edge_gid = disk_edge_parts[4];
@@ -902,21 +914,46 @@ void DiskStorage::DiskAccessor::PrefetchInEdges(const VertexAccessor &vertex_acc
});
}
// void DiskStorage::DiskAccessor::PrefetchOutEdgesTmp(const VertexAccessor &vertex_acc) {
// spdlog::debug("Prefetch output edges");
// PrefetchEdges([&vertex_acc](const std::vector<std::string> &disk_edge_parts) -> bool {
// auto disk_vertex_out_edge_gid = disk_edge_parts[0];
// auto edge_gid = disk_edge_parts[4];
// auto out_edges_res = vertex_acc.OutEdges(storage::View::NEW);
// if (out_edges_res.HasValue()) {
// for (const auto &edge_acc : out_edges_res.GetValue()) {
// if (utils::SerializeIdType(edge_acc.Gid()) == edge_gid) {
// // We already inserted this edge into the vertex's out_edges list.
// return false;
// }
// }
// }
// return disk_vertex_out_edge_gid == utils::SerializeIdType(vertex_acc.Gid());
// });
// }
void DiskStorage::DiskAccessor::PrefetchOutEdges(const VertexAccessor &vertex_acc) {
PrefetchEdges([&vertex_acc](const std::vector<std::string> &disk_edge_parts) -> bool {
auto disk_vertex_out_edge_gid = disk_edge_parts[0];
auto edge_gid = disk_edge_parts[4];
auto out_edges_res = vertex_acc.OutEdges(storage::View::NEW);
if (out_edges_res.HasValue()) {
for (const auto &edge_acc : out_edges_res.GetValue()) {
if (utils::SerializeIdType(edge_acc.Gid()) == edge_gid) {
// We already inserted this edge into the vertex's out_edges list.
return false;
}
}
}
return disk_vertex_out_edge_gid == utils::SerializeIdType(vertex_acc.Gid());
});
spdlog::debug("Prefetch out edges");
rocksdb::ReadOptions read_opts;
read_opts.total_order_seek = true;
read_opts.prefix_same_as_start = true;
auto strTs = utils::StringTimestamp(transaction_.start_timestamp);
rocksdb::Slice ts(strTs);
read_opts.timestamp = &ts;
auto *disk_storage = static_cast<DiskStorage *>(storage_);
auto it = std::unique_ptr<rocksdb::Iterator>(
disk_transaction_->GetIterator(read_opts, disk_storage->kvstore_->edge_chandle));
auto gid_str = std::to_string(vertex_acc.Gid().AsUint());
spdlog::debug("Gid string: {}", gid_str);
it->Seek(gid_str);
while (it->Valid()) {
spdlog::debug("In loop execution");
auto key_str = it->key().ToStringView();
if (!key_str.starts_with(gid_str)) break;
spdlog::debug("Key str: {} gid str: {}", key_str, gid_str);
DeserializeEdge(key_str, it->value());
it->Next();
}
}
Result<EdgeAccessor> DiskStorage::DiskAccessor::CreateEdge(const VertexAccessor *from, const VertexAccessor *to,

View File

@@ -15,6 +15,8 @@
namespace memgraph::utils {
enum RocksDBType { VERTEX, EDGE, GID };
inline std::optional<std::string> GetOldDiskKeyOrNull(storage::Delta *head) {
while (head->next != nullptr) {
head = head->next;
@@ -25,4 +27,26 @@ inline std::optional<std::string> GetOldDiskKeyOrNull(storage::Delta *head) {
return std::nullopt;
}
inline RocksDBType GetRocksDBKeyType(uint32_t num_separators) {
if (num_separators == 0) {
return RocksDBType::GID;
}
if (num_separators == 4) {
return RocksDBType::EDGE;
}
return RocksDBType::VERTEX;
}
inline bool ComparingVertexWithVertex(RocksDBType type_a, RocksDBType type_b) {
return type_a == RocksDBType::VERTEX && type_b == RocksDBType::VERTEX;
}
inline bool ComparingEdgeWithEdge(RocksDBType type_a, RocksDBType type_b) {
return type_a == RocksDBType::EDGE && type_b == RocksDBType::EDGE;
}
inline bool ComparingEdgeWithGID(RocksDBType type_a, RocksDBType type_b) {
return type_a == RocksDBType::EDGE && type_b == RocksDBType::GID;
}
} // namespace memgraph::utils

3
tests/unit/storage_v2_edge_ondisk.cpp Normal file → Executable file
View File

@@ -4247,9 +4247,6 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW);
ASSERT_FALSE(vertex_from);
ASSERT_TRUE(vertex_to);
// We prefetch edges implicitly when go thorough query Accessor
acc->PrefetchOutEdges(*vertex_from);
acc->PrefetchInEdges(*vertex_from);
acc->PrefetchOutEdges(*vertex_to);
acc->PrefetchInEdges(*vertex_to);