Compare commits

..

61 Commits

Author SHA1 Message Date
gvolfing
451da803dc Add another test to ExpandOne 2022-09-20 18:40:23 +02:00
gvolfing
d1f51aa3f9 Add checkf for edge types in the ExpandOne handle 2022-09-20 18:04:32 +02:00
gvolfing
aec3bc3d05 Fix ExpandOne behavior 2022-09-20 16:39:52 +02:00
gvolfing
7fe5eb2c49 Clean-up tests 2022-09-20 09:52:57 +02:00
gvolfing
26e57d1988 Add the rest of the implementation for ExpandOne handle, fix segfault issue 2022-09-19 19:58:20 +02:00
gvolfing
f066d4f0b8 Save for tomorrow 2022-09-18 13:29:31 +02:00
gvolfing
359ac507a3 Merge branch 'T0919-MG-implement-message-based-actions' into T0919-MG-save-branch-for-outcommented-messages 2022-09-15 19:22:34 +02:00
gvolfing
4530e8fe50 Make ConvertPropertyMap take its argument as rvalue& 2022-09-15 17:18:23 +02:00
gvolfing
ef205dce4d Fix clang-tidy warnings 2022-09-15 13:36:32 +02:00
gvolfing
fd1668403a Restructure EdgeId message struct 2022-09-15 11:57:57 +02:00
gvolfing
b8bca320c8 Make enum types CamelCase, remove dead comments 2022-09-15 11:34:40 +02:00
gvolfing
3140f81d61 Apply suggestions from code review
Co-authored-by: Jure Bajic <jure.bajic@memgraph.com>
2022-09-15 11:07:46 +02:00
gvolfing
59bb54c26e React to PR comments 3 2022-09-14 17:12:43 +02:00
gvolfing
c6bc9d39ab Merge branch 'E118-MG-lexicographically-ordered-storage' into T0919-MG-implement-message-based-actions 2022-09-14 16:51:28 +02:00
gvolfing
d0930f66c8 React to PR comments 2 2022-09-14 16:48:07 +02:00
gvolfing
7db0f9ee23 React to PR comments 1 2022-09-14 14:19:44 +02:00
János Benjamin Antal
3a956d2266 Fix random hangs in simulator 2022-09-13 13:16:26 +02:00
János Benjamin Antal
9284ba0805 Small fixes 2022-09-13 11:09:32 +02:00
gvolfing
4c53713217 Remove outcommented code 2022-09-13 11:08:54 +02:00
gvolfing
ae04e717f9 Enable back the all of the simulator tests 2022-09-13 10:56:14 +02:00
gvolfing
1706be3341 Enable back the rest of the simulator tests 2022-09-13 10:53:46 +02:00
gvolfing
4d53d53859 Clean up used messages and their handles 2022-09-13 10:34:13 +02:00
gvolfing
f5d2bcd979 Cleanup 2022-09-12 18:09:28 +02:00
gvolfing
16028f50e8 Finish impl of ScanAll handle and its test, uncomment non-priority cases, general cleanup 2022-09-12 17:13:54 +02:00
János Benjamin Antal
bfdc69fdbd Fix Value special member functions 2022-09-12 14:06:30 +02:00
János Benjamin Antal
f49c962c5e Fix promise timeout handling 2022-09-12 14:06:02 +02:00
gvolfing
c71f16d9aa Save progress 2022-09-12 13:35:36 +02:00
gvolfing
2751d1fc94 Add test for DeleteEdges message handle 2022-09-11 21:04:29 +02:00
gvolfing
0d728d8a0a Test AddEdges message handle 2022-09-11 20:41:45 +02:00
gvolfing
0cfd0703ab Test UpdateVertices message handle 2022-09-11 19:56:02 +02:00
gvolfing
8ca0c0df53 Add test for DeleteVertices message handle 2022-09-11 15:54:46 +02:00
gvolfing
3feaa79688 Make Test and CreateVertices message handle work 2022-09-11 09:59:58 +02:00
gvolfing
a119fa3f73 Merge branch 'E118-MG-lexicographically-ordered-storage' into T0919-MG-implement-message-based-actions 2022-09-09 13:26:23 +02:00
gvolfing
f50d0f3a75 Add additional constructors to Value and modify test for message handlers 2022-09-09 13:20:13 +02:00
gvolfing
4d650aa595 Add test file 2022-09-08 17:09:53 +02:00
gvolfing
1316a90f8a Add additional dependencies and delete unused headers and type aliases 2022-09-08 16:46:21 +02:00
gvolfing
8bb6e00317 Add files for tests 2022-09-08 14:01:34 +02:00
gvolfing
c690b349f7 Add message routing capabilites to ShardRsm 2022-09-08 13:09:21 +02:00
gvolfing
9c2ecbd0a1 Add definition for ScanVerticesHandle 2022-09-08 12:47:57 +02:00
gvolfing
5b31ddc4fa Add move ctors where needed 2022-09-08 11:09:02 +02:00
gvolfing
a308bf16a2 Uncomment parts that require dinamically allocated resources from the union 2022-09-08 07:53:03 +02:00
gvolfing
3b5a736a97 Add partial implementation of ScanAll 2022-09-07 21:57:00 +02:00
gvolfing
8ebd85b6ea Update messages 2022-09-07 14:03:10 +02:00
gvolfing
db190baeef Add handle declaration for ScanVertices request 2022-09-06 20:27:57 +02:00
gvolfing
a1ec8d19d1 TODO 2022-09-06 15:31:54 +02:00
gvolfing
c576d02974 Add definition for UpdateEdge handle, modify its message 2022-09-06 15:26:14 +02:00
gvolfing
941ef01b88 Add definition for UpdateVertexHandle 2022-09-06 15:26:14 +02:00
gvolfing
6a7b2e586c Add implementation for DeleteEdges handle 2022-09-06 15:26:14 +02:00
gvolfing
10aa6b977c Modify existing messages, add new one for updates, more cleanup 2022-09-06 15:26:14 +02:00
gvolfing
c8ca234fec General cleanup 2022-09-06 15:26:14 +02:00
gvolfing
3e05b690a9 Make handle implementations exit the loops on the first encountered error 2022-09-06 15:26:14 +02:00
gvolfing
316fbfda52 Add implementation to CreateEdge handle, and modify messages 2022-09-06 15:26:14 +02:00
gvolfing
81b1173336 Add message types related to edges and the declarations for their handles 2022-09-06 15:26:14 +02:00
gvolfing
88240a39d9 Modify DeleteVertices message type, add implementation for its handle 2022-09-06 15:26:14 +02:00
gvolfing
31581ce4aa Refactor bridging helper functions 2022-09-06 15:26:14 +02:00
gvolfing
df60eae124 Change the underlying datastructure of property map in the defined messages, and bridge it to the VertexAccessor 2022-09-06 15:26:14 +02:00
gvolfing
950338833e Add debug messages and refactor the CreateVertices handle 2022-09-06 15:26:14 +02:00
gvolfing
921dc8b740 Add other message types relating to vertices 2022-09-06 15:26:14 +02:00
gvolfing
02f63fd68d Rename class ShardMessageHandler to ShardRsm 2022-09-06 15:26:14 +02:00
gvolfing
b1feee5b08 Rename file shard_message_handler to shard_rsm 2022-09-06 15:26:14 +02:00
gvolfing
d7c89d9cd5 Add CreateVerticies skeleton 2022-09-06 15:26:14 +02:00
122 changed files with 12839 additions and 10536 deletions

4
.gitignore vendored
View File

@@ -54,7 +54,6 @@ src/distributed/token_sharing_rpc_messages.hpp
src/distributed/updates_rpc_messages.hpp
src/query/v2/frontend/ast/ast.hpp
src/query/frontend/ast/ast.hpp
src/storage/v3/bindings/ast/ast.hpp
src/query/distributed/frontend/ast/ast_serialization.hpp
src/query/v2/distributed/frontend/ast/ast_serialization.hpp
src/durability/distributed/state_delta.hpp
@@ -62,15 +61,12 @@ src/durability/single_node/state_delta.hpp
src/durability/single_node_ha/state_delta.hpp
src/query/frontend/semantic/symbol.hpp
src/query/v2/frontend/semantic/symbol.hpp
src/expr/semantic/symbol.hpp
src/query/distributed/frontend/semantic/symbol_serialization.hpp
src/query/v2/distributed/frontend/semantic/symbol_serialization.hpp
src/query/distributed/plan/ops.hpp
src/query/v2/distributed/plan/ops.hpp
src/query/plan/operator.hpp
src/query/v2/plan/operator.hpp
src/parser/opencypher/generated
src/expr/semantic/symbol.hpp
src/raft/log_entry.hpp
src/raft/raft_rpc_messages.hpp
src/raft/snapshot_metadata.hpp

View File

@@ -1,9 +1,10 @@
set(coordinator_src_files
coordinator.cpp
shard_map.cpp)
coordinator.hpp
shard_map.hpp
hybrid_logical_clock.hpp)
find_package(fmt REQUIRED)
find_package(Threads REQUIRED)
add_library(mg-coordinator STATIC ${coordinator_src_files})
target_link_libraries(mg-coordinator stdc++fs Threads::Threads fmt::fmt mg-utils mg-storage-v3)
target_link_libraries(mg-coordinator stdc++fs Threads::Threads fmt::fmt mg-utils)

View File

@@ -1,129 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include <coordinator/coordinator.hpp>
namespace memgraph::coordinator {
CoordinatorWriteResponses Coordinator::ApplyWrite(HeartbeatRequest &&heartbeat_request) {
spdlog::info("Coordinator handling HeartbeatRequest");
// add this storage engine to any under-replicated shards that it is not already a part of
auto initializing_rsms_for_shard_manager =
shard_map_.AssignShards(heartbeat_request.from_storage_manager, heartbeat_request.initialized_rsms);
return HeartbeatResponse{
.shards_to_initialize = initializing_rsms_for_shard_manager,
};
}
CoordinatorWriteResponses Coordinator::ApplyWrite(HlcRequest &&hlc_request) {
HlcResponse res{};
auto hlc_shard_map = shard_map_.GetHlc();
MG_ASSERT(!(hlc_request.last_shard_map_version.logical_id > hlc_shard_map.logical_id));
res.new_hlc = Hlc{
.logical_id = ++highest_allocated_timestamp_,
// TODO(tyler) probably pass some more context to the Coordinator here
// so that we can use our wall clock and enforce monotonicity.
// .coordinator_wall_clock = io_.Now(),
};
// Allways return fresher shard_map for now.
res.fresher_shard_map = std::make_optional(shard_map_);
return res;
}
CoordinatorWriteResponses Coordinator::ApplyWrite(AllocateEdgeIdBatchRequest &&ahr) {
AllocateEdgeIdBatchResponse res{};
uint64_t low = highest_allocated_edge_id_;
highest_allocated_edge_id_ += ahr.batch_size;
uint64_t high = highest_allocated_edge_id_;
res.low = low;
res.high = high;
return res;
}
/// This splits the shard immediately beneath the provided
/// split key, keeping the assigned peers identical for now,
/// but letting them be gradually migrated over time.
CoordinatorWriteResponses Coordinator::ApplyWrite(SplitShardRequest &&split_shard_request) {
SplitShardResponse res{};
if (split_shard_request.previous_shard_map_version != shard_map_.shard_map_version) {
res.success = false;
} else {
res.success = shard_map_.SplitShard(split_shard_request.previous_shard_map_version, split_shard_request.label_id,
split_shard_request.split_key);
}
return res;
}
/// This adds the provided storage engine to the standby storage engine pool,
/// which can be used to rebalance storage over time.
CoordinatorWriteResponses Coordinator::ApplyWrite(
RegisterStorageEngineRequest && /* register_storage_engine_request */) {
RegisterStorageEngineResponse res{};
// TODO
return res;
}
/// This begins the process of draining the provided storage engine from all raft
/// clusters that it might be participating in.
CoordinatorWriteResponses Coordinator::ApplyWrite(
DeregisterStorageEngineRequest && /* register_storage_engine_request */) {
DeregisterStorageEngineResponse res{};
// TODO
return res;
}
CoordinatorWriteResponses Coordinator::ApplyWrite(InitializeLabelRequest &&initialize_label_request) {
InitializeLabelResponse res{};
std::optional<LabelId> new_label_id = shard_map_.InitializeNewLabel(
initialize_label_request.label_name, initialize_label_request.schema, initialize_label_request.replication_factor,
initialize_label_request.last_shard_map_version);
if (new_label_id) {
res.new_label_id = new_label_id.value();
res.fresher_shard_map = std::nullopt;
res.success = true;
} else {
res.fresher_shard_map = shard_map_;
res.success = false;
}
return res;
}
CoordinatorWriteResponses Coordinator::ApplyWrite(AllocatePropertyIdsRequest &&allocate_property_ids_request) {
AllocatePropertyIdsResponse res{};
auto property_ids = shard_map_.AllocatePropertyIds(allocate_property_ids_request.property_names);
res.property_ids = property_ids;
return res;
}
} // namespace memgraph::coordinator

View File

@@ -12,30 +12,26 @@
#pragma once
#include <optional>
#include <set>
#include <string>
#include <unordered_set>
#include <variant>
#include <vector>
#include <boost/uuid/uuid.hpp>
#include <coordinator/hybrid_logical_clock.hpp>
#include <coordinator/shard_map.hpp>
#include <io/simulator/simulator.hpp>
#include <io/time.hpp>
#include <io/transport.hpp>
#include <storage/v3/id_types.hpp>
#include <storage/v3/schemas.hpp>
#include "coordinator/hybrid_logical_clock.hpp"
#include "coordinator/shard_map.hpp"
#include "io/simulator/simulator.hpp"
#include "io/time.hpp"
#include "io/transport.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/schemas.hpp"
namespace memgraph::coordinator {
using memgraph::io::Address;
using memgraph::storage::v3::LabelId;
using memgraph::storage::v3::PropertyId;
using memgraph::storage::v3::SchemaProperty;
using Address = memgraph::io::Address;
using SimT = memgraph::io::simulator::SimulatorTransport;
using PrimaryKey = std::vector<PropertyValue>;
using memgraph::storage::v3::SchemaProperty;
struct HlcRequest {
Hlc last_shard_map_version;
@@ -54,11 +50,6 @@ struct GetShardMapResponse {
ShardMap shard_map;
};
struct AllocateHlcBatchRequest {
Hlc low;
Hlc high;
};
struct AllocateHlcBatchResponse {
bool success;
Hlc low;
@@ -85,7 +76,7 @@ struct AllocatePropertyIdsResponse {
struct SplitShardRequest {
Hlc previous_shard_map_version;
LabelId label_id;
PrimaryKey split_key;
CompoundKey split_key;
};
struct SplitShardResponse {
@@ -111,34 +102,26 @@ struct DeregisterStorageEngineResponse {
struct InitializeLabelRequest {
std::string label_name;
std::vector<SchemaProperty> schema;
size_t replication_factor;
Hlc last_shard_map_version;
};
struct InitializeLabelResponse {
bool success;
LabelId new_label_id;
std::optional<ShardMap> fresher_shard_map;
};
struct HeartbeatRequest {
Address from_storage_manager;
std::set<boost::uuids::uuid> initialized_rsms;
};
struct HeartbeatResponse {
std::vector<ShardToInitialize> shards_to_initialize;
};
struct HeartbeatRequest {};
struct HeartbeatResponse {};
using CoordinatorWriteRequests =
std::variant<HlcRequest, AllocateEdgeIdBatchRequest, SplitShardRequest, RegisterStorageEngineRequest,
DeregisterStorageEngineRequest, InitializeLabelRequest, AllocatePropertyIdsRequest, HeartbeatRequest>;
using CoordinatorWriteResponses = std::variant<HlcResponse, AllocateEdgeIdBatchResponse, SplitShardResponse,
RegisterStorageEngineResponse, DeregisterStorageEngineResponse,
InitializeLabelResponse, AllocatePropertyIdsResponse, HeartbeatResponse>;
DeregisterStorageEngineRequest, InitializeLabelRequest, AllocatePropertyIdsRequest>;
using CoordinatorWriteResponses =
std::variant<HlcResponse, AllocateEdgeIdBatchResponse, SplitShardResponse, RegisterStorageEngineResponse,
DeregisterStorageEngineResponse, InitializeLabelResponse, AllocatePropertyIdsResponse>;
using CoordinatorReadRequests = std::variant<GetShardMapRequest>;
using CoordinatorReadResponses = std::variant<GetShardMapResponse>;
using CoordinatorReadRequests = std::variant<GetShardMapRequest, HeartbeatRequest>;
using CoordinatorReadResponses = std::variant<GetShardMapResponse, HeartbeatResponse>;
class Coordinator {
public:
@@ -163,34 +146,114 @@ class Coordinator {
/// Query engines need to periodically request batches of unique edge IDs.
uint64_t highest_allocated_edge_id_;
static CoordinatorReadResponses HandleRead(HeartbeatRequest && /* heartbeat_request */) {
return HeartbeatResponse{};
}
CoordinatorReadResponses HandleRead(GetShardMapRequest && /* get_shard_map_request */) {
GetShardMapResponse res;
res.shard_map = shard_map_;
return res;
}
CoordinatorWriteResponses ApplyWrite(HeartbeatRequest &&heartbeat_request);
CoordinatorWriteResponses ApplyWrite(HlcRequest &&hlc_request) {
HlcResponse res{};
CoordinatorWriteResponses ApplyWrite(HlcRequest &&hlc_request);
auto hlc_shard_map = shard_map_.GetHlc();
CoordinatorWriteResponses ApplyWrite(AllocateEdgeIdBatchRequest &&ahr);
MG_ASSERT(!(hlc_request.last_shard_map_version.logical_id > hlc_shard_map.logical_id));
res.new_hlc = Hlc{
.logical_id = ++highest_allocated_timestamp_,
// TODO(tyler) probably pass some more context to the Coordinator here
// so that we can use our wall clock and enforce monotonicity.
// .coordinator_wall_clock = io_.Now(),
};
// Allways return fresher shard_map for now.
res.fresher_shard_map = std::make_optional(shard_map_);
return res;
}
CoordinatorWriteResponses ApplyWrite(AllocateEdgeIdBatchRequest &&ahr) {
AllocateEdgeIdBatchResponse res{};
uint64_t low = highest_allocated_edge_id_;
highest_allocated_edge_id_ += ahr.batch_size;
uint64_t high = highest_allocated_edge_id_;
res.low = low;
res.high = high;
return res;
}
/// This splits the shard immediately beneath the provided
/// split key, keeping the assigned peers identical for now,
/// but letting them be gradually migrated over time.
CoordinatorWriteResponses ApplyWrite(SplitShardRequest &&split_shard_request);
CoordinatorWriteResponses ApplyWrite(SplitShardRequest &&split_shard_request) {
SplitShardResponse res{};
if (split_shard_request.previous_shard_map_version != shard_map_.shard_map_version) {
res.success = false;
} else {
res.success = shard_map_.SplitShard(split_shard_request.previous_shard_map_version, split_shard_request.label_id,
split_shard_request.split_key);
}
return res;
}
/// This adds the provided storage engine to the standby storage engine pool,
/// which can be used to rebalance storage over time.
static CoordinatorWriteResponses ApplyWrite(RegisterStorageEngineRequest && /* register_storage_engine_request */);
static CoordinatorWriteResponses ApplyWrite(RegisterStorageEngineRequest && /* register_storage_engine_request */) {
RegisterStorageEngineResponse res{};
// TODO
return res;
}
/// This begins the process of draining the provided storage engine from all raft
/// clusters that it might be participating in.
static CoordinatorWriteResponses ApplyWrite(DeregisterStorageEngineRequest && /* register_storage_engine_request */);
static CoordinatorWriteResponses ApplyWrite(DeregisterStorageEngineRequest && /* register_storage_engine_request */) {
DeregisterStorageEngineResponse res{};
// TODO
// const Address &address = register_storage_engine_request.address;
// storage_engine_pool_.erase(address);
// res.success = true;
CoordinatorWriteResponses ApplyWrite(InitializeLabelRequest &&initialize_label_request);
return res;
}
CoordinatorWriteResponses ApplyWrite(AllocatePropertyIdsRequest &&allocate_property_ids_request);
CoordinatorWriteResponses ApplyWrite(InitializeLabelRequest &&initialize_label_request) {
InitializeLabelResponse res{};
bool success = shard_map_.InitializeNewLabel(initialize_label_request.label_name, initialize_label_request.schema,
initialize_label_request.last_shard_map_version);
if (success) {
res.fresher_shard_map = shard_map_;
res.success = false;
} else {
res.fresher_shard_map = std::nullopt;
res.success = true;
}
return res;
}
CoordinatorWriteResponses ApplyWrite(AllocatePropertyIdsRequest &&allocate_property_ids_request) {
AllocatePropertyIdsResponse res{};
auto property_ids = shard_map_.AllocatePropertyIds(allocate_property_ids_request.property_names);
res.property_ids = property_ids;
return res;
}
};
} // namespace memgraph::coordinator

View File

@@ -21,4 +21,5 @@ using memgraph::io::rsm::RsmClient;
template <typename IoImpl>
using CoordinatorClient = RsmClient<IoImpl, CoordinatorWriteRequests, CoordinatorWriteResponses,
CoordinatorReadRequests, CoordinatorReadResponses>;
} // namespace memgraph::coordinator

View File

@@ -11,8 +11,6 @@
#pragma once
#include <compare>
#include "io/time.hpp"
namespace memgraph::coordinator {
@@ -24,13 +22,7 @@ struct Hlc {
uint64_t logical_id;
Time coordinator_wall_clock;
auto operator<=>(const Hlc &other) const { return logical_id <=> other.logical_id; }
bool operator==(const Hlc &other) const = default;
bool operator<(const Hlc &other) const = default;
bool operator==(const uint64_t other) const { return logical_id == other; }
bool operator<(const uint64_t other) const { return logical_id < other; }
bool operator>=(const uint64_t other) const { return logical_id >= other; }
};
} // namespace memgraph::coordinator

View File

@@ -1,90 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "coordinator/shard_map.hpp"
#include "storage/v3/temporal.hpp"
namespace memgraph::coordinator {
using memgraph::common::SchemaType;
using memgraph::storage::v3::TemporalData;
using memgraph::storage::v3::TemporalType;
PrimaryKey SchemaToMinKey(const std::vector<SchemaProperty> &schema) {
PrimaryKey ret{};
const int64_t min_int = std::numeric_limits<int64_t>::min();
const TemporalData date{TemporalType::Date, min_int};
const TemporalData local_time{TemporalType::LocalTime, min_int};
const TemporalData local_date_time{TemporalType::LocalDateTime, min_int};
const TemporalData duration{TemporalType::Duration, min_int};
for (const auto &schema_property : schema) {
switch (schema_property.type) {
case SchemaType::BOOL:
ret.emplace_back(PropertyValue(false));
break;
case SchemaType::INT:
ret.emplace_back(PropertyValue(min_int));
break;
case SchemaType::STRING:
ret.emplace_back(PropertyValue(""));
break;
case SchemaType::DATE:
ret.emplace_back(PropertyValue(date));
break;
case SchemaType::LOCALTIME:
ret.emplace_back(PropertyValue(local_time));
break;
case SchemaType::LOCALDATETIME:
ret.emplace_back(PropertyValue(local_date_time));
break;
case SchemaType::DURATION:
ret.emplace_back(PropertyValue(duration));
break;
}
}
return ret;
}
std::optional<LabelId> ShardMap::InitializeNewLabel(std::string label_name, std::vector<SchemaProperty> schema,
size_t replication_factor, Hlc last_shard_map_version) {
if (shard_map_version != last_shard_map_version || labels.contains(label_name)) {
return std::nullopt;
}
const LabelId label_id = LabelId::FromUint(++max_label_id);
labels.emplace(std::move(label_name), label_id);
PrimaryKey initial_key = SchemaToMinKey(schema);
Shard empty_shard = {};
Shards shards = {
{initial_key, empty_shard},
};
LabelSpace label_space{
.schema = std::move(schema),
.shards = shards,
.replication_factor = replication_factor,
};
label_spaces.emplace(label_id, label_space);
IncrementShardMapVersion();
return label_id;
}
} // namespace memgraph::coordinator

View File

@@ -11,30 +11,20 @@
#pragma once
#include <limits>
#include <map>
#include <set>
#include <vector>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include "common/types.hpp"
#include "coordinator/hybrid_logical_clock.hpp"
#include "io/address.hpp"
#include "storage/v3/config.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/property_value.hpp"
#include "storage/v3/schemas.hpp"
#include "storage/v3/temporal.hpp"
namespace memgraph::coordinator {
using memgraph::io::Address;
using memgraph::storage::v3::Config;
using memgraph::storage::v3::LabelId;
using memgraph::storage::v3::PropertyId;
using memgraph::storage::v3::PropertyValue;
using memgraph::storage::v3::SchemaProperty;
enum class Status : uint8_t {
@@ -48,30 +38,18 @@ enum class Status : uint8_t {
struct AddressAndStatus {
memgraph::io::Address address;
Status status;
friend bool operator<(const AddressAndStatus &lhs, const AddressAndStatus &rhs) { return lhs.address < rhs.address; }
};
using PrimaryKey = std::vector<PropertyValue>;
using CompoundKey = std::vector<memgraph::storage::v3::PropertyValue>;
using Shard = std::vector<AddressAndStatus>;
using Shards = std::map<PrimaryKey, Shard>;
using Shards = std::map<CompoundKey, Shard>;
using LabelName = std::string;
using PropertyName = std::string;
using PropertyMap = std::map<PropertyName, PropertyId>;
struct ShardToInitialize {
boost::uuids::uuid uuid;
LabelId label_id;
PrimaryKey min_key;
std::optional<PrimaryKey> max_key;
Config config;
};
PrimaryKey SchemaToMinKey(const std::vector<SchemaProperty> &schema);
struct LabelSpace {
std::vector<SchemaProperty> schema;
std::map<PrimaryKey, Shard> shards;
size_t replication_factor;
std::map<CompoundKey, Shard> shards;
};
struct ShardMap {
@@ -83,12 +61,6 @@ struct ShardMap {
std::map<LabelId, LabelSpace> label_spaces;
std::map<LabelId, std::vector<SchemaProperty>> schemas;
Shards GetShards(const LabelName &label) {
const auto id = labels.at(label);
auto &shards = label_spaces.at(id).shards;
return shards;
}
// TODO(gabor) later we will want to update the wallclock time with
// the given Io<impl>'s time as well
Hlc IncrementShardMapVersion() noexcept {
@@ -98,70 +70,7 @@ struct ShardMap {
Hlc GetHlc() const noexcept { return shard_map_version; }
// Returns the shard UUIDs that have been assigned but not yet acknowledged for this storage manager
std::vector<ShardToInitialize> AssignShards(Address storage_manager, std::set<boost::uuids::uuid> initialized) {
std::vector<ShardToInitialize> ret{};
bool mutated = false;
for (auto &[label_id, label_space] : label_spaces) {
for (auto &[low_key, shard] : label_space.shards) {
// TODO(tyler) avoid these triple-nested loops by having the heartbeat include better info
bool machine_contains_shard = false;
for (auto &aas : shard) {
if (initialized.contains(aas.address.unique_id)) {
spdlog::info("marking shard as full consensus participant: {}", aas.address.unique_id);
aas.status = Status::CONSENSUS_PARTICIPANT;
machine_contains_shard = true;
} else {
const bool same_machine = aas.address.last_known_ip == storage_manager.last_known_ip &&
aas.address.last_known_port == storage_manager.last_known_port;
if (same_machine) {
machine_contains_shard = true;
ret.push_back(ShardToInitialize{
.uuid = aas.address.unique_id,
.label_id = label_id,
.min_key = low_key,
.max_key = std::nullopt,
.config = Config{},
});
}
}
}
if (!machine_contains_shard && shard.size() < label_space.replication_factor) {
Address address = storage_manager;
// TODO(tyler) use deterministic UUID so that coordinators don't diverge here
address.unique_id = boost::uuids::uuid{boost::uuids::random_generator()()},
ret.push_back(ShardToInitialize{
.uuid = address.unique_id,
.label_id = label_id,
.min_key = low_key,
.max_key = std::nullopt,
.config = Config{},
});
AddressAndStatus aas = {
.address = address,
.status = Status::INITIALIZING,
};
shard.emplace_back(aas);
}
}
}
if (mutated) {
IncrementShardMapVersion();
}
return ret;
}
bool SplitShard(Hlc previous_shard_map_version, LabelId label_id, const PrimaryKey &key) {
bool SplitShard(Hlc previous_shard_map_version, LabelId label_id, const CompoundKey &key) {
if (previous_shard_map_version != shard_map_version) {
return false;
}
@@ -169,11 +78,10 @@ struct ShardMap {
auto &label_space = label_spaces.at(label_id);
auto &shards_in_map = label_space.shards;
MG_ASSERT(!shards_in_map.empty());
MG_ASSERT(!shards_in_map.contains(key));
MG_ASSERT(label_spaces.contains(label_id));
// Finding the Shard that the new PrimaryKey should map to.
// Finding the Shard that the new CompoundKey should map to.
auto prev = std::prev(shards_in_map.upper_bound(key));
Shard duplicated_shard = prev->second;
@@ -183,16 +91,32 @@ struct ShardMap {
return true;
}
std::optional<LabelId> InitializeNewLabel(std::string label_name, std::vector<SchemaProperty> schema,
size_t replication_factor, Hlc last_shard_map_version);
bool InitializeNewLabel(std::string label_name, std::vector<SchemaProperty> schema, Hlc last_shard_map_version) {
if (shard_map_version != last_shard_map_version || labels.contains(label_name)) {
return false;
}
const LabelId label_id = LabelId::FromUint(++max_label_id);
labels.emplace(std::move(label_name), label_id);
LabelSpace label_space{
.schema = std::move(schema),
.shards = Shards{},
};
label_spaces.emplace(label_id, label_space);
IncrementShardMapVersion();
return true;
}
void AddServer(Address server_address) {
// Find a random place for the server to plug in
}
LabelId GetLabelId(const std::string &label) const { return labels.at(label); }
Shards GetShardsForRange(const LabelName &label_name, const PrimaryKey &start_key, const PrimaryKey &end_key) const {
Shards GetShardsForRange(const LabelName &label_name, const CompoundKey &start_key, const CompoundKey &end_key) const {
MG_ASSERT(start_key <= end_key);
MG_ASSERT(labels.contains(label_name));
@@ -215,7 +139,7 @@ struct ShardMap {
return shards;
}
Shard GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const {
Shard GetShardForKey(const LabelName &label_name, const CompoundKey &key) const {
MG_ASSERT(labels.contains(label_name));
LabelId label_id = labels.at(label_name);
@@ -228,17 +152,6 @@ struct ShardMap {
return std::prev(label_space.shards.upper_bound(key))->second;
}
Shard GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const {
MG_ASSERT(label_spaces.contains(label_id));
const auto &label_space = label_spaces.at(label_id);
MG_ASSERT(label_space.shards.begin()->first <= key,
"the ShardMap must always contain a minimal key that is less than or equal to any requested key");
return std::prev(label_space.shards.upper_bound(key))->second;
}
PropertyMap AllocatePropertyIds(const std::vector<PropertyName> &new_properties) {
PropertyMap ret{};

View File

@@ -32,21 +32,4 @@ class ExpressionRuntimeException : public utils::BasicException {
using utils::BasicException::BasicException;
};
class RedeclareVariableError : public SemanticException {
public:
explicit RedeclareVariableError(const std::string &name) : SemanticException("Redeclaring variable: " + name + ".") {}
};
class UnboundVariableError : public SemanticException {
public:
explicit UnboundVariableError(const std::string &name) : SemanticException("Unbound variable: " + name + ".") {}
};
class TypeMismatchError : public SemanticException {
public:
TypeMismatchError(const std::string &name, const std::string &datum, const std::string &expected)
: SemanticException(fmt::format("Type mismatch: {} already defined as {}, expected {}.", name, datum, expected)) {
}
};
} // namespace memgraph::expr

View File

@@ -17,7 +17,6 @@
#include <map>
#include <optional>
#include <regex>
#include <type_traits>
#include <vector>
#include "expr/ast.hpp"
@@ -28,11 +27,8 @@
namespace memgraph::expr {
struct StorageTag {};
struct QueryEngineTag {};
template <typename TypedValue, typename EvaluationContext, typename DbAccessor, typename StorageView, typename LabelId,
typename PropertyValue, typename ConvFunctor, typename Error, typename Tag = StorageTag>
typename PropertyValue, typename ConvFunction, typename Error>
class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
public:
ExpressionEvaluator(Frame<TypedValue> *frame, const SymbolTable &symbol_table, const EvaluationContext &ctx,
@@ -381,43 +377,6 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
}
}
template <typename VertexAccessor, typename TTag = Tag,
typename TReturnType = std::enable_if_t<std::is_same_v<TTag, StorageTag>, bool>>
TReturnType HasLabelImpl(const VertexAccessor &vertex, const LabelIx &label, StorageTag /*tag*/) {
auto has_label = vertex.HasLabel(view_, GetLabel(label));
if (has_label.HasError() && has_label.GetError() == Error::NONEXISTENT_OBJECT) {
// This is a very nasty and temporary hack in order to make MERGE
// work. The old storage had the following logic when returning an
// `OLD` view: `return old ? old : new`. That means that if the
// `OLD` view didn't exist, it returned the NEW view. With this hack
// we simulate that behavior.
// TODO (mferencevic, teon.banek): Remove once MERGE is
// reimplemented.
has_label = vertex.HasLabel(StorageView::NEW, GetLabel(label));
}
if (has_label.HasError()) {
switch (has_label.GetError()) {
case Error::DELETED_OBJECT:
throw ExpressionRuntimeException("Trying to access labels on a deleted node.");
case Error::NONEXISTENT_OBJECT:
throw ExpressionRuntimeException("Trying to access labels from a node that doesn't exist.");
case Error::SERIALIZATION_ERROR:
case Error::VERTEX_HAS_EDGES:
case Error::PROPERTIES_DISABLED:
throw ExpressionRuntimeException("Unexpected error when accessing labels.");
}
}
return *has_label;
}
template <typename VertexAccessor, typename TTag = Tag,
typename TReturnType = std::enable_if_t<std::is_same_v<TTag, QueryEngineTag>, bool>>
TReturnType HasLabelImpl(const VertexAccessor &vertex, const LabelIx &label_ix, QueryEngineTag /*tag*/) {
auto label = typename VertexAccessor::Label{LabelId::FromUint(label_ix.ix)};
auto has_label = vertex.HasLabel(label);
return !has_label;
}
TypedValue Visit(LabelsTest &labels_test) override {
auto expression_result = labels_test.expression_->Accept(*this);
switch (expression_result.type()) {
@@ -425,12 +384,35 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
return TypedValue(ctx_->memory);
case TypedValue::Type::Vertex: {
const auto &vertex = expression_result.ValueVertex();
if (std::ranges::all_of(labels_test.labels_, [&vertex, this](const auto label_test) {
return this->HasLabelImpl(vertex, label_test, Tag{});
})) {
return TypedValue(true, ctx_->memory);
for (const auto &label : labels_test.labels_) {
auto has_label = vertex.HasLabel(view_, GetLabel(label));
if (has_label.HasError() && has_label.GetError() == Error::NONEXISTENT_OBJECT) {
// This is a very nasty and temporary hack in order to make MERGE
// work. The old storage had the following logic when returning an
// `OLD` view: `return old ? old : new`. That means that if the
// `OLD` view didn't exist, it returned the NEW view. With this hack
// we simulate that behavior.
// TODO (mferencevic, teon.banek): Remove once MERGE is
// reimplemented.
has_label = vertex.HasLabel(StorageView::NEW, GetLabel(label));
}
if (has_label.HasError()) {
switch (has_label.GetError()) {
case Error::DELETED_OBJECT:
throw ExpressionRuntimeException("Trying to access labels on a deleted node.");
case Error::NONEXISTENT_OBJECT:
throw ExpressionRuntimeException("Trying to access labels from a node that doesn't exist.");
case Error::SERIALIZATION_ERROR:
case Error::VERTEX_HAS_EDGES:
case Error::PROPERTIES_DISABLED:
throw ExpressionRuntimeException("Unexpected error when accessing labels.");
}
}
if (!*has_label) {
return TypedValue(false, ctx_->memory);
}
}
return TypedValue(false, ctx_->memory);
return TypedValue(true, ctx_->memory);
}
default:
throw ExpressionRuntimeException("Only nodes have labels.");
@@ -713,24 +695,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
}
private:
template <class TRecordAccessor, class TTag = Tag,
class TReturnType = std::enable_if_t<std::is_same_v<TTag, QueryEngineTag>, TypedValue>>
TReturnType GetProperty(const TRecordAccessor &record_accessor, PropertyIx prop) {
auto maybe_prop = record_accessor.GetProperty(prop.name);
// Handler non existent property
return conv_(maybe_prop);
}
template <class TRecordAccessor, class TTag = Tag,
class TReturnType = std::enable_if_t<std::is_same_v<TTag, QueryEngineTag>, TypedValue>>
TReturnType GetProperty(const TRecordAccessor &record_accessor, const std::string_view name) {
auto maybe_prop = record_accessor.GetProperty(std::string(name));
// Handler non existent property
return conv_(maybe_prop);
}
template <class TRecordAccessor, class TTag = Tag,
class TReturnType = std::enable_if_t<std::is_same_v<TTag, StorageTag>, TypedValue>>
template <class TRecordAccessor>
TypedValue GetProperty(const TRecordAccessor &record_accessor, PropertyIx prop) {
auto maybe_prop = record_accessor.GetProperty(view_, ctx_->properties[prop.ix]);
if (maybe_prop.HasError() && maybe_prop.GetError() == Error::NONEXISTENT_OBJECT) {
@@ -754,11 +719,10 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
throw ExpressionRuntimeException("Unexpected error when getting a property.");
}
}
return conv_(*maybe_prop, ctx_->memory);
return conv_(*maybe_prop);
}
template <class TRecordAccessor, class TTag = Tag,
class TReturnType = std::enable_if_t<std::is_same_v<TTag, StorageTag>, TypedValue>>
template <class TRecordAccessor>
TypedValue GetProperty(const TRecordAccessor &record_accessor, const std::string_view name) {
auto maybe_prop = record_accessor.GetProperty(view_, dba_->NameToProperty(name));
if (maybe_prop.HasError() && maybe_prop.GetError() == Error::NONEXISTENT_OBJECT) {
@@ -782,7 +746,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
throw ExpressionRuntimeException("Unexpected error when getting a property.");
}
}
return conv_(*maybe_prop, ctx_->memory);
return conv_(*maybe_prop);
}
LabelId GetLabel(LabelIx label) { return ctx_->labels[label.ix]; }
@@ -793,7 +757,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
DbAccessor *dba_;
// which switching approach should be used when evaluating
StorageView view_;
ConvFunctor conv_;
ConvFunction conv_;
};
/// A helper function for evaluating an expression that's an int.

View File

@@ -116,15 +116,13 @@ class TypedValueT {
return hash;
}
case TypedValueT::Type::Vertex:
return 34;
case TypedValueT::Type::Edge:
return 35;
return 0;
case TypedValueT::Type::Path: {
// const auto &vertices = value.ValuePath().vertices();
// const auto &edges = value.ValuePath().edges();
// return utils::FnvCollection<decltype(vertices), TVertexAccessor>{}(vertices) ^
// utils::FnvCollection<decltype(edges), TEdgeAccessor>{}(edges);
return 36;
const auto &vertices = value.ValuePath().vertices();
const auto &edges = value.ValuePath().edges();
return utils::FnvCollection<decltype(vertices), TVertexAccessor>{}(vertices) ^
utils::FnvCollection<decltype(edges), TEdgeAccessor>{}(edges);
}
case TypedValueT::Type::Date:
return utils::DateHash{}(value.ValueDate());

View File

@@ -99,7 +99,7 @@ class Shared {
bool IsReady() const {
std::unique_lock<std::mutex> lock(mu_);
return item_.has_value();
return item_;
}
std::optional<T> TryGet() {
@@ -148,6 +148,12 @@ class Future {
old.consumed_or_moved_ = true;
}
Future &operator=(Future &&old) noexcept {
MG_ASSERT(!old.consumed_or_moved_, "Future moved from after already being moved from or consumed.");
shared_ = std::move(old.shared_);
old.consumed_or_moved_ = true;
}
Future(const Future &) = delete;
Future &operator=(const Future &) = delete;
~Future() = default;

View File

@@ -25,7 +25,7 @@ class LocalSystem {
public:
Io<LocalTransport> Register(Address address) {
LocalTransport local_transport(local_transport_handle_);
LocalTransport local_transport(local_transport_handle_, address);
return Io{local_transport, address};
}

View File

@@ -25,16 +25,18 @@ namespace memgraph::io::local_transport {
class LocalTransport {
std::shared_ptr<LocalTransportHandle> local_transport_handle_;
const Address address_;
public:
explicit LocalTransport(std::shared_ptr<LocalTransportHandle> local_transport_handle)
: local_transport_handle_(std::move(local_transport_handle)) {}
LocalTransport(std::shared_ptr<LocalTransportHandle> local_transport_handle, Address address)
: local_transport_handle_(std::move(local_transport_handle)), address_(address) {}
template <Message RequestT, Message ResponseT>
ResponseFuture<ResponseT> Request(Address to_address, Address from_address, RequestId request_id, RequestT request,
Duration timeout) {
ResponseFuture<ResponseT> Request(Address to_address, RequestId request_id, RequestT request, Duration timeout) {
auto [future, promise] = memgraph::io::FuturePromisePair<ResponseResult<ResponseT>>();
Address from_address = address_;
local_transport_handle_->SubmitRequest(to_address, from_address, request_id, std::move(request), timeout,
std::move(promise));
@@ -42,8 +44,9 @@ class LocalTransport {
}
template <Message... Ms>
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Address receiver_address, Duration timeout) {
return local_transport_handle_->template Receive<Ms...>(receiver_address, timeout);
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Duration timeout) {
Address from_address = address_;
return local_transport_handle_->template Receive<Ms...>(timeout);
}
template <Message M>

View File

@@ -36,13 +36,6 @@ class LocalTransportHandle {
std::vector<OpaqueMessage> can_receive_;
public:
~LocalTransportHandle() {
for (auto &&[pk, promise] : promises_) {
std::move(promise.promise).TimeOut();
}
promises_.clear();
}
void ShutDown() {
std::unique_lock<std::mutex> lock(mu_);
should_shut_down_ = true;
@@ -60,13 +53,11 @@ class LocalTransportHandle {
}
template <Message... Ms>
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Address /* receiver_address */, Duration timeout) {
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Duration timeout) {
std::unique_lock lock(mu_);
Time before = Now();
spdlog::info("can_receive_ size: {}", can_receive_.size());
while (can_receive_.empty()) {
Time now = Now();
@@ -98,26 +89,23 @@ class LocalTransportHandle {
template <Message M>
void Send(Address to_address, Address from_address, RequestId request_id, M &&message) {
std::any message_any(std::forward<M>(message));
OpaqueMessage opaque_message{.to_address = to_address,
.from_address = from_address,
.request_id = request_id,
.message = std::move(message_any)};
OpaqueMessage opaque_message{
.from_address = from_address, .request_id = request_id, .message = std::move(message_any)};
PromiseKey promise_key{
.requester_address = to_address, .request_id = opaque_message.request_id, .replier_address = from_address};
PromiseKey promise_key{.requester_address = to_address,
.request_id = opaque_message.request_id,
.replier_address = opaque_message.from_address};
{
std::unique_lock<std::mutex> lock(mu_);
if (promises_.contains(promise_key)) {
spdlog::info("using message to fill promise");
// complete waiting promise if it's there
DeadlineAndOpaquePromise dop = std::move(promises_.at(promise_key));
promises_.erase(promise_key);
dop.promise.Fill(std::move(opaque_message));
} else {
spdlog::info("placing message in can_receive_");
can_receive_.emplace_back(std::move(opaque_message));
}
} // lock dropped

View File

@@ -203,30 +203,4 @@ struct DeadlineAndOpaquePromise {
OpaquePromise promise;
};
template <class From>
std::type_info const &type_info_for_variant(From const &from) {
return std::visit([](auto &&x) -> decltype(auto) { return typeid(x); }, from);
}
template <typename From, typename Return, typename Head, typename... Rest>
std::optional<Return> ConvertVariantInner(From &&a) {
if (typeid(Head) == type_info_for_variant(a)) {
Head concrete = std::get<Head>(std::forward<From>(a));
return concrete;
}
if constexpr (sizeof...(Rest) > 0) {
return ConvertVariantInner<From, Return, Rest...>(std::forward<From>(a));
} else {
return std::nullopt;
}
}
/// This function converts a variant to another variant holding a subset OR superset of
/// possible types.
template <class From, class... Ms>
requires(sizeof...(Ms) > 0) std::optional<std::variant<Ms...>> ConvertVariant(From &&from) {
return ConvertVariantInner<From, std::variant<Ms...>, Ms...>(std::forward<From>(from));
}
} // namespace memgraph::io

View File

@@ -1,46 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <variant>
#include <coordinator/coordinator.hpp>
#include <io/rsm/raft.hpp>
#include "query/v2/requests.hpp"
#include "utils/concepts.hpp"
namespace memgraph::io::messages {
using memgraph::coordinator::CoordinatorReadRequests;
using memgraph::coordinator::CoordinatorWriteRequests;
using memgraph::coordinator::CoordinatorWriteResponses;
using StorageReadRequest = msgs::ReadRequests;
using StorageWriteRequest = msgs::WriteRequests;
using memgraph::io::rsm::AppendRequest;
using memgraph::io::rsm::AppendResponse;
using memgraph::io::rsm::ReadRequest;
using memgraph::io::rsm::VoteRequest;
using memgraph::io::rsm::VoteResponse;
using memgraph::io::rsm::WriteRequest;
using memgraph::io::rsm::WriteResponse;
using CoordinatorMessages =
std::variant<ReadRequest<CoordinatorReadRequests>, AppendRequest<CoordinatorWriteRequests>, AppendResponse,
WriteRequest<CoordinatorWriteRequests>, VoteRequest, VoteResponse>;
using ShardMessages = std::variant<ReadRequest<StorageReadRequest>, AppendRequest<StorageWriteRequest>, AppendResponse,
WriteRequest<StorageWriteRequest>, VoteRequest, VoteResponse>;
using ShardManagerMessages = std::variant<WriteResponse<CoordinatorWriteResponses>>;
} // namespace memgraph::io::messages

View File

@@ -53,6 +53,7 @@ static constexpr size_t kMaximumAppendBatchSize = 1024;
using Term = uint64_t;
using LogIndex = uint64_t;
using LogSize = uint64_t;
using RequestId = uint64_t;
template <typename WriteOperation>
struct WriteRequest {
@@ -229,60 +230,22 @@ class Raft {
Io<IoImpl> io_;
std::vector<Address> peers_;
ReplicatedState replicated_state_;
Time next_cron_;
public:
Raft(Io<IoImpl> &&io, std::vector<Address> peers, ReplicatedState &&replicated_state)
: io_(std::forward<Io<IoImpl>>(io)),
peers_(peers),
replicated_state_(std::forward<ReplicatedState>(replicated_state)) {
if (peers.empty()) {
role_ = Leader{};
}
}
/// Periodic protocol maintenance. Returns the time that Cron should be called again
/// in the future.
Time Cron() {
// dispatch periodic logic based on our role to a specific Cron method.
std::optional<Role> new_role = std::visit([&](auto &role) { return Cron(role); }, role_);
if (new_role) {
role_ = std::move(new_role).value();
}
const Duration random_cron_interval = RandomTimeout(kMinimumCronInterval, kMaximumCronInterval);
return io_.Now() + random_cron_interval;
}
/// Returns the Address for our underlying Io implementation
Address GetAddress() { return io_.GetAddress(); }
using ReceiveVariant = std::variant<ReadRequest<ReadOperation>, AppendRequest<WriteOperation>, AppendResponse,
WriteRequest<WriteOperation>, VoteRequest, VoteResponse>;
void Handle(ReceiveVariant &&message_variant, RequestId request_id, Address from_address) {
// dispatch the message to a handler based on our role,
// which can be specified in the Handle first argument,
// or it can be `auto` if it's a handler for several roles
// or messages.
std::optional<Role> new_role = std::visit(
[&](auto &&msg, auto &role) mutable {
return Handle(role, std::forward<decltype(msg)>(msg), request_id, from_address);
},
std::forward<ReceiveVariant>(message_variant), role_);
// TODO(tyler) (M3) maybe replace std::visit with get_if for explicit prioritized matching, [[likely]] etc...
if (new_role) {
role_ = std::move(new_role).value();
}
}
replicated_state_(std::forward<ReplicatedState>(replicated_state)) {}
void Run() {
Time last_cron = io_.Now();
while (!io_.ShouldShutDown()) {
const auto now = io_.Now();
if (now >= next_cron_) {
next_cron_ = Cron();
const Duration random_cron_interval = RandomTimeout(kMinimumCronInterval, kMaximumCronInterval);
if (now - last_cron > random_cron_interval) {
Cron();
last_cron = now;
}
const Duration receive_timeout = RandomTimeout(kMinimumReceiveTimeout, kMaximumReceiveTimeout);
@@ -486,6 +449,16 @@ class Raft {
/// been received.
/////////////////////////////////////////////////////////////
/// Periodic protocol maintenance.
void Cron() {
// dispatch periodic logic based on our role to a specific Cron method.
std::optional<Role> new_role = std::visit([&](auto &role) { return Cron(role); }, role_);
if (new_role) {
role_ = std::move(new_role).value();
}
}
// Raft paper - 5.2
// Candidates keep sending Vote to peers until:
// 1. receiving Append with a higher term (become Follower)
@@ -568,6 +541,26 @@ class Raft {
/// message that has been received.
/////////////////////////////////////////////////////////////
using ReceiveVariant = std::variant<ReadRequest<ReadOperation>, AppendRequest<WriteOperation>, AppendResponse,
WriteRequest<WriteOperation>, VoteRequest, VoteResponse>;
void Handle(ReceiveVariant &&message_variant, RequestId request_id, Address from_address) {
// dispatch the message to a handler based on our role,
// which can be specified in the Handle first argument,
// or it can be `auto` if it's a handler for several roles
// or messages.
std::optional<Role> new_role = std::visit(
[&](auto &&msg, auto &role) mutable {
return Handle(role, std::forward<decltype(msg)>(msg), request_id, from_address);
},
std::forward<ReceiveVariant>(message_variant), role_);
// TODO(tyler) (M3) maybe replace std::visit with get_if for explicit prioritized matching, [[likely]] etc...
if (new_role) {
role_ = std::move(new_role).value();
}
}
// all roles can receive Vote and possibly become a follower
template <AllRoles ALL>
std::optional<Role> Handle(ALL & /* variable */, VoteRequest &&req, RequestId request_id, Address from_address) {
@@ -913,11 +906,7 @@ class Raft {
leader.pending_client_requests.emplace(log_index, pcr);
if (peers_.empty()) {
BumpCommitIndexAndReplyToClients(leader);
} else {
BroadcastAppendEntries(leader.followers);
}
BroadcastAppendEntries(leader.followers);
return std::nullopt;
}

View File

@@ -38,7 +38,7 @@ void SimulatorHandle::IncrementServerCountAndWaitForQuiescentState(Address addre
server_addresses_.insert(address);
while (true) {
const size_t blocked_servers = blocked_on_receive_.size();
const size_t blocked_servers = blocked_on_receive_;
const bool all_servers_blocked = blocked_servers == server_addresses_.size();
@@ -53,7 +53,7 @@ void SimulatorHandle::IncrementServerCountAndWaitForQuiescentState(Address addre
bool SimulatorHandle::MaybeTickSimulator() {
std::unique_lock<std::mutex> lock(mu_);
const size_t blocked_servers = blocked_on_receive_.size();
const size_t blocked_servers = blocked_on_receive_;
if (blocked_servers < server_addresses_.size()) {
// we only need to advance the simulator when all

View File

@@ -48,7 +48,7 @@ class SimulatorHandle {
Time cluster_wide_time_microseconds_;
bool should_shut_down_ = false;
SimulatorStats stats_;
std::set<Address> blocked_on_receive_;
size_t blocked_on_receive_ = 0;
std::set<Address> server_addresses_;
std::mt19937 rng_;
SimulatorConfig config_;
@@ -115,7 +115,7 @@ class SimulatorHandle {
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(const Address &receiver, Duration timeout) {
std::unique_lock<std::mutex> lock(mu_);
blocked_on_receive_.emplace(receiver);
blocked_on_receive_ += 1;
const Time deadline = cluster_wide_time_microseconds_ + timeout;
@@ -130,7 +130,7 @@ class SimulatorHandle {
// than asserting that the last item in can_rx matches.
auto m_opt = std::move(message).Take<Ms...>();
blocked_on_receive_.erase(receiver);
blocked_on_receive_ -= 1;
return std::move(m_opt).value();
}
@@ -144,7 +144,7 @@ class SimulatorHandle {
}
}
blocked_on_receive_.erase(receiver);
blocked_on_receive_ -= 1;
return TimedOut{};
}

View File

@@ -33,21 +33,19 @@ class SimulatorTransport {
: simulator_handle_(simulator_handle), address_(address), rng_(std::mt19937{seed}) {}
template <Message RequestT, Message ResponseT>
ResponseFuture<ResponseT> Request(Address to_address, Address from_address, uint64_t request_id, RequestT request,
Duration timeout) {
ResponseFuture<ResponseT> Request(Address address, uint64_t request_id, RequestT request, Duration timeout) {
std::function<bool()> maybe_tick_simulator = [this] { return simulator_handle_->MaybeTickSimulator(); };
auto [future, promise] =
memgraph::io::FuturePromisePairWithNotifier<ResponseResult<ResponseT>>(maybe_tick_simulator);
simulator_handle_->SubmitRequest(to_address, from_address, request_id, std::move(request), timeout,
std::move(promise));
simulator_handle_->SubmitRequest(address, address_, request_id, std::move(request), timeout, std::move(promise));
return std::move(future);
}
template <Message... Ms>
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Address receiver_address, Duration timeout) {
return simulator_handle_->template Receive<Ms...>(receiver_address, timeout);
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Duration timeout) {
return simulator_handle_->template Receive<Ms...>(address_, timeout);
}
template <Message M>

View File

@@ -83,26 +83,23 @@ class Io {
template <Message RequestT, Message ResponseT>
ResponseFuture<ResponseT> RequestWithTimeout(Address address, RequestT request, Duration timeout) {
const RequestId request_id = ++request_id_counter_;
const Address from_address = address_;
return implementation_.template Request<RequestT, ResponseT>(address, from_address, request_id, request, timeout);
return implementation_.template Request<RequestT, ResponseT>(address, request_id, request, timeout);
}
/// Issue a request that times out after the default timeout. This tends
/// to be used by clients.
template <Message RequestT, Message ResponseT>
ResponseFuture<ResponseT> Request(Address to_address, RequestT request) {
ResponseFuture<ResponseT> Request(Address address, RequestT request) {
const RequestId request_id = ++request_id_counter_;
const Duration timeout = default_timeout_;
const Address from_address = address_;
return implementation_.template Request<RequestT, ResponseT>(to_address, from_address, request_id,
std::move(request), timeout);
return implementation_.template Request<RequestT, ResponseT>(address, request_id, std::move(request), timeout);
}
/// Wait for an explicit number of microseconds for a request of one of the
/// provided types to arrive. This tends to be used by servers.
template <Message... Ms>
RequestResult<Ms...> ReceiveWithTimeout(Duration timeout) {
return implementation_.template Receive<Ms...>(address_, timeout);
return implementation_.template Receive<Ms...>(timeout);
}
/// Wait the default number of microseconds for a request of one of the
@@ -110,7 +107,7 @@ class Io {
template <Message... Ms>
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive() {
const Duration timeout = default_timeout_;
return implementation_.template Receive<Ms...>(address_, timeout);
return implementation_.template Receive<Ms...>(timeout);
}
/// Send a message in a best-effort fashion. This is used for messaging where
@@ -137,8 +134,5 @@ class Io {
}
Address GetAddress() { return address_; }
void SetAddress(Address address) { address_ = address; }
Io<I> ForkLocal() { return Io(implementation_, address_.ForkUniqueAddress()); }
};
}; // namespace memgraph::io

View File

@@ -1,42 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <boost/asio/ip/tcp.hpp>
#include "io/address.hpp"
#include "storage/v3/property_value.hpp"
#include "storage/v3/schemas.hpp"
namespace memgraph::machine_manager {
using memgraph::io::Address;
using memgraph::storage::v3::SchemaProperty;
using CompoundKey = std::vector<memgraph::storage::v3::PropertyValue>;
struct InitialLabelSpace {
std::string label_name;
std::vector<SchemaProperty> schema;
size_t replication_factor;
std::vector<CompoundKey> split_points;
};
struct MachineConfig {
std::vector<InitialLabelSpace> initial_label_spaces;
std::vector<Address> coordinator_addresses;
bool is_storage;
bool is_coordinator;
bool is_query_engine;
boost::asio::ip::address listen_ip;
uint16_t listen_port;
};
} // namespace memgraph::machine_manager

View File

@@ -1,177 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <coordinator/coordinator_rsm.hpp>
#include <io/message_conversion.hpp>
#include <io/messages.hpp>
#include <io/rsm/rsm_client.hpp>
#include <io/time.hpp>
#include <machine_manager/machine_config.hpp>
#include <storage/v3/shard_manager.hpp>
namespace memgraph::machine_manager {
using memgraph::coordinator::Coordinator;
using memgraph::coordinator::CoordinatorReadRequests;
using memgraph::coordinator::CoordinatorReadResponses;
using memgraph::coordinator::CoordinatorRsm;
using memgraph::coordinator::CoordinatorWriteRequests;
using memgraph::coordinator::CoordinatorWriteResponses;
using memgraph::io::ConvertVariant;
using memgraph::io::Duration;
using memgraph::io::RequestId;
using memgraph::io::Time;
using memgraph::io::messages::CoordinatorMessages;
using memgraph::io::messages::ShardManagerMessages;
using memgraph::io::messages::ShardMessages;
using memgraph::io::messages::StorageReadRequest;
using memgraph::io::messages::StorageWriteRequest;
using memgraph::io::rsm::AppendRequest;
using memgraph::io::rsm::AppendResponse;
using memgraph::io::rsm::ReadRequest;
using memgraph::io::rsm::VoteRequest;
using memgraph::io::rsm::VoteResponse;
using memgraph::io::rsm::WriteRequest;
using memgraph::io::rsm::WriteResponse;
using memgraph::storage::v3::ShardManager;
/// The MachineManager is responsible for:
/// * starting the entire system and ensuring that high-level
/// operational requirements continue to be met
/// * acting as a machine's single caller of Io::Receive
/// * routing incoming messages from the Io interface to the
/// appropriate Coordinator or to the StorageManager
/// (it's not necessary to route anything in this layer
/// to the query engine because the query engine only
/// communicates using higher-level Futures that will
/// be filled immediately when the response comes in
/// at the lower-level transport layer.
///
/// Every storage engine has exactly one RsmEngine.
template <typename IoImpl>
class MachineManager {
io::Io<IoImpl> io_;
MachineConfig config_;
CoordinatorRsm<IoImpl> coordinator_;
ShardManager<IoImpl> shard_manager_;
Time next_cron_;
public:
// TODO initialize ShardManager with "real" coordinator addresses instead of io.GetAddress
// which is only true for single-machine config.
MachineManager(io::Io<IoImpl> io, MachineConfig config, Coordinator coordinator)
: io_(io),
config_(config),
coordinator_{std::move(io.ForkLocal()), {}, std::move(coordinator)},
shard_manager_(ShardManager{io.ForkLocal(), coordinator_.GetAddress()}) {}
Address CoordinatorAddress() { return coordinator_.GetAddress(); }
void Run() {
while (!io_.ShouldShutDown()) {
const auto now = io_.Now();
if (now >= next_cron_) {
next_cron_ = Cron();
}
Duration receive_timeout = next_cron_ - now;
// Note: this parameter pack must be kept in-sync with the ReceiveWithTimeout parameter pack below
using AllMessages =
std::variant<ReadRequest<CoordinatorReadRequests>, AppendRequest<CoordinatorWriteRequests>, AppendResponse,
WriteRequest<CoordinatorWriteRequests>, VoteRequest, VoteResponse,
WriteResponse<CoordinatorWriteResponses>, ReadRequest<StorageReadRequest>,
AppendRequest<StorageWriteRequest>, WriteRequest<StorageWriteRequest>>;
spdlog::info("MM waiting on Receive");
// Note: this parameter pack must be kept in-sync with the AllMessages parameter pack above
auto request_result = io_.template ReceiveWithTimeout<
ReadRequest<CoordinatorReadRequests>, AppendRequest<CoordinatorWriteRequests>, AppendResponse,
WriteRequest<CoordinatorWriteRequests>, VoteRequest, VoteResponse, WriteResponse<CoordinatorWriteResponses>,
ReadRequest<StorageReadRequest>, AppendRequest<StorageWriteRequest>, WriteRequest<StorageWriteRequest>>(
receive_timeout);
if (request_result.HasError()) {
// time to do Cron
spdlog::info("MM got timeout");
continue;
}
auto &&request_envelope = std::move(request_result.GetValue());
spdlog::info("MM got message to {}", request_envelope.to_address.ToString());
// If message is for the coordinator, cast it to subset and pass it to the coordinator
bool to_coordinator = coordinator_.GetAddress() == request_envelope.to_address;
spdlog::info("coordinator: {}", coordinator_.GetAddress().ToString());
if (to_coordinator) {
std::optional<CoordinatorMessages> conversion_attempt =
ConvertVariant<AllMessages, ReadRequest<CoordinatorReadRequests>, AppendRequest<CoordinatorWriteRequests>,
AppendResponse, WriteRequest<CoordinatorWriteRequests>, VoteRequest, VoteResponse>(
std::move(request_envelope.message));
MG_ASSERT(conversion_attempt.has_value(), "coordinator message conversion failed");
spdlog::info("got coordinator message");
CoordinatorMessages &&cm = std::move(conversion_attempt.value());
coordinator_.Handle(std::forward<CoordinatorMessages>(cm), request_envelope.request_id,
request_envelope.from_address);
continue;
}
bool to_sm = shard_manager_.GetAddress() == request_envelope.to_address;
spdlog::info("smm: {}", shard_manager_.GetAddress().ToString());
if (to_sm) {
std::optional<ShardManagerMessages> conversion_attempt =
ConvertVariant<AllMessages, WriteResponse<CoordinatorWriteResponses>>(std::move(request_envelope.message));
MG_ASSERT(conversion_attempt.has_value(), "shard manager message conversion failed");
spdlog::info("got shard manager message");
ShardManagerMessages &&smm = std::move(conversion_attempt.value());
shard_manager_.Receive(std::forward<ShardManagerMessages>(smm), request_envelope.request_id,
request_envelope.from_address);
continue;
}
// treat this as a message to a specific shard rsm and cast it accordingly
std::optional<ShardMessages> conversion_attempt =
ConvertVariant<AllMessages, ReadRequest<StorageReadRequest>, AppendRequest<StorageWriteRequest>,
AppendResponse, WriteRequest<StorageWriteRequest>, VoteRequest, VoteResponse>(
std::move(request_envelope.message));
MG_ASSERT(conversion_attempt.has_value(), "shard rsm message conversion failed for {}",
request_envelope.to_address.ToString());
spdlog::info("got shard rsm message");
ShardMessages &&sm = std::move(conversion_attempt.value());
shard_manager_.Route(std::forward<ShardMessages>(sm), request_envelope.request_id, request_envelope.to_address,
request_envelope.from_address);
}
}
private:
Time Cron() {
spdlog::info("running MachineManager::Cron, address {}", io_.GetAddress().ToString());
return shard_manager_.Cron();
}
};
} // namespace memgraph::machine_manager

View File

@@ -23,18 +23,17 @@ set(mg_query_v2_sources
plan/rewrite/index_lookup.cpp
plan/rule_based_planner.cpp
plan/variable_start_planner.cpp
# procedure/mg_procedure_impl.cpp
# procedure/mg_procedure_helpers.cpp
# procedure/module.cpp
# procedure/py_module.cpp
procedure/mg_procedure_impl.cpp
procedure/mg_procedure_helpers.cpp
procedure/module.cpp
procedure/py_module.cpp
serialization/property_value.cpp
# stream/streams.cpp
# stream/sources.cpp
# stream/common.cpp
# trigger.cpp
# trigger_context.cpp
bindings/typed_value.cpp
accessors.cpp)
stream/streams.cpp
stream/sources.cpp
stream/common.cpp
trigger.cpp
trigger_context.cpp
bindings/typed_value.cpp)
find_package(Boost REQUIRED)

View File

@@ -1,75 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "query/v2/accessors.hpp"
#include "query/v2/requests.hpp"
namespace memgraph::query::v2::accessors {
EdgeAccessor::EdgeAccessor(Edge edge, std::vector<std::pair<PropertyId, Value>> props)
: edge(std::move(edge)), properties(std::move(props)) {}
uint64_t EdgeAccessor::EdgeType() const { return edge.type.id; }
std::vector<std::pair<PropertyId, Value>> EdgeAccessor::Properties() const {
return properties;
// std::map<std::string, TypedValue> res;
// for (const auto &[name, value] : *properties) {
// res[name] = ValueToTypedValue(value);
// }
// return res;
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
Value EdgeAccessor::GetProperty(const std::string & /*prop_name*/) const {
// TODO(kostasrim) fix this
return {};
}
Edge EdgeAccessor::GetEdge() const { return edge; }
VertexAccessor EdgeAccessor::To() const { return VertexAccessor(Vertex{edge.dst}, {}); }
VertexAccessor EdgeAccessor::From() const { return VertexAccessor(Vertex{edge.src}, {}); }
VertexAccessor::VertexAccessor(Vertex v, std::vector<std::pair<PropertyId, Value>> props)
: vertex(std::move(v)), properties(std::move(props)) {}
std::vector<Label> VertexAccessor::Labels() const { return vertex.labels; }
bool VertexAccessor::HasLabel(Label &label) const {
return std::find_if(vertex.labels.begin(), vertex.labels.end(),
[label](const auto &l) { return l.id == label.id; }) != vertex.labels.end();
}
std::vector<std::pair<PropertyId, Value>> VertexAccessor::Properties() const {
// std::map<std::string, TypedValue> res;
// for (const auto &[name, value] : *properties) {
// res[name] = ValueToTypedValue(value);
// }
// return res;
return properties;
}
Value VertexAccessor::GetProperty(PropertyId prop_id) const {
return std::find_if(properties.begin(), properties.end(), [&](auto &pr) { return prop_id == pr.first; })->second;
// return ValueToTypedValue(properties[prop_name]);
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
Value VertexAccessor::GetProperty(const std::string & /*prop_name*/) const {
// TODO(kostasrim) Add string mapping
return {};
// return ValueToTypedValue(properties[prop_name]);
}
msgs::Vertex VertexAccessor::GetVertex() const { return vertex; }
} // namespace memgraph::query::v2::accessors

View File

@@ -1,187 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <optional>
#include <utility>
#include <vector>
#include "query/exceptions.hpp"
#include "query/v2/requests.hpp"
#include "storage/v3/view.hpp"
#include "utils/bound.hpp"
#include "utils/exceptions.hpp"
#include "utils/memory.hpp"
#include "utils/memory_tracker.hpp"
namespace memgraph::query::v2::accessors {
using Value = memgraph::msgs::Value;
using Edge = memgraph::msgs::Edge;
using Vertex = memgraph::msgs::Vertex;
using Label = memgraph::msgs::Label;
using PropertyId = memgraph::msgs::PropertyId;
class VertexAccessor;
class EdgeAccessor final {
public:
EdgeAccessor(Edge edge, std::vector<std::pair<PropertyId, Value>> props);
uint64_t EdgeType() const;
std::vector<std::pair<PropertyId, Value>> Properties() const;
Value GetProperty(const std::string &prop_name) const;
Edge GetEdge() const;
// Dummy function
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
inline size_t CypherId() const { return 10; }
// bool HasSrcAccessor const { return src == nullptr; }
// bool HasDstAccessor const { return dst == nullptr; }
VertexAccessor To() const;
VertexAccessor From() const;
friend bool operator==(const EdgeAccessor &lhs, const EdgeAccessor &rhs) {
return lhs.edge == rhs.edge && lhs.properties == rhs.properties;
}
friend bool operator!=(const EdgeAccessor &lhs, const EdgeAccessor &rhs) { return !(lhs == rhs); }
private:
Edge edge;
std::vector<std::pair<PropertyId, Value>> properties;
};
class VertexAccessor final {
public:
using PropertyId = msgs::PropertyId;
using Label = msgs::Label;
VertexAccessor(Vertex v, std::vector<std::pair<PropertyId, Value>> props);
std::vector<Label> Labels() const;
bool HasLabel(Label &label) const;
std::vector<std::pair<PropertyId, Value>> Properties() const;
Value GetProperty(PropertyId prop_id) const;
Value GetProperty(const std::string &prop_name) const;
msgs::Vertex GetVertex() const;
// Dummy function
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
inline size_t CypherId() const { return 10; }
// auto InEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types) const
// -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.InEdges(view)))> {
// auto maybe_edges = impl_.InEdges(view, edge_types);
// if (maybe_edges.HasError()) return maybe_edges.GetError();
// return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
// }
//
// auto InEdges(storage::View view) const { return InEdges(view, {}); }
//
// auto InEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types, const VertexAccessor &dest)
// const
// -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.InEdges(view)))> {
// auto maybe_edges = impl_.InEdges(view, edge_types, &dest.impl_);
// if (maybe_edges.HasError()) return maybe_edges.GetError();
// return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
// }
//
// auto OutEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types) const
// -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.OutEdges(view)))> {
// auto maybe_edges = impl_.OutEdges(view, edge_types);
// if (maybe_edges.HasError()) return maybe_edges.GetError();
// return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
// }
//
// auto OutEdges(storage::View view) const { return OutEdges(view, {}); }
//
// auto OutEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types,
// const VertexAccessor &dest) const
// -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.OutEdges(view)))> {
// auto maybe_edges = impl_.OutEdges(view, edge_types, &dest.impl_);
// if (maybe_edges.HasError()) return maybe_edges.GetError();
// return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
// }
// storage::Result<size_t> InDegree(storage::View view) const { return impl_.InDegree(view); }
//
// storage::Result<size_t> OutDegree(storage::View view) const { return impl_.OutDegree(view); }
//
friend bool operator==(const VertexAccessor &lhs, const VertexAccessor &rhs) {
return lhs.vertex == rhs.vertex && lhs.properties == rhs.properties;
}
friend bool operator!=(const VertexAccessor &lhs, const VertexAccessor &rhs) { return !(lhs == rhs); }
private:
Vertex vertex;
std::vector<std::pair<PropertyId, Value>> properties;
};
// inline VertexAccessor EdgeAccessor::To() const { return VertexAccessor(impl_.ToVertex()); }
// inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.FromVertex()); }
// Highly mocked interface. Won't work if used.
class Path {
public:
// Empty for now
explicit Path(const VertexAccessor & /*vertex*/, utils::MemoryResource *memory = utils::NewDeleteResource())
: mem(memory) {}
template <typename... TOthers>
explicit Path(const VertexAccessor &vertex, const TOthers &...others) {}
template <typename... TOthers>
Path(std::allocator_arg_t /*unused*/, utils::MemoryResource *memory, const VertexAccessor &vertex,
const TOthers &...others) {}
Path(const Path & /*other*/) {}
Path(const Path & /*other*/, utils::MemoryResource *memory) : mem(memory) {}
Path(Path && /*other*/) noexcept {}
Path(Path && /*other*/, utils::MemoryResource *memory) : mem(memory) {}
Path &operator=(const Path &path) {
if (this == &path) {
return *this;
}
return *this;
}
Path &operator=(Path &&path) noexcept {
if (this == &path) {
return *this;
}
return *this;
}
~Path() {}
friend bool operator==(const Path & /*lhs*/, const Path & /*rhs*/) { return true; };
utils::MemoryResource *GetMemoryResource() { return mem; }
private:
utils::MemoryResource *mem = utils::NewDeleteResource();
};
} // namespace memgraph::query::v2::accessors

View File

@@ -16,29 +16,19 @@
#include "expr/interpret/eval.hpp"
#include "query/v2/bindings/typed_value.hpp"
#include "query/v2/context.hpp"
#include "query/v2/conversions.hpp"
#include "query/v2/db_accessor.hpp"
#include "query/v2/requests.hpp"
#include "storage/v3/conversions.hpp"
#include "storage/v3/property_value.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/property_store.hpp"
#include "storage/v3/view.hpp"
#include "storage/v3/conversions.hpp"
namespace memgraph::query::v2 {
inline const auto lam = [](const auto &val) { return ValueToTypedValue(val); };
namespace detail {
class Callable {
public:
auto operator()(const memgraph::storage::v3::PropertyValue &val) const {
return memgraph::storage::v3::PropertyToTypedValue<TypedValue>(val);
};
auto operator()(const msgs::Value &val) const { return ValueToTypedValue(val); };
};
inline const auto lam = [](const auto &val) { return memgraph::storage::v3::PropertyToTypedValue<TypedValue>(val); };
} // namespace detail
using ExpressionEvaluator =
memgraph::expr::ExpressionEvaluator<TypedValue, memgraph::query::v2::EvaluationContext, DbAccessor,
storage::v3::View, storage::v3::LabelId, msgs::Value, detail::Callable,
memgraph::storage::v3::Error, memgraph::expr::QueryEngineTag>;
memgraph::expr::ExpressionEvaluator<TypedValue, EvaluationContext, DbAccessor, storage::v3::View,
storage::v3::LabelId, storage::v3::PropertyStore, decltype(lam),
memgraph::storage::v3::Error>;
} // namespace memgraph::query::v2

View File

@@ -10,10 +10,10 @@
// licenses/APL.txt.
#include "expr/typed_value.hpp"
#include "query/v2/accessors.hpp"
#include "query/v2/db_accessor.hpp"
#include "query/v2/path.hpp"
namespace memgraph::expr {
namespace v2 = memgraph::query::v2;
template class TypedValueT<v2::accessors::VertexAccessor, v2::accessors::EdgeAccessor, v2::accessors::Path>;
template class TypedValueT<v2::VertexAccessor, v2::EdgeAccessor, v2::Path>;
} // namespace memgraph::expr

View File

@@ -14,14 +14,13 @@
#include "query/v2/bindings/bindings.hpp"
#include "expr/typed_value.hpp"
#include "query/v2/accessors.hpp"
#include "query/v2/db_accessor.hpp"
#include "query/v2/path.hpp"
namespace memgraph::expr {
namespace v2 = memgraph::query::v2;
extern template class memgraph::expr::TypedValueT<v2::accessors::VertexAccessor, v2::accessors::EdgeAccessor,
v2::accessors::Path>;
extern template class memgraph::expr::TypedValueT<v2::VertexAccessor, v2::EdgeAccessor, v2::Path>;
} // namespace memgraph::expr
namespace memgraph::query::v2 {
using TypedValue =
memgraph::expr::TypedValueT<v2::accessors::VertexAccessor, v2::accessors::EdgeAccessor, v2::accessors::Path>;
using TypedValue = memgraph::expr::TypedValueT<VertexAccessor, EdgeAccessor, Path>;
} // namespace memgraph::query::v2

View File

@@ -18,8 +18,7 @@
#include "query/v2/metadata.hpp"
#include "query/v2/parameters.hpp"
#include "query/v2/plan/profile.hpp"
//#include "query/v2/trigger.hpp"
#include "query/v2/shard_request_manager.hpp"
#include "query/v2/trigger.hpp"
#include "utils/async_timer.hpp"
namespace memgraph::query::v2 {
@@ -71,9 +70,8 @@ struct ExecutionContext {
plan::ProfilingStats stats;
plan::ProfilingStats *stats_root{nullptr};
ExecutionStats execution_stats;
// TriggerContextCollector *trigger_context_collector{nullptr};
TriggerContextCollector *trigger_context_collector{nullptr};
utils::AsyncTimer timer;
std::unique_ptr<msgs::ShardRequestManagerInterface> shard_request_manager{nullptr};
};
static_assert(std::is_move_assignable_v<ExecutionContext>, "ExecutionContext must be move assignable!");

View File

@@ -1,100 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "bindings/typed_value.hpp"
#include "query/v2/accessors.hpp"
#include "query/v2/requests.hpp"
namespace memgraph::query::v2 {
inline TypedValue ValueToTypedValue(const msgs::Value &value) {
using Value = msgs::Value;
switch (value.type) {
case Value::Type::Null:
return {};
case Value::Type::Bool:
return TypedValue(value.bool_v);
case Value::Type::Int64:
return TypedValue(value.int_v);
case Value::Type::Double:
return TypedValue(value.double_v);
case Value::Type::String:
return TypedValue(value.string_v);
case Value::Type::List: {
const auto &lst = value.list_v;
std::vector<TypedValue> dst;
dst.reserve(lst.size());
for (const auto &elem : lst) {
dst.push_back(ValueToTypedValue(elem));
}
return TypedValue(std::move(dst));
}
case Value::Type::Map: {
const auto &value_map = value.map_v;
std::map<std::string, TypedValue> dst;
for (const auto &[key, val] : value_map) {
dst[key] = ValueToTypedValue(val);
}
return TypedValue(std::move(dst));
}
case Value::Type::Vertex:
return TypedValue(accessors::VertexAccessor(value.vertex_v, {}));
case Value::Type::Edge:
return TypedValue(accessors::EdgeAccessor(value.edge_v, {}));
case Value::Type::Path:
break;
}
throw std::runtime_error("Incorrect type in conversion");
}
inline msgs::Value TypedValueToValue(const TypedValue &value) {
using Value = msgs::Value;
switch (value.type()) {
case TypedValue::Type::Null:
return {};
case TypedValue::Type::Bool:
return Value(value.ValueBool());
case TypedValue::Type::Int:
return Value(value.ValueInt());
case TypedValue::Type::Double:
return Value(value.ValueDouble());
case TypedValue::Type::String:
return Value(std::string(value.ValueString()));
case TypedValue::Type::List: {
const auto &lst = value.ValueList();
std::vector<Value> dst;
dst.reserve(lst.size());
for (const auto &elem : lst) {
dst.push_back(TypedValueToValue(elem));
}
return Value(std::move(dst));
}
case TypedValue::Type::Map: {
const auto &value_map = value.ValueMap();
std::map<std::string, Value> dst;
for (const auto &[key, val] : value_map) {
dst[std::string(key)] = TypedValueToValue(val);
}
return Value(std::move(dst));
}
case TypedValue::Type::Vertex:
return Value(value.ValueVertex().GetVertex());
case TypedValue::Type::Edge:
return Value(value.ValueEdge().GetEdge());
case TypedValue::Type::Path:
default:
break;
}
throw std::runtime_error("Incorrect type in conversion");
}
} // namespace memgraph::query::v2

View File

@@ -263,6 +263,8 @@ class DbAccessor final {
return std::nullopt;
}
void FinalizeTransaction() { accessor_->FinalizeTransaction(); }
VerticesIterable Vertices(storage::v3::View view) { return VerticesIterable(accessor_->Vertices(view)); }
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label) {
@@ -358,13 +360,17 @@ class DbAccessor final {
// TODO(jbajic) Query engine should have a map of labels, properties and edge
// types
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
storage::v3::PropertyId NameToProperty(const std::string_view name) { return accessor_->NameToProperty(name); }
storage::v3::PropertyId NameToProperty(const std::string_view /*name*/) {
return storage::v3::PropertyId::FromUint(0);
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
storage::v3::LabelId NameToLabel(const std::string_view name) { return accessor_->NameToLabel(name); }
storage::v3::LabelId NameToLabel(const std::string_view /*name*/) { return storage::v3::LabelId::FromUint(0); }
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
storage::v3::EdgeTypeId NameToEdgeType(const std::string_view name) { return accessor_->NameToEdgeType(name); }
storage::v3::EdgeTypeId NameToEdgeType(const std::string_view /*name*/) {
return storage::v3::EdgeTypeId::FromUint(0);
}
const std::string &PropertyToName(storage::v3::PropertyId prop) const { return accessor_->PropertyToName(prop); }
@@ -374,7 +380,7 @@ class DbAccessor final {
void AdvanceCommand() { accessor_->AdvanceCommand(); }
void Commit() { return accessor_->Commit(coordinator::Hlc{}); }
utils::BasicResult<storage::v3::ConstraintViolation, void> Commit() { return accessor_->Commit(); }
void Abort() { accessor_->Abort(); }
@@ -405,6 +411,8 @@ class DbAccessor final {
storage::v3::IndicesInfo ListAllIndices() const { return accessor_->ListAllIndices(); }
storage::v3::ConstraintsInfo ListAllConstraints() const { return accessor_->ListAllConstraints(); }
const storage::v3::SchemaValidator &GetSchemaValidator() const { return accessor_->GetSchemaValidator(); }
storage::v3::SchemasInfo ListAllSchemas() const { return accessor_->ListAllSchemas(); }

View File

@@ -269,6 +269,10 @@ PullPlanDump::PullPlanDump(DbAccessor *dba)
CreateLabelIndicesPullChunk(),
// Dump all label property indices
CreateLabelPropertyIndicesPullChunk(),
// Dump all existence constraints
CreateExistenceConstraintsPullChunk(),
// Dump all unique constraints
CreateUniqueConstraintsPullChunk(),
// Create internal index for faster edge creation
CreateInternalIndexPullChunk(),
// Dump all vertices
@@ -360,6 +364,60 @@ PullPlanDump::PullChunk PullPlanDump::CreateLabelPropertyIndicesPullChunk() {
};
}
PullPlanDump::PullChunk PullPlanDump::CreateExistenceConstraintsPullChunk() {
return [this, global_index = 0U](AnyStream *stream, std::optional<int> n) mutable -> std::optional<size_t> {
// Delay the construction of constraint vectors
if (!constraints_info_) {
constraints_info_.emplace(dba_->ListAllConstraints());
}
const auto &existence = constraints_info_->existence;
size_t local_counter = 0;
while (global_index < existence.size() && (!n || local_counter < *n)) {
const auto &constraint = existence[global_index];
std::ostringstream os;
DumpExistenceConstraint(&os, dba_, constraint.first, constraint.second);
stream->Result({TypedValue(os.str())});
++global_index;
++local_counter;
}
if (global_index == existence.size()) {
return local_counter;
}
return std::nullopt;
};
}
PullPlanDump::PullChunk PullPlanDump::CreateUniqueConstraintsPullChunk() {
return [this, global_index = 0U](AnyStream *stream, std::optional<int> n) mutable -> std::optional<size_t> {
// Delay the construction of constraint vectors
if (!constraints_info_) {
constraints_info_.emplace(dba_->ListAllConstraints());
}
const auto &unique = constraints_info_->unique;
size_t local_counter = 0;
while (global_index < unique.size() && (!n || local_counter < *n)) {
const auto &constraint = unique[global_index];
std::ostringstream os;
DumpUniqueConstraint(&os, dba_, constraint.first, constraint.second);
stream->Result({TypedValue(os.str())});
++global_index;
++local_counter;
}
if (global_index == unique.size()) {
return local_counter;
}
return std::nullopt;
};
}
PullPlanDump::PullChunk PullPlanDump::CreateInternalIndexPullChunk() {
return [this](AnyStream *stream, std::optional<int>) mutable -> std::optional<size_t> {
if (vertices_iterable_.begin() != vertices_iterable_.end()) {

View File

@@ -32,6 +32,7 @@ struct PullPlanDump {
query::v2::DbAccessor *dba_ = nullptr;
std::optional<storage::v3::IndicesInfo> indices_info_ = std::nullopt;
std::optional<storage::v3::ConstraintsInfo> constraints_info_ = std::nullopt;
using VertexAccessorIterable = decltype(std::declval<query::v2::DbAccessor>().Vertices(storage::v3::View::OLD));
using VertexAccessorIterableIterator = decltype(std::declval<VertexAccessorIterable>().begin());
@@ -54,6 +55,8 @@ struct PullPlanDump {
PullChunk CreateLabelIndicesPullChunk();
PullChunk CreateLabelPropertyIndicesPullChunk();
PullChunk CreateExistenceConstraintsPullChunk();
PullChunk CreateUniqueConstraintsPullChunk();
PullChunk CreateInternalIndexPullChunk();
PullChunk CreateVertexPullChunk();
PullChunk CreateEdgePullChunk();

View File

@@ -11,6 +11,7 @@
#include "query/v2/bindings/ast_visitor.hpp"
#include "query/v2/frontend/ast/ast.hpp"
#include "query/v2/procedure/module.hpp"
#include "utils/memory.hpp"
namespace memgraph::query::v2 {
@@ -85,7 +86,12 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
AddPrivilege(AuthQuery::Privilege::CREATE);
return false;
}
bool PreVisit(CallProcedure & /*procedure*/) override {
bool PreVisit(CallProcedure &procedure) override {
const auto maybe_proc =
procedure::FindProcedure(procedure::gModuleRegistry, procedure.procedure_name_, utils::NewDeleteResource());
if (maybe_proc && maybe_proc->second->info.required_privilege) {
AddPrivilege(*maybe_proc->second->info.required_privilege);
}
return false;
}
bool PreVisit(Delete & /*unused*/) override {

View File

@@ -21,12 +21,11 @@
#include <type_traits>
#include "query/v2/bindings/typed_value.hpp"
#include "query/v2/conversions.hpp"
#include "query/v2/db_accessor.hpp"
#include "query/v2/exceptions.hpp"
#include "query/v2/procedure/cypher_types.hpp"
//#include "query/v2/procedure/mg_procedure_impl.hpp"
//#include "query/v2/procedure/module.hpp"
#include "query/v2/procedure/mg_procedure_impl.hpp"
#include "query/v2/procedure/module.hpp"
#include "storage/v3/conversions.hpp"
#include "utils/string.hpp"
#include "utils/temporal.hpp"
@@ -388,7 +387,39 @@ TypedValue Last(const TypedValue *args, int64_t nargs, const FunctionContext &ct
return TypedValue(list.back(), ctx.memory);
}
TypedValue Properties(const TypedValue * /*args*/, int64_t /*nargs*/, const FunctionContext & /*ctx*/) { return {}; }
TypedValue Properties(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
FType<Or<Null, Vertex, Edge>>("properties", args, nargs);
auto *dba = ctx.db_accessor;
auto get_properties = [&](const auto &record_accessor) {
TypedValue::TMap properties(ctx.memory);
auto maybe_props = record_accessor.Properties(ctx.view);
if (maybe_props.HasError()) {
switch (maybe_props.GetError()) {
case storage::v3::Error::DELETED_OBJECT:
throw QueryRuntimeException("Trying to get properties from a deleted object.");
case storage::v3::Error::NONEXISTENT_OBJECT:
throw query::v2::QueryRuntimeException("Trying to get properties from an object that doesn't exist.");
case storage::v3::Error::SERIALIZATION_ERROR:
case storage::v3::Error::VERTEX_HAS_EDGES:
case storage::v3::Error::PROPERTIES_DISABLED:
throw QueryRuntimeException("Unexpected error when getting properties.");
}
}
for (const auto &property : *maybe_props) {
properties.emplace(dba->PropertyToName(property.first),
storage::v3::PropertyToTypedValue<TypedValue>(property.second));
}
return TypedValue(std::move(properties));
};
const auto &value = args[0];
if (value.IsNull()) {
return TypedValue(ctx.memory);
} else if (value.IsVertex()) {
return get_properties(value.ValueVertex());
} else {
return get_properties(value.ValueEdge());
}
}
TypedValue Size(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
FType<Or<Null, List, String, Map, Path>>("size", args, nargs);
@@ -404,8 +435,7 @@ TypedValue Size(const TypedValue *args, int64_t nargs, const FunctionContext &ct
// to do it.
return TypedValue(static_cast<int64_t>(value.ValueMap().size()), ctx.memory);
} else {
// TODO(kostasrim) Fix the dummy return
return TypedValue(int64_t(0), ctx.memory);
return TypedValue(static_cast<int64_t>(value.ValuePath().edges().size()), ctx.memory);
}
}
@@ -439,22 +469,25 @@ TypedValue Degree(const TypedValue *args, int64_t nargs, const FunctionContext &
FType<Or<Null, Vertex>>("degree", args, nargs);
if (args[0].IsNull()) return TypedValue(ctx.memory);
const auto &vertex = args[0].ValueVertex();
// TODO(kostasrim) Fix dummy values
return TypedValue(int64_t(0), ctx.memory);
size_t out_degree = UnwrapDegreeResult(vertex.OutDegree(ctx.view));
size_t in_degree = UnwrapDegreeResult(vertex.InDegree(ctx.view));
return TypedValue(static_cast<int64_t>(out_degree + in_degree), ctx.memory);
}
TypedValue InDegree(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
FType<Or<Null, Vertex>>("inDegree", args, nargs);
if (args[0].IsNull()) return TypedValue(ctx.memory);
const auto &vertex = args[0].ValueVertex();
return TypedValue(int64_t(0), ctx.memory);
size_t in_degree = UnwrapDegreeResult(vertex.InDegree(ctx.view));
return TypedValue(static_cast<int64_t>(in_degree), ctx.memory);
}
TypedValue OutDegree(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
FType<Or<Null, Vertex>>("outDegree", args, nargs);
if (args[0].IsNull()) return TypedValue(ctx.memory);
const auto &vertex = args[0].ValueVertex();
return TypedValue(int64_t(0), ctx.memory);
size_t out_degree = UnwrapDegreeResult(vertex.OutDegree(ctx.view));
return TypedValue(static_cast<int64_t>(out_degree), ctx.memory);
}
TypedValue ToBoolean(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
@@ -520,7 +553,7 @@ TypedValue Type(const TypedValue *args, int64_t nargs, const FunctionContext &ct
FType<Or<Null, Edge>>("type", args, nargs);
auto *dba = ctx.db_accessor;
if (args[0].IsNull()) return TypedValue(ctx.memory);
return TypedValue(static_cast<int64_t>(args[0].ValueEdge().EdgeType()), ctx.memory);
return TypedValue(dba->EdgeTypeToName(args[0].ValueEdge().EdgeType()), ctx.memory);
}
TypedValue ValueType(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
@@ -560,27 +593,81 @@ TypedValue ValueType(const TypedValue *args, int64_t nargs, const FunctionContex
}
// TODO: How is Keys different from Properties function?
TypedValue Keys(const TypedValue *args, int64_t nargs, const FunctionContext & /*ctx*/) {
TypedValue Keys(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
FType<Or<Null, Vertex, Edge>>("keys", args, nargs);
return {};
auto *dba = ctx.db_accessor;
auto get_keys = [&](const auto &record_accessor) {
TypedValue::TVector keys(ctx.memory);
auto maybe_props = record_accessor.Properties(ctx.view);
if (maybe_props.HasError()) {
switch (maybe_props.GetError()) {
case storage::v3::Error::DELETED_OBJECT:
throw QueryRuntimeException("Trying to get keys from a deleted object.");
case storage::v3::Error::NONEXISTENT_OBJECT:
throw query::v2::QueryRuntimeException("Trying to get keys from an object that doesn't exist.");
case storage::v3::Error::SERIALIZATION_ERROR:
case storage::v3::Error::VERTEX_HAS_EDGES:
case storage::v3::Error::PROPERTIES_DISABLED:
throw QueryRuntimeException("Unexpected error when getting keys.");
}
}
for (const auto &property : *maybe_props) {
keys.emplace_back(dba->PropertyToName(property.first));
}
return TypedValue(std::move(keys));
};
const auto &value = args[0];
if (value.IsNull()) {
return TypedValue(ctx.memory);
} else if (value.IsVertex()) {
return get_keys(value.ValueVertex());
} else {
return get_keys(value.ValueEdge());
}
}
TypedValue Labels(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
FType<Or<Null, Vertex>>("labels", args, nargs);
auto *dba = ctx.db_accessor;
if (args[0].IsNull()) return TypedValue(ctx.memory);
return {};
TypedValue::TVector labels(ctx.memory);
auto maybe_labels = args[0].ValueVertex().Labels(ctx.view);
if (maybe_labels.HasError()) {
switch (maybe_labels.GetError()) {
case storage::v3::Error::DELETED_OBJECT:
throw QueryRuntimeException("Trying to get labels from a deleted node.");
case storage::v3::Error::NONEXISTENT_OBJECT:
throw query::v2::QueryRuntimeException("Trying to get labels from a node that doesn't exist.");
case storage::v3::Error::SERIALIZATION_ERROR:
case storage::v3::Error::VERTEX_HAS_EDGES:
case storage::v3::Error::PROPERTIES_DISABLED:
throw QueryRuntimeException("Unexpected error when getting labels.");
}
}
for (const auto &label : *maybe_labels) {
labels.emplace_back(dba->LabelToName(label));
}
return TypedValue(std::move(labels));
}
TypedValue Nodes(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
FType<Or<Null, Path>>("nodes", args, nargs);
if (args[0].IsNull()) return TypedValue(ctx.memory);
return {};
const auto &vertices = args[0].ValuePath().vertices();
TypedValue::TVector values(ctx.memory);
values.reserve(vertices.size());
for (const auto &v : vertices) values.emplace_back(v);
return TypedValue(std::move(values));
}
TypedValue Relationships(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
FType<Or<Null, Path>>("relationships", args, nargs);
if (args[0].IsNull()) return TypedValue(ctx.memory);
return {};
const auto &edges = args[0].ValuePath().edges();
TypedValue::TVector values(ctx.memory);
values.reserve(edges.size());
for (const auto &e : edges) values.emplace_back(e);
return TypedValue(std::move(values));
}
TypedValue Range(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
@@ -790,9 +877,9 @@ TypedValue Id(const TypedValue *args, int64_t nargs, const FunctionContext &ctx)
if (arg.IsNull()) {
return TypedValue(ctx.memory);
} else if (arg.IsVertex()) {
return TypedValue(int64_t(arg.ValueVertex().CypherId()), ctx.memory);
return TypedValue(arg.ValueVertex().CypherId(), ctx.memory);
} else {
return TypedValue(int64_t(arg.ValueEdge().CypherId()), ctx.memory);
return TypedValue(arg.ValueEdge().CypherId(), ctx.memory);
}
}
@@ -1093,6 +1180,49 @@ TypedValue Duration(const TypedValue *args, int64_t nargs, const FunctionContext
return TypedValue(utils::Duration(duration_parameters), ctx.memory);
}
std::function<TypedValue(const TypedValue *, const int64_t, const FunctionContext &)> UserFunction(
const mgp_func &func, const std::string &fully_qualified_name) {
return [func, fully_qualified_name](const TypedValue *args, int64_t nargs, const FunctionContext &ctx) -> TypedValue {
/// Find function is called to aquire the lock on Module pointer while user-defined function is executed
const auto &maybe_found =
procedure::FindFunction(procedure::gModuleRegistry, fully_qualified_name, utils::NewDeleteResource());
if (!maybe_found) {
throw QueryRuntimeException(
"Function '{}' has been unloaded. Please check query modules to confirm that function is loaded in Memgraph.",
fully_qualified_name);
}
const auto &func_cb = func.cb;
mgp_memory memory{ctx.memory};
mgp_func_context functx{ctx.db_accessor, ctx.view};
auto graph = mgp_graph::NonWritableGraph(*ctx.db_accessor, ctx.view);
std::vector<TypedValue> args_list;
args_list.reserve(nargs);
for (std::size_t i = 0; i < nargs; ++i) {
args_list.emplace_back(args[i]);
}
auto function_argument_list = mgp_list(ctx.memory);
procedure::ConstructArguments(args_list, func, fully_qualified_name, function_argument_list, graph);
mgp_func_result maybe_res;
func_cb(&function_argument_list, &functx, &maybe_res, &memory);
if (maybe_res.error_msg) {
throw QueryRuntimeException(*maybe_res.error_msg);
}
if (!maybe_res.value) {
throw QueryRuntimeException(
"Function '{}' didn't set the result nor the error message. Please either set the result by using "
"mgp_func_result_set_value or the error by using mgp_func_result_set_error_msg.",
fully_qualified_name);
}
return {*(maybe_res.value), ctx.memory};
};
}
} // namespace
std::function<TypedValue(const TypedValue *, int64_t, const FunctionContext &ctx)> NameToFunction(
@@ -1179,6 +1309,14 @@ std::function<TypedValue(const TypedValue *, int64_t, const FunctionContext &ctx
if (function_name == "LOCALDATETIME") return LocalDateTime;
if (function_name == "DURATION") return Duration;
const auto &maybe_found =
procedure::FindFunction(procedure::gModuleRegistry, function_name, utils::NewDeleteResource());
if (maybe_found) {
const auto *func = (*maybe_found).second;
return UserFunction(*func, function_name);
}
return nullptr;
}

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,8 @@
#include "query/v2/plan/operator.hpp"
#include "query/v2/plan/read_write_type_checker.hpp"
#include "query/v2/stream.hpp"
#include "query/v2/stream/streams.hpp"
#include "query/v2/trigger.hpp"
#include "storage/v3/isolation_level.hpp"
#include "storage/v3/name_id_mapper.hpp"
#include "utils/event_counter.hpp"
@@ -178,8 +180,13 @@ struct InterpreterContext {
utils::SkipList<QueryCacheEntry> ast_cache;
utils::SkipList<PlanCacheEntry> plan_cache;
TriggerStore trigger_store;
utils::ThreadPool after_commit_trigger_pool{1};
const InterpreterConfig config;
query::v2::stream::Streams streams;
storage::v3::LabelId NameToLabelId(std::string_view label_name) {
return storage::v3::LabelId::FromUint(query_id_mapper.NameToId(label_name));
}
@@ -327,6 +334,7 @@ class Interpreter final {
// move this unique_ptr into a shrared_ptr.
std::unique_ptr<storage::v3::Shard::Accessor> db_accessor_;
std::optional<DbAccessor> execution_db_accessor_;
std::optional<TriggerContextCollector> trigger_context_collector_;
bool in_explicit_transaction_{false};
bool expect_rollback_{false};

File diff suppressed because it is too large Load Diff

View File

@@ -25,20 +25,15 @@
#include "query/v2/common.hpp"
#include "query/v2/frontend/ast/ast.hpp"
#include "expr/semantic/symbol.hpp"
#include "query/v2/bindings/typed_value.hpp"
#include "query/v2/bindings/frame.hpp"
#include "query/v2/bindings/symbol_table.hpp"
#include "query/v2//bindings/typed_value.hpp"
#include "query/v2//bindings/frame.hpp"
#include "query/v2//bindings/symbol_table.hpp"
#include "storage/v3/id_types.hpp"
#include "utils/bound.hpp"
#include "utils/fnv.hpp"
#include "utils/memory.hpp"
#include "utils/visitor.hpp"
#include "utils/logging.hpp"
#include "query/v2/accessors.hpp"
using VertexAccessor = memgraph::query::v2::accessors::VertexAccessor;
using EdgeAccessor = memgraph::query::v2::accessors::EdgeAccessor;
using Path = memgraph::query::v2::accessors::Path;
cpp<#
(lcp:namespace memgraph)

View File

@@ -272,7 +272,6 @@ class RuleBasedPlanner {
PropertiesMapList vector_props;
vector_props.reserve(node_properties->size());
for (const auto &kv : *node_properties) {
// TODO(kostasrim) GetProperty should be implemented in terms of ShardRequestManager NameToProperty
vector_props.push_back({GetProperty(kv.first), kv.second});
}
return std::move(vector_props);

View File

@@ -14,7 +14,6 @@
#include <chrono>
#include <iostream>
#include <map>
#include <memory>
#include <optional>
#include <unordered_map>
#include <utility>
@@ -27,6 +26,15 @@
namespace memgraph::msgs {
// TODO(gvolfing) introduce structure for property updates
// TODO(gvolfing) remove tuples from ExpandOne and add specific structs instead
// TODO(gvolfing) discuss with kostas:
// - EdgeId
// - Everything about ExpandOne
// - Edge properties
// - Ctors
using coordinator::Hlc;
using storage::v3::LabelId;
@@ -34,45 +42,38 @@ struct Value;
struct Label {
LabelId id;
friend bool operator==(const Label &lhs, const Label &rhs) { return lhs.id == rhs.id; }
};
// TODO(kostasrim) update this with CompoundKey, same for the rest of the file.
using PrimaryKey = std::vector<Value>;
using VertexId = std::pair<Label, PrimaryKey>;
inline bool operator==(const VertexId &lhs, const VertexId &rhs) {
return (lhs.first == rhs.first) && (lhs.second == rhs.second);
}
using Gid = size_t;
using PropertyId = memgraph::storage::v3::PropertyId;
// struct PropertyUpdate{
// PropertyId id;
// Value value;
// };
struct EdgeType {
uint64_t id;
friend bool operator==(const EdgeType &lhs, const EdgeType &rhs) = default;
};
struct EdgeId {
Gid gid;
};
struct Vertex {
VertexId id;
std::vector<Label> labels;
friend bool operator==(const Vertex &lhs, const Vertex &rhs) {
return (lhs.id == rhs.id) && (lhs.labels == rhs.labels);
}
};
struct Edge {
VertexId src;
VertexId dst;
std::optional<std::vector<std::pair<PropertyId, Value>>> properties;
EdgeId id;
EdgeType type;
friend bool operator==(const Edge &lhs, const Edge &rhs) {
return (lhs.src == rhs.src) && (lhs.dst == rhs.dst) && (lhs.type == rhs.type);
}
};
struct Vertex {
VertexId id;
std::vector<Label> labels;
};
struct PathPart {
@@ -95,7 +96,6 @@ struct Value {
explicit Value(const double val) : type(Type::Double), double_v(val) {}
explicit Value(const Vertex val) : type(Type::Vertex), vertex_v(val) {}
explicit Value(const Edge val) : type(Type::Edge), edge_v(val) {}
explicit Value(const std::string &val) : type(Type::String) { new (&string_v) std::string(val); }
explicit Value(const char *val) : type(Type::String) { new (&string_v) std::string(val); }
@@ -315,34 +315,6 @@ struct Value {
Edge edge_v;
Path path_v;
};
friend bool operator==(const Value &lhs, const Value &rhs) {
if (lhs.type != rhs.type) {
return false;
}
switch (lhs.type) {
case Value::Type::Null:
return true;
case Value::Type::Bool:
return lhs.bool_v == rhs.bool_v;
case Value::Type::Int64:
return lhs.int_v == rhs.int_v;
case Value::Type::Double:
return lhs.double_v == rhs.double_v;
case Value::Type::String:
return lhs.string_v == rhs.string_v;
case Value::Type::List:
return lhs.list_v == rhs.list_v;
case Value::Type::Map:
return lhs.map_v == rhs.map_v;
case Value::Type::Vertex:
return lhs.vertex_v == rhs.vertex_v;
case Value::Type::Edge:
return lhs.edge_v == rhs.edge_v;
case Value::Type::Path:
return true;
}
}
};
struct ValuesMap {
@@ -376,25 +348,19 @@ struct OrderBy {
enum class StorageView { OLD = 0, NEW = 1 };
// #NoCommit #JBA to modify
struct ScanVerticesRequest {
Hlc transaction_id;
VertexId start_id;
std::optional<std::vector<PropertyId>> props_to_return;
std::optional<std::vector<std::string>> filter_expressions; // #NoCommit what the use of that exactly?
std::optional<std::vector<std::string>> filter_expressions;
std::optional<size_t> batch_limit;
StorageView storage_view;
std::optional<Label> label_to_filter_;
std::optional<PropertyId> property_to_filter_;
std::optional<std::vector<std::pair<storage::v3::PropertyId, std::string>>> property_value_pairs_to_filter_;
// #NoCommit not bool but the string version of the ast? to double check
};
struct ScanResultRow {
Vertex vertex;
Value vertex;
// empty() is no properties returned
std::vector<std::pair<PropertyId, Value>> props;
std::map<PropertyId, Value> props;
};
struct ScanVerticesResponse {
@@ -423,11 +389,6 @@ struct GetPropertiesResponse {
enum class EdgeDirection : uint8_t { OUT = 1, IN = 2, BOTH = 3 };
struct VertexEdgeId {
VertexId vertex_id;
std::optional<EdgeId> next_id;
};
struct ExpandOneRequest {
Hlc transaction_id;
std::vector<VertexId> src_vertices;
@@ -464,8 +425,15 @@ struct ExpandOneResultRow {
// The drawback of this is currently the key of the map is always interpreted as a string in Value, not as an
// integer, which should be in case of mapped properties.
Vertex src_vertex;
std::optional<Values> src_vertex_properties;
Values edges;
std::optional<std::map<PropertyId, Value>> src_vertex_properties;
// NOTE: If the desired edges are specified in the request,
// edges_with_specific_properties will have a value and it will
// return the properties as a vector of property values. The order
// of the values returned should be the same as the PropertyIds
// were defined in the request.
std::optional<std::vector<std::tuple<VertexId, Gid, std::map<PropertyId, Value>>>> edges_with_all_properties;
std::optional<std::vector<std::tuple<VertexId, Gid, std::vector<Value>>>> edges_with_specific_properties;
};
struct ExpandOneResponse {
@@ -473,12 +441,14 @@ struct ExpandOneResponse {
};
struct UpdateVertexProp {
VertexId primary_key;
PrimaryKey primary_key;
std::vector<std::pair<PropertyId, Value>> property_updates;
};
struct UpdateEdgeProp {
Edge edge;
EdgeId edge_id;
VertexId src;
VertexId dst;
std::vector<std::pair<PropertyId, Value>> property_updates;
};
@@ -491,14 +461,7 @@ struct NewVertex {
std::vector<std::pair<PropertyId, Value>> properties;
};
struct NewVertexLabel {
std::string label;
PrimaryKey primary_key;
std::vector<std::pair<PropertyId, Value>> properties;
};
struct CreateVerticesRequest {
std::string label;
Hlc transaction_id;
std::vector<NewVertex> new_vertices;
};
@@ -557,21 +520,12 @@ struct UpdateEdgesResponse {
bool success;
};
struct CommitRequest {
Hlc transaction_id;
Hlc commit_timestamp;
};
struct CommitResponse {
bool success;
};
using ReadRequests = std::variant<ExpandOneRequest, GetPropertiesRequest, ScanVerticesRequest>;
using ReadResponses = std::variant<ExpandOneResponse, GetPropertiesResponse, ScanVerticesResponse>;
using WriteRequests = std::variant<CreateVerticesRequest, DeleteVerticesRequest, UpdateVerticesRequest,
CreateEdgesRequest, DeleteEdgesRequest, UpdateEdgesRequest, CommitRequest>;
CreateEdgesRequest, DeleteEdgesRequest, UpdateEdgesRequest>;
using WriteResponses = std::variant<CreateVerticesResponse, DeleteVerticesResponse, UpdateVerticesResponse,
CreateEdgesResponse, DeleteEdgesResponse, UpdateEdgesResponse, CommitResponse>;
CreateEdgesResponse, DeleteEdgesResponse, UpdateEdgesResponse>;
} // namespace memgraph::msgs

View File

@@ -1,405 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <chrono>
#include <deque>
#include <iostream>
#include <map>
#include <optional>
#include <random>
#include <set>
#include <stdexcept>
#include <thread>
#include <unordered_map>
#include <vector>
#include "coordinator/coordinator_client.hpp"
#include "coordinator/coordinator_rsm.hpp"
#include "coordinator/shard_map.hpp"
#include "io/address.hpp"
#include "io/errors.hpp"
#include "io/rsm/raft.hpp"
#include "io/rsm/rsm_client.hpp"
#include "io/rsm/shard_rsm.hpp"
#include "io/simulator/simulator.hpp"
#include "io/simulator/simulator_transport.hpp"
#include "query/v2/accessors.hpp"
#include "query/v2/requests.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/value_conversions.hpp"
#include "utils/result.hpp"
namespace memgraph::msgs {
template <typename TStorageClient>
class RsmStorageClientManager {
public:
using CompoundKey = memgraph::io::rsm::ShardRsmKey;
using Shard = memgraph::coordinator::Shard;
using LabelId = memgraph::storage::v3::LabelId;
RsmStorageClientManager() = default;
RsmStorageClientManager(const RsmStorageClientManager &) = delete;
RsmStorageClientManager(RsmStorageClientManager &&) = delete;
RsmStorageClientManager &operator=(const RsmStorageClientManager &) = delete;
RsmStorageClientManager &operator=(RsmStorageClientManager &&) = delete;
~RsmStorageClientManager() = default;
void AddClient(const LabelId label_id, Shard key, TStorageClient client) {
cli_cache_[label_id].insert({std::move(key), std::move(client)});
}
bool Exists(const LabelId label_id, const Shard &key) { return cli_cache_[label_id].contains(key); }
void PurgeCache() { cli_cache_.clear(); }
TStorageClient &GetClient(const LabelId label_id, const Shard &key) { return cli_cache_[label_id].find(key)->second; }
private:
std::map<LabelId, std::map<Shard, TStorageClient>> cli_cache_;
};
template <typename TRequest>
struct ExecutionState {
using CompoundKey = memgraph::io::rsm::ShardRsmKey;
using Shard = memgraph::coordinator::Shard;
enum State : int8_t { INITIALIZING, EXECUTING, COMPLETED };
// label is optional because some operators can create/remove etc, vertices. These kind of requests contain the label
// on the request itself.
std::optional<std::string> label;
// CompoundKey is optional because some operators require to iterate over all the available keys
// of a shard. One example is ScanAll, where we only require the field label.
std::optional<CompoundKey> key;
// Transaction id to be filled by the ShardRequestManager implementation
memgraph::coordinator::Hlc transaction_id;
// Initialized by ShardRequestManager implementation. This vector is filled with the shards that
// the ShardRequestManager impl will send requests to. When a request to a shard exhausts it, meaning that
// it pulled all the requested data from the given Shard, it will be removed from the Vector. When the Vector becomes
// empty, it means that all of the requests have completed succefully.
std::vector<Shard> shard_cache;
// 1-1 mapping with `shard_cache`.
// A vector that tracks request metatdata for each shard (For example, next_id for a ScanAll on Shard A)
std::vector<TRequest> requests;
State state = INITIALIZING;
};
class ShardRequestManagerInterface {
public:
using VertexAccessor = memgraph::query::v2::accessors::VertexAccessor;
ShardRequestManagerInterface() = default;
ShardRequestManagerInterface(const ShardRequestManagerInterface &) = delete;
ShardRequestManagerInterface(ShardRequestManagerInterface &&) = delete;
ShardRequestManagerInterface &operator=(const ShardRequestManagerInterface &) = delete;
ShardRequestManagerInterface &&operator=(ShardRequestManagerInterface &&) = delete;
virtual ~ShardRequestManagerInterface() = default;
virtual void StartTransaction() = 0;
virtual std::vector<VertexAccessor> Request(ExecutionState<ScanVerticesRequest> &state) = 0;
virtual std::vector<CreateVerticesResponse> Request(ExecutionState<CreateVerticesRequest> &state,
std::vector<NewVertex> new_vertices) = 0;
virtual std::vector<ExpandOneResponse> Request(ExecutionState<ExpandOneRequest> &state) = 0;
virtual memgraph::storage::v3::PropertyId NameToProperty(const std::string &name) const = 0;
virtual memgraph::storage::v3::LabelId LabelNameToLabelId(const std::string &name) const = 0;
virtual bool IsPrimaryKey(PropertyId name) const = 0;
};
// TODO(kostasrim)rename this class template
template <typename TTransport>
class ShardRequestManager : public ShardRequestManagerInterface {
public:
using WriteRequests = CreateVerticesRequest;
using WriteResponses = CreateVerticesResponse;
using ReadRequests = std::variant<ScanVerticesRequest, ExpandOneRequest>;
using ReadResponses = std::variant<ScanVerticesResponse, ExpandOneResponse>;
using StorageClient =
memgraph::coordinator::RsmClient<TTransport, WriteRequests, WriteResponses, ReadRequests, ReadResponses>;
using CoordinatorClient = memgraph::coordinator::CoordinatorClient<TTransport>;
using Address = memgraph::io::Address;
using Shard = memgraph::coordinator::Shard;
using ShardMap = memgraph::coordinator::ShardMap;
using CompoundKey = memgraph::coordinator::PrimaryKey;
using VertexAccessor = memgraph::query::v2::accessors::VertexAccessor;
ShardRequestManager(CoordinatorClient coord, memgraph::io::Io<TTransport> &&io)
: coord_cli_(std::move(coord)), io_(std::move(io)) {}
ShardRequestManager(const ShardRequestManager &) = delete;
ShardRequestManager(ShardRequestManager &&) = delete;
ShardRequestManager &operator=(const ShardRequestManager &) = delete;
ShardRequestManager &operator=(ShardRequestManager &&) = delete;
~ShardRequestManager() override {}
void StartTransaction() override {
memgraph::coordinator::HlcRequest req{.last_shard_map_version = shards_map_.GetHlc()};
auto write_res = coord_cli_.SendWriteRequest(req);
if (write_res.HasError()) {
throw std::runtime_error("HLC request failed");
}
auto coordinator_write_response = write_res.GetValue();
auto hlc_response = std::get<memgraph::coordinator::HlcResponse>(coordinator_write_response);
// Transaction ID to be used later...
transaction_id_ = hlc_response.new_hlc;
if (hlc_response.fresher_shard_map) {
shards_map_ = hlc_response.fresher_shard_map.value();
}
}
memgraph::storage::v3::PropertyId NameToProperty(const std::string &name) const override {
return *shards_map_.GetPropertyId(name);
}
memgraph::storage::v3::LabelId LabelNameToLabelId(const std::string &name) const override {
return shards_map_.GetLabelId(name);
}
bool IsPrimaryKey(const PropertyId name) const override {
return std::find_if(shards_map_.properties.begin(), shards_map_.properties.end(),
[name](auto &pr) { return pr.second == name; }) != shards_map_.properties.end();
}
// TODO(kostasrim) Simplify return result
std::vector<VertexAccessor> Request(ExecutionState<ScanVerticesRequest> &state) override {
MaybeInitializeExecutionState(state);
std::vector<ScanVerticesResponse> responses;
auto &shard_cache_ref = state.shard_cache;
size_t id = 0;
for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++id) {
auto &storage_client = GetStorageClientForShard(
*state.label, storage::conversions::ConvertPropertyVector(state.requests[id].start_id.second));
// TODO(kostasrim) Currently requests return the result directly. Adjust this when the API works MgFuture
// instead.
auto read_response_result = storage_client.SendReadRequest(state.requests[id]);
// RETRY on timeouts?
// Sometimes this produces a timeout. Temporary solution is to use a while(true) as was done in shard_map test
if (read_response_result.HasError()) {
throw std::runtime_error("ScanAll request timedout");
}
auto &response = std::get<ScanVerticesResponse>(read_response_result.GetValue());
if (!response.success) {
throw std::runtime_error("ScanAll request did not succeed");
}
if (!response.next_start_id) {
shard_it = shard_cache_ref.erase(shard_it);
} else {
state.requests[id].start_id.second = response.next_start_id->second;
++shard_it;
}
responses.push_back(std::move(response));
}
// We are done with this state
MaybeCompleteState(state);
// TODO(kostasrim) Before returning start prefetching the batch (this shall be done once we get MgFuture as return
// result of storage_client.SendReadRequest()).
return PostProcess(std::move(responses));
}
std::vector<CreateVerticesResponse> Request(ExecutionState<CreateVerticesRequest> &state,
std::vector<NewVertex> new_vertices) override {
MG_ASSERT(!new_vertices.empty());
MaybeInitializeExecutionState(state, new_vertices);
std::vector<CreateVerticesResponse> responses;
auto &shard_cache_ref = state.shard_cache;
size_t id = 0;
for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++id) {
// This is fine because all new_vertices of each request end up on the same shard
const auto labels = state.requests[id].new_vertices[0].label_ids;
auto primary_key = state.requests[id].new_vertices[0].primary_key;
auto &storage_client = GetStorageClientForShard(*shard_it, labels[0].id);
auto write_response_result = storage_client.SendWriteRequest(state.requests[id]);
// RETRY on timeouts?
// Sometimes this produces a timeout. Temporary solution is to use a while(true) as was done in shard_map test
if (write_response_result.HasError()) {
throw std::runtime_error("CreateVertices request timedout");
}
if (!write_response_result.GetValue().success) {
throw std::runtime_error("CreateVertices request did not succeed");
}
responses.push_back(write_response_result.GetValue());
shard_it = shard_cache_ref.erase(shard_it);
}
// We are done with this state
MaybeCompleteState(state);
// TODO(kostasrim) Before returning start prefetching the batch (this shall be done once we get MgFuture as return
// result of storage_client.SendReadRequest()).
return responses;
}
std::vector<ExpandOneResponse> Request(ExecutionState<ExpandOneRequest> &state) override {
// TODO(kostasrim)Update to limit the batch size here
// Expansions of the destination must be handled by the caller. For example
// match (u:L1 { prop : 1 })-[:Friend]-(v:L1)
// For each vertex U, the ExpandOne will result in <U, Edges>. The destination vertex and its properties
// must be fetched again with an ExpandOne(Edges.dst)
MaybeInitializeExecutionState(state);
std::vector<ExpandOneResponse> responses;
auto &shard_cache_ref = state.shard_cache;
size_t id = 0;
// pending_requests on shards
for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++id) {
const Label primary_label = state.requests[id].src_vertices[0].first;
auto &storage_client = GetStorageClientForShard(*shard_it, primary_label.id);
auto read_response_result = storage_client.SendReadRequest(state.requests[id]);
// RETRY on timeouts?
// Sometimes this produces a timeout. Temporary solution is to use a while(true) as was done in shard_map
if (read_response_result.HasError()) {
throw std::runtime_error("ExpandOne request timedout");
}
auto &response = std::get<ExpandOneResponse>(read_response_result.GetValue());
responses.push_back(std::move(response));
}
return responses;
}
private:
std::vector<VertexAccessor> PostProcess(std::vector<ScanVerticesResponse> &&responses) const {
std::vector<VertexAccessor> accessors;
for (auto &response : responses) {
for (auto &result_row : response.results) {
accessors.emplace_back(VertexAccessor(std::move(result_row.vertex), std::move(result_row.props)));
}
}
return accessors;
}
template <typename ExecutionState>
void ThrowIfStateCompleted(ExecutionState &state) const {
if (state.state == ExecutionState::COMPLETED) [[unlikely]] {
throw std::runtime_error("State is completed and must be reset");
}
}
template <typename ExecutionState>
void MaybeCompleteState(ExecutionState &state) const {
if (state.requests.empty()) {
state.state = ExecutionState::COMPLETED;
}
}
template <typename ExecutionState>
bool ShallNotInitializeState(ExecutionState &state) const {
return state.state != ExecutionState::INITIALIZING;
}
void MaybeInitializeExecutionState(ExecutionState<CreateVerticesRequest> &state,
std::vector<NewVertex> new_vertices) {
ThrowIfStateCompleted(state);
if (ShallNotInitializeState(state)) {
return;
}
state.transaction_id = transaction_id_;
std::map<Shard, CreateVerticesRequest> per_shard_request_table;
for (auto &new_vertex : new_vertices) {
auto shard = shards_map_.GetShardForKey(new_vertex.label_ids[0].id,
storage::conversions::ConvertPropertyVector(new_vertex.primary_key));
if (!per_shard_request_table.contains(shard)) {
CreateVerticesRequest create_v_rqst{.transaction_id = transaction_id_};
per_shard_request_table.insert(std::pair(shard, std::move(create_v_rqst)));
state.shard_cache.push_back(shard);
}
per_shard_request_table[shard].new_vertices.push_back(std::move(new_vertex));
}
for (auto &[shard, rqst] : per_shard_request_table) {
state.requests.push_back(std::move(rqst));
}
state.state = ExecutionState<CreateVerticesRequest>::EXECUTING;
}
void MaybeInitializeExecutionState(ExecutionState<ScanVerticesRequest> &state) {
ThrowIfStateCompleted(state);
if (ShallNotInitializeState(state)) {
return;
}
state.transaction_id = transaction_id_;
auto shards = shards_map_.GetShards(*state.label);
for (auto &[key, shard] : shards) {
state.shard_cache.push_back(std::move(shard));
ScanVerticesRequest rqst;
rqst.transaction_id = transaction_id_;
rqst.start_id.second = storage::conversions::ConvertValueVector(key);
state.requests.push_back(std::move(rqst));
}
state.state = ExecutionState<ScanVerticesRequest>::EXECUTING;
}
void MaybeInitializeExecutionState(ExecutionState<ExpandOneRequest> &state) {
ThrowIfStateCompleted(state);
if (ShallNotInitializeState(state)) {
return;
}
state.transaction_id = transaction_id_;
std::map<Shard, ExpandOneRequest> per_shard_request_table;
MG_ASSERT(state.requests.size() == 1);
auto top_level_rqst = std::move(*state.requests.begin());
auto top_level_rqst_template = top_level_rqst;
top_level_rqst_template.src_vertices.clear();
top_level_rqst_template.edge_types.clear();
state.requests.clear();
size_t id = 0;
for (const auto &vertex : top_level_rqst.src_vertices) {
auto shard =
shards_map_.GetShardForKey(vertex.first.id, storage::conversions::ConvertPropertyVector(vertex.second));
if (!per_shard_request_table.contains(shard)) {
ExpandOneRequest expand_v_rqst = top_level_rqst_template;
per_shard_request_table.insert(std::pair(shard, std::move(expand_v_rqst)));
state.shard_cache.push_back(shard);
}
per_shard_request_table[shard].src_vertices.push_back(vertex);
per_shard_request_table[shard].edge_types.push_back(top_level_rqst.edge_types[id]);
++id;
}
for (auto &[shard, rqst] : per_shard_request_table) {
state.requests.push_back(std::move(rqst));
}
state.state = ExecutionState<ExpandOneRequest>::EXECUTING;
}
StorageClient &GetStorageClientForShard(Shard shard, LabelId label_id) {
if (!storage_cli_manager_.Exists(label_id, shard)) {
AddStorageClientToManager(shard, label_id);
}
return storage_cli_manager_.GetClient(label_id, shard);
}
StorageClient &GetStorageClientForShard(const std::string &label, const CompoundKey &key) {
auto shard = shards_map_.GetShardForKey(label, key);
auto label_id = shards_map_.GetLabelId(label);
return GetStorageClientForShard(std::move(shard), label_id);
}
void AddStorageClientToManager(Shard target_shard, const LabelId &label_id) {
MG_ASSERT(!target_shard.empty());
auto leader_addr = target_shard.front();
std::vector<Address> addresses;
addresses.reserve(target_shard.size());
for (auto &address : target_shard) {
addresses.push_back(std::move(address.address));
}
auto cli = StorageClient(io_, std::move(leader_addr.address), std::move(addresses));
storage_cli_manager_.AddClient(label_id, target_shard, std::move(cli));
}
ShardMap shards_map_;
CoordinatorClient coord_cli_;
RsmStorageClientManager<StorageClient> storage_cli_manager_;
memgraph::io::Io<TTransport> io_;
memgraph::coordinator::Hlc transaction_id_;
// TODO(kostasrim) Add batch prefetching
};
} // namespace memgraph::msgs

View File

@@ -490,7 +490,7 @@ Streams::StreamsMap::iterator Streams::CreateConsumer(StreamsMap &map, const std
total_retries = interpreter_context_->config.stream_transaction_conflict_retries,
retry_interval = interpreter_context_->config.stream_transaction_retry_interval](
const std::vector<typename TStream::Message> &messages) mutable {
auto accessor = interpreter_context->db->Access(coordinator::Hlc{});
auto accessor = interpreter_context->db->Access();
EventCounter::IncrementCounter(EventCounter::MessagesConsumed, messages.size());
CallCustomTransformation(transformation_name, messages, result, accessor, *memory_resource, stream_name);
@@ -738,7 +738,7 @@ TransformationResult Streams::Check(const std::string &stream_name, std::optiona
auto consumer_function = [interpreter_context = interpreter_context_, memory_resource, &stream_name,
&transformation_name = transformation_name, &result,
&test_result]<typename T>(const std::vector<T> &messages) mutable {
auto accessor = interpreter_context->db->Access(coordinator::Hlc{});
auto accessor = interpreter_context->db->Access();
CallCustomTransformation(transformation_name, messages, result, accessor, *memory_resource, stream_name);
auto result_row = std::vector<TypedValue>();

View File

@@ -1,12 +1,11 @@
define_add_lcp(add_storage_ast lcp_storage_v3_cpp_files generated_lcp_storage_v3_files)
add_storage_ast(bindings/ast/ast.lcp)
add_custom_target(generate_lcp_ast_storage_v3 DEPENDS ${generated_lcp_storage_v3_files})
set(storage_v3_src_files
${lcp_storage_v3_cpp_files}
commit_log.cpp
constraints.cpp
temporal.cpp
durability/durability.cpp
durability/serialization.cpp
durability/snapshot.cpp
durability/wal.cpp
edge_accessor.cpp
indices.cpp
key_store.cpp
@@ -16,18 +15,30 @@ set(storage_v3_src_files
schemas.cpp
schema_validator.cpp
shard.cpp
bindings/typed_value.cpp
shard_rsm.cpp
storage.cpp)
storage.cpp
shard_rsm.cpp)
# #### Replication #####
define_add_lcp(add_lcp_storage lcp_storage_cpp_files generated_lcp_storage_files)
add_lcp_storage(replication/rpc.lcp SLK_SERIALIZE)
add_custom_target(generate_lcp_storage_v3 DEPENDS ${generated_lcp_storage_files})
set(storage_v3_src_files
${storage_v3_src_files}
replication/replication_client.cpp
replication/replication_server.cpp
replication/serialization.cpp
replication/slk.cpp
${lcp_storage_cpp_files})
# ######################
find_package(gflags REQUIRED)
find_package(Threads REQUIRED)
add_library(mg-storage-v3 STATIC ${storage_v3_src_files})
add_dependencies(mg-storage-v3 generate_lcp_ast_storage_v3)
target_link_libraries(mg-storage-v3 Threads::Threads mg-utils gflags)
target_include_directories(mg-storage-v3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/bindings)
add_dependencies(mg-storage-v3 generate_lcp_storage)
target_link_libraries(mg-storage-v3 mg-slk mg-expr mg-io)
target_link_libraries(mg-storage-v3 mg-rpc mg-slk mg-expr)

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#define MG_AST_INCLUDE_PATH "storage/v3/bindings/ast/ast.hpp" // NOLINT(cppcoreguidelines-macro-usage)
#define MG_INJECTED_NAMESPACE_NAME memgraph::storage::v3 // NOLINT(cppcoreguidelines-macro-usage)

View File

@@ -1,20 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "storage/v3/bindings/bindings.hpp"
#include "expr/ast/cypher_main_visitor.hpp"
namespace memgraph::storage::v3 {
using CypherMainVisitor = memgraph::expr::CypherMainVisitor;
} // namespace memgraph::storage::v3

View File

@@ -1,203 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "storage/v3/shard.hpp"
namespace memgraph::storage::v3 {
class DbAccessor final {
Shard::Accessor *accessor_;
class VerticesIterable final {
storage::v3::VerticesIterable iterable_;
public:
class Iterator final {
storage::v3::VerticesIterable::Iterator it_;
public:
explicit Iterator(storage::v3::VerticesIterable::Iterator it) : it_(it) {}
VertexAccessor operator*() const { return VertexAccessor(*it_); }
Iterator &operator++() {
++it_;
return *this;
}
bool operator==(const Iterator &other) const { return it_ == other.it_; }
bool operator!=(const Iterator &other) const { return !(other == *this); }
};
explicit VerticesIterable(storage::v3::VerticesIterable iterable) : iterable_(std::move(iterable)) {}
Iterator begin() { return Iterator(iterable_.begin()); }
Iterator end() { return Iterator(iterable_.end()); }
};
public:
explicit DbAccessor(storage::v3::Shard::Accessor *accessor) : accessor_(accessor) {}
// TODO(jbajic) Fix Remove Gid
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
std::optional<VertexAccessor> FindVertex(uint64_t /*unused*/) { return std::nullopt; }
std::optional<VertexAccessor> FindVertex(storage::v3::PrimaryKey &primary_key, storage::v3::View view) {
auto maybe_vertex = accessor_->FindVertex(primary_key, view);
if (maybe_vertex) return VertexAccessor(*maybe_vertex);
return std::nullopt;
}
VerticesIterable Vertices(storage::v3::View view) { return VerticesIterable(accessor_->Vertices(view)); }
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label) {
return VerticesIterable(accessor_->Vertices(label, view));
}
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label, storage::v3::PropertyId property) {
return VerticesIterable(accessor_->Vertices(label, property, view));
}
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label, storage::v3::PropertyId property,
const storage::v3::PropertyValue &value) {
return VerticesIterable(accessor_->Vertices(label, property, value, view));
}
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label, storage::v3::PropertyId property,
const std::optional<utils::Bound<storage::v3::PropertyValue>> &lower,
const std::optional<utils::Bound<storage::v3::PropertyValue>> &upper) {
return VerticesIterable(accessor_->Vertices(label, property, lower, upper, view));
}
storage::v3::ResultSchema<VertexAccessor> InsertVertexAndValidate(
const storage::v3::LabelId primary_label, const std::vector<storage::v3::LabelId> &labels,
const std::vector<std::pair<storage::v3::PropertyId, storage::v3::PropertyValue>> &properties) {
auto maybe_vertex_acc = accessor_->CreateVertexAndValidate(primary_label, labels, properties);
if (maybe_vertex_acc.HasError()) {
return {std::move(maybe_vertex_acc.GetError())};
}
return VertexAccessor{maybe_vertex_acc.GetValue()};
}
storage::v3::Result<EdgeAccessor> InsertEdge(VertexAccessor *from, VertexAccessor *to,
const storage::v3::EdgeTypeId &edge_type) {
static constexpr auto kDummyGid = storage::v3::Gid::FromUint(0);
auto maybe_edge = accessor_->CreateEdge(from->Id(storage::v3::View::NEW).GetValue(),
to->Id(storage::v3::View::NEW).GetValue(), edge_type, kDummyGid);
if (maybe_edge.HasError()) return {maybe_edge.GetError()};
return EdgeAccessor(*maybe_edge);
}
storage::v3::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge) {
auto res = accessor_->DeleteEdge(edge->FromVertex(), edge->ToVertex(), edge->Gid());
if (res.HasError()) {
return res.GetError();
}
const auto &value = res.GetValue();
if (!value) {
return std::optional<EdgeAccessor>{};
}
return std::make_optional<EdgeAccessor>(*value);
}
storage::v3::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachRemoveVertex(
VertexAccessor *vertex_accessor) {
using ReturnType = std::pair<VertexAccessor, std::vector<EdgeAccessor>>;
auto res = accessor_->DetachDeleteVertex(vertex_accessor);
if (res.HasError()) {
return res.GetError();
}
const auto &value = res.GetValue();
if (!value) {
return std::optional<ReturnType>{};
}
const auto &[vertex, edges] = *value;
std::vector<EdgeAccessor> deleted_edges;
deleted_edges.reserve(edges.size());
std::transform(edges.begin(), edges.end(), std::back_inserter(deleted_edges),
[](const auto &deleted_edge) { return EdgeAccessor{deleted_edge}; });
return std::make_optional<ReturnType>(vertex, std::move(deleted_edges));
}
storage::v3::Result<std::optional<VertexAccessor>> RemoveVertex(VertexAccessor *vertex_accessor) {
auto res = accessor_->DeleteVertex(vertex_accessor);
if (res.HasError()) {
return res.GetError();
}
const auto &value = res.GetValue();
if (!value) {
return std::optional<VertexAccessor>{};
}
return {std::make_optional<VertexAccessor>(*value)};
}
storage::v3::LabelId NameToLabel(const std::string_view name) { return accessor_->NameToLabel(name); }
storage::v3::PropertyId NameToProperty(const std::string_view name) { return accessor_->NameToProperty(name); }
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
storage::v3::EdgeTypeId NameToEdgeType(const std::string_view name) { return accessor_->NameToEdgeType(name); }
const std::string &PropertyToName(storage::v3::PropertyId prop) const { return accessor_->PropertyToName(prop); }
const std::string &LabelToName(storage::v3::LabelId label) const { return accessor_->LabelToName(label); }
const std::string &EdgeTypeToName(storage::v3::EdgeTypeId type) const { return accessor_->EdgeTypeToName(type); }
void AdvanceCommand() { accessor_->AdvanceCommand(); }
void Abort() { accessor_->Abort(); }
bool LabelIndexExists(storage::v3::LabelId label) const { return accessor_->LabelIndexExists(label); }
bool LabelPropertyIndexExists(storage::v3::LabelId label, storage::v3::PropertyId prop) const {
return accessor_->LabelPropertyIndexExists(label, prop);
}
int64_t VerticesCount() const { return accessor_->ApproximateVertexCount(); }
int64_t VerticesCount(storage::v3::LabelId label) const { return accessor_->ApproximateVertexCount(label); }
int64_t VerticesCount(storage::v3::LabelId label, storage::v3::PropertyId property) const {
return accessor_->ApproximateVertexCount(label, property);
}
int64_t VerticesCount(storage::v3::LabelId label, storage::v3::PropertyId property,
const storage::v3::PropertyValue &value) const {
return accessor_->ApproximateVertexCount(label, property, value);
}
int64_t VerticesCount(storage::v3::LabelId label, storage::v3::PropertyId property,
const std::optional<utils::Bound<storage::v3::PropertyValue>> &lower,
const std::optional<utils::Bound<storage::v3::PropertyValue>> &upper) const {
return accessor_->ApproximateVertexCount(label, property, lower, upper);
}
storage::v3::IndicesInfo ListAllIndices() const { return accessor_->ListAllIndices(); }
const storage::v3::SchemaValidator &GetSchemaValidator() const { return accessor_->GetSchemaValidator(); }
storage::v3::SchemasInfo ListAllSchemas() const { return accessor_->ListAllSchemas(); }
};
} // namespace memgraph::storage::v3

View File

@@ -1,104 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "storage/v2/property_value.hpp"
#include "storage/v3/bindings/bindings.hpp"
#include "expr/interpret/eval.hpp"
#include "storage/v3/bindings/db_accessor.hpp"
#include "storage/v3/bindings/typed_value.hpp"
#include "storage/v3/conversions.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/property_store.hpp"
#include "storage/v3/property_value.hpp"
#include "storage/v3/view.hpp"
#include "utils/memory.hpp"
namespace memgraph::storage::v3 {
struct PropertyToTypedValueConverter {
TypedValue operator()(const auto &val) { return memgraph::storage::v3::PropertyToTypedValue<TypedValue>(val); }
TypedValue operator()(const auto &val, utils::MemoryResource *mem) {
return memgraph::storage::v3::PropertyToTypedValue<TypedValue>(val, mem);
}
};
struct Parameters {
public:
/**
* Adds a value to the stripped arguments under a token position.
*
* @param position Token position in query of value.
* @param value
*/
void Add(int position, const storage::v3::PropertyValue &value) { storage_.emplace_back(position, value); }
/**
* Returns the value found for the given token position.
*
* @param position Token position in query of value.
* @return Value for the given token position.
*/
const storage::v3::PropertyValue &AtTokenPosition(int position) const {
auto found = std::find_if(storage_.begin(), storage_.end(), [&](const auto &a) { return a.first == position; });
MG_ASSERT(found != storage_.end(), "Token position must be present in container");
return found->second;
}
/**
* Returns the position-th stripped value. Asserts that this
* container has at least (position + 1) elements.
*
* @param position Which stripped param is sought.
* @return Token position and value for sought param.
*/
const std::pair<int, storage::v3::PropertyValue> &At(int position) const {
MG_ASSERT(position < static_cast<int>(storage_.size()), "Invalid position");
return storage_[position];
}
/** Returns the number of arguments in this container */
auto size() const { return storage_.size(); }
auto begin() const { return storage_.begin(); }
auto end() const { return storage_.end(); }
private:
std::vector<std::pair<int, storage::v3::PropertyValue>> storage_;
};
struct EvaluationContext {
/// Memory for allocations during evaluation of a *single* Pull call.
///
/// Although the assigned memory may live longer than the duration of a Pull
/// (e.g. memory is the same as the whole execution memory), you have to treat
/// it as if the lifetime is only valid during the Pull.
utils::MemoryResource *memory{utils::NewDeleteResource()};
int64_t timestamp{-1};
Parameters parameters;
/// All properties indexable via PropertyIx
std::vector<storage::v3::PropertyId> properties;
/// All labels indexable via LabelIx
std::vector<storage::v3::LabelId> labels;
/// All counters generated by `counter` function, mutable because the function
/// modifies the values
mutable std::unordered_map<std::string, int64_t> counters;
};
using ExpressionEvaluator =
memgraph::expr::ExpressionEvaluator<TypedValue, EvaluationContext, DbAccessor, storage::v3::View,
storage::v3::LabelId, storage::v3::PropertyStore, PropertyToTypedValueConverter,
memgraph::storage::v3::Error>;
} // namespace memgraph::storage::v3

View File

@@ -1,21 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "storage/v3/bindings/bindings.hpp"
#include "expr/interpret/frame.hpp"
#include "storage/v3/bindings/typed_value.hpp"
namespace memgraph::storage::v3 {
using Frame = memgraph::expr::Frame<TypedValue>;
} // namespace memgraph::storage::v3

View File

@@ -1,18 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "storage/v3/bindings/bindings.hpp"
#include "expr/ast/pretty_print.hpp"
namespace memgraph::storage::v3 {} // namespace memgraph::storage::v3

View File

@@ -1,16 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "storage/v3/bindings/bindings.hpp"
#include "expr/semantic/symbol_generator.hpp"

View File

@@ -1,20 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "storage/v3/bindings/bindings.hpp"
#include "expr/semantic/symbol_table.hpp"
namespace memgraph::storage::v3 {
using SymbolTable = memgraph::expr::SymbolTable;
} // namespace memgraph::storage::v3

View File

@@ -1,23 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "expr/typed_value.hpp"
#include "storage/v3/edge_accessor.hpp"
#include "storage/v3/path.hpp"
#include "storage/v3/vertex_accessor.hpp"
namespace memgraph::expr {
namespace v3 = memgraph::storage::v3;
template class TypedValueT<v3::VertexAccessor, v3::EdgeAccessor, v3::Path>;
} // namespace memgraph::expr

View File

@@ -1,28 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "expr/typed_value.hpp"
#include "storage/v3/bindings/bindings.hpp"
#include "storage/v3/edge_accessor.hpp"
#include "storage/v3/path.hpp"
#include "storage/v3/vertex_accessor.hpp"
namespace memgraph::expr {
namespace v3 = memgraph::storage::v3;
extern template class memgraph::expr::TypedValueT<v3::VertexAccessor, v3::EdgeAccessor, v3::Path>;
} // namespace memgraph::expr
namespace memgraph::storage::v3 {
using TypedValue = memgraph::expr::TypedValueT<VertexAccessor, EdgeAccessor, Path>;
} // namespace memgraph::storage::v3

View File

@@ -0,0 +1,108 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "storage/v3/commit_log.hpp"
#include "utils/memory.hpp"
namespace memgraph::storage::v3 {
CommitLog::CommitLog() : allocator_(utils::NewDeleteResource()) {}
CommitLog::CommitLog(uint64_t oldest_active)
: head_start_{oldest_active / kIdsInBlock * kIdsInBlock},
next_start_{head_start_ + kIdsInBlock},
allocator_{utils::NewDeleteResource()} {
head_ = allocator_.allocate(1);
allocator_.construct(head_);
// set all the previous ids
const auto field_idx = (oldest_active % kIdsInBlock) / kIdsInField;
for (size_t i = 0; i < field_idx; ++i) {
head_->field[i] = std::numeric_limits<uint64_t>::max();
}
const auto idx_in_field = oldest_active % kIdsInField;
if (idx_in_field != 0) {
head_->field[field_idx] = std::numeric_limits<uint64_t>::max();
head_->field[field_idx] >>= kIdsInField - idx_in_field;
}
oldest_active_ = oldest_active;
}
CommitLog::~CommitLog() {
while (head_) {
Block *tmp = head_->next;
head_->~Block();
allocator_.deallocate(head_, 1);
head_ = tmp;
}
}
void CommitLog::MarkFinished(uint64_t id) {
Block *block = FindOrCreateBlock(id);
block->field[(id % kIdsInBlock) / kIdsInField] |= 1ULL << (id % kIdsInField);
if (id == oldest_active_) {
UpdateOldestActive();
}
}
uint64_t CommitLog::OldestActive() const noexcept { return oldest_active_; }
void CommitLog::UpdateOldestActive() {
while (head_) {
// This is necessary for amortized constant complexity. If we always start
// from the 0th field, the amount of steps we make through each block is
// quadratic in kBlockSize.
uint64_t start_field = oldest_active_ >= head_start_ ? (oldest_active_ - head_start_) / kIdsInField : 0;
for (uint64_t i = start_field; i < kBlockSize; ++i) {
if (head_->field[i] != std::numeric_limits<uint64_t>::max()) {
// NOLINTNEXTLINE(cppcoreguidelines-narrowing-conversions)
oldest_active_ = head_start_ + i * kIdsInField + __builtin_ffsl(static_cast<int64_t>(~head_->field[i])) - 1;
return;
}
}
// All IDs in this block are marked, we can delete it now.
Block *tmp = head_->next;
head_->~Block();
allocator_.deallocate(head_, 1);
head_ = tmp;
head_start_ += kIdsInBlock;
}
oldest_active_ = next_start_;
}
CommitLog::Block *CommitLog::FindOrCreateBlock(const uint64_t id) {
if (!head_) {
head_ = allocator_.allocate(1);
allocator_.construct(head_);
head_start_ = next_start_;
next_start_ += kIdsInBlock;
}
Block *current = head_;
uint64_t current_start = head_start_;
while (id >= current_start + kIdsInBlock) {
if (!current->next) {
current->next = allocator_.allocate(1);
allocator_.construct(current->next);
next_start_ += kIdsInBlock;
}
current = current->next;
current_start += kIdsInBlock;
}
return current;
}
} // namespace memgraph::storage::v3

View File

@@ -0,0 +1,78 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
/// @file commit_log.hpp
#pragma once
#include <cstdint>
#include <mutex>
#include "utils/memory.hpp"
#include "utils/spin_lock.hpp"
namespace memgraph::storage::v3 {
/// This class keeps track of finalized transactions to provide info on the
/// oldest active transaction (minimal transaction ID which could still be
/// active).
///
/// Basically, it is a set which, at the beginning, contains all transaction
/// IDs and supports two operations: remove an ID from the set (\ref
/// SetFinished) and retrieve the minimal ID still in the set (\ref
/// OldestActive).
///
/// This class is thread-safe.
class CommitLog final {
public:
// TODO(mtomic): use pool allocator for blocks
CommitLog();
/// Create a commit log which has the oldest active id set to
/// oldest_active
/// @param oldest_active the oldest active id
explicit CommitLog(uint64_t oldest_active);
CommitLog(const CommitLog &) = delete;
CommitLog &operator=(const CommitLog &) = delete;
CommitLog(CommitLog &&) = delete;
CommitLog &operator=(CommitLog &&) = delete;
~CommitLog();
/// Mark a transaction as finished.
/// @throw std::bad_alloc
void MarkFinished(uint64_t id);
/// Retrieve the oldest transaction still not marked as finished.
uint64_t OldestActive() const noexcept;
private:
static constexpr uint64_t kBlockSize = 8192;
static constexpr uint64_t kIdsInField = sizeof(uint64_t) * 8;
static constexpr uint64_t kIdsInBlock = kBlockSize * kIdsInField;
struct Block {
Block *next{nullptr};
uint64_t field[kBlockSize]{};
};
void UpdateOldestActive();
/// @throw std::bad_alloc
Block *FindOrCreateBlock(uint64_t id);
Block *head_{nullptr};
uint64_t head_start_{0};
uint64_t next_start_{0};
uint64_t oldest_active_{0};
utils::Allocator<Block> allocator_;
};
} // namespace memgraph::storage::v3

View File

@@ -14,8 +14,6 @@
#include <chrono>
#include <cstdint>
#include <filesystem>
#include "io/time.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/isolation_level.hpp"
#include "storage/v3/transaction.hpp"
@@ -26,14 +24,36 @@ namespace memgraph::storage::v3 {
/// the storage. This class also defines the default behavior.
struct Config {
struct Gc {
// Interval after which the committed deltas are cleaned up
io::Duration reclamation_interval{};
// TODO(antaljanosbenjamin): How to handle garbage collection?
enum class Type { NONE };
Type type{Type::NONE};
std::chrono::milliseconds interval{std::chrono::milliseconds(1000)};
} gc;
struct Items {
bool properties_on_edges{true};
} items;
struct Durability {
enum class SnapshotWalMode { DISABLED, PERIODIC_SNAPSHOT, PERIODIC_SNAPSHOT_WITH_WAL };
std::filesystem::path storage_directory{"storage"};
bool recover_on_startup{false};
SnapshotWalMode snapshot_wal_mode{SnapshotWalMode::DISABLED};
std::chrono::milliseconds snapshot_interval{std::chrono::minutes(2)};
uint64_t snapshot_retention_count{3};
uint64_t wal_file_size_kibibytes{static_cast<uint64_t>(20 * 1024)};
uint64_t wal_file_flush_every_n_tx{100000};
bool snapshot_on_exit{false};
} durability;
struct Transaction {
IsolationLevel isolation_level{IsolationLevel::SNAPSHOT_ISOLATION};
} transaction;

View File

@@ -0,0 +1,414 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "storage/v3/constraints.hpp"
#include <algorithm>
#include <cstring>
#include <map>
#include "storage/v3/mvcc.hpp"
#include "storage/v3/vertex.hpp"
#include "utils/logging.hpp"
namespace memgraph::storage::v3 {
namespace {
/// Helper function that determines position of the given `property` in the
/// sorted `property_array` using binary search. In the case that `property`
/// cannot be found, `std::nullopt` is returned.
std::optional<size_t> FindPropertyPosition(const PropertyIdArray &property_array, PropertyId property) {
const auto *it = std::lower_bound(property_array.values, property_array.values + property_array.size, property);
if (it == property_array.values + property_array.size || *it != property) {
return std::nullopt;
}
return it - property_array.values;
}
/// Helper function for validating unique constraints on commit. Returns true if
/// the last committed version of the given vertex contains the given label and
/// set of property values. This function should be called when commit lock is
/// active.
bool LastCommittedVersionHasLabelProperty(const Vertex &vertex, LabelId label, const std::set<PropertyId> &properties,
const std::vector<PropertyValue> &value_array, const Transaction &transaction,
uint64_t commit_timestamp) {
MG_ASSERT(properties.size() == value_array.size(), "Invalid database state!");
PropertyIdArray property_array(properties.size());
bool current_value_equal_to_value[kUniqueConstraintsMaxProperties];
memset(current_value_equal_to_value, 0, sizeof(current_value_equal_to_value));
// Since the commit lock is active, any transaction that tries to write to
// a vertex which is part of the given `transaction` will result in a
// serialization error. But, note that the given `vertex`'s data does not have
// to be modified in the current `transaction`, meaning that a guard lock to
// access vertex's data is still necessary because another active transaction
// could modify it in the meantime.
Delta *delta{nullptr};
bool deleted{false};
bool has_label{false};
{
delta = vertex.delta;
deleted = vertex.deleted;
has_label = VertexHasLabel(vertex, label);
size_t i = 0;
for (const auto &property : properties) {
current_value_equal_to_value[i] = vertex.properties.IsPropertyEqual(property, value_array[i]);
property_array.values[i] = property;
i++;
}
}
while (delta != nullptr) {
auto ts = delta->timestamp->load(std::memory_order_acquire);
if (ts < commit_timestamp || ts == transaction.transaction_id) {
break;
}
switch (delta->action) {
case Delta::Action::SET_PROPERTY: {
auto pos = FindPropertyPosition(property_array, delta->property.key);
if (pos) {
current_value_equal_to_value[*pos] = delta->property.value == value_array[*pos];
}
break;
}
case Delta::Action::DELETE_OBJECT: {
MG_ASSERT(!deleted, "Invalid database state!");
deleted = true;
break;
}
case Delta::Action::RECREATE_OBJECT: {
MG_ASSERT(deleted, "Invalid database state!");
deleted = false;
break;
}
case Delta::Action::ADD_LABEL: {
if (delta->label == label) {
MG_ASSERT(!has_label, "Invalid database state!");
has_label = true;
break;
}
}
case Delta::Action::REMOVE_LABEL: {
if (delta->label == label) {
MG_ASSERT(has_label, "Invalid database state!");
has_label = false;
break;
}
}
case Delta::Action::ADD_IN_EDGE:
case Delta::Action::ADD_OUT_EDGE:
case Delta::Action::REMOVE_IN_EDGE:
case Delta::Action::REMOVE_OUT_EDGE:
break;
}
delta = delta->next.load(std::memory_order_acquire);
}
for (size_t i = 0; i < properties.size(); ++i) {
if (!current_value_equal_to_value[i]) {
return false;
}
}
return !deleted && has_label;
}
/// Helper function for unique constraint garbage collection. Returns true if
/// there's a reachable version of the vertex that has the given label and
/// property values.
bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, const std::set<PropertyId> &properties,
const std::vector<PropertyValue> &values, uint64_t timestamp) {
MG_ASSERT(properties.size() == values.size(), "Invalid database state!");
PropertyIdArray property_array(properties.size());
bool current_value_equal_to_value[kUniqueConstraintsMaxProperties];
memset(current_value_equal_to_value, 0, sizeof(current_value_equal_to_value));
bool has_label{false};
bool deleted{false};
Delta *delta{nullptr};
{
has_label = VertexHasLabel(vertex, label);
deleted = vertex.deleted;
delta = vertex.delta;
size_t i = 0;
for (const auto &property : properties) {
current_value_equal_to_value[i] = vertex.properties.IsPropertyEqual(property, values[i]);
property_array.values[i] = property;
i++;
}
}
{
bool all_values_match = true;
for (size_t i = 0; i < values.size(); ++i) {
if (!current_value_equal_to_value[i]) {
all_values_match = false;
break;
}
}
if (!deleted && has_label && all_values_match) {
return true;
}
}
while (delta != nullptr) {
auto ts = delta->timestamp->load(std::memory_order_acquire);
if (ts < timestamp) {
break;
}
switch (delta->action) {
case Delta::Action::ADD_LABEL:
if (delta->label == label) {
MG_ASSERT(!has_label, "Invalid database state!");
has_label = true;
}
break;
case Delta::Action::REMOVE_LABEL:
if (delta->label == label) {
MG_ASSERT(has_label, "Invalid database state!");
has_label = false;
}
break;
case Delta::Action::SET_PROPERTY: {
auto pos = FindPropertyPosition(property_array, delta->property.key);
if (pos) {
current_value_equal_to_value[*pos] = delta->property.value == values[*pos];
}
break;
}
case Delta::Action::RECREATE_OBJECT: {
MG_ASSERT(deleted, "Invalid database state!");
deleted = false;
break;
}
case Delta::Action::DELETE_OBJECT: {
MG_ASSERT(!deleted, "Invalid database state!");
deleted = true;
break;
}
case Delta::Action::ADD_IN_EDGE:
case Delta::Action::ADD_OUT_EDGE:
case Delta::Action::REMOVE_IN_EDGE:
case Delta::Action::REMOVE_OUT_EDGE:
break;
}
bool all_values_match = true;
for (size_t i = 0; i < values.size(); ++i) {
if (!current_value_equal_to_value[i]) {
all_values_match = false;
break;
}
}
if (!deleted && has_label && all_values_match) {
return true;
}
delta = delta->next.load(std::memory_order_acquire);
}
return false;
}
/// Helper function that, given the set of `properties`, extracts corresponding
/// property values from the `vertex`.
/// @throw std::bad_alloc
std::optional<std::vector<PropertyValue>> ExtractPropertyValues(const Vertex &vertex,
const std::set<PropertyId> &properties) {
std::vector<PropertyValue> value_array;
value_array.reserve(properties.size());
for (const auto &prop : properties) {
auto value = vertex.properties.GetProperty(prop);
if (value.IsNull()) {
return std::nullopt;
}
value_array.emplace_back(std::move(value));
}
return std::move(value_array);
}
} // namespace
bool operator==(const ConstraintViolation &lhs, const ConstraintViolation &rhs) {
return lhs.type == rhs.type && lhs.label == rhs.label && lhs.properties == rhs.properties;
}
bool UniqueConstraints::Entry::operator<(const Entry &rhs) const {
if (values < rhs.values) {
return true;
}
if (rhs.values < values) {
return false;
}
return std::make_tuple(vertex, timestamp) < std::make_tuple(rhs.vertex, rhs.timestamp);
}
bool UniqueConstraints::Entry::operator==(const Entry &rhs) const {
return values == rhs.values && vertex == rhs.vertex && timestamp == rhs.timestamp;
}
bool UniqueConstraints::Entry::operator<(const std::vector<PropertyValue> &rhs) const { return values < rhs; }
bool UniqueConstraints::Entry::operator==(const std::vector<PropertyValue> &rhs) const { return values == rhs; }
void UniqueConstraints::UpdateBeforeCommit(const Vertex *vertex, const Transaction &tx) {
for (auto &[label_props, storage] : constraints_) {
if (!VertexHasLabel(*vertex, label_props.first)) {
continue;
}
auto values = ExtractPropertyValues(*vertex, label_props.second);
if (values) {
auto acc = storage.access();
acc.insert(Entry{std::move(*values), vertex, tx.start_timestamp});
}
}
}
utils::BasicResult<ConstraintViolation, UniqueConstraints::CreationStatus> UniqueConstraints::CreateConstraint(
LabelId label, const std::set<PropertyId> &properties, VerticesSkipList::Accessor vertices) {
if (properties.empty()) {
return CreationStatus::EMPTY_PROPERTIES;
}
if (properties.size() > kUniqueConstraintsMaxProperties) {
return CreationStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED;
}
auto [constraint, emplaced] =
constraints_.emplace(std::piecewise_construct, std::forward_as_tuple(label, properties), std::forward_as_tuple());
if (!emplaced) {
// Constraint already exists.
return CreationStatus::ALREADY_EXISTS;
}
bool violation_found = false;
{
auto acc = constraint->second.access();
for (const auto &lo_vertex : vertices) {
const auto &vertex = lo_vertex.vertex;
if (vertex.deleted || !VertexHasLabel(vertex, label)) {
continue;
}
auto values = ExtractPropertyValues(vertex, properties);
if (!values) {
continue;
}
// Check whether there already is a vertex with the same values for the
// given label and property.
auto it = acc.find_equal_or_greater(*values);
if (it != acc.end() && it->values == *values) {
violation_found = true;
break;
}
acc.insert(Entry{std::move(*values), &vertex, 0});
}
}
if (violation_found) {
// In the case of the violation, storage for the current constraint has to
// be removed.
constraints_.erase(constraint);
return ConstraintViolation{ConstraintViolation::Type::UNIQUE, label, properties};
}
return CreationStatus::SUCCESS;
}
UniqueConstraints::DeletionStatus UniqueConstraints::DropConstraint(LabelId label,
const std::set<PropertyId> &properties) {
if (properties.empty()) {
return UniqueConstraints::DeletionStatus::EMPTY_PROPERTIES;
}
if (properties.size() > kUniqueConstraintsMaxProperties) {
return UniqueConstraints::DeletionStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED;
}
if (constraints_.erase({label, properties}) > 0) {
return UniqueConstraints::DeletionStatus::SUCCESS;
}
return UniqueConstraints::DeletionStatus::NOT_FOUND;
}
std::optional<ConstraintViolation> UniqueConstraints::Validate(const Vertex &vertex, const Transaction &tx,
uint64_t commit_timestamp) const {
if (vertex.deleted) {
return std::nullopt;
}
for (const auto &[label_props, storage] : constraints_) {
const auto &label = label_props.first;
const auto &properties = label_props.second;
if (!VertexHasLabel(vertex, label)) {
continue;
}
auto value_array = ExtractPropertyValues(vertex, properties);
if (!value_array) {
continue;
}
auto acc = storage.access();
auto it = acc.find_equal_or_greater(*value_array);
for (; it != acc.end(); ++it) {
if (*value_array < it->values) {
break;
}
// The `vertex` that is going to be committed violates a unique constraint
// if it's different than a vertex indexed in the list of constraints and
// has the same label and property value as the last committed version of
// the vertex from the list.
if (&vertex != it->vertex &&
LastCommittedVersionHasLabelProperty(*it->vertex, label, properties, *value_array, tx, commit_timestamp)) {
return ConstraintViolation{ConstraintViolation::Type::UNIQUE, label, properties};
}
}
}
return std::nullopt;
}
std::vector<std::pair<LabelId, std::set<PropertyId>>> UniqueConstraints::ListConstraints() const {
std::vector<std::pair<LabelId, std::set<PropertyId>>> ret;
ret.reserve(constraints_.size());
for (const auto &[label_props, _] : constraints_) {
ret.push_back(label_props);
}
return ret;
}
void UniqueConstraints::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
for (auto &[label_props, storage] : constraints_) {
auto acc = storage.access();
for (auto it = acc.begin(); it != acc.end();) {
auto next_it = it;
++next_it;
if (it->timestamp >= oldest_active_start_timestamp) {
it = next_it;
continue;
}
if ((next_it != acc.end() && it->vertex == next_it->vertex && it->values == next_it->values) ||
!AnyVersionHasLabelProperty(*it->vertex, label_props.first, label_props.second, it->values,
oldest_active_start_timestamp)) {
acc.remove(*it);
}
it = next_it;
}
}
}
} // namespace memgraph::storage::v3

View File

@@ -0,0 +1,202 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <optional>
#include <set>
#include <vector>
#include "storage/v3/id_types.hpp"
#include "storage/v3/transaction.hpp"
#include "storage/v3/vertex.hpp"
#include "storage/v3/vertices_skip_list.hpp"
#include "utils/logging.hpp"
#include "utils/result.hpp"
#include "utils/skip_list.hpp"
namespace memgraph::storage::v3 {
// NOLINTNEXTLINE(misc-definitions-in-headers)
const size_t kUniqueConstraintsMaxProperties = 32;
/// Utility class to store data in a fixed size array. The array is used
/// instead of `std::vector` to avoid `std::bad_alloc` exception where not
/// necessary.
template <class T>
struct FixedCapacityArray {
size_t size;
T values[kUniqueConstraintsMaxProperties];
explicit FixedCapacityArray(size_t array_size) : size(array_size) {
MG_ASSERT(size <= kUniqueConstraintsMaxProperties, "Invalid array size!");
}
};
using PropertyIdArray = FixedCapacityArray<PropertyId>;
struct ConstraintViolation {
enum class Type {
EXISTENCE,
UNIQUE,
};
Type type;
LabelId label;
// While multiple properties are supported by unique constraints, the
// `properties` set will always have exactly one element in the case of
// existence constraint violation.
std::set<PropertyId> properties;
};
bool operator==(const ConstraintViolation &lhs, const ConstraintViolation &rhs);
class UniqueConstraints {
private:
struct Entry {
std::vector<PropertyValue> values;
const Vertex *vertex;
uint64_t timestamp;
bool operator<(const Entry &rhs) const;
bool operator==(const Entry &rhs) const;
bool operator<(const std::vector<PropertyValue> &rhs) const;
bool operator==(const std::vector<PropertyValue> &rhs) const;
};
public:
/// Status for creation of unique constraints.
/// Note that this does not cover the case when the constraint is violated.
enum class CreationStatus {
SUCCESS,
ALREADY_EXISTS,
EMPTY_PROPERTIES,
PROPERTIES_SIZE_LIMIT_EXCEEDED,
};
/// Status for deletion of unique constraints.
enum class DeletionStatus {
SUCCESS,
NOT_FOUND,
EMPTY_PROPERTIES,
PROPERTIES_SIZE_LIMIT_EXCEEDED,
};
/// Indexes the given vertex for relevant labels and properties.
/// This method should be called before committing and validating vertices
/// against unique constraints.
/// @throw std::bad_alloc
void UpdateBeforeCommit(const Vertex *vertex, const Transaction &tx);
/// Creates unique constraint on the given `label` and a list of `properties`.
/// Returns constraint violation if there are multiple vertices with the same
/// label and property values. Returns `CreationStatus::ALREADY_EXISTS` if
/// constraint already existed, `CreationStatus::EMPTY_PROPERTIES` if the
/// given list of properties is empty,
/// `CreationStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED` if the list of properties
/// exceeds the maximum allowed number of properties, and
/// `CreationStatus::SUCCESS` on success.
/// @throw std::bad_alloc
utils::BasicResult<ConstraintViolation, CreationStatus> CreateConstraint(LabelId label,
const std::set<PropertyId> &properties,
VerticesSkipList::Accessor vertices);
/// Deletes the specified constraint. Returns `DeletionStatus::NOT_FOUND` if
/// there is not such constraint in the storage,
/// `DeletionStatus::EMPTY_PROPERTIES` if the given set of `properties` is
/// empty, `DeletionStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED` if the given set
/// of `properties` exceeds the maximum allowed number of properties, and
/// `DeletionStatus::SUCCESS` on success.
DeletionStatus DropConstraint(LabelId label, const std::set<PropertyId> &properties);
bool ConstraintExists(LabelId label, const std::set<PropertyId> &properties) {
return constraints_.find({label, properties}) != constraints_.end();
}
/// Validates the given vertex against unique constraints before committing.
/// This method should be called while commit lock is active with
/// `commit_timestamp` being a potential commit timestamp of the transaction.
/// @throw std::bad_alloc
std::optional<ConstraintViolation> Validate(const Vertex &vertex, const Transaction &tx,
uint64_t commit_timestamp) const;
std::vector<std::pair<LabelId, std::set<PropertyId>>> ListConstraints() const;
/// GC method that removes outdated entries from constraints' storages.
void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
void Clear() { constraints_.clear(); }
private:
std::map<std::pair<LabelId, std::set<PropertyId>>, utils::SkipList<Entry>> constraints_;
};
struct Constraints {
std::vector<std::pair<LabelId, PropertyId>> existence_constraints;
UniqueConstraints unique_constraints;
};
/// Adds a unique constraint to `constraints`. Returns true if the constraint
/// was successfully added, false if it already exists and a
/// `ConstraintViolation` if there is an existing vertex violating the
/// constraint.
///
/// @throw std::bad_alloc
/// @throw std::length_error
inline utils::BasicResult<ConstraintViolation, bool> CreateExistenceConstraint(Constraints *constraints, LabelId label,
PropertyId property,
VerticesSkipList::Accessor vertices) {
if (utils::Contains(constraints->existence_constraints, std::make_pair(label, property))) {
return false;
}
for (const auto &lgo_vertex : vertices) {
const auto &vertex = lgo_vertex.vertex;
if (!vertex.deleted && VertexHasLabel(vertex, label) && !vertex.properties.HasProperty(property)) {
return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label, std::set<PropertyId>{property}};
}
}
constraints->existence_constraints.emplace_back(label, property);
return true;
}
/// Removes a unique constraint from `constraints`. Returns true if the
/// constraint was removed, and false if it doesn't exist.
inline bool DropExistenceConstraint(Constraints *constraints, LabelId label, PropertyId property) {
auto it = std::find(constraints->existence_constraints.begin(), constraints->existence_constraints.end(),
std::make_pair(label, property));
if (it == constraints->existence_constraints.end()) {
return false;
}
constraints->existence_constraints.erase(it);
return true;
}
/// Verifies that the given vertex satisfies all existence constraints. Returns
/// `std::nullopt` if all checks pass, and `ConstraintViolation` describing the
/// violated constraint otherwise.
[[nodiscard]] inline std::optional<ConstraintViolation> ValidateExistenceConstraints(const Vertex &vertex,
const Constraints &constraints) {
for (const auto &[label, property] : constraints.existence_constraints) {
if (!vertex.deleted && VertexHasLabel(vertex, label) && !vertex.properties.HasProperty(property)) {
return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label, std::set<PropertyId>{property}};
}
}
return std::nullopt;
}
/// Returns a list of all created existence constraints.
inline std::vector<std::pair<LabelId, PropertyId>> ListExistenceConstraints(const Constraints &constraints) {
return constraints.existence_constraints;
}
} // namespace memgraph::storage::v3

View File

@@ -10,9 +10,7 @@
// licenses/APL.txt.
#include "expr/typed_value.hpp"
#include "query/v2/requests.hpp"
#include "storage/v3/property_value.hpp"
#include "utils/memory.hpp"
#pragma once
@@ -69,57 +67,6 @@ TTypedValue PropertyToTypedValue(const PropertyValue &value) {
LOG_FATAL("Unsupported type");
}
template <typename TTypedValue>
TTypedValue PropertyToTypedValue(const PropertyValue &value, utils::MemoryResource *mem) {
switch (value.type()) {
case storage::v3::PropertyValue::Type::Null:
return TTypedValue(mem);
case storage::v3::PropertyValue::Type::Bool:
return TTypedValue(value.ValueBool(), mem);
case storage::v3::PropertyValue::Type::Int:
return TTypedValue(value.ValueInt(), mem);
case storage::v3::PropertyValue::Type::Double:
return TTypedValue(value.ValueDouble(), mem);
case storage::v3::PropertyValue::Type::String:
return TTypedValue(value.ValueString(), mem);
case storage::v3::PropertyValue::Type::List: {
const auto &src = value.ValueList();
std::vector<TTypedValue> dst;
dst.reserve(src.size());
for (const auto &elem : src) {
dst.push_back(PropertyToTypedValue<TTypedValue>(elem, mem));
}
return TTypedValue(std::move(dst), mem);
}
case storage::v3::PropertyValue::Type::Map: {
const auto &src = value.ValueMap();
std::map<std::string, TTypedValue> dst;
for (const auto &elem : src) {
dst.insert({std::string(elem.first), PropertyToTypedValue<TTypedValue>(elem.second, mem)});
}
return TTypedValue(std::move(dst), mem);
}
case storage::v3::PropertyValue::Type::TemporalData: {
const auto &temporal_data = value.ValueTemporalData();
switch (temporal_data.type) {
case storage::v3::TemporalType::Date: {
return TTypedValue(utils::Date(temporal_data.microseconds), mem);
}
case storage::v3::TemporalType::LocalTime: {
return TTypedValue(utils::LocalTime(temporal_data.microseconds), mem);
}
case storage::v3::TemporalType::LocalDateTime: {
return TTypedValue(utils::LocalDateTime(temporal_data.microseconds), mem);
}
case storage::v3::TemporalType::Duration: {
return TTypedValue(utils::Duration(temporal_data.microseconds), mem);
}
}
}
}
LOG_FATAL("Unsupported type");
}
template <typename TTypedValue>
storage::v3::PropertyValue TypedToPropertyValue(const TTypedValue &value) {
switch (value.type()) {
@@ -166,40 +113,4 @@ storage::v3::PropertyValue TypedToPropertyValue(const TTypedValue &value) {
}
throw expr::TypedValueException("Unsupported conversion from TTypedValue to PropertyValue");
}
template <typename TTypedValue>
msgs::Value TypedValueToValue(const TTypedValue &value) {
using Value = msgs::Value;
switch (value.type()) {
case TTypedValue::Type::Null:
return {};
case TTypedValue::Type::Bool:
return Value(value.ValueBool());
case TTypedValue::Type::Int:
return Value(value.ValueInt());
case TTypedValue::Type::Double:
return Value(value.ValueDouble());
case TTypedValue::Type::String:
return Value(std::string(value.ValueString()));
case TTypedValue::Type::List: {
const auto &src = value.ValueList();
std::vector<msgs::Value> dst;
dst.reserve(src.size());
std::transform(src.begin(), src.end(), std::back_inserter(dst),
[](const auto &val) { return TypedValueToValue(val); });
return Value(std::move(dst));
}
case TTypedValue::Type::Map: {
const auto &src = value.ValueMap();
std::map<std::string, Value> dst;
for (const auto &elem : src) {
dst.insert({std::string(elem.first), TypedValueToValue(elem.second)});
}
return Value(std::move(dst));
}
default:
break;
}
throw expr::TypedValueException("Unsupported conversion from TTypedValue to PropertyValue");
}
} // namespace memgraph::storage::v3

View File

@@ -11,7 +11,9 @@
#pragma once
#include <atomic>
#include <memory>
#include "storage/v3/edge_ref.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/property_value.hpp"
@@ -24,7 +26,6 @@ namespace memgraph::storage::v3 {
struct Vertex;
struct Edge;
struct Delta;
struct CommitInfo;
// This class stores one of three pointers (`Delta`, `Vertex` and `Edge`)
// without using additional memory for storing the type. The type is stored in
@@ -62,30 +63,31 @@ class PreviousPtr {
Edge *edge{nullptr};
};
PreviousPtr() {}
PreviousPtr() : storage_(0) {}
PreviousPtr(const PreviousPtr &other) noexcept : storage_(other.storage_) {}
PreviousPtr(const PreviousPtr &other) noexcept : storage_(other.storage_.load(std::memory_order_acquire)) {}
PreviousPtr(PreviousPtr &&) = delete;
PreviousPtr &operator=(const PreviousPtr &) = delete;
PreviousPtr &operator=(PreviousPtr &&) = delete;
~PreviousPtr() = default;
Pointer Get() const {
if (storage_ == 0) {
uintptr_t value = storage_.load(std::memory_order_acquire);
if (value == 0) {
return {};
}
uintptr_t type = storage_ & kMask;
uintptr_t type = value & kMask;
if (type == kDelta) {
// NOLINTNEXTLINE(performance-no-int-to-ptr)
return Pointer{reinterpret_cast<Delta *>(storage_ & ~kMask)};
return Pointer{reinterpret_cast<Delta *>(value & ~kMask)};
}
if (type == kVertex) {
// NOLINTNEXTLINE(performance-no-int-to-ptr)
return Pointer{reinterpret_cast<Vertex *>(storage_ & ~kMask)};
return Pointer{reinterpret_cast<Vertex *>(value & ~kMask)};
}
if (type == kEdge) {
// NOLINTNEXTLINE(performance-no-int-to-ptr)
return Pointer{reinterpret_cast<Edge *>(storage_ & ~kMask)};
return Pointer{reinterpret_cast<Edge *>(value & ~kMask)};
}
LOG_FATAL("Invalid pointer type!");
}
@@ -93,23 +95,23 @@ class PreviousPtr {
void Set(Delta *delta) {
auto value = reinterpret_cast<uintptr_t>(delta);
MG_ASSERT((value & kMask) == 0, "Invalid pointer!");
storage_ = value | kDelta;
storage_.store(value | kDelta, std::memory_order_release);
}
void Set(Vertex *vertex) {
auto value = reinterpret_cast<uintptr_t>(vertex);
MG_ASSERT((value & kMask) == 0, "Invalid pointer!");
storage_ = value | kVertex;
storage_.store(value | kVertex, std::memory_order_release);
}
void Set(Edge *edge) {
auto value = reinterpret_cast<uintptr_t>(edge);
MG_ASSERT((value & kMask) == 0, "Invalid pointer!");
storage_ = value | kEdge;
storage_.store(value | kEdge, std::memory_order_release);
}
private:
uintptr_t storage_{0};
std::atomic<uintptr_t> storage_;
};
inline bool operator==(const PreviousPtr::Pointer &a, const PreviousPtr::Pointer &b) {
@@ -157,47 +159,47 @@ struct Delta {
struct RemoveInEdgeTag {};
struct RemoveOutEdgeTag {};
Delta(DeleteObjectTag /*unused*/, CommitInfo *commit_info, uint64_t command_id)
: action(Action::DELETE_OBJECT), commit_info(commit_info), command_id(command_id) {}
Delta(DeleteObjectTag /*unused*/, std::atomic<uint64_t> *timestamp, uint64_t command_id)
: action(Action::DELETE_OBJECT), timestamp(timestamp), command_id(command_id) {}
Delta(RecreateObjectTag /*unused*/, CommitInfo *commit_info, uint64_t command_id)
: action(Action::RECREATE_OBJECT), commit_info(commit_info), command_id(command_id) {}
Delta(RecreateObjectTag /*unused*/, std::atomic<uint64_t> *timestamp, uint64_t command_id)
: action(Action::RECREATE_OBJECT), timestamp(timestamp), command_id(command_id) {}
Delta(AddLabelTag /*unused*/, LabelId label, CommitInfo *commit_info, uint64_t command_id)
: action(Action::ADD_LABEL), commit_info(commit_info), command_id(command_id), label(label) {}
Delta(AddLabelTag /*unused*/, LabelId label, std::atomic<uint64_t> *timestamp, uint64_t command_id)
: action(Action::ADD_LABEL), timestamp(timestamp), command_id(command_id), label(label) {}
Delta(RemoveLabelTag /*unused*/, LabelId label, CommitInfo *commit_info, uint64_t command_id)
: action(Action::REMOVE_LABEL), commit_info(commit_info), command_id(command_id), label(label) {}
Delta(RemoveLabelTag /*unused*/, LabelId label, std::atomic<uint64_t> *timestamp, uint64_t command_id)
: action(Action::REMOVE_LABEL), timestamp(timestamp), command_id(command_id), label(label) {}
Delta(SetPropertyTag /*unused*/, PropertyId key, const PropertyValue &value, CommitInfo *commit_info,
Delta(SetPropertyTag /*unused*/, PropertyId key, const PropertyValue &value, std::atomic<uint64_t> *timestamp,
uint64_t command_id)
: action(Action::SET_PROPERTY), commit_info(commit_info), command_id(command_id), property({key, value}) {}
: action(Action::SET_PROPERTY), timestamp(timestamp), command_id(command_id), property({key, value}) {}
Delta(AddInEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge, CommitInfo *commit_info,
uint64_t command_id)
Delta(AddInEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge,
std::atomic<uint64_t> *timestamp, uint64_t command_id)
: action(Action::ADD_IN_EDGE),
commit_info(commit_info),
timestamp(timestamp),
command_id(command_id),
vertex_edge({edge_type, std::move(vertex_id), edge}) {}
Delta(AddOutEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge, CommitInfo *commit_info,
uint64_t command_id)
Delta(AddOutEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge,
std::atomic<uint64_t> *timestamp, uint64_t command_id)
: action(Action::ADD_OUT_EDGE),
commit_info(commit_info),
timestamp(timestamp),
command_id(command_id),
vertex_edge({edge_type, std::move(vertex_id), edge}) {}
Delta(RemoveInEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge, CommitInfo *commit_info,
uint64_t command_id)
Delta(RemoveInEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge,
std::atomic<uint64_t> *timestamp, uint64_t command_id)
: action(Action::REMOVE_IN_EDGE),
commit_info(commit_info),
timestamp(timestamp),
command_id(command_id),
vertex_edge({edge_type, std::move(vertex_id), edge}) {}
Delta(RemoveOutEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge, CommitInfo *commit_info,
uint64_t command_id)
Delta(RemoveOutEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge,
std::atomic<uint64_t> *timestamp, uint64_t command_id)
: action(Action::REMOVE_OUT_EDGE),
commit_info(commit_info),
timestamp(timestamp),
command_id(command_id),
vertex_edge({edge_type, std::move(vertex_id), edge}) {}
@@ -218,7 +220,6 @@ struct Delta {
case Action::REMOVE_IN_EDGE:
case Action::REMOVE_OUT_EDGE:
std::destroy_at(&vertex_edge.vertex_id);
break;
case Action::SET_PROPERTY:
property.value.~PropertyValue();
break;
@@ -228,10 +229,10 @@ struct Delta {
Action action;
// TODO: optimize with in-place copy
CommitInfo *commit_info;
std::atomic<uint64_t> *timestamp;
uint64_t command_id;
PreviousPtr prev;
Delta *next{nullptr};
std::atomic<Delta *> next{nullptr};
union {
LabelId label;

View File

@@ -0,0 +1,346 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "storage/v3/durability/durability.hpp"
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <algorithm>
#include <tuple>
#include <utility>
#include <vector>
#include "storage/v3/durability/paths.hpp"
#include "storage/v3/durability/snapshot.hpp"
#include "storage/v3/durability/wal.hpp"
#include "utils/logging.hpp"
#include "utils/memory_tracker.hpp"
#include "utils/message.hpp"
namespace memgraph::storage::v3::durability {
void VerifyStorageDirectoryOwnerAndProcessUserOrDie(const std::filesystem::path &storage_directory) {
// Get the process user ID.
auto process_euid = geteuid();
// Get the data directory owner ID.
struct stat statbuf;
auto ret = stat(storage_directory.c_str(), &statbuf);
if (ret != 0 && errno == ENOENT) {
// The directory doesn't currently exist.
return;
}
MG_ASSERT(ret == 0, "Couldn't get stat for '{}' because of: {} ({})", storage_directory, strerror(errno), errno);
auto directory_owner = statbuf.st_uid;
auto get_username = [](auto uid) {
auto info = getpwuid(uid);
if (!info) return std::to_string(uid);
return std::string(info->pw_name);
};
auto user_process = get_username(process_euid);
auto user_directory = get_username(directory_owner);
MG_ASSERT(process_euid == directory_owner,
"The process is running as user {}, but the data directory is "
"owned by user {}. Please start the process as user {}!",
user_process, user_directory, user_directory);
}
std::vector<SnapshotDurabilityInfo> GetSnapshotFiles(const std::filesystem::path &snapshot_directory,
const std::string_view uuid) {
std::vector<SnapshotDurabilityInfo> snapshot_files;
std::error_code error_code;
if (utils::DirExists(snapshot_directory)) {
for (const auto &item : std::filesystem::directory_iterator(snapshot_directory, error_code)) {
if (!item.is_regular_file()) continue;
try {
auto info = ReadSnapshotInfo(item.path());
if (uuid.empty() || info.uuid == uuid) {
snapshot_files.emplace_back(item.path(), std::move(info.uuid), info.start_timestamp);
}
} catch (const RecoveryFailure &) {
continue;
}
}
MG_ASSERT(!error_code, "Couldn't recover data because an error occurred: {}!", error_code.message());
}
return snapshot_files;
}
std::optional<std::vector<WalDurabilityInfo>> GetWalFiles(const std::filesystem::path &wal_directory,
const std::string_view uuid,
const std::optional<size_t> current_seq_num) {
if (!utils::DirExists(wal_directory)) return std::nullopt;
std::vector<WalDurabilityInfo> wal_files;
std::error_code error_code;
for (const auto &item : std::filesystem::directory_iterator(wal_directory, error_code)) {
if (!item.is_regular_file()) continue;
try {
auto info = ReadWalInfo(item.path());
if ((uuid.empty() || info.uuid == uuid) && (!current_seq_num || info.seq_num < *current_seq_num))
wal_files.emplace_back(info.seq_num, info.from_timestamp, info.to_timestamp, std::move(info.uuid),
std::move(info.epoch_id), item.path());
} catch (const RecoveryFailure &e) {
spdlog::warn("Failed to read {}", item.path());
continue;
}
}
MG_ASSERT(!error_code, "Couldn't recover data because an error occurred: {}!", error_code.message());
std::sort(wal_files.begin(), wal_files.end());
return std::move(wal_files);
}
// Function used to recover all discovered indices and constraints. The
// indices and constraints must be recovered after the data recovery is done
// to ensure that the indices and constraints are consistent at the end of the
// recovery process.
void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_constraints, Indices *indices,
Constraints *constraints, VerticesSkipList *vertices) {
spdlog::info("Recreating indices from metadata.");
// Recover label indices.
spdlog::info("Recreating {} label indices from metadata.", indices_constraints.indices.label.size());
for (const auto &item : indices_constraints.indices.label) {
if (!indices->label_index.CreateIndex(item, vertices->access()))
throw RecoveryFailure("The label index must be created here!");
spdlog::info("A label index is recreated from metadata.");
}
spdlog::info("Label indices are recreated.");
// Recover label+property indices.
spdlog::info("Recreating {} label+property indices from metadata.",
indices_constraints.indices.label_property.size());
for (const auto &item : indices_constraints.indices.label_property) {
if (!indices->label_property_index.CreateIndex(item.first, item.second, vertices->access()))
throw RecoveryFailure("The label+property index must be created here!");
spdlog::info("A label+property index is recreated from metadata.");
}
spdlog::info("Label+property indices are recreated.");
spdlog::info("Indices are recreated.");
spdlog::info("Recreating constraints from metadata.");
// Recover existence constraints.
spdlog::info("Recreating {} existence constraints from metadata.", indices_constraints.constraints.existence.size());
for (const auto &item : indices_constraints.constraints.existence) {
auto ret = CreateExistenceConstraint(constraints, item.first, item.second, vertices->access());
if (ret.HasError() || !ret.GetValue()) throw RecoveryFailure("The existence constraint must be created here!");
spdlog::info("A existence constraint is recreated from metadata.");
}
spdlog::info("Existence constraints are recreated from metadata.");
// Recover unique constraints.
spdlog::info("Recreating {} unique constraints from metadata.", indices_constraints.constraints.unique.size());
for (const auto &item : indices_constraints.constraints.unique) {
auto ret = constraints->unique_constraints.CreateConstraint(item.first, item.second, vertices->access());
if (ret.HasError() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS)
throw RecoveryFailure("The unique constraint must be created here!");
spdlog::info("A unique constraint is recreated from metadata.");
}
spdlog::info("Unique constraints are recreated from metadata.");
spdlog::info("Constraints are recreated from metadata.");
}
std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_directory,
const std::filesystem::path &wal_directory, std::string *uuid,
std::string *epoch_id,
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
VerticesSkipList *vertices, utils::SkipList<Edge> *edges, uint64_t *edge_count,
NameIdMapper *name_id_mapper, Indices *indices, Constraints *constraints,
Config::Items items, uint64_t *wal_seq_num) {
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
spdlog::info("Recovering persisted data using snapshot ({}) and WAL directory ({}).", snapshot_directory,
wal_directory);
if (!utils::DirExists(snapshot_directory) && !utils::DirExists(wal_directory)) {
spdlog::warn(utils::MessageWithLink("Snapshot or WAL directory don't exist, there is nothing to recover.",
"https://memgr.ph/durability"));
return std::nullopt;
}
auto snapshot_files = GetSnapshotFiles(snapshot_directory);
RecoveryInfo recovery_info;
RecoveredIndicesAndConstraints indices_constraints;
std::optional<uint64_t> snapshot_timestamp;
if (!snapshot_files.empty()) {
spdlog::info("Try recovering from snapshot directory {}.", snapshot_directory);
// Order the files by name
std::sort(snapshot_files.begin(), snapshot_files.end());
// UUID used for durability is the UUID of the last snapshot file.
*uuid = snapshot_files.back().uuid;
std::optional<RecoveredSnapshot> recovered_snapshot;
for (auto it = snapshot_files.rbegin(); it != snapshot_files.rend(); ++it) {
const auto &[path, file_uuid, _] = *it;
if (file_uuid != *uuid) {
spdlog::warn("The snapshot file {} isn't related to the latest snapshot file!", path);
continue;
}
spdlog::info("Starting snapshot recovery from {}.", path);
try {
recovered_snapshot = LoadSnapshot(path, vertices, edges, epoch_history, name_id_mapper, edge_count, items);
spdlog::info("Snapshot recovery successful!");
break;
} catch (const RecoveryFailure &e) {
spdlog::warn("Couldn't recover snapshot from {} because of: {}.", path, e.what());
continue;
}
}
MG_ASSERT(recovered_snapshot,
"The database is configured to recover on startup, but couldn't "
"recover using any of the specified snapshots! Please inspect them "
"and restart the database.");
recovery_info = recovered_snapshot->recovery_info;
indices_constraints = std::move(recovered_snapshot->indices_constraints);
snapshot_timestamp = recovered_snapshot->snapshot_info.start_timestamp;
*epoch_id = std::move(recovered_snapshot->snapshot_info.epoch_id);
if (!utils::DirExists(wal_directory)) {
RecoverIndicesAndConstraints(indices_constraints, indices, constraints, vertices);
return recovered_snapshot->recovery_info;
}
} else {
spdlog::info("No snapshot file was found, collecting information from WAL directory {}.", wal_directory);
std::error_code error_code;
if (!utils::DirExists(wal_directory)) return std::nullopt;
// We use this smaller struct that contains only a subset of information
// necessary for the rest of the recovery function.
// Also, the struct is sorted primarily on the path it contains.
struct WalFileInfo {
explicit WalFileInfo(std::filesystem::path path, std::string uuid, std::string epoch_id)
: path(std::move(path)), uuid(std::move(uuid)), epoch_id(std::move(epoch_id)) {}
std::filesystem::path path;
std::string uuid;
std::string epoch_id;
// NOLINTNEXTLINE(modernize-use-nullptr): bug in clang-tidy
auto operator<=>(const WalFileInfo &) const = default;
};
std::vector<WalFileInfo> wal_files;
for (const auto &item : std::filesystem::directory_iterator(wal_directory, error_code)) {
if (!item.is_regular_file()) continue;
try {
auto info = ReadWalInfo(item.path());
wal_files.emplace_back(item.path(), std::move(info.uuid), std::move(info.epoch_id));
} catch (const RecoveryFailure &e) {
continue;
}
}
MG_ASSERT(!error_code, "Couldn't recover data because an error occurred: {}!", error_code.message());
if (wal_files.empty()) {
spdlog::warn(utils::MessageWithLink("No snapshot or WAL file found.", "https://memgr.ph/durability"));
return std::nullopt;
}
std::sort(wal_files.begin(), wal_files.end());
// UUID used for durability is the UUID of the last WAL file.
// Same for the epoch id.
*uuid = std::move(wal_files.back().uuid);
*epoch_id = std::move(wal_files.back().epoch_id);
}
auto maybe_wal_files = GetWalFiles(wal_directory, *uuid);
if (!maybe_wal_files) {
spdlog::warn(
utils::MessageWithLink("Couldn't get WAL file info from the WAL directory.", "https://memgr.ph/durability"));
return std::nullopt;
}
// Array of all discovered WAL files, ordered by sequence number.
auto &wal_files = *maybe_wal_files;
// By this point we should have recovered from a snapshot, or we should have
// found some WAL files to recover from in the above `else`. This is just a
// sanity check to circumvent the following case: The database didn't recover
// from a snapshot, the above `else` triggered to find the recovery UUID from
// a WAL file. The above `else` has an early exit in case there are no WAL
// files. Because we reached this point there must have been some WAL files
// and we must have some WAL files after this second WAL directory iteration.
MG_ASSERT(snapshot_timestamp || !wal_files.empty(),
"The database didn't recover from a snapshot and didn't find any WAL "
"files that match the last WAL file!");
if (!wal_files.empty()) {
spdlog::info("Checking WAL files.");
{
const auto &first_wal = wal_files[0];
if (first_wal.seq_num != 0) {
// We don't have all WAL files. We need to see whether we need them all.
if (!snapshot_timestamp) {
// We didn't recover from a snapshot and we must have all WAL files
// starting from the first one (seq_num == 0) to be able to recover
// data from them.
LOG_FATAL(
"There are missing prefix WAL files and data can't be "
"recovered without them!");
} else if (first_wal.from_timestamp >= *snapshot_timestamp) {
// We recovered from a snapshot and we must have at least one WAL file
// that has at least one delta that was created before the snapshot in order to
// verify that nothing is missing from the beginning of the WAL chain.
LOG_FATAL(
"You must have at least one WAL file that contains at least one "
"delta that was created before the snapshot file!");
}
}
}
std::optional<uint64_t> previous_seq_num;
auto last_loaded_timestamp = snapshot_timestamp;
spdlog::info("Trying to load WAL files.");
for (auto &wal_file : wal_files) {
if (previous_seq_num && (wal_file.seq_num - *previous_seq_num) > 1) {
LOG_FATAL("You are missing a WAL file with the sequence number {}!", *previous_seq_num + 1);
}
previous_seq_num = wal_file.seq_num;
if (wal_file.epoch_id != *epoch_id) {
// This way we skip WALs finalized only because of role change.
// We can also set the last timestamp to 0 if last loaded timestamp
// is nullopt as this can only happen if the WAL file with seq = 0
// does not contain any deltas and we didn't find any snapshots.
if (last_loaded_timestamp) {
epoch_history->emplace_back(wal_file.epoch_id, *last_loaded_timestamp);
}
*epoch_id = std::move(wal_file.epoch_id);
}
try {
auto info = LoadWal(wal_file.path, &indices_constraints, last_loaded_timestamp, vertices, edges, name_id_mapper,
edge_count, items);
recovery_info.next_vertex_id = std::max(recovery_info.next_vertex_id, info.next_vertex_id);
recovery_info.next_edge_id = std::max(recovery_info.next_edge_id, info.next_edge_id);
recovery_info.next_timestamp = std::max(recovery_info.next_timestamp, info.next_timestamp);
recovery_info.last_commit_timestamp = info.last_commit_timestamp;
} catch (const RecoveryFailure &e) {
LOG_FATAL("Couldn't recover WAL deltas from {} because of: {}", wal_file.path, e.what());
}
if (recovery_info.next_timestamp != 0) {
last_loaded_timestamp.emplace(recovery_info.next_timestamp - 1);
}
}
// The sequence number needs to be recovered even though `LoadWal` didn't
// load any deltas from that file.
*wal_seq_num = *previous_seq_num + 1;
spdlog::info("All necessary WAL files are loaded successfully.");
}
RecoverIndicesAndConstraints(indices_constraints, indices, constraints, vertices);
return recovery_info;
}
} // namespace memgraph::storage::v3::durability

View File

@@ -0,0 +1,112 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <cstdint>
#include <filesystem>
#include <optional>
#include <string>
#include <variant>
#include "storage/v3/config.hpp"
#include "storage/v3/constraints.hpp"
#include "storage/v3/durability/metadata.hpp"
#include "storage/v3/durability/wal.hpp"
#include "storage/v3/edge.hpp"
#include "storage/v3/indices.hpp"
#include "storage/v3/name_id_mapper.hpp"
#include "storage/v3/vertex.hpp"
#include "utils/skip_list.hpp"
namespace memgraph::storage::v3::durability {
/// Verifies that the owner of the storage directory is the same user that
/// started the current process. If the verification fails, the process is
/// killed (`CHECK` failure).
void VerifyStorageDirectoryOwnerAndProcessUserOrDie(const std::filesystem::path &storage_directory);
// Used to capture the snapshot's data related to durability
struct SnapshotDurabilityInfo {
explicit SnapshotDurabilityInfo(std::filesystem::path path, std::string uuid, const uint64_t start_timestamp)
: path(std::move(path)), uuid(std::move(uuid)), start_timestamp(start_timestamp) {}
std::filesystem::path path;
std::string uuid;
uint64_t start_timestamp;
auto operator<=>(const SnapshotDurabilityInfo &) const = default;
};
/// Get list of snapshot files with their UUID.
/// @param snapshot_directory Directory containing the Snapshot files.
/// @param uuid UUID of the Snapshot files. If not empty, fetch only Snapshot
/// file with the specified UUID. Otherwise, fetch only Snapshot files in the
/// snapshot_directory.
/// @return List of snapshot files defined with its path and UUID.
std::vector<SnapshotDurabilityInfo> GetSnapshotFiles(const std::filesystem::path &snapshot_directory,
std::string_view uuid = "");
/// Used to capture a WAL's data related to durability
struct WalDurabilityInfo {
explicit WalDurabilityInfo(const uint64_t seq_num, const uint64_t from_timestamp, const uint64_t to_timestamp,
std::string uuid, std::string epoch_id, std::filesystem::path path)
: seq_num(seq_num),
from_timestamp(from_timestamp),
to_timestamp(to_timestamp),
uuid(std::move(uuid)),
epoch_id(std::move(epoch_id)),
path(std::move(path)) {}
uint64_t seq_num;
uint64_t from_timestamp;
uint64_t to_timestamp;
std::string uuid;
std::string epoch_id;
std::filesystem::path path;
auto operator<=>(const WalDurabilityInfo &) const = default;
};
/// Get list of WAL files ordered by the sequence number
/// @param wal_directory Directory containing the WAL files.
/// @param uuid UUID of the WAL files. If not empty, fetch only WAL files
/// with the specified UUID. Otherwise, fetch all WAL files in the
/// wal_directory.
/// @param current_seq_num Sequence number of the WAL file which is currently
/// being written. If specified, load only finalized WAL files, i.e. WAL files
/// with seq_num < current_seq_num.
/// @return List of WAL files. Each WAL file is defined with its sequence
/// number, from timestamp, to timestamp and path.
std::optional<std::vector<WalDurabilityInfo>> GetWalFiles(const std::filesystem::path &wal_directory,
std::string_view uuid = "",
std::optional<size_t> current_seq_num = {});
// Helper function used to recover all discovered indices and constraints. The
// indices and constraints must be recovered after the data recovery is done
// to ensure that the indices and constraints are consistent at the end of the
// recovery process.
/// @throw RecoveryFailure
void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_constraints, Indices *indices,
Constraints *constraints, VerticesSkipList *vertices);
/// Recovers data either from a snapshot and/or WAL files.
/// @throw RecoveryFailure
/// @throw std::bad_alloc
std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_directory,
const std::filesystem::path &wal_directory, std::string *uuid,
std::string *epoch_id,
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
VerticesSkipList *vertices, utils::SkipList<Edge> *edges, uint64_t *edge_count,
NameIdMapper *name_id_mapper, Indices *indices, Constraints *constraints,
Config::Items items, uint64_t *wal_seq_num);
} // namespace memgraph::storage::v3::durability

View File

@@ -11,10 +11,13 @@
#pragma once
#include "storage/v3/bindings/bindings.hpp"
#include "utils/exceptions.hpp"
#include "expr/semantic/symbol.hpp"
namespace memgraph::storage::v3::durability {
namespace memgraph::storage::v3 {
using Symbol = memgraph::expr::Symbol;
} // namespace memgraph::storage::v3
/// Exception used to handle errors during recovery.
class RecoveryFailure : public utils::BasicException {
using utils::BasicException::BasicException;
};
} // namespace memgraph::storage::v3::durability

View File

@@ -0,0 +1,106 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <cstdint>
namespace memgraph::storage::v3::durability {
/// Markers that are used to indicate crucial parts of the snapshot/WAL.
/// IMPORTANT: Don't forget to update the list of all markers `kMarkersAll` when
/// you add a new Marker.
enum class Marker : uint8_t {
TYPE_NULL = 0x10,
TYPE_BOOL = 0x11,
TYPE_INT = 0x12,
TYPE_DOUBLE = 0x13,
TYPE_STRING = 0x14,
TYPE_LIST = 0x15,
TYPE_MAP = 0x16,
TYPE_PROPERTY_VALUE = 0x17,
TYPE_TEMPORAL_DATA = 0x18,
SECTION_VERTEX = 0x20,
SECTION_EDGE = 0x21,
SECTION_MAPPER = 0x22,
SECTION_METADATA = 0x23,
SECTION_INDICES = 0x24,
SECTION_CONSTRAINTS = 0x25,
SECTION_DELTA = 0x26,
SECTION_EPOCH_HISTORY = 0x27,
SECTION_OFFSETS = 0x42,
DELTA_VERTEX_CREATE = 0x50,
DELTA_VERTEX_DELETE = 0x51,
DELTA_VERTEX_ADD_LABEL = 0x52,
DELTA_VERTEX_REMOVE_LABEL = 0x53,
DELTA_VERTEX_SET_PROPERTY = 0x54,
DELTA_EDGE_CREATE = 0x55,
DELTA_EDGE_DELETE = 0x56,
DELTA_EDGE_SET_PROPERTY = 0x57,
DELTA_TRANSACTION_END = 0x58,
DELTA_LABEL_INDEX_CREATE = 0x59,
DELTA_LABEL_INDEX_DROP = 0x5a,
DELTA_LABEL_PROPERTY_INDEX_CREATE = 0x5b,
DELTA_LABEL_PROPERTY_INDEX_DROP = 0x5c,
DELTA_EXISTENCE_CONSTRAINT_CREATE = 0x5d,
DELTA_EXISTENCE_CONSTRAINT_DROP = 0x5e,
DELTA_UNIQUE_CONSTRAINT_CREATE = 0x5f,
DELTA_UNIQUE_CONSTRAINT_DROP = 0x60,
VALUE_FALSE = 0x00,
VALUE_TRUE = 0xff,
};
/// List of all available markers.
/// IMPORTANT: Don't forget to update this list when you add a new Marker.
static const Marker kMarkersAll[] = {
Marker::TYPE_NULL,
Marker::TYPE_BOOL,
Marker::TYPE_INT,
Marker::TYPE_DOUBLE,
Marker::TYPE_STRING,
Marker::TYPE_LIST,
Marker::TYPE_MAP,
Marker::TYPE_TEMPORAL_DATA,
Marker::TYPE_PROPERTY_VALUE,
Marker::SECTION_VERTEX,
Marker::SECTION_EDGE,
Marker::SECTION_MAPPER,
Marker::SECTION_METADATA,
Marker::SECTION_INDICES,
Marker::SECTION_CONSTRAINTS,
Marker::SECTION_DELTA,
Marker::SECTION_EPOCH_HISTORY,
Marker::SECTION_OFFSETS,
Marker::DELTA_VERTEX_CREATE,
Marker::DELTA_VERTEX_DELETE,
Marker::DELTA_VERTEX_ADD_LABEL,
Marker::DELTA_VERTEX_REMOVE_LABEL,
Marker::DELTA_VERTEX_SET_PROPERTY,
Marker::DELTA_EDGE_CREATE,
Marker::DELTA_EDGE_DELETE,
Marker::DELTA_EDGE_SET_PROPERTY,
Marker::DELTA_TRANSACTION_END,
Marker::DELTA_LABEL_INDEX_CREATE,
Marker::DELTA_LABEL_INDEX_DROP,
Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE,
Marker::DELTA_LABEL_PROPERTY_INDEX_DROP,
Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE,
Marker::DELTA_EXISTENCE_CONSTRAINT_DROP,
Marker::DELTA_UNIQUE_CONSTRAINT_CREATE,
Marker::DELTA_UNIQUE_CONSTRAINT_DROP,
Marker::VALUE_FALSE,
Marker::VALUE_TRUE,
};
} // namespace memgraph::storage::v3::durability

View File

@@ -0,0 +1,75 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <algorithm>
#include <optional>
#include <set>
#include <utility>
#include <vector>
#include "storage/v3/durability/exceptions.hpp"
#include "storage/v3/id_types.hpp"
namespace memgraph::storage::v3::durability {
/// Structure used to hold metadata about the recovered snapshot/WAL.
struct RecoveryInfo {
uint64_t next_vertex_id{0};
uint64_t next_edge_id{0};
uint64_t next_timestamp{0};
// last timestamp read from a WAL file
std::optional<uint64_t> last_commit_timestamp;
};
/// Structure used to track indices and constraints during recovery.
struct RecoveredIndicesAndConstraints {
struct {
std::vector<LabelId> label;
std::vector<std::pair<LabelId, PropertyId>> label_property;
} indices;
struct {
std::vector<std::pair<LabelId, PropertyId>> existence;
std::vector<std::pair<LabelId, std::set<PropertyId>>> unique;
} constraints;
};
// Helper function used to insert indices/constraints into the recovered
// indices/constraints object.
// @throw RecoveryFailure
template <typename TObj>
void AddRecoveredIndexConstraint(std::vector<TObj> *list, TObj obj, const char *error_message) {
auto it = std::find(list->begin(), list->end(), obj);
if (it == list->end()) {
list->push_back(obj);
} else {
throw RecoveryFailure(error_message);
}
}
// Helper function used to remove indices/constraints from the recovered
// indices/constraints object.
// @throw RecoveryFailure
template <typename TObj>
void RemoveRecoveredIndexConstraint(std::vector<TObj> *list, TObj obj, const char *error_message) {
auto it = std::find(list->begin(), list->end(), obj);
if (it != list->end()) {
std::swap(*it, list->back());
list->pop_back();
} else {
throw RecoveryFailure(error_message);
}
}
} // namespace memgraph::storage::v3::durability

View File

@@ -0,0 +1,50 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <cstdint>
#include <string>
#include "utils/timestamp.hpp"
namespace memgraph::storage::v3::durability {
static const std::string kSnapshotDirectory{"snapshots"};
static const std::string kWalDirectory{"wal"};
static const std::string kBackupDirectory{".backup"};
static const std::string kLockFile{".lock"};
// This is the prefix used for Snapshot and WAL filenames. It is a timestamp
// format that equals to: YYYYmmddHHMMSSffffff
const std::string kTimestampFormat = "{:04d}{:02d}{:02d}{:02d}{:02d}{:02d}{:06d}";
// Generates the name for a snapshot in a well-defined sortable format with the
// start timestamp appended to the file name.
inline std::string MakeSnapshotName(uint64_t start_timestamp) {
std::string date_str = utils::Timestamp::Now().ToString(kTimestampFormat);
return date_str + "_timestamp_" + std::to_string(start_timestamp);
}
// Generates the name for a WAL file in a well-defined sortable format.
inline std::string MakeWalName() {
std::string date_str = utils::Timestamp::Now().ToString(kTimestampFormat);
return date_str + "_current";
}
// Generates the name for a WAL file in a well-defined sortable format with the
// range of timestamps contained [from, to] appended to the name.
inline std::string RemakeWalName(const std::string &current_name, uint64_t from_timestamp, uint64_t to_timestamp) {
return current_name.substr(0, current_name.size() - 8) + "_from_" + std::to_string(from_timestamp) + "_to_" +
std::to_string(to_timestamp);
}
} // namespace memgraph::storage::v3::durability

View File

@@ -0,0 +1,468 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "storage/v3/durability/serialization.hpp"
#include "storage/v3/temporal.hpp"
#include "utils/endian.hpp"
namespace memgraph::storage::v3::durability {
//////////////////////////
// Encoder implementation.
//////////////////////////
namespace {
void WriteSize(Encoder *encoder, uint64_t size) {
size = utils::HostToLittleEndian(size);
encoder->Write(reinterpret_cast<const uint8_t *>(&size), sizeof(size));
}
} // namespace
void Encoder::Initialize(const std::filesystem::path &path, const std::string_view &magic, uint64_t version) {
file_.Open(path, utils::OutputFile::Mode::OVERWRITE_EXISTING);
Write(reinterpret_cast<const uint8_t *>(magic.data()), magic.size());
auto version_encoded = utils::HostToLittleEndian(version);
Write(reinterpret_cast<const uint8_t *>(&version_encoded), sizeof(version_encoded));
}
void Encoder::OpenExisting(const std::filesystem::path &path) {
file_.Open(path, utils::OutputFile::Mode::APPEND_TO_EXISTING);
}
void Encoder::Close() {
if (file_.IsOpen()) {
file_.Close();
}
}
void Encoder::Write(const uint8_t *data, uint64_t size) { file_.Write(data, size); }
void Encoder::WriteMarker(Marker marker) {
auto value = static_cast<uint8_t>(marker);
Write(&value, sizeof(value));
}
void Encoder::WriteBool(bool value) {
WriteMarker(Marker::TYPE_BOOL);
if (value) {
WriteMarker(Marker::VALUE_TRUE);
} else {
WriteMarker(Marker::VALUE_FALSE);
}
}
void Encoder::WriteUint(uint64_t value) {
value = utils::HostToLittleEndian(value);
WriteMarker(Marker::TYPE_INT);
Write(reinterpret_cast<const uint8_t *>(&value), sizeof(value));
}
void Encoder::WriteDouble(double value) {
auto value_uint = utils::MemcpyCast<uint64_t>(value);
value_uint = utils::HostToLittleEndian(value_uint);
WriteMarker(Marker::TYPE_DOUBLE);
Write(reinterpret_cast<const uint8_t *>(&value_uint), sizeof(value_uint));
}
void Encoder::WriteString(const std::string_view &value) {
WriteMarker(Marker::TYPE_STRING);
WriteSize(this, value.size());
Write(reinterpret_cast<const uint8_t *>(value.data()), value.size());
}
void Encoder::WritePropertyValue(const PropertyValue &value) {
WriteMarker(Marker::TYPE_PROPERTY_VALUE);
switch (value.type()) {
case PropertyValue::Type::Null: {
WriteMarker(Marker::TYPE_NULL);
break;
}
case PropertyValue::Type::Bool: {
WriteBool(value.ValueBool());
break;
}
case PropertyValue::Type::Int: {
WriteUint(utils::MemcpyCast<uint64_t>(value.ValueInt()));
break;
}
case PropertyValue::Type::Double: {
WriteDouble(value.ValueDouble());
break;
}
case PropertyValue::Type::String: {
WriteString(value.ValueString());
break;
}
case PropertyValue::Type::List: {
const auto &list = value.ValueList();
WriteMarker(Marker::TYPE_LIST);
WriteSize(this, list.size());
for (const auto &item : list) {
WritePropertyValue(item);
}
break;
}
case PropertyValue::Type::Map: {
const auto &map = value.ValueMap();
WriteMarker(Marker::TYPE_MAP);
WriteSize(this, map.size());
for (const auto &item : map) {
WriteString(item.first);
WritePropertyValue(item.second);
}
break;
}
case PropertyValue::Type::TemporalData: {
const auto temporal_data = value.ValueTemporalData();
WriteMarker(Marker::TYPE_TEMPORAL_DATA);
WriteUint(static_cast<uint64_t>(temporal_data.type));
WriteUint(utils::MemcpyCast<uint64_t>(temporal_data.microseconds));
break;
}
}
}
uint64_t Encoder::GetPosition() { return file_.GetPosition(); }
void Encoder::SetPosition(uint64_t position) {
file_.SetPosition(utils::OutputFile::Position::SET, static_cast<ssize_t>(position));
}
void Encoder::Sync() { file_.Sync(); }
void Encoder::Finalize() {
file_.Sync();
file_.Close();
}
void Encoder::DisableFlushing() { file_.DisableFlushing(); }
void Encoder::EnableFlushing() { file_.EnableFlushing(); }
void Encoder::TryFlushing() { file_.TryFlushing(); }
std::pair<const uint8_t *, size_t> Encoder::CurrentFileBuffer() const { return file_.CurrentBuffer(); }
size_t Encoder::GetSize() { return file_.GetSize(); }
//////////////////////////
// Decoder implementation.
//////////////////////////
namespace {
std::optional<Marker> CastToMarker(uint8_t value) {
for (auto marker : kMarkersAll) {
if (static_cast<uint8_t>(marker) == value) {
return marker;
}
}
return std::nullopt;
}
std::optional<uint64_t> ReadSize(Decoder *decoder) {
uint64_t size{0};
if (!decoder->Read(reinterpret_cast<uint8_t *>(&size), sizeof(size))) return std::nullopt;
size = utils::LittleEndianToHost(size);
return size;
}
} // namespace
std::optional<uint64_t> Decoder::Initialize(const std::filesystem::path &path, const std::string &magic) {
if (!file_.Open(path)) return std::nullopt;
std::string file_magic(magic.size(), '\0');
if (!Read(reinterpret_cast<uint8_t *>(file_magic.data()), file_magic.size())) return std::nullopt;
if (file_magic != magic) return std::nullopt;
uint64_t version_encoded{0};
if (!Read(reinterpret_cast<uint8_t *>(&version_encoded), sizeof(version_encoded))) return std::nullopt;
return utils::LittleEndianToHost(version_encoded);
}
bool Decoder::Read(uint8_t *data, size_t size) { return file_.Read(data, size); }
bool Decoder::Peek(uint8_t *data, size_t size) { return file_.Peek(data, size); }
std::optional<Marker> Decoder::PeekMarker() {
uint8_t value{0};
if (!Peek(&value, sizeof(value))) return std::nullopt;
auto marker = CastToMarker(value);
if (!marker) return std::nullopt;
return *marker;
}
std::optional<Marker> Decoder::ReadMarker() {
uint8_t value{0};
if (!Read(&value, sizeof(value))) return std::nullopt;
auto marker = CastToMarker(value);
if (!marker) return std::nullopt;
return *marker;
}
std::optional<bool> Decoder::ReadBool() {
auto marker = ReadMarker();
if (!marker || *marker != Marker::TYPE_BOOL) return std::nullopt;
auto value = ReadMarker();
if (!value || (*value != Marker::VALUE_FALSE && *value != Marker::VALUE_TRUE)) return std::nullopt;
return *value == Marker::VALUE_TRUE;
}
std::optional<uint64_t> Decoder::ReadUint() {
auto marker = ReadMarker();
if (!marker || *marker != Marker::TYPE_INT) return std::nullopt;
uint64_t value{0};
if (!Read(reinterpret_cast<uint8_t *>(&value), sizeof(value))) return std::nullopt;
value = utils::LittleEndianToHost(value);
return value;
}
std::optional<double> Decoder::ReadDouble() {
auto marker = ReadMarker();
if (!marker || *marker != Marker::TYPE_DOUBLE) return std::nullopt;
uint64_t value_int{0};
if (!Read(reinterpret_cast<uint8_t *>(&value_int), sizeof(value_int))) return std::nullopt;
value_int = utils::LittleEndianToHost(value_int);
auto value = utils::MemcpyCast<double>(value_int);
return value;
}
std::optional<std::string> Decoder::ReadString() {
auto marker = ReadMarker();
if (!marker || *marker != Marker::TYPE_STRING) return std::nullopt;
auto size = ReadSize(this);
if (!size) return std::nullopt;
std::string value(*size, '\0');
if (!Read(reinterpret_cast<uint8_t *>(value.data()), *size)) return std::nullopt;
return value;
}
namespace {
std::optional<TemporalData> ReadTemporalData(Decoder &decoder) {
const auto inner_marker = decoder.ReadMarker();
if (!inner_marker || *inner_marker != Marker::TYPE_TEMPORAL_DATA) return std::nullopt;
const auto type = decoder.ReadUint();
if (!type) return std::nullopt;
const auto microseconds = decoder.ReadUint();
if (!microseconds) return std::nullopt;
return TemporalData{static_cast<TemporalType>(*type), utils::MemcpyCast<int64_t>(*microseconds)};
}
} // namespace
std::optional<PropertyValue> Decoder::ReadPropertyValue() {
auto pv_marker = ReadMarker();
if (!pv_marker || *pv_marker != Marker::TYPE_PROPERTY_VALUE) return std::nullopt;
auto marker = PeekMarker();
if (!marker) return std::nullopt;
switch (*marker) {
case Marker::TYPE_NULL: {
auto inner_marker = ReadMarker();
if (!inner_marker || *inner_marker != Marker::TYPE_NULL) return std::nullopt;
return PropertyValue();
}
case Marker::TYPE_BOOL: {
auto value = ReadBool();
if (!value) return std::nullopt;
return PropertyValue(*value);
}
case Marker::TYPE_INT: {
auto value = ReadUint();
if (!value) return std::nullopt;
return PropertyValue(utils::MemcpyCast<int64_t>(*value));
}
case Marker::TYPE_DOUBLE: {
auto value = ReadDouble();
if (!value) return std::nullopt;
return PropertyValue(*value);
}
case Marker::TYPE_STRING: {
auto value = ReadString();
if (!value) return std::nullopt;
return PropertyValue(std::move(*value));
}
case Marker::TYPE_LIST: {
auto inner_marker = ReadMarker();
if (!inner_marker || *inner_marker != Marker::TYPE_LIST) return std::nullopt;
auto size = ReadSize(this);
if (!size) return std::nullopt;
std::vector<PropertyValue> value;
value.reserve(*size);
for (uint64_t i = 0; i < *size; ++i) {
auto item = ReadPropertyValue();
if (!item) return std::nullopt;
value.emplace_back(std::move(*item));
}
return PropertyValue(std::move(value));
}
case Marker::TYPE_MAP: {
auto inner_marker = ReadMarker();
if (!inner_marker || *inner_marker != Marker::TYPE_MAP) return std::nullopt;
auto size = ReadSize(this);
if (!size) return std::nullopt;
std::map<std::string, PropertyValue> value;
for (uint64_t i = 0; i < *size; ++i) {
auto key = ReadString();
if (!key) return std::nullopt;
auto item = ReadPropertyValue();
if (!item) return std::nullopt;
value.emplace(std::move(*key), std::move(*item));
}
return PropertyValue(std::move(value));
}
case Marker::TYPE_TEMPORAL_DATA: {
const auto maybe_temporal_data = ReadTemporalData(*this);
if (!maybe_temporal_data) return std::nullopt;
return PropertyValue(*maybe_temporal_data);
}
case Marker::TYPE_PROPERTY_VALUE:
case Marker::SECTION_VERTEX:
case Marker::SECTION_EDGE:
case Marker::SECTION_MAPPER:
case Marker::SECTION_METADATA:
case Marker::SECTION_INDICES:
case Marker::SECTION_CONSTRAINTS:
case Marker::SECTION_DELTA:
case Marker::SECTION_EPOCH_HISTORY:
case Marker::SECTION_OFFSETS:
case Marker::DELTA_VERTEX_CREATE:
case Marker::DELTA_VERTEX_DELETE:
case Marker::DELTA_VERTEX_ADD_LABEL:
case Marker::DELTA_VERTEX_REMOVE_LABEL:
case Marker::DELTA_VERTEX_SET_PROPERTY:
case Marker::DELTA_EDGE_CREATE:
case Marker::DELTA_EDGE_DELETE:
case Marker::DELTA_EDGE_SET_PROPERTY:
case Marker::DELTA_TRANSACTION_END:
case Marker::DELTA_LABEL_INDEX_CREATE:
case Marker::DELTA_LABEL_INDEX_DROP:
case Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE:
case Marker::DELTA_LABEL_PROPERTY_INDEX_DROP:
case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE:
case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP:
case Marker::DELTA_UNIQUE_CONSTRAINT_CREATE:
case Marker::DELTA_UNIQUE_CONSTRAINT_DROP:
case Marker::VALUE_FALSE:
case Marker::VALUE_TRUE:
return std::nullopt;
}
}
bool Decoder::SkipString() {
auto marker = ReadMarker();
if (!marker || *marker != Marker::TYPE_STRING) return false;
auto maybe_size = ReadSize(this);
if (!maybe_size) return false;
const uint64_t kBufferSize = 262144;
uint8_t buffer[kBufferSize];
uint64_t size = *maybe_size;
while (size > 0) {
uint64_t to_read = size < kBufferSize ? size : kBufferSize;
if (!Read(reinterpret_cast<uint8_t *>(&buffer), to_read)) return false;
size -= to_read;
}
return true;
}
bool Decoder::SkipPropertyValue() {
auto pv_marker = ReadMarker();
if (!pv_marker || *pv_marker != Marker::TYPE_PROPERTY_VALUE) return false;
auto marker = PeekMarker();
if (!marker) return false;
switch (*marker) {
case Marker::TYPE_NULL: {
auto inner_marker = ReadMarker();
return inner_marker && *inner_marker == Marker::TYPE_NULL;
}
case Marker::TYPE_BOOL: {
return !!ReadBool();
}
case Marker::TYPE_INT: {
return !!ReadUint();
}
case Marker::TYPE_DOUBLE: {
return !!ReadDouble();
}
case Marker::TYPE_STRING: {
return SkipString();
}
case Marker::TYPE_LIST: {
auto inner_marker = ReadMarker();
if (!inner_marker || *inner_marker != Marker::TYPE_LIST) return false;
auto size = ReadSize(this);
if (!size) return false;
for (uint64_t i = 0; i < *size; ++i) {
if (!SkipPropertyValue()) return false;
}
return true;
}
case Marker::TYPE_MAP: {
auto inner_marker = ReadMarker();
if (!inner_marker || *inner_marker != Marker::TYPE_MAP) return false;
auto size = ReadSize(this);
if (!size) return false;
for (uint64_t i = 0; i < *size; ++i) {
if (!SkipString()) return false;
if (!SkipPropertyValue()) return false;
}
return true;
}
case Marker::TYPE_TEMPORAL_DATA: {
return !!ReadTemporalData(*this);
}
case Marker::TYPE_PROPERTY_VALUE:
case Marker::SECTION_VERTEX:
case Marker::SECTION_EDGE:
case Marker::SECTION_MAPPER:
case Marker::SECTION_METADATA:
case Marker::SECTION_INDICES:
case Marker::SECTION_CONSTRAINTS:
case Marker::SECTION_DELTA:
case Marker::SECTION_EPOCH_HISTORY:
case Marker::SECTION_OFFSETS:
case Marker::DELTA_VERTEX_CREATE:
case Marker::DELTA_VERTEX_DELETE:
case Marker::DELTA_VERTEX_ADD_LABEL:
case Marker::DELTA_VERTEX_REMOVE_LABEL:
case Marker::DELTA_VERTEX_SET_PROPERTY:
case Marker::DELTA_EDGE_CREATE:
case Marker::DELTA_EDGE_DELETE:
case Marker::DELTA_EDGE_SET_PROPERTY:
case Marker::DELTA_TRANSACTION_END:
case Marker::DELTA_LABEL_INDEX_CREATE:
case Marker::DELTA_LABEL_INDEX_DROP:
case Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE:
case Marker::DELTA_LABEL_PROPERTY_INDEX_DROP:
case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE:
case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP:
case Marker::DELTA_UNIQUE_CONSTRAINT_CREATE:
case Marker::DELTA_UNIQUE_CONSTRAINT_DROP:
case Marker::VALUE_FALSE:
case Marker::VALUE_TRUE:
return false;
}
}
std::optional<uint64_t> Decoder::GetSize() { return file_.GetSize(); }
std::optional<uint64_t> Decoder::GetPosition() { return file_.GetPosition(); }
bool Decoder::SetPosition(uint64_t position) {
return !!file_.SetPosition(utils::InputFile::Position::SET, static_cast<ssize_t>(position));
}
} // namespace memgraph::storage::v3::durability

View File

@@ -0,0 +1,143 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <cstdint>
#include <filesystem>
#include <string_view>
#include "storage/v3/config.hpp"
#include "storage/v3/durability/marker.hpp"
#include "storage/v3/name_id_mapper.hpp"
#include "storage/v3/property_value.hpp"
#include "utils/file.hpp"
namespace memgraph::storage::v3::durability {
/// Encoder interface class. Used to implement streams to different targets
/// (e.g. file and network).
class BaseEncoder {
protected:
BaseEncoder() = default;
~BaseEncoder() = default;
public:
BaseEncoder(const BaseEncoder &) = delete;
BaseEncoder(BaseEncoder &&) = delete;
BaseEncoder &operator=(const BaseEncoder &) = delete;
BaseEncoder &operator=(BaseEncoder &&) = delete;
virtual void WriteMarker(Marker marker) = 0;
virtual void WriteBool(bool value) = 0;
virtual void WriteUint(uint64_t value) = 0;
virtual void WriteDouble(double value) = 0;
virtual void WriteString(const std::string_view &value) = 0;
virtual void WritePropertyValue(const PropertyValue &value) = 0;
};
/// Encoder that is used to generate a snapshot/WAL.
class Encoder final : public BaseEncoder {
public:
void Initialize(const std::filesystem::path &path, const std::string_view &magic, uint64_t version);
void OpenExisting(const std::filesystem::path &path);
void Close();
// Main write function, the only one that is allowed to write to the `file_`
// directly.
void Write(const uint8_t *data, uint64_t size);
void WriteMarker(Marker marker) override;
void WriteBool(bool value) override;
void WriteUint(uint64_t value) override;
void WriteDouble(double value) override;
void WriteString(const std::string_view &value) override;
void WritePropertyValue(const PropertyValue &value) override;
uint64_t GetPosition();
void SetPosition(uint64_t position);
void Sync();
void Finalize();
// Disable flushing of the internal buffer.
void DisableFlushing();
// Enable flushing of the internal buffer.
void EnableFlushing();
// Try flushing the internal buffer.
void TryFlushing();
// Get the current internal buffer with its size.
std::pair<const uint8_t *, size_t> CurrentFileBuffer() const;
// Get the total size of the current file.
size_t GetSize();
private:
utils::OutputFile file_;
};
/// Decoder interface class. Used to implement streams from different sources
/// (e.g. file and network).
class BaseDecoder {
protected:
BaseDecoder() = default;
~BaseDecoder() = default;
public:
BaseDecoder(const BaseDecoder &) = delete;
BaseDecoder(BaseDecoder &&) = delete;
BaseDecoder &operator=(const BaseDecoder &) = delete;
BaseDecoder &operator=(BaseDecoder &&) = delete;
virtual std::optional<Marker> ReadMarker() = 0;
virtual std::optional<bool> ReadBool() = 0;
virtual std::optional<uint64_t> ReadUint() = 0;
virtual std::optional<double> ReadDouble() = 0;
virtual std::optional<std::string> ReadString() = 0;
virtual std::optional<PropertyValue> ReadPropertyValue() = 0;
virtual bool SkipString() = 0;
virtual bool SkipPropertyValue() = 0;
};
/// Decoder that is used to read a generated snapshot/WAL.
class Decoder final : public BaseDecoder {
public:
std::optional<uint64_t> Initialize(const std::filesystem::path &path, const std::string &magic);
// Main read functions, the only one that are allowed to read from the `file_`
// directly.
bool Read(uint8_t *data, size_t size);
bool Peek(uint8_t *data, size_t size);
std::optional<Marker> PeekMarker();
std::optional<Marker> ReadMarker() override;
std::optional<bool> ReadBool() override;
std::optional<uint64_t> ReadUint() override;
std::optional<double> ReadDouble() override;
std::optional<std::string> ReadString() override;
std::optional<PropertyValue> ReadPropertyValue() override;
bool SkipString() override;
bool SkipPropertyValue() override;
std::optional<uint64_t> GetSize();
std::optional<uint64_t> GetPosition();
bool SetPosition(uint64_t position);
private:
utils::InputFile file_;
};
} // namespace memgraph::storage::v3::durability

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,77 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <cstdint>
#include <filesystem>
#include <string>
#include "storage/v3/config.hpp"
#include "storage/v3/constraints.hpp"
#include "storage/v3/durability/metadata.hpp"
#include "storage/v3/edge.hpp"
#include "storage/v3/indices.hpp"
#include "storage/v3/name_id_mapper.hpp"
#include "storage/v3/schema_validator.hpp"
#include "storage/v3/transaction.hpp"
#include "storage/v3/vertex.hpp"
#include "utils/file_locker.hpp"
#include "utils/skip_list.hpp"
namespace memgraph::storage::v3::durability {
/// Structure used to hold information about a snapshot.
struct SnapshotInfo {
uint64_t offset_edges;
uint64_t offset_vertices;
uint64_t offset_indices;
uint64_t offset_constraints;
uint64_t offset_mapper;
uint64_t offset_epoch_history;
uint64_t offset_metadata;
std::string uuid;
std::string epoch_id;
uint64_t start_timestamp;
uint64_t edges_count;
uint64_t vertices_count;
};
/// Structure used to hold information about the snapshot that has been
/// recovered.
struct RecoveredSnapshot {
SnapshotInfo snapshot_info;
RecoveryInfo recovery_info;
RecoveredIndicesAndConstraints indices_constraints;
};
/// Function used to read information about the snapshot file.
/// @throw RecoveryFailure
SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path);
/// Function used to load the snapshot data into the storage.
/// @throw RecoveryFailure
RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipList *vertices,
utils::SkipList<Edge> *edges,
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
NameIdMapper *name_id_mapper, uint64_t *edge_count, Config::Items items);
/// Function used to create a snapshot using the given transaction.
void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snapshot_directory,
const std::filesystem::path &wal_directory, uint64_t snapshot_retention_count,
VerticesSkipList *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper,
Indices *indices, Constraints *constraints, Config::Items items,
const SchemaValidator &schema_validator, const std::string &uuid, std::string_view epoch_id,
const std::deque<std::pair<std::string, uint64_t>> &epoch_history,
utils::FileRetainer *file_retainer);
} // namespace memgraph::storage::v3::durability

View File

@@ -0,0 +1,37 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <cstdint>
#include <string>
#include <type_traits>
namespace memgraph::storage::v3::durability {
// The current version of snapshot and WAL encoding / decoding.
// IMPORTANT: Please bump this version for every snapshot and/or WAL format
// change!!!
const uint64_t kVersion{14};
const uint64_t kOldestSupportedVersion{14};
const uint64_t kUniqueConstraintVersion{13};
// Magic values written to the start of a snapshot/WAL file to identify it.
const std::string kSnapshotMagic{"MGsn"};
const std::string kWalMagic{"MGwl"};
static_assert(std::is_same_v<uint8_t, unsigned char>);
// Checks whether the loaded snapshot/WAL version is supported.
inline bool IsVersionSupported(uint64_t version) { return version >= kOldestSupportedVersion && version <= kVersion; }
} // namespace memgraph::storage::v3::durability

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,262 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <cstdint>
#include <filesystem>
#include <set>
#include <string>
#include "storage/v3/config.hpp"
#include "storage/v3/delta.hpp"
#include "storage/v3/durability/metadata.hpp"
#include "storage/v3/durability/serialization.hpp"
#include "storage/v3/edge.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/name_id_mapper.hpp"
#include "storage/v3/property_value.hpp"
#include "storage/v3/vertex.hpp"
#include "storage/v3/vertices_skip_list.hpp"
#include "utils/file_locker.hpp"
#include "utils/skip_list.hpp"
namespace memgraph::storage::v3::durability {
/// Structure used to hold information about a WAL.
struct WalInfo {
uint64_t offset_metadata;
uint64_t offset_deltas;
std::string uuid;
std::string epoch_id;
uint64_t seq_num;
uint64_t from_timestamp;
uint64_t to_timestamp;
uint64_t num_deltas;
};
/// Structure used to return loaded WAL delta data.
struct WalDeltaData {
enum class Type {
VERTEX_CREATE,
VERTEX_DELETE,
VERTEX_ADD_LABEL,
VERTEX_REMOVE_LABEL,
VERTEX_SET_PROPERTY,
EDGE_CREATE,
EDGE_DELETE,
EDGE_SET_PROPERTY,
TRANSACTION_END,
LABEL_INDEX_CREATE,
LABEL_INDEX_DROP,
LABEL_PROPERTY_INDEX_CREATE,
LABEL_PROPERTY_INDEX_DROP,
EXISTENCE_CONSTRAINT_CREATE,
EXISTENCE_CONSTRAINT_DROP,
UNIQUE_CONSTRAINT_CREATE,
UNIQUE_CONSTRAINT_DROP,
};
Type type{Type::TRANSACTION_END};
struct {
Gid gid;
} vertex_create_delete;
struct {
Gid gid;
std::string label;
} vertex_add_remove_label;
struct {
Gid gid;
std::string property;
PropertyValue value;
} vertex_edge_set_property;
struct {
Gid gid;
std::string edge_type;
Gid from_vertex;
Gid to_vertex;
} edge_create_delete;
struct {
std::string label;
} operation_label;
struct {
std::string label;
std::string property;
} operation_label_property;
struct {
std::string label;
std::set<std::string> properties;
} operation_label_properties;
};
bool operator==(const WalDeltaData &a, const WalDeltaData &b);
bool operator!=(const WalDeltaData &a, const WalDeltaData &b);
/// Enum used to indicate a global database operation that isn't transactional.
enum class StorageGlobalOperation {
LABEL_INDEX_CREATE,
LABEL_INDEX_DROP,
LABEL_PROPERTY_INDEX_CREATE,
LABEL_PROPERTY_INDEX_DROP,
EXISTENCE_CONSTRAINT_CREATE,
EXISTENCE_CONSTRAINT_DROP,
UNIQUE_CONSTRAINT_CREATE,
UNIQUE_CONSTRAINT_DROP,
};
constexpr bool IsWalDeltaDataTypeTransactionEnd(const WalDeltaData::Type type) {
switch (type) {
// These delta actions are all found inside transactions so they don't
// indicate a transaction end.
case WalDeltaData::Type::VERTEX_CREATE:
case WalDeltaData::Type::VERTEX_DELETE:
case WalDeltaData::Type::VERTEX_ADD_LABEL:
case WalDeltaData::Type::VERTEX_REMOVE_LABEL:
case WalDeltaData::Type::EDGE_CREATE:
case WalDeltaData::Type::EDGE_DELETE:
case WalDeltaData::Type::VERTEX_SET_PROPERTY:
case WalDeltaData::Type::EDGE_SET_PROPERTY:
return false;
// This delta explicitly indicates that a transaction is done.
case WalDeltaData::Type::TRANSACTION_END:
// These operations aren't transactional and they are encoded only using
// a single delta, so they each individually mark the end of their
// 'transaction'.
case WalDeltaData::Type::LABEL_INDEX_CREATE:
case WalDeltaData::Type::LABEL_INDEX_DROP:
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE:
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP:
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE:
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP:
case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE:
case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP:
return true;
}
}
/// Function used to read information about the WAL file.
/// @throw RecoveryFailure
WalInfo ReadWalInfo(const std::filesystem::path &path);
/// Function used to read the WAL delta header. The function returns the delta
/// timestamp.
/// @throw RecoveryFailure
uint64_t ReadWalDeltaHeader(BaseDecoder *decoder);
/// Function used to read the current WAL delta data. The function returns the
/// read delta data. The WAL delta header must be read before calling this
/// function.
/// @throw RecoveryFailure
WalDeltaData ReadWalDeltaData(BaseDecoder *decoder);
/// Function used to skip the current WAL delta data. The function returns the
/// skipped delta type. The WAL delta header must be read before calling this
/// function.
/// @throw RecoveryFailure
WalDeltaData::Type SkipWalDeltaData(BaseDecoder *decoder);
/// Function used to encode a `Delta` that originated from a `Vertex`.
void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Config::Items items, const Delta &delta,
const Vertex &vertex, uint64_t timestamp);
/// Function used to encode a `Delta` that originated from an `Edge`.
void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, const Delta &delta, const Edge &edge,
uint64_t timestamp);
/// Function used to encode the transaction end.
void EncodeTransactionEnd(BaseEncoder *encoder, uint64_t timestamp);
/// Function used to encode non-transactional operation.
void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, StorageGlobalOperation operation,
LabelId label, const std::set<PropertyId> &properties, uint64_t timestamp);
/// Function used to load the WAL data into the storage.
/// @throw RecoveryFailure
RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConstraints *indices_constraints,
std::optional<uint64_t> last_loaded_timestamp, VerticesSkipList *vertices,
utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, uint64_t *edge_count,
Config::Items items);
/// WalFile class used to append deltas and operations to the WAL file.
class WalFile {
public:
WalFile(const std::filesystem::path &wal_directory, std::string_view uuid, std::string_view epoch_id,
Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num, utils::FileRetainer *file_retainer);
WalFile(std::filesystem::path current_wal_path, Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num,
uint64_t from_timestamp, uint64_t to_timestamp, uint64_t count, utils::FileRetainer *file_retainer);
WalFile(const WalFile &) = delete;
WalFile(WalFile &&) = delete;
WalFile &operator=(const WalFile &) = delete;
WalFile &operator=(WalFile &&) = delete;
~WalFile();
void AppendDelta(const Delta &delta, const Vertex &vertex, uint64_t timestamp);
void AppendDelta(const Delta &delta, const Edge &edge, uint64_t timestamp);
void AppendTransactionEnd(uint64_t timestamp);
void AppendOperation(StorageGlobalOperation operation, LabelId label, const std::set<PropertyId> &properties,
uint64_t timestamp);
void Sync();
uint64_t GetSize();
uint64_t SequenceNumber() const;
auto FromTimestamp() const { return from_timestamp_; }
auto ToTimestamp() const { return to_timestamp_; }
auto Count() const { return count_; }
// Disable flushing of the internal buffer.
void DisableFlushing();
// Enable flushing of the internal buffer.
void EnableFlushing();
// Try flushing the internal buffer.
void TryFlushing();
// Get the internal buffer with its size.
std::pair<const uint8_t *, size_t> CurrentFileBuffer() const;
// Get the path of the current WAL file.
const auto &Path() const { return path_; }
void FinalizeWal();
void DeleteWal();
private:
void UpdateStats(uint64_t timestamp);
Config::Items items_;
NameIdMapper *name_id_mapper_;
Encoder wal_;
std::filesystem::path path_;
uint64_t from_timestamp_;
uint64_t to_timestamp_;
uint64_t count_;
uint64_t seq_num_;
utils::FileRetainer *file_retainer_;
};
} // namespace memgraph::storage::v3::durability

View File

@@ -92,10 +92,6 @@ Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::ClearProperties() {
return std::move(properties);
}
Result<PropertyValue> EdgeAccessor::GetProperty(View view, PropertyId property) const {
return GetProperty(property, view);
}
Result<PropertyValue> EdgeAccessor::GetProperty(PropertyId property, View view) const {
if (!config_.properties_on_edges) return PropertyValue();
auto exists = true;

View File

@@ -28,6 +28,7 @@ namespace memgraph::storage::v3 {
struct Vertex;
class VertexAccessor;
struct Indices;
struct Constraints;
class EdgeAccessor final {
private:
@@ -35,13 +36,14 @@ class EdgeAccessor final {
public:
EdgeAccessor(EdgeRef edge, EdgeTypeId edge_type, VertexId from_vertex, VertexId to_vertex, Transaction *transaction,
Indices *indices, Config::Items config, bool for_deleted = false)
Indices *indices, Constraints *constraints, Config::Items config, bool for_deleted = false)
: edge_(edge),
edge_type_(edge_type),
from_vertex_(std::move(from_vertex)),
to_vertex_(std::move(to_vertex)),
transaction_(transaction),
indices_(indices),
constraints_(constraints),
config_(config),
for_deleted_(for_deleted) {}
@@ -65,8 +67,6 @@ class EdgeAccessor final {
/// @throw std::bad_alloc
Result<PropertyValue> GetProperty(PropertyId property, View view) const;
Result<PropertyValue> GetProperty(View view, PropertyId property) const;
/// @throw std::bad_alloc
Result<std::map<PropertyId, PropertyValue>> Properties(View view) const;
@@ -91,6 +91,7 @@ class EdgeAccessor final {
VertexId to_vertex_;
Transaction *transaction_;
Indices *indices_;
Constraints *constraints_;
Config::Items config_;
// if the accessor was created for a deleted edge.

View File

@@ -32,16 +32,16 @@ namespace {
template <typename TCallback>
bool AnyVersionSatisfiesPredicate(uint64_t timestamp, const Delta *delta, const TCallback &predicate) {
while (delta != nullptr) {
const auto commit_info = *delta->commit_info;
auto ts = delta->timestamp->load(std::memory_order_acquire);
// This is a committed change that we see so we shouldn't undo it.
if (commit_info.is_locally_committed && commit_info.start_or_commit_timestamp.logical_id < timestamp) {
if (ts < timestamp) {
break;
}
if (predicate(*delta)) {
return true;
}
// Move to the next delta.
delta = delta->next;
delta = delta->next.load(std::memory_order_acquire);
}
return false;
}
@@ -266,7 +266,7 @@ void LabelIndex::UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transacti
auto it = index_.find(label);
if (it == index_.end()) return;
auto acc = it->second.access();
acc.insert(Entry{vertex, tx.start_timestamp.logical_id});
acc.insert(Entry{vertex, tx.start_timestamp});
}
bool LabelIndex::CreateIndex(LabelId label, VerticesSkipList::Accessor vertices) {
@@ -302,20 +302,20 @@ std::vector<LabelId> LabelIndex::ListIndices() const {
return ret;
}
void LabelIndex::RemoveObsoleteEntries(const uint64_t clean_up_before_timestamp) {
void LabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
for (auto &label_storage : index_) {
auto vertices_acc = label_storage.second.access();
for (auto it = vertices_acc.begin(); it != vertices_acc.end();) {
auto next_it = it;
++next_it;
if (it->timestamp >= clean_up_before_timestamp) {
if (it->timestamp >= oldest_active_start_timestamp) {
it = next_it;
continue;
}
if ((next_it != vertices_acc.end() && it->vertex == next_it->vertex) ||
!AnyVersionHasLabel(*it->vertex, label_storage.first, clean_up_before_timestamp)) {
!AnyVersionHasLabel(*it->vertex, label_storage.first, oldest_active_start_timestamp)) {
vertices_acc.remove(*it);
}
@@ -327,7 +327,7 @@ void LabelIndex::RemoveObsoleteEntries(const uint64_t clean_up_before_timestamp)
LabelIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator)
: self_(self),
index_iterator_(index_iterator),
current_vertex_accessor_(nullptr, nullptr, nullptr, self_->config_, *self_->vertex_validator_),
current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_, *self_->vertex_validator_),
current_vertex_(nullptr) {
AdvanceUntilValid();
}
@@ -345,21 +345,22 @@ void LabelIndex::Iterable::Iterator::AdvanceUntilValid() {
}
if (CurrentVersionHasLabel(*index_iterator_->vertex, self_->label_, self_->transaction_, self_->view_)) {
current_vertex_ = index_iterator_->vertex;
current_vertex_accessor_ = VertexAccessor{current_vertex_, self_->transaction_, self_->indices_, self_->config_,
*self_->vertex_validator_};
current_vertex_accessor_ = VertexAccessor{current_vertex_, self_->transaction_, self_->indices_,
self_->constraints_, self_->config_, *self_->vertex_validator_};
break;
}
}
}
LabelIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, View view,
Transaction *transaction, Indices *indices, Config::Items config,
const VertexValidator &vertex_validator)
Transaction *transaction, Indices *indices, Constraints *constraints,
Config::Items config, const VertexValidator &vertex_validator)
: index_accessor_(std::move(index_accessor)),
label_(label),
view_(view),
transaction_(transaction),
indices_(indices),
constraints_(constraints),
config_(config),
vertex_validator_(&vertex_validator) {}
@@ -395,7 +396,7 @@ void LabelPropertyIndex::UpdateOnAddLabel(LabelId label, Vertex *vertex, const T
auto prop_value = vertex->properties.GetProperty(label_prop.second);
if (!prop_value.IsNull()) {
auto acc = storage.access();
acc.insert(Entry{std::move(prop_value), vertex, tx.start_timestamp.logical_id});
acc.insert(Entry{std::move(prop_value), vertex, tx.start_timestamp});
}
}
}
@@ -411,7 +412,7 @@ void LabelPropertyIndex::UpdateOnSetProperty(PropertyId property, const Property
}
if (utils::Contains(vertex->labels, label_prop.first)) {
auto acc = storage.access();
acc.insert(Entry{value, vertex, tx.start_timestamp.logical_id});
acc.insert(Entry{value, vertex, tx.start_timestamp});
}
}
}
@@ -454,21 +455,21 @@ std::vector<std::pair<LabelId, PropertyId>> LabelPropertyIndex::ListIndices() co
return ret;
}
void LabelPropertyIndex::RemoveObsoleteEntries(const uint64_t clean_up_before_timestamp) {
void LabelPropertyIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
for (auto &[label_property, index] : index_) {
auto index_acc = index.access();
for (auto it = index_acc.begin(); it != index_acc.end();) {
auto next_it = it;
++next_it;
if (it->timestamp >= clean_up_before_timestamp) {
if (it->timestamp >= oldest_active_start_timestamp) {
it = next_it;
continue;
}
if ((next_it != index_acc.end() && it->vertex == next_it->vertex && it->value == next_it->value) ||
!AnyVersionHasLabelProperty(*it->vertex, label_property.first, label_property.second, it->value,
clean_up_before_timestamp)) {
oldest_active_start_timestamp)) {
index_acc.remove(*it);
}
it = next_it;
@@ -479,7 +480,7 @@ void LabelPropertyIndex::RemoveObsoleteEntries(const uint64_t clean_up_before_ti
LabelPropertyIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator)
: self_(self),
index_iterator_(index_iterator),
current_vertex_accessor_(nullptr, nullptr, nullptr, self_->config_, *self_->vertex_validator_),
current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_, *self_->vertex_validator_),
current_vertex_(nullptr) {
AdvanceUntilValid();
}
@@ -518,8 +519,8 @@ void LabelPropertyIndex::Iterable::Iterator::AdvanceUntilValid() {
if (CurrentVersionHasLabelProperty(*index_iterator_->vertex, self_->label_, self_->property_,
index_iterator_->value, self_->transaction_, self_->view_)) {
current_vertex_ = index_iterator_->vertex;
current_vertex_accessor_ = VertexAccessor(current_vertex_, self_->transaction_, self_->indices_, self_->config_,
*self_->vertex_validator_);
current_vertex_accessor_ = VertexAccessor(current_vertex_, self_->transaction_, self_->indices_,
self_->constraints_, self_->config_, *self_->vertex_validator_);
break;
}
}
@@ -541,8 +542,8 @@ LabelPropertyIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_ac
PropertyId property,
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view,
Transaction *transaction, Indices *indices, Config::Items config,
const VertexValidator &vertex_validator)
Transaction *transaction, Indices *indices, Constraints *constraints,
Config::Items config, const VertexValidator &vertex_validator)
: index_accessor_(std::move(index_accessor)),
label_(label),
property_(property),
@@ -551,6 +552,7 @@ LabelPropertyIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_ac
view_(view),
transaction_(transaction),
indices_(indices),
constraints_(constraints),
config_(config),
vertex_validator_(&vertex_validator) {
// We have to fix the bounds that the user provided to us. If the user
@@ -696,9 +698,9 @@ void LabelPropertyIndex::RunGC() {
}
}
void RemoveObsoleteEntries(Indices *indices, const uint64_t clean_up_before_timestamp) {
indices->label_index.RemoveObsoleteEntries(clean_up_before_timestamp);
indices->label_property_index.RemoveObsoleteEntries(clean_up_before_timestamp);
void RemoveObsoleteEntries(Indices *indices, uint64_t oldest_active_start_timestamp) {
indices->label_index.RemoveObsoleteEntries(oldest_active_start_timestamp);
indices->label_property_index.RemoveObsoleteEntries(oldest_active_start_timestamp);
}
void UpdateOnAddLabel(Indices *indices, LabelId label, Vertex *vertex, const Transaction &tx) {

View File

@@ -28,6 +28,7 @@
namespace memgraph::storage::v3 {
struct Indices;
struct Constraints;
class LabelIndex {
private:
@@ -52,8 +53,8 @@ class LabelIndex {
};
public:
LabelIndex(Indices *indices, Config::Items config, const VertexValidator &vertex_validator)
: indices_(indices), config_(config), vertex_validator_{&vertex_validator} {}
LabelIndex(Indices *indices, Constraints *constraints, Config::Items config, const VertexValidator &vertex_validator)
: indices_(indices), constraints_(constraints), config_(config), vertex_validator_{&vertex_validator} {}
/// @throw std::bad_alloc
void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx);
@@ -68,12 +69,12 @@ class LabelIndex {
std::vector<LabelId> ListIndices() const;
void RemoveObsoleteEntries(uint64_t clean_up_before_timestamp);
void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
class Iterable {
public:
Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, View view, Transaction *transaction,
Indices *indices, Config::Items config, const VertexValidator &vertex_validator);
Indices *indices, Constraints *constraints, Config::Items config, const VertexValidator &vertex_validator);
class Iterator {
public:
@@ -104,6 +105,7 @@ class LabelIndex {
View view_;
Transaction *transaction_;
Indices *indices_;
Constraints *constraints_;
Config::Items config_;
const VertexValidator *vertex_validator_;
};
@@ -112,7 +114,7 @@ class LabelIndex {
Iterable Vertices(LabelId label, View view, Transaction *transaction) {
auto it = index_.find(label);
MG_ASSERT(it != index_.end(), "Index for label {} doesn't exist", label.AsUint());
return {it->second.access(), label, view, transaction, indices_, config_, *vertex_validator_};
return {it->second.access(), label, view, transaction, indices_, constraints_, config_, *vertex_validator_};
}
int64_t ApproximateVertexCount(LabelId label) {
@@ -128,6 +130,7 @@ class LabelIndex {
private:
std::map<LabelId, utils::SkipList<Entry>> index_;
Indices *indices_;
Constraints *constraints_;
Config::Items config_;
const VertexValidator *vertex_validator_;
};
@@ -147,8 +150,9 @@ class LabelPropertyIndex {
};
public:
LabelPropertyIndex(Indices *indices, Config::Items config, const VertexValidator &vertex_validator)
: indices_(indices), config_(config), vertex_validator_{&vertex_validator} {}
LabelPropertyIndex(Indices *indices, Constraints *constraints, Config::Items config,
const VertexValidator &vertex_validator)
: indices_(indices), constraints_(constraints), config_(config), vertex_validator_{&vertex_validator} {}
/// @throw std::bad_alloc
void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx);
@@ -165,14 +169,14 @@ class LabelPropertyIndex {
std::vector<std::pair<LabelId, PropertyId>> ListIndices() const;
void RemoveObsoleteEntries(uint64_t clean_up_before_timestamp);
void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
class Iterable {
public:
Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, PropertyId property,
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, Transaction *transaction,
Indices *indices, Config::Items config, const VertexValidator &vertex_validator);
Indices *indices, Constraints *constraints, Config::Items config, const VertexValidator &vertex_validator);
class Iterator {
public:
@@ -207,6 +211,7 @@ class LabelPropertyIndex {
View view_;
Transaction *transaction_;
Indices *indices_;
Constraints *constraints_;
Config::Items config_;
const VertexValidator *vertex_validator_;
};
@@ -217,8 +222,8 @@ class LabelPropertyIndex {
auto it = index_.find({label, property});
MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(),
property.AsUint());
return {it->second.access(), label, property, lower_bound, upper_bound, view,
transaction, indices_, config_, *vertex_validator_};
return {it->second.access(), label, property, lower_bound, upper_bound, view,
transaction, indices_, constraints_, config_, *vertex_validator_};
}
int64_t ApproximateVertexCount(LabelId label, PropertyId property) const {
@@ -245,13 +250,15 @@ class LabelPropertyIndex {
private:
std::map<std::pair<LabelId, PropertyId>, utils::SkipList<Entry>> index_;
Indices *indices_;
Constraints *constraints_;
Config::Items config_;
const VertexValidator *vertex_validator_;
};
struct Indices {
Indices(Config::Items config, const VertexValidator &vertex_validator)
: label_index(this, config, vertex_validator), label_property_index(this, config, vertex_validator) {}
Indices(Constraints *constraints, Config::Items config, const VertexValidator &vertex_validator)
: label_index(this, constraints, config, vertex_validator),
label_property_index(this, constraints, config, vertex_validator) {}
// Disable copy and move because members hold pointer to `this`.
Indices(const Indices &) = delete;
@@ -266,7 +273,7 @@ struct Indices {
/// This function should be called from garbage collection to clean-up the
/// index.
void RemoveObsoleteEntries(Indices *indices, uint64_t clean_up_before_timestamp);
void RemoveObsoleteEntries(Indices *indices, uint64_t oldest_active_start_timestamp);
// Indices are updated whenever an update occurs, instead of only on commit or
// advance command. This is necessary because we want indices to support `NEW`

View File

@@ -28,9 +28,11 @@ inline void ApplyDeltasForRead(Transaction *transaction, const Delta *delta, Vie
// if the transaction is not committed, then its deltas have transaction_id for the timestamp, otherwise they have
// its commit timestamp set.
// This allows the transaction to see its changes even though it's committed.
const auto &commit_info = *transaction->commit_info;
const auto commit_timestamp = transaction->commit_timestamp
? transaction->commit_timestamp->load(std::memory_order_acquire)
: transaction->transaction_id;
while (delta != nullptr) {
const auto &delta_commit_info = *delta->commit_info;
auto ts = delta->timestamp->load(std::memory_order_acquire);
auto cid = delta->command_id;
// For SNAPSHOT ISOLATION -> we can only see the changes which were committed before the start of the current
@@ -42,24 +44,21 @@ inline void ApplyDeltasForRead(Transaction *transaction, const Delta *delta, Vie
// id value, that the change is committed.
//
// For READ UNCOMMITTED -> we accept any change.
if ((transaction->isolation_level == IsolationLevel::SNAPSHOT_ISOLATION && delta_commit_info.is_locally_committed &&
delta_commit_info.start_or_commit_timestamp.logical_id < transaction->start_timestamp.logical_id) ||
(transaction->isolation_level == IsolationLevel::READ_COMMITTED && delta_commit_info.is_locally_committed) ||
if ((transaction->isolation_level == IsolationLevel::SNAPSHOT_ISOLATION && ts < transaction->start_timestamp) ||
(transaction->isolation_level == IsolationLevel::READ_COMMITTED && ts < kTransactionInitialId) ||
(transaction->isolation_level == IsolationLevel::READ_UNCOMMITTED)) {
break;
}
// We shouldn't undo our newest changes because the user requested a NEW
// view of the database.
if (view == View::NEW && delta_commit_info.start_or_commit_timestamp == commit_info.start_or_commit_timestamp &&
cid <= transaction->command_id) {
if (view == View::NEW && ts == commit_timestamp && cid <= transaction->command_id) {
break;
}
// We shouldn't undo our older changes because the user requested a OLD view
// of the database.
if (view == View::OLD && delta_commit_info.start_or_commit_timestamp == commit_info.start_or_commit_timestamp &&
delta->command_id < transaction->command_id) {
if (view == View::OLD && ts == commit_timestamp && cid < transaction->command_id) {
break;
}
@@ -67,7 +66,7 @@ inline void ApplyDeltasForRead(Transaction *transaction, const Delta *delta, Vie
callback(*delta);
// Move to the next delta.
delta = delta->next;
delta = delta->next.load(std::memory_order_acquire);
}
}
@@ -80,10 +79,8 @@ template <typename TObj>
inline bool PrepareForWrite(Transaction *transaction, TObj *object) {
if (object->delta == nullptr) return true;
const auto &delta_commit_info = *object->delta->commit_info;
if (delta_commit_info.start_or_commit_timestamp == transaction->commit_info->start_or_commit_timestamp ||
(delta_commit_info.is_locally_committed &&
delta_commit_info.start_or_commit_timestamp < transaction->start_timestamp)) {
auto ts = object->delta->timestamp->load(std::memory_order_acquire);
if (ts == transaction->transaction_id || ts < transaction->start_timestamp) {
return true;
}
@@ -97,7 +94,8 @@ inline bool PrepareForWrite(Transaction *transaction, TObj *object) {
/// a `DELETE_OBJECT` delta).
/// @throw std::bad_alloc
inline Delta *CreateDeleteObjectDelta(Transaction *transaction) {
return &transaction->deltas.emplace_back(Delta::DeleteObjectTag(), transaction->commit_info.get(),
transaction->EnsureCommitTimestampExists();
return &transaction->deltas.emplace_back(Delta::DeleteObjectTag(), transaction->commit_timestamp.get(),
transaction->command_id);
}
@@ -106,7 +104,8 @@ inline Delta *CreateDeleteObjectDelta(Transaction *transaction) {
/// @throw std::bad_alloc
template <typename TObj, class... Args>
inline void CreateAndLinkDelta(Transaction *transaction, TObj *object, Args &&...args) {
auto delta = &transaction->deltas.emplace_back(std::forward<Args>(args)..., transaction->commit_info.get(),
transaction->EnsureCommitTimestampExists();
auto delta = &transaction->deltas.emplace_back(std::forward<Args>(args)..., transaction->commit_timestamp.get(),
transaction->command_id);
// The operations are written in such order so that both `next` and `prev`
@@ -118,7 +117,7 @@ inline void CreateAndLinkDelta(Transaction *transaction, TObj *object, Args &&..
// TODO(antaljanosbenjamin): clang-tidy detects (in my opinion a false positive) issue in
// `Shard::Accessor::CreateEdge`.
// NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
delta->next = object->delta;
delta->next.store(object->delta, std::memory_order_release);
// 2. We need to set the previous delta of the new delta to the object.
delta->prev.Set(object);
// 3. We need to set the previous delta of the existing delta to the new

View File

@@ -11,11 +11,9 @@
#pragma once
#include <algorithm>
#include <cstdint>
#include <atomic>
#include <string>
#include <string_view>
#include <unordered_map>
#include "utils/logging.hpp"
#include "utils/skip_list.hpp"
@@ -23,47 +21,78 @@
namespace memgraph::storage::v3 {
class NameIdMapper final {
private:
struct MapNameToId {
std::string name;
uint64_t id;
bool operator<(const MapNameToId &other) const { return name < other.name; }
bool operator==(const MapNameToId &other) const { return name == other.name; }
bool operator<(const std::string_view &other) const { return name < other; }
bool operator==(const std::string_view &other) const { return name == other; }
};
struct MapIdToName {
uint64_t id;
std::string name;
bool operator<(const MapIdToName &other) const { return id < other.id; }
bool operator==(const MapIdToName &other) const { return id == other.id; }
bool operator<(uint64_t other) const { return id < other; }
bool operator==(uint64_t other) const { return id == other; }
};
public:
static constexpr uint64_t kUnmappedId{0};
NameIdMapper() = default;
explicit NameIdMapper(std::unordered_map<uint64_t, std::string> id_to_name) : id_to_name_{std::move(id_to_name)} {
for (const auto &[id, name] : id_to_name_) {
name_to_id_.emplace(name, id);
/// @throw std::bad_alloc if unable to insert a new mapping
uint64_t NameToId(const std::string_view &name) {
auto name_to_id_acc = name_to_id_.access();
auto found = name_to_id_acc.find(name);
uint64_t id{0};
if (found == name_to_id_acc.end()) {
uint64_t new_id = counter_.fetch_add(1, std::memory_order_acq_rel);
// Try to insert the mapping with the `new_id`, but use the id that is in
// the object itself. The object that cointains the mapping is designed to
// be a map, so that if the inserted name already exists `insert` will
// return an iterator to the existing item. This prevents assignment of
// two IDs to the same name when the mapping is being inserted
// concurrently from two threads. One ID is wasted in that case, though.
id = name_to_id_acc.insert({std::string(name), new_id}).first->id;
} else {
id = found->id;
}
auto id_to_name_acc = id_to_name_.access();
// We have to try to insert the ID to name mapping even if we are not the
// one who assigned the ID because we have to make sure that after this
// method returns that both mappings exist.
if (id_to_name_acc.find(id) == id_to_name_acc.end()) {
// We first try to find the `id` in the map to avoid making an unnecessary
// temporary memory allocation when the object already exists.
id_to_name_acc.insert({id, std::string(name)});
}
return id;
}
void StoreMapping(std::unordered_map<uint64_t, std::string> id_to_name) {
id_to_name_ = std::move(id_to_name);
for (const auto &[id, name] : id_to_name_) {
name_to_id_.emplace(name, id);
}
}
uint64_t NameToId(std::string_view name) const {
if (auto it = name_to_id_.find(name); it != name_to_id_.end()) {
return it->second;
}
return kUnmappedId;
}
const std::string &IdToName(const uint64_t id) const {
auto it = id_to_name_.find(id);
MG_ASSERT(it != id_to_name_.end(), "Id not know in mapper!");
return it->second;
// NOTE: Currently this function returns a `const std::string &` instead of a
// `std::string` to avoid making unnecessary copies of the string.
// Usually, this wouldn't be correct because the accessor to the
// `utils::SkipList` is destroyed in this function and that removes the
// guarantee that the reference to the value contained in the list will be
// valid.
// Currently, we never delete anything from the `utils::SkipList` so the
// references will always be valid. If you change this class to remove unused
// names, be sure to change the signature of this function.
const std::string &IdToName(uint64_t id) const {
auto id_to_name_acc = id_to_name_.access();
auto result = id_to_name_acc.find(id);
MG_ASSERT(result != id_to_name_acc.end(), "Trying to get a name for an invalid ID!");
return result->name;
}
private:
// Necessary for comparison with string_view nad string
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0919r1.html
// https://www.cppstories.com/2021/heterogeneous-access-cpp20/
struct StringHash {
using is_transparent = void;
[[nodiscard]] size_t operator()(const char *txt) const { return std::hash<std::string_view>{}(txt); }
[[nodiscard]] size_t operator()(std::string_view txt) const { return std::hash<std::string_view>{}(txt); }
[[nodiscard]] size_t operator()(const std::string &txt) const { return std::hash<std::string>{}(txt); }
};
std::unordered_map<uint64_t, std::string> id_to_name_;
std::unordered_map<std::string, uint64_t, StringHash, std::equal_to<>> name_to_id_;
std::atomic<uint64_t> counter_{0};
utils::SkipList<MapNameToId> name_to_id_;
utils::SkipList<MapIdToName> id_to_name_;
};
} // namespace memgraph::storage::v3

View File

@@ -1,92 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "storage/v3/edge_accessor.hpp"
#include "storage/v3/vertex_accessor.hpp"
#include "utils/logging.hpp"
#include "utils/memory.hpp"
namespace memgraph::storage::v3 {
class Path {
public:
using allocator_type = utils::Allocator<char>;
explicit Path(const VertexAccessor & /*vertex*/) {}
template <typename... TOthers>
explicit Path(const VertexAccessor &vertex, const TOthers &...others) {}
template <typename... TOthers>
Path(std::allocator_arg_t alloc, utils::MemoryResource *memory, const VertexAccessor &vertex,
const TOthers &...others) {}
Path(const Path & /*other*/) {}
Path(const Path & /*other*/, utils::MemoryResource * /*memory*/) {}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
Path(Path &&other) noexcept : Path(std::move(other), other.GetMemoryResource()) {}
Path(Path && /*other*/, utils::MemoryResource * /*memory*/) {}
Path &operator=(const Path &) = default;
Path &operator=(Path &&) = default;
~Path() = default;
void Expand(const VertexAccessor &vertex) {}
void Expand(const EdgeAccessor &edge) {}
template <typename TFirst, typename... TOthers>
void Expand(const TFirst &first, const TOthers &...others) {}
auto size() const;
std::pmr::vector<VertexAccessor> &vertices() {
MG_ASSERT(false, "Using vertices on Path from storage!");
return vertices_;
}
std::pmr::vector<EdgeAccessor> &edges() {
MG_ASSERT(false, "Using edges on Path from storage!");
return edges_;
}
const std::pmr::vector<VertexAccessor> &vertices() const {
MG_ASSERT(false, "Using vertices on Path from storage!");
return vertices_;
}
const std::pmr::vector<EdgeAccessor> &edges() const {
MG_ASSERT(false, "Using edges on Path from storage!");
return edges_;
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
utils::MemoryResource *GetMemoryResource() const {
MG_ASSERT(false, "Using GetMemoryResource on Path from storage!");
return nullptr;
}
bool operator==(const Path & /*other*/) const {
MG_ASSERT(false, "Using operator= on Path from storage!");
return false;
};
private:
std::pmr::vector<VertexAccessor> vertices_;
std::pmr::vector<EdgeAccessor> edges_;
};
} // namespace memgraph::storage::v3

2
src/storage/v3/replication/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
# autogenerated files
rpc.hpp

View File

@@ -0,0 +1,44 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <chrono>
#include <optional>
#include <string>
namespace memgraph::storage::v3::replication {
struct ReplicationClientConfig {
std::optional<double> timeout;
// The default delay between main checking/pinging replicas is 1s because
// that seems like a reasonable timeframe in which main should notice a
// replica is down.
std::chrono::seconds replica_check_frequency{1};
struct SSL {
std::string key_file;
std::string cert_file;
};
std::optional<SSL> ssl;
};
struct ReplicationServerConfig {
struct SSL {
std::string key_file;
std::string cert_file;
std::string ca_file;
bool verify_peer;
};
std::optional<SSL> ssl;
};
} // namespace memgraph::storage::v3::replication

View File

@@ -10,7 +10,10 @@
// licenses/APL.txt.
#pragma once
#include <cstdint>
#include "storage/v3/bindings/bindings.hpp"
namespace memgraph::storage::v3::replication {
enum class ReplicationMode : std::uint8_t { SYNC, ASYNC };
#include "expr/ast/ast_visitor.hpp"
enum class ReplicaState : std::uint8_t { READY, REPLICATING, RECOVERY, INVALID };
} // namespace memgraph::storage::v3::replication

View File

@@ -0,0 +1,617 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "storage/v3/replication/replication_client.hpp"
#include <algorithm>
#include <type_traits>
#include "storage/v3/durability/durability.hpp"
#include "storage/v3/replication/config.hpp"
#include "storage/v3/replication/enums.hpp"
#include "storage/v3/transaction.hpp"
#include "utils/file_locker.hpp"
#include "utils/logging.hpp"
#include "utils/message.hpp"
namespace memgraph::storage::v3 {
namespace {
template <typename>
[[maybe_unused]] inline constexpr bool always_false_v = false;
} // namespace
////// ReplicationClient //////
Shard::ReplicationClient::ReplicationClient(std::string name, Shard *shard, const io::network::Endpoint &endpoint,
const replication::ReplicationMode mode,
const replication::ReplicationClientConfig &config)
: name_(std::move(name)), shard_(shard), mode_(mode) {
if (config.ssl) {
rpc_context_.emplace(config.ssl->key_file, config.ssl->cert_file);
} else {
rpc_context_.emplace();
}
rpc_client_.emplace(endpoint, &*rpc_context_);
TryInitializeClientSync();
if (config.timeout && replica_state_ != replication::ReplicaState::INVALID) {
timeout_.emplace(*config.timeout);
timeout_dispatcher_.emplace();
}
// Help the user to get the most accurate replica state possible.
if (config.replica_check_frequency > std::chrono::seconds(0)) {
replica_checker_.Run("Replica Checker", config.replica_check_frequency, [&] { FrequentCheck(); });
}
}
void Shard::ReplicationClient::TryInitializeClientAsync() {
thread_pool_.AddTask([this] {
rpc_client_->Abort();
this->TryInitializeClientSync();
});
}
void Shard::ReplicationClient::FrequentCheck() {
const auto is_success = std::invoke([this]() {
try {
auto stream{rpc_client_->Stream<replication::FrequentHeartbeatRpc>()};
const auto response = stream.AwaitResponse();
return response.success;
} catch (const rpc::RpcFailedException &) {
return false;
}
});
// States: READY, REPLICATING, RECOVERY, INVALID
// If success && ready, replicating, recovery -> stay the same because something good is going on.
// If success && INVALID -> [it's possible that replica came back to life] -> TryInitializeClient.
// If fail -> [replica is not reachable at all] -> INVALID state.
// NOTE: TryInitializeClient might return nothing if there is a branching point.
// NOTE: The early return pattern simplified the code, but the behavior should be as explained.
if (!is_success) {
replica_state_.store(replication::ReplicaState::INVALID);
return;
}
if (replica_state_.load() == replication::ReplicaState::INVALID) {
TryInitializeClientAsync();
}
}
/// @throws rpc::RpcFailedException
void Shard::ReplicationClient::InitializeClient() {
uint64_t current_commit_timestamp{kTimestampInitialId};
auto stream{rpc_client_->Stream<replication::HeartbeatRpc>(shard_->last_commit_timestamp_, shard_->epoch_id_)};
const auto response = stream.AwaitResponse();
std::optional<uint64_t> branching_point;
if (response.epoch_id != shard_->epoch_id_ && response.current_commit_timestamp != kTimestampInitialId) {
const auto &epoch_history = shard_->epoch_history_;
const auto epoch_info_iter =
std::find_if(epoch_history.crbegin(), epoch_history.crend(),
[&](const auto &epoch_info) { return epoch_info.first == response.epoch_id; });
if (epoch_info_iter == epoch_history.crend()) {
branching_point = 0;
} else if (epoch_info_iter->second != response.current_commit_timestamp) {
branching_point = epoch_info_iter->second;
}
}
if (branching_point) {
spdlog::error(
"Replica {} cannot be used with this instance. Please start a clean "
"instance of Memgraph server on the specified endpoint.",
name_);
return;
}
current_commit_timestamp = response.current_commit_timestamp;
spdlog::trace("Current timestamp on replica: {}", current_commit_timestamp);
spdlog::trace("Current timestamp on main: {}", shard_->last_commit_timestamp_);
if (current_commit_timestamp == shard_->last_commit_timestamp_) {
spdlog::debug("Replica '{}' up to date", name_);
std::unique_lock client_guard{client_lock_};
replica_state_.store(replication::ReplicaState::READY);
} else {
spdlog::debug("Replica '{}' is behind", name_);
{
std::unique_lock client_guard{client_lock_};
replica_state_.store(replication::ReplicaState::RECOVERY);
}
thread_pool_.AddTask([=, this] { this->RecoverReplica(current_commit_timestamp); });
}
}
void Shard::ReplicationClient::TryInitializeClientSync() {
try {
InitializeClient();
} catch (const rpc::RpcFailedException &) {
std::unique_lock client_guarde{client_lock_};
replica_state_.store(replication::ReplicaState::INVALID);
spdlog::error(utils::MessageWithLink("Failed to connect to replica {} at the endpoint {}.", name_,
rpc_client_->Endpoint(), "https://memgr.ph/replication"));
}
}
void Shard::ReplicationClient::HandleRpcFailure() {
spdlog::error(utils::MessageWithLink("Couldn't replicate data to {}.", name_, "https://memgr.ph/replication"));
TryInitializeClientAsync();
}
replication::SnapshotRes Shard::ReplicationClient::TransferSnapshot(const std::filesystem::path &path) {
auto stream{rpc_client_->Stream<replication::SnapshotRpc>()};
replication::Encoder encoder(stream.GetBuilder());
encoder.WriteFile(path);
return stream.AwaitResponse();
}
replication::WalFilesRes Shard::ReplicationClient::TransferWalFiles(
const std::vector<std::filesystem::path> &wal_files) {
MG_ASSERT(!wal_files.empty(), "Wal files list is empty!");
auto stream{rpc_client_->Stream<replication::WalFilesRpc>(wal_files.size())};
replication::Encoder encoder(stream.GetBuilder());
for (const auto &wal : wal_files) {
spdlog::debug("Sending wal file: {}", wal);
encoder.WriteFile(wal);
}
return stream.AwaitResponse();
}
void Shard::ReplicationClient::StartTransactionReplication(const uint64_t current_wal_seq_num) {
std::unique_lock guard(client_lock_);
const auto status = replica_state_.load();
switch (status) {
case replication::ReplicaState::RECOVERY:
spdlog::debug("Replica {} is behind MAIN instance", name_);
return;
case replication::ReplicaState::REPLICATING:
spdlog::debug("Replica {} missed a transaction", name_);
// We missed a transaction because we're still replicating
// the previous transaction so we need to go to RECOVERY
// state to catch up with the missing transaction
// We cannot queue the recovery process here because
// an error can happen while we're replicating the previous
// transaction after which the client should go to
// INVALID state before starting the recovery process
replica_state_.store(replication::ReplicaState::RECOVERY);
return;
case replication::ReplicaState::INVALID:
HandleRpcFailure();
return;
case replication::ReplicaState::READY:
MG_ASSERT(!replica_stream_);
try {
replica_stream_.emplace(ReplicaStream{this, shard_->last_commit_timestamp_, current_wal_seq_num});
replica_state_.store(replication::ReplicaState::REPLICATING);
} catch (const rpc::RpcFailedException &) {
replica_state_.store(replication::ReplicaState::INVALID);
HandleRpcFailure();
}
return;
}
}
void Shard::ReplicationClient::IfStreamingTransaction(const std::function<void(ReplicaStream &handler)> &callback) {
// We can only check the state because it guarantees to be only
// valid during a single transaction replication (if the assumption
// that this and other transaction replication functions can only be
// called from a one thread stands)
if (replica_state_ != replication::ReplicaState::REPLICATING) {
return;
}
try {
callback(*replica_stream_);
} catch (const rpc::RpcFailedException &) {
{
std::unique_lock client_guard{client_lock_};
replica_state_.store(replication::ReplicaState::INVALID);
}
HandleRpcFailure();
}
}
void Shard::ReplicationClient::FinalizeTransactionReplication() {
// We can only check the state because it guarantees to be only
// valid during a single transaction replication (if the assumption
// that this and other transaction replication functions can only be
// called from a one thread stands)
if (replica_state_ != replication::ReplicaState::REPLICATING) {
return;
}
if (mode_ == replication::ReplicationMode::ASYNC) {
thread_pool_.AddTask([this] { this->FinalizeTransactionReplicationInternal(); });
} else if (timeout_) {
MG_ASSERT(mode_ == replication::ReplicationMode::SYNC, "Only SYNC replica can have a timeout.");
MG_ASSERT(timeout_dispatcher_, "Timeout thread is missing");
timeout_dispatcher_->WaitForTaskToFinish();
timeout_dispatcher_->active = true;
thread_pool_.AddTask([&, this] {
this->FinalizeTransactionReplicationInternal();
std::unique_lock main_guard(timeout_dispatcher_->main_lock);
// TimerThread can finish waiting for timeout
timeout_dispatcher_->active = false;
// Notify the main thread
timeout_dispatcher_->main_cv.notify_one();
});
timeout_dispatcher_->StartTimeoutTask(*timeout_);
// Wait until one of the threads notifies us that they finished executing
// Both threads should first set the active flag to false
{
std::unique_lock main_guard(timeout_dispatcher_->main_lock);
timeout_dispatcher_->main_cv.wait(main_guard, [&] { return !timeout_dispatcher_->active.load(); });
}
// TODO (antonio2368): Document and/or polish SEMI-SYNC to ASYNC fallback.
if (replica_state_ == replication::ReplicaState::REPLICATING) {
mode_ = replication::ReplicationMode::ASYNC;
timeout_.reset();
// This can only happen if we timeouted so we are sure that
// Timeout task finished
// We need to delete timeout dispatcher AFTER the replication
// finished because it tries to acquire the timeout lock
// and acces the `active` variable`
thread_pool_.AddTask([this] { timeout_dispatcher_.reset(); });
}
} else {
FinalizeTransactionReplicationInternal();
}
}
void Shard::ReplicationClient::FinalizeTransactionReplicationInternal() {
MG_ASSERT(replica_stream_, "Missing stream for transaction deltas");
try {
auto response = replica_stream_->Finalize();
replica_stream_.reset();
std::unique_lock client_guard(client_lock_);
if (!response.success || replica_state_ == replication::ReplicaState::RECOVERY) {
replica_state_.store(replication::ReplicaState::RECOVERY);
thread_pool_.AddTask([&, this] { this->RecoverReplica(response.current_commit_timestamp); });
} else {
replica_state_.store(replication::ReplicaState::READY);
}
} catch (const rpc::RpcFailedException &) {
replica_stream_.reset();
{
std::unique_lock client_guard(client_lock_);
replica_state_.store(replication::ReplicaState::INVALID);
}
HandleRpcFailure();
}
}
void Shard::ReplicationClient::RecoverReplica(uint64_t replica_commit) {
while (true) {
auto file_locker = shard_->file_retainer_.AddLocker();
const auto steps = GetRecoverySteps(replica_commit, &file_locker);
for (const auto &recovery_step : steps) {
try {
std::visit(
[&, this]<typename T>(T &&arg) {
using StepType = std::remove_cvref_t<T>;
if constexpr (std::is_same_v<StepType, RecoverySnapshot>) {
spdlog::debug("Sending the latest snapshot file: {}", arg);
auto response = TransferSnapshot(arg);
replica_commit = response.current_commit_timestamp;
} else if constexpr (std::is_same_v<StepType, RecoveryWals>) {
spdlog::debug("Sending the latest wal files");
auto response = TransferWalFiles(arg);
replica_commit = response.current_commit_timestamp;
} else if constexpr (std::is_same_v<StepType, RecoveryCurrentWal>) {
if (shard_->wal_file_ && shard_->wal_file_->SequenceNumber() == arg.current_wal_seq_num) {
shard_->wal_file_->DisableFlushing();
spdlog::debug("Sending current wal file");
replica_commit = ReplicateCurrentWal();
shard_->wal_file_->EnableFlushing();
}
} else {
static_assert(always_false_v<T>, "Missing type from variant visitor");
}
},
recovery_step);
} catch (const rpc::RpcFailedException &) {
{
std::unique_lock client_guard{client_lock_};
replica_state_.store(replication::ReplicaState::INVALID);
}
HandleRpcFailure();
return;
}
}
spdlog::trace("Current timestamp on replica: {}", replica_commit);
// To avoid the situation where we read a correct commit timestamp in
// one thread, and after that another thread commits a different a
// transaction and THEN we set the state to READY in the first thread,
// we set this lock before checking the timestamp.
// We will detect that the state is invalid during the next commit,
// because replication::AppendDeltasRpc sends the last commit timestamp which
// replica checks if it's the same last commit timestamp it received
// and we will go to recovery.
// By adding this lock, we can avoid that, and go to RECOVERY immediately.
std::unique_lock client_guard{client_lock_};
SPDLOG_INFO("Replica timestamp: {}", replica_commit);
SPDLOG_INFO("Last commit: {}", shard_->last_commit_timestamp_);
if (shard_->last_commit_timestamp_ == replica_commit) {
replica_state_.store(replication::ReplicaState::READY);
return;
}
}
}
uint64_t Shard::ReplicationClient::ReplicateCurrentWal() {
const auto &wal_file = shard_->wal_file_;
auto stream = TransferCurrentWalFile();
stream.AppendFilename(wal_file->Path().filename());
utils::InputFile file;
MG_ASSERT(file.Open(shard_->wal_file_->Path()), "Failed to open current WAL file!");
const auto [buffer, buffer_size] = wal_file->CurrentFileBuffer();
stream.AppendSize(file.GetSize() + buffer_size);
stream.AppendFileData(&file);
stream.AppendBufferData(buffer, buffer_size);
auto response = stream.Finalize();
return response.current_commit_timestamp;
}
/// This method tries to find the optimal path for recoverying a single replica.
/// Based on the last commit transfered to replica it tries to update the
/// replica using durability files - WALs and Snapshots. WAL files are much
/// smaller in size as they contain only the Deltas (changes) made during the
/// transactions while Snapshots contain all the data. For that reason we prefer
/// WALs as much as possible. As the WAL file that is currently being updated
/// can change during the process we ignore it as much as possible. Also, it
/// uses the transaction lock so lokcing it can be really expensive. After we
/// fetch the list of finalized WALs, we try to find the longest chain of
/// sequential WALs, starting from the latest one, that will update the recovery
/// with the all missed updates. If the WAL chain cannot be created, replica is
/// behind by a lot, so we use the regular recovery process, we send the latest
/// snapshot and all the necessary WAL files, starting from the newest WAL that
/// contains a timestamp before the snapshot. If we registered the existence of
/// the current WAL, we add the sequence number we read from it to the recovery
/// process. After all the other steps are finished, if the current WAL contains
/// the same sequence number, it's the same WAL we read while fetching the
/// recovery steps, so we can safely send it to the replica.
/// We assume that the property of preserving at least 1 WAL before the snapshot
/// is satisfied as we extract the timestamp information from it.
std::vector<Shard::ReplicationClient::RecoveryStep> Shard::ReplicationClient::GetRecoverySteps(
const uint64_t replica_commit, utils::FileRetainer::FileLocker *file_locker) {
// First check if we can recover using the current wal file only
// otherwise save the seq_num of the current wal file
// This lock is also necessary to force the missed transaction to finish.
std::optional<uint64_t> current_wal_seq_num;
std::optional<uint64_t> current_wal_from_timestamp;
if (shard_->wal_file_) {
current_wal_seq_num.emplace(shard_->wal_file_->SequenceNumber());
current_wal_from_timestamp.emplace(shard_->wal_file_->FromTimestamp());
}
auto locker_acc = file_locker->Access();
auto wal_files = durability::GetWalFiles(shard_->wal_directory_, shard_->uuid_, current_wal_seq_num);
MG_ASSERT(wal_files, "Wal files could not be loaded");
auto snapshot_files = durability::GetSnapshotFiles(shard_->snapshot_directory_, shard_->uuid_);
std::optional<durability::SnapshotDurabilityInfo> latest_snapshot;
if (!snapshot_files.empty()) {
std::sort(snapshot_files.begin(), snapshot_files.end());
latest_snapshot.emplace(std::move(snapshot_files.back()));
}
std::vector<RecoveryStep> recovery_steps;
// No finalized WAL files were found. This means the difference is contained
// inside the current WAL or the snapshot.
if (wal_files->empty()) {
if (current_wal_from_timestamp && replica_commit >= *current_wal_from_timestamp) {
MG_ASSERT(current_wal_seq_num);
recovery_steps.emplace_back(RecoveryCurrentWal{*current_wal_seq_num});
return recovery_steps;
}
// Without the finalized WAL containing the current timestamp of replica,
// we cannot know if the difference is only in the current WAL or we need
// to send the snapshot.
if (latest_snapshot) {
locker_acc.AddPath(latest_snapshot->path);
recovery_steps.emplace_back(std::in_place_type_t<RecoverySnapshot>{}, std::move(latest_snapshot->path));
}
// if there are no finalized WAL files, snapshot left the current WAL
// as the WAL file containing a transaction before snapshot creation
// so we can be sure that the current WAL is present
MG_ASSERT(current_wal_seq_num);
recovery_steps.emplace_back(RecoveryCurrentWal{*current_wal_seq_num});
return recovery_steps;
}
// Find the longest chain of WALs for recovery.
// The chain consists ONLY of sequential WALs.
auto rwal_it = wal_files->rbegin();
// if the last finalized WAL is before the replica commit
// then we can recovery only from current WAL
if (rwal_it->to_timestamp <= replica_commit) {
MG_ASSERT(current_wal_seq_num);
recovery_steps.emplace_back(RecoveryCurrentWal{*current_wal_seq_num});
return recovery_steps;
}
uint64_t previous_seq_num{rwal_it->seq_num};
for (; rwal_it != wal_files->rend(); ++rwal_it) {
// If the difference between two consecutive wal files is not 0 or 1
// we have a missing WAL in our chain
if (previous_seq_num - rwal_it->seq_num > 1) {
break;
}
// Find first WAL that contains up to replica commit, i.e. WAL
// that is before the replica commit or conatins the replica commit
// as the last committed transaction OR we managed to find the first WAL
// file.
if (replica_commit >= rwal_it->from_timestamp || rwal_it->seq_num == 0) {
if (replica_commit >= rwal_it->to_timestamp) {
// We want the WAL after because the replica already contains all the
// commits from this WAL
--rwal_it;
}
std::vector<std::filesystem::path> wal_chain;
auto distance_from_first = std::distance(rwal_it, wal_files->rend() - 1);
// We have managed to create WAL chain
// We need to lock these files and add them to the chain
for (auto result_wal_it = wal_files->begin() + distance_from_first; result_wal_it != wal_files->end();
++result_wal_it) {
locker_acc.AddPath(result_wal_it->path);
wal_chain.push_back(std::move(result_wal_it->path));
}
recovery_steps.emplace_back(std::in_place_type_t<RecoveryWals>{}, std::move(wal_chain));
if (current_wal_seq_num) {
recovery_steps.emplace_back(RecoveryCurrentWal{*current_wal_seq_num});
}
return recovery_steps;
}
previous_seq_num = rwal_it->seq_num;
}
MG_ASSERT(latest_snapshot, "Invalid durability state, missing snapshot");
// We didn't manage to find a WAL chain, we need to send the latest snapshot
// with its WALs
locker_acc.AddPath(latest_snapshot->path);
recovery_steps.emplace_back(std::in_place_type_t<RecoverySnapshot>{}, std::move(latest_snapshot->path));
std::vector<std::filesystem::path> recovery_wal_files;
auto wal_it = wal_files->begin();
for (; wal_it != wal_files->end(); ++wal_it) {
// Assuming recovery process is correct the snashpot should
// always retain a single WAL that contains a transaction
// before its creation
if (latest_snapshot->start_timestamp < wal_it->to_timestamp) {
if (latest_snapshot->start_timestamp < wal_it->from_timestamp) {
MG_ASSERT(wal_it != wal_files->begin(), "Invalid durability files state");
--wal_it;
}
break;
}
}
for (; wal_it != wal_files->end(); ++wal_it) {
locker_acc.AddPath(wal_it->path);
recovery_wal_files.push_back(std::move(wal_it->path));
}
// We only have a WAL before the snapshot
if (recovery_wal_files.empty()) {
locker_acc.AddPath(wal_files->back().path);
recovery_wal_files.push_back(std::move(wal_files->back().path));
}
recovery_steps.emplace_back(std::in_place_type_t<RecoveryWals>{}, std::move(recovery_wal_files));
if (current_wal_seq_num) {
recovery_steps.emplace_back(RecoveryCurrentWal{*current_wal_seq_num});
}
return recovery_steps;
}
////// TimeoutDispatcher //////
void Shard::ReplicationClient::TimeoutDispatcher::WaitForTaskToFinish() {
// Wait for the previous timeout task to finish
std::unique_lock main_guard(main_lock);
main_cv.wait(main_guard, [&] { return finished; });
}
void Shard::ReplicationClient::TimeoutDispatcher::StartTimeoutTask(const double timeout) {
timeout_pool.AddTask([timeout, this] {
finished = false;
using std::chrono::steady_clock;
const auto timeout_duration =
std::chrono::duration_cast<steady_clock::duration>(std::chrono::duration<double>(timeout));
const auto end_time = steady_clock::now() + timeout_duration;
while (active && (steady_clock::now() < end_time)) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::unique_lock main_guard(main_lock);
finished = true;
active = false;
main_cv.notify_one();
});
}
////// ReplicaStream //////
Shard::ReplicationClient::ReplicaStream::ReplicaStream(ReplicationClient *self,
const uint64_t previous_commit_timestamp,
const uint64_t current_seq_num)
: self_(self),
stream_(self_->rpc_client_->Stream<replication::AppendDeltasRpc>(previous_commit_timestamp, current_seq_num)) {
replication::Encoder encoder{stream_.GetBuilder()};
encoder.WriteString(self_->shard_->epoch_id_);
}
void Shard::ReplicationClient::ReplicaStream::AppendDelta(const Delta &delta, const Vertex &vertex,
uint64_t final_commit_timestamp) {
replication::Encoder encoder(stream_.GetBuilder());
EncodeDelta(&encoder, &self_->shard_->name_id_mapper_, self_->shard_->config_.items, delta, vertex,
final_commit_timestamp);
}
void Shard::ReplicationClient::ReplicaStream::AppendDelta(const Delta &delta, const Edge &edge,
uint64_t final_commit_timestamp) {
replication::Encoder encoder(stream_.GetBuilder());
EncodeDelta(&encoder, &self_->shard_->name_id_mapper_, delta, edge, final_commit_timestamp);
}
void Shard::ReplicationClient::ReplicaStream::AppendTransactionEnd(uint64_t final_commit_timestamp) {
replication::Encoder encoder(stream_.GetBuilder());
EncodeTransactionEnd(&encoder, final_commit_timestamp);
}
void Shard::ReplicationClient::ReplicaStream::AppendOperation(durability::StorageGlobalOperation operation,
LabelId label, const std::set<PropertyId> &properties,
uint64_t timestamp) {
replication::Encoder encoder(stream_.GetBuilder());
EncodeOperation(&encoder, &self_->shard_->name_id_mapper_, operation, label, properties, timestamp);
}
replication::AppendDeltasRes Shard::ReplicationClient::ReplicaStream::Finalize() { return stream_.AwaitResponse(); }
////// CurrentWalHandler //////
Shard::ReplicationClient::CurrentWalHandler::CurrentWalHandler(ReplicationClient *self)
: self_(self), stream_(self_->rpc_client_->Stream<replication::CurrentWalRpc>()) {}
void Shard::ReplicationClient::CurrentWalHandler::AppendFilename(const std::string &filename) {
replication::Encoder encoder(stream_.GetBuilder());
encoder.WriteString(filename);
}
void Shard::ReplicationClient::CurrentWalHandler::AppendSize(const size_t size) {
replication::Encoder encoder(stream_.GetBuilder());
encoder.WriteUint(size);
}
void Shard::ReplicationClient::CurrentWalHandler::AppendFileData(utils::InputFile *file) {
replication::Encoder encoder(stream_.GetBuilder());
encoder.WriteFileData(file);
}
void Shard::ReplicationClient::CurrentWalHandler::AppendBufferData(const uint8_t *buffer, const size_t buffer_size) {
replication::Encoder encoder(stream_.GetBuilder());
encoder.WriteBuffer(buffer, buffer_size);
}
replication::CurrentWalRes Shard::ReplicationClient::CurrentWalHandler::Finalize() { return stream_.AwaitResponse(); }
} // namespace memgraph::storage::v3

View File

@@ -0,0 +1,203 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <atomic>
#include <chrono>
#include <thread>
#include <variant>
#include "rpc/client.hpp"
#include "storage/v3/config.hpp"
#include "storage/v3/delta.hpp"
#include "storage/v3/durability/wal.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/mvcc.hpp"
#include "storage/v3/name_id_mapper.hpp"
#include "storage/v3/property_value.hpp"
#include "storage/v3/replication/config.hpp"
#include "storage/v3/replication/enums.hpp"
#include "storage/v3/replication/rpc.hpp"
#include "storage/v3/replication/serialization.hpp"
#include "storage/v3/shard.hpp"
#include "utils/file.hpp"
#include "utils/file_locker.hpp"
#include "utils/spin_lock.hpp"
#include "utils/synchronized.hpp"
#include "utils/thread_pool.hpp"
namespace memgraph::storage::v3 {
class Shard::ReplicationClient {
public:
ReplicationClient(std::string name, Shard *shard, const io::network::Endpoint &endpoint,
replication::ReplicationMode mode, const replication::ReplicationClientConfig &config = {});
// Handler used for transfering the current transaction.
class ReplicaStream {
private:
friend class ReplicationClient;
explicit ReplicaStream(ReplicationClient *self, uint64_t previous_commit_timestamp, uint64_t current_seq_num);
public:
/// @throw rpc::RpcFailedException
void AppendDelta(const Delta &delta, const Vertex &vertex, uint64_t final_commit_timestamp);
/// @throw rpc::RpcFailedException
void AppendDelta(const Delta &delta, const Edge &edge, uint64_t final_commit_timestamp);
/// @throw rpc::RpcFailedException
void AppendTransactionEnd(uint64_t final_commit_timestamp);
/// @throw rpc::RpcFailedException
void AppendOperation(durability::StorageGlobalOperation operation, LabelId label,
const std::set<PropertyId> &properties, uint64_t timestamp);
private:
/// @throw rpc::RpcFailedException
replication::AppendDeltasRes Finalize();
ReplicationClient *self_;
rpc::Client::StreamHandler<replication::AppendDeltasRpc> stream_;
};
// Handler for transfering the current WAL file whose data is
// contained in the internal buffer and the file.
class CurrentWalHandler {
private:
friend class ReplicationClient;
explicit CurrentWalHandler(ReplicationClient *self);
public:
void AppendFilename(const std::string &filename);
void AppendSize(size_t size);
void AppendFileData(utils::InputFile *file);
void AppendBufferData(const uint8_t *buffer, size_t buffer_size);
/// @throw rpc::RpcFailedException
replication::CurrentWalRes Finalize();
private:
ReplicationClient *self_;
rpc::Client::StreamHandler<replication::CurrentWalRpc> stream_;
};
void StartTransactionReplication(uint64_t current_wal_seq_num);
// Replication clients can be removed at any point
// so to avoid any complexity of checking if the client was removed whenever
// we want to send part of transaction and to avoid adding some GC logic this
// function will run a callback if, after previously callling
// StartTransactionReplication, stream is created.
void IfStreamingTransaction(const std::function<void(ReplicaStream &handler)> &callback);
void FinalizeTransactionReplication();
// Transfer the snapshot file.
// @param path Path of the snapshot file.
replication::SnapshotRes TransferSnapshot(const std::filesystem::path &path);
CurrentWalHandler TransferCurrentWalFile() { return CurrentWalHandler{this}; }
// Transfer the WAL files
replication::WalFilesRes TransferWalFiles(const std::vector<std::filesystem::path> &wal_files);
const auto &Name() const { return name_; }
auto State() const { return replica_state_.load(); }
auto Mode() const { return mode_; }
auto Timeout() const { return timeout_; }
const auto &Endpoint() const { return rpc_client_->Endpoint(); }
private:
void FinalizeTransactionReplicationInternal();
void RecoverReplica(uint64_t replica_commit);
uint64_t ReplicateCurrentWal();
using RecoveryWals = std::vector<std::filesystem::path>;
struct RecoveryCurrentWal {
uint64_t current_wal_seq_num;
explicit RecoveryCurrentWal(const uint64_t current_wal_seq_num) : current_wal_seq_num(current_wal_seq_num) {}
};
using RecoverySnapshot = std::filesystem::path;
using RecoveryStep = std::variant<RecoverySnapshot, RecoveryWals, RecoveryCurrentWal>;
std::vector<RecoveryStep> GetRecoverySteps(uint64_t replica_commit, utils::FileRetainer::FileLocker *file_locker);
void FrequentCheck();
void InitializeClient();
void TryInitializeClientSync();
void TryInitializeClientAsync();
void HandleRpcFailure();
std::string name_;
Shard *shard_;
std::optional<communication::ClientContext> rpc_context_;
std::optional<rpc::Client> rpc_client_;
std::optional<ReplicaStream> replica_stream_;
replication::ReplicationMode mode_{replication::ReplicationMode::SYNC};
// Dispatcher class for timeout tasks
struct TimeoutDispatcher {
explicit TimeoutDispatcher(){};
void WaitForTaskToFinish();
void StartTimeoutTask(double timeout);
// If the Timeout task should continue waiting
std::atomic<bool> active{false};
std::mutex main_lock;
std::condition_variable main_cv;
private:
// if the Timeout task finished executing
bool finished{true};
utils::ThreadPool timeout_pool{1};
};
std::optional<double> timeout_;
std::optional<TimeoutDispatcher> timeout_dispatcher_;
utils::SpinLock client_lock_;
// This thread pool is used for background tasks so we don't
// block the main storage thread
// We use only 1 thread for 2 reasons:
// - background tasks ALWAYS contain some kind of RPC communication.
// We can't have multiple RPC communication from a same client
// because that's not logically valid (e.g. you cannot send a snapshot
// and WAL at a same time because WAL will arrive earlier and be applied
// before the snapshot which is not correct)
// - the implementation is simplified as we have a total control of what
// this pool is executing. Also, we can simply queue multiple tasks
// and be sure of the execution order.
// Not having mulitple possible threads in the same client allows us
// to ignore concurrency problems inside the client.
utils::ThreadPool thread_pool_{1};
std::atomic<replication::ReplicaState> replica_state_{replication::ReplicaState::INVALID};
utils::Scheduler replica_checker_;
};
} // namespace memgraph::storage::v3

View File

@@ -0,0 +1,570 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "storage/v3/replication/replication_server.hpp"
#include <atomic>
#include <filesystem>
#include "storage/v3/durability/durability.hpp"
#include "storage/v3/durability/paths.hpp"
#include "storage/v3/durability/serialization.hpp"
#include "storage/v3/durability/snapshot.hpp"
#include "storage/v3/durability/version.hpp"
#include "storage/v3/durability/wal.hpp"
#include "storage/v3/replication/config.hpp"
#include "storage/v3/transaction.hpp"
#include "utils/exceptions.hpp"
namespace memgraph::storage::v3 {
namespace {
std::pair<uint64_t, durability::WalDeltaData> ReadDelta(durability::BaseDecoder *decoder) {
try {
auto timestamp = ReadWalDeltaHeader(decoder);
SPDLOG_INFO(" Timestamp {}", timestamp);
auto delta = ReadWalDeltaData(decoder);
return {timestamp, delta};
} catch (const slk::SlkReaderException &) {
throw utils::BasicException("Missing data!");
} catch (const durability::RecoveryFailure &) {
throw utils::BasicException("Invalid data!");
}
};
} // namespace
Shard::ReplicationServer::ReplicationServer(Shard *shard, io::network::Endpoint endpoint,
const replication::ReplicationServerConfig &config)
: shard_(shard) {
// Create RPC server.
if (config.ssl) {
rpc_server_context_.emplace(config.ssl->key_file, config.ssl->cert_file, config.ssl->ca_file,
config.ssl->verify_peer);
} else {
rpc_server_context_.emplace();
}
// NOTE: The replication server must have a single thread for processing
// because there is no need for more processing threads - each replica can
// have only a single main server. Also, the single-threaded guarantee
// simplifies the rest of the implementation.
rpc_server_.emplace(std::move(endpoint), &*rpc_server_context_,
/* workers_count = */ 1);
rpc_server_->Register<replication::HeartbeatRpc>([this](auto *req_reader, auto *res_builder) {
spdlog::debug("Received HeartbeatRpc");
this->HeartbeatHandler(req_reader, res_builder);
});
rpc_server_->Register<replication::FrequentHeartbeatRpc>([](auto *req_reader, auto *res_builder) {
spdlog::debug("Received FrequentHeartbeatRpc");
FrequentHeartbeatHandler(req_reader, res_builder);
});
rpc_server_->Register<replication::AppendDeltasRpc>([this](auto *req_reader, auto *res_builder) {
spdlog::debug("Received AppendDeltasRpc");
this->AppendDeltasHandler(req_reader, res_builder);
});
rpc_server_->Register<replication::SnapshotRpc>([this](auto *req_reader, auto *res_builder) {
spdlog::debug("Received SnapshotRpc");
this->SnapshotHandler(req_reader, res_builder);
});
rpc_server_->Register<replication::WalFilesRpc>([this](auto *req_reader, auto *res_builder) {
spdlog::debug("Received WalFilesRpc");
this->WalFilesHandler(req_reader, res_builder);
});
rpc_server_->Register<replication::CurrentWalRpc>([this](auto *req_reader, auto *res_builder) {
spdlog::debug("Received CurrentWalRpc");
this->CurrentWalHandler(req_reader, res_builder);
});
rpc_server_->Start();
}
void Shard::ReplicationServer::HeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::HeartbeatReq req;
slk::Load(&req, req_reader);
replication::HeartbeatRes res{true, shard_->last_commit_timestamp_, shard_->epoch_id_};
slk::Save(res, res_builder);
}
void Shard::ReplicationServer::FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::FrequentHeartbeatReq req;
slk::Load(&req, req_reader);
replication::FrequentHeartbeatRes res{true};
slk::Save(res, res_builder);
}
void Shard::ReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::AppendDeltasReq req;
slk::Load(&req, req_reader);
replication::Decoder decoder(req_reader);
auto maybe_epoch_id = decoder.ReadString();
MG_ASSERT(maybe_epoch_id, "Invalid replication message");
if (*maybe_epoch_id != shard_->epoch_id_) {
shard_->epoch_history_.emplace_back(std::move(shard_->epoch_id_), shard_->last_commit_timestamp_);
shard_->epoch_id_ = std::move(*maybe_epoch_id);
}
if (shard_->wal_file_) {
if (req.seq_num > shard_->wal_file_->SequenceNumber() || *maybe_epoch_id != shard_->epoch_id_) {
shard_->wal_file_->FinalizeWal();
shard_->wal_file_.reset();
shard_->wal_seq_num_ = req.seq_num;
} else {
MG_ASSERT(shard_->wal_file_->SequenceNumber() == req.seq_num, "Invalid sequence number of current wal file");
shard_->wal_seq_num_ = req.seq_num + 1;
}
} else {
shard_->wal_seq_num_ = req.seq_num;
}
if (req.previous_commit_timestamp != shard_->last_commit_timestamp_) {
// Empty the stream
bool transaction_complete = false;
while (!transaction_complete) {
SPDLOG_INFO("Skipping delta");
const auto [timestamp, delta] = ReadDelta(&decoder);
transaction_complete = durability::IsWalDeltaDataTypeTransactionEnd(delta.type);
}
replication::AppendDeltasRes res{false, shard_->last_commit_timestamp_};
slk::Save(res, res_builder);
return;
}
ReadAndApplyDelta(&decoder);
replication::AppendDeltasRes res{true, shard_->last_commit_timestamp_};
slk::Save(res, res_builder);
}
void Shard::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::SnapshotReq req;
slk::Load(&req, req_reader);
replication::Decoder decoder(req_reader);
utils::EnsureDirOrDie(shard_->snapshot_directory_);
const auto maybe_snapshot_path = decoder.ReadFile(shard_->snapshot_directory_);
MG_ASSERT(maybe_snapshot_path, "Failed to load snapshot!");
spdlog::info("Received snapshot saved to {}", *maybe_snapshot_path);
// Clear the database
shard_->vertices_.clear();
shard_->edges_.clear();
shard_->constraints_ = Constraints();
shard_->indices_.label_index =
LabelIndex(&shard_->indices_, &shard_->constraints_, shard_->config_.items, shard_->vertex_validator_);
shard_->indices_.label_property_index =
LabelPropertyIndex(&shard_->indices_, &shard_->constraints_, shard_->config_.items, shard_->vertex_validator_);
try {
spdlog::debug("Loading snapshot");
auto recovered_snapshot = durability::RecoveredSnapshot{};
// durability::LoadSnapshot(*maybe_snapshot_path, &shard_->vertices_, &shard_->edges_,
// &shard_->epoch_history_,
// &shard_->name_id_mapper_, &shard_->edge_count_, shard_->config_.items);
spdlog::debug("Snapshot loaded successfully");
// If this step is present it should always be the first step of
// the recovery so we use the UUID we read from snasphost
shard_->uuid_ = std::move(recovered_snapshot.snapshot_info.uuid);
shard_->epoch_id_ = std::move(recovered_snapshot.snapshot_info.epoch_id);
const auto &recovery_info = recovered_snapshot.recovery_info;
shard_->timestamp_ = std::max(shard_->timestamp_, recovery_info.next_timestamp);
// durability::RecoverIndicesAndConstraints(recovered_snapshot.indices_constraints, &shard_->indices_,
// &shard_->constraints_, &shard_->vertices_);
} catch (const durability::RecoveryFailure &e) {
LOG_FATAL("Couldn't load the snapshot because of: {}", e.what());
}
replication::SnapshotRes res{true, shard_->last_commit_timestamp_};
slk::Save(res, res_builder);
// Delete other durability files
auto snapshot_files = durability::GetSnapshotFiles(shard_->snapshot_directory_, shard_->uuid_);
for (const auto &[path, uuid, _] : snapshot_files) {
if (path != *maybe_snapshot_path) {
shard_->file_retainer_.DeleteFile(path);
}
}
auto wal_files = durability::GetWalFiles(shard_->wal_directory_, shard_->uuid_);
if (wal_files) {
for (const auto &wal_file : *wal_files) {
shard_->file_retainer_.DeleteFile(wal_file.path);
}
shard_->wal_file_.reset();
}
}
void Shard::ReplicationServer::WalFilesHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::WalFilesReq req;
slk::Load(&req, req_reader);
const auto wal_file_number = req.file_number;
spdlog::debug("Received WAL files: {}", wal_file_number);
replication::Decoder decoder(req_reader);
utils::EnsureDirOrDie(shard_->wal_directory_);
for (auto i = 0; i < wal_file_number; ++i) {
LoadWal(&decoder);
}
replication::WalFilesRes res{true, shard_->last_commit_timestamp_};
slk::Save(res, res_builder);
}
void Shard::ReplicationServer::CurrentWalHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::CurrentWalReq req;
slk::Load(&req, req_reader);
replication::Decoder decoder(req_reader);
utils::EnsureDirOrDie(shard_->wal_directory_);
LoadWal(&decoder);
replication::CurrentWalRes res{true, shard_->last_commit_timestamp_};
slk::Save(res, res_builder);
}
void Shard::ReplicationServer::LoadWal(replication::Decoder *decoder) {
const auto temp_wal_directory = std::filesystem::temp_directory_path() / "memgraph" / durability::kWalDirectory;
utils::EnsureDir(temp_wal_directory);
auto maybe_wal_path = decoder->ReadFile(temp_wal_directory);
MG_ASSERT(maybe_wal_path, "Failed to load WAL!");
spdlog::trace("Received WAL saved to {}", *maybe_wal_path);
try {
auto wal_info = durability::ReadWalInfo(*maybe_wal_path);
if (wal_info.seq_num == 0) {
shard_->uuid_ = wal_info.uuid;
}
if (wal_info.epoch_id != shard_->epoch_id_) {
shard_->epoch_history_.emplace_back(wal_info.epoch_id, shard_->last_commit_timestamp_);
shard_->epoch_id_ = std::move(wal_info.epoch_id);
}
if (shard_->wal_file_) {
if (shard_->wal_file_->SequenceNumber() != wal_info.seq_num) {
shard_->wal_file_->FinalizeWal();
shard_->wal_seq_num_ = wal_info.seq_num;
shard_->wal_file_.reset();
}
} else {
shard_->wal_seq_num_ = wal_info.seq_num;
}
durability::Decoder wal;
const auto version = wal.Initialize(*maybe_wal_path, durability::kWalMagic);
if (!version) throw durability::RecoveryFailure("Couldn't read WAL magic and/or version!");
if (!durability::IsVersionSupported(*version)) throw durability::RecoveryFailure("Invalid WAL version!");
wal.SetPosition(wal_info.offset_deltas);
for (size_t i = 0; i < wal_info.num_deltas;) {
i += ReadAndApplyDelta(&wal);
}
spdlog::debug("{} loaded successfully", *maybe_wal_path);
} catch (const durability::RecoveryFailure &e) {
LOG_FATAL("Couldn't recover WAL deltas from {} because of: {}", *maybe_wal_path, e.what());
}
}
Shard::ReplicationServer::~ReplicationServer() {
if (rpc_server_) {
rpc_server_->Shutdown();
rpc_server_->AwaitShutdown();
}
}
uint64_t Shard::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *decoder) {
auto edge_acc = shard_->edges_.access();
// auto vertex_acc = shard_->vertices_.access();
std::optional<std::pair<uint64_t, Shard::Accessor>> commit_timestamp_and_accessor;
// auto get_transaction = [this, &commit_timestamp_and_accessor](uint64_t commit_timestamp) {
// if (!commit_timestamp_and_accessor) {
// commit_timestamp_and_accessor.emplace(commit_timestamp, shard_->Access());
// } else if (commit_timestamp_and_accessor->first != commit_timestamp) {
// throw utils::BasicException("Received more than one transaction!");
// }
// return &commit_timestamp_and_accessor->second;
// };
uint64_t applied_deltas = 0;
auto max_commit_timestamp = shard_->last_commit_timestamp_;
for (bool transaction_complete = false; !transaction_complete; ++applied_deltas) {
const auto [timestamp, delta] = ReadDelta(decoder);
if (timestamp > max_commit_timestamp) {
max_commit_timestamp = timestamp;
}
transaction_complete = durability::IsWalDeltaDataTypeTransactionEnd(delta.type);
if (timestamp < shard_->timestamp_) {
continue;
}
// SPDLOG_INFO(" Delta {}", applied_deltas);
// switch (delta.type) {
// case durability::WalDeltaData::Type::VERTEX_CREATE: {
// spdlog::trace(" Create vertex {}", delta.vertex_create_delete.gid.AsUint());
// auto *transaction = get_transaction(timestamp);
// transaction->CreateVertex(delta.vertex_create_delete.gid);
// break;
// }
// case durability::WalDeltaData::Type::VERTEX_DELETE: {
// spdlog::trace(" Delete vertex {}", delta.vertex_create_delete.gid.AsUint());
// auto *transaction = get_transaction(timestamp);
// auto vertex = transaction->FindVertex(delta.vertex_create_delete.gid, View::NEW);
// if (!vertex) throw utils::BasicException("Invalid transaction!");
// auto ret = transaction->DeleteVertex(&*vertex);
// if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
// break;
// }
// case durability::WalDeltaData::Type::VERTEX_ADD_LABEL: {
// spdlog::trace(" Vertex {} add label {}", delta.vertex_add_remove_label.gid.AsUint(),
// delta.vertex_add_remove_label.label);
// auto *transaction = get_transaction(timestamp);
// auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, View::NEW);
// if (!vertex) throw utils::BasicException("Invalid transaction!");
// auto ret = vertex->AddLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label));
// if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
// break;
// }
// case durability::WalDeltaData::Type::VERTEX_REMOVE_LABEL: {
// spdlog::trace(" Vertex {} remove label {}", delta.vertex_add_remove_label.gid.AsUint(),
// delta.vertex_add_remove_label.label);
// auto *transaction = get_transaction(timestamp);
// auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, View::NEW);
// if (!vertex) throw utils::BasicException("Invalid transaction!");
// auto ret = vertex->RemoveLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label));
// if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
// break;
// }
// case durability::WalDeltaData::Type::VERTEX_SET_PROPERTY: {
// spdlog::trace(" Vertex {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(),
// delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value);
// auto *transaction = get_transaction(timestamp);
// auto vertex = transaction->FindVertex(delta.vertex_edge_set_property.gid, View::NEW);
// if (!vertex) throw utils::BasicException("Invalid transaction!");
// auto ret = vertex->SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property),
// delta.vertex_edge_set_property.value);
// if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
// break;
// }
// case durability::WalDeltaData::Type::EDGE_CREATE: {
// spdlog::trace(" Create edge {} of type {} from vertex {} to vertex {}",
// delta.edge_create_delete.gid.AsUint(), delta.edge_create_delete.edge_type,
// delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint());
// auto *transaction = get_transaction(timestamp);
// auto from_vertex = transaction->FindVertex(delta.edge_create_delete.from_vertex, View::NEW);
// if (!from_vertex) throw utils::BasicException("Invalid transaction!");
// auto to_vertex = transaction->FindVertex(delta.edge_create_delete.to_vertex, View::NEW);
// if (!to_vertex) throw utils::BasicException("Invalid transaction!");
// auto edge = transaction->CreateEdge(&*from_vertex, &*to_vertex,
// transaction->NameToEdgeType(delta.edge_create_delete.edge_type),
// delta.edge_create_delete.gid);
// if (edge.HasError()) throw utils::BasicException("Invalid transaction!");
// break;
// }
// case durability::WalDeltaData::Type::EDGE_DELETE: {
// spdlog::trace(" Delete edge {} of type {} from vertex {} to vertex {}",
// delta.edge_create_delete.gid.AsUint(), delta.edge_create_delete.edge_type,
// delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint());
// auto *transaction = get_transaction(timestamp);
// auto from_vertex = transaction->FindVertex(delta.edge_create_delete.from_vertex, View::NEW);
// if (!from_vertex) throw utils::BasicException("Invalid transaction!");
// auto to_vertex = transaction->FindVertex(delta.edge_create_delete.to_vertex, View::NEW);
// if (!to_vertex) throw utils::BasicException("Invalid transaction!");
// auto edges = from_vertex->OutEdges(View::NEW,
// {transaction->NameToEdgeType(delta.edge_create_delete.edge_type)},
// &*to_vertex);
// if (edges.HasError()) throw utils::BasicException("Invalid transaction!");
// if (edges->size() != 1) throw utils::BasicException("Invalid transaction!");
// auto &edge = (*edges)[0];
// auto ret = transaction->DeleteEdge(&edge);
// if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
// break;
// }
// case durability::WalDeltaData::Type::EDGE_SET_PROPERTY: {
// spdlog::trace(" Edge {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(),
// delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value);
// if (!shard_->config_.items.properties_on_edges)
// throw utils::BasicException(
// "Can't set properties on edges because properties on edges "
// "are disabled!");
// // auto *transaction = get_transaction(timestamp);
// // The following block of code effectively implements `FindEdge` and
// // yields an accessor that is only valid for managing the edge's
// // properties.
// auto edge = edge_acc.find(delta.vertex_edge_set_property.gid);
// if (edge == edge_acc.end()) throw utils::BasicException("Invalid transaction!");
// // The edge visibility check must be done here manually because we
// // don't allow direct access to the edges through the public API.
// {
// auto is_visible = !edge->deleted;
// auto *delta = edge->delta;
// ApplyDeltasForRead(&transaction->transaction_, delta, View::NEW, [&is_visible](const Delta &delta) {
// switch (delta.action) {
// case Delta::Action::ADD_LABEL:
// case Delta::Action::REMOVE_LABEL:
// case Delta::Action::SET_PROPERTY:
// case Delta::Action::ADD_IN_EDGE:
// case Delta::Action::ADD_OUT_EDGE:
// case Delta::Action::REMOVE_IN_EDGE:
// case Delta::Action::REMOVE_OUT_EDGE:
// break;
// case Delta::Action::RECREATE_OBJECT: {
// is_visible = true;
// break;
// }
// case Delta::Action::DELETE_OBJECT: {
// is_visible = false;
// break;
// }
// }
// });
// if (!is_visible) throw utils::BasicException("Invalid transaction!");
// }
// EdgeRef edge_ref(&*edge);
// // Here we create an edge accessor that we will use to get the
// // properties of the edge. The accessor is created with an invalid
// // type and invalid from/to pointers because we don't know them
// // here, but that isn't an issue because we won't use that part of
// // the API here.
// auto ea = EdgeAccessor{edge_ref,
// EdgeTypeId::FromUint(0UL),
// nullptr,
// nullptr,
// &transaction->transaction_,
// &shard_->indices_,
// &shard_->constraints_,
// shard_->config_.items,
// shard_->schema_validator_};
// auto ret = ea.SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property),
// delta.vertex_edge_set_property.value);
// if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
// break;
// }
// case durability::WalDeltaData::Type::TRANSACTION_END: {
// spdlog::trace(" Transaction end");
// if (!commit_timestamp_and_accessor || commit_timestamp_and_accessor->first != timestamp)
// throw utils::BasicException("Invalid data!");
// auto ret = commit_timestamp_and_accessor->second.Commit(commit_timestamp_and_accessor->first);
// if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
// commit_timestamp_and_accessor = std::nullopt;
// break;
// }
// case durability::WalDeltaData::Type::LABEL_INDEX_CREATE: {
// spdlog::trace(" Create label index on :{}", delta.operation_label.label);
// // Need to send the timestamp
// if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
// if (!shard_->CreateIndex(shard_->NameToLabel(delta.operation_label.label), timestamp))
// throw utils::BasicException("Invalid transaction!");
// break;
// }
// case durability::WalDeltaData::Type::LABEL_INDEX_DROP: {
// spdlog::trace(" Drop label index on :{}", delta.operation_label.label);
// if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
// if (!shard_->DropIndex(shard_->NameToLabel(delta.operation_label.label), timestamp))
// throw utils::BasicException("Invalid transaction!");
// break;
// }
// case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: {
// spdlog::trace(" Create label+property index on :{} ({})", delta.operation_label_property.label,
// delta.operation_label_property.property);
// if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
// if (!shard_->CreateIndex(shard_->NameToLabel(delta.operation_label_property.label),
// shard_->NameToProperty(delta.operation_label_property.property), timestamp))
// throw utils::BasicException("Invalid transaction!");
// break;
// }
// case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: {
// spdlog::trace(" Drop label+property index on :{} ({})", delta.operation_label_property.label,
// delta.operation_label_property.property);
// if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
// if (!shard_->DropIndex(shard_->NameToLabel(delta.operation_label_property.label),
// shard_->NameToProperty(delta.operation_label_property.property), timestamp))
// throw utils::BasicException("Invalid transaction!");
// break;
// }
// case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: {
// spdlog::trace(" Create existence constraint on :{} ({})", delta.operation_label_property.label,
// delta.operation_label_property.property);
// if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
// auto ret = shard_->CreateExistenceConstraint(
// shard_->NameToLabel(delta.operation_label_property.label),
// shard_->NameToProperty(delta.operation_label_property.property), timestamp);
// if (!ret.HasValue() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
// break;
// }
// case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: {
// spdlog::trace(" Drop existence constraint on :{} ({})", delta.operation_label_property.label,
// delta.operation_label_property.property);
// if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
// if (!shard_->DropExistenceConstraint(shard_->NameToLabel(delta.operation_label_property.label),
// shard_->NameToProperty(delta.operation_label_property.property),
// timestamp))
// throw utils::BasicException("Invalid transaction!");
// break;
// }
// case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: {
// std::stringstream ss;
// utils::PrintIterable(ss, delta.operation_label_properties.properties);
// spdlog::trace(" Create unique constraint on :{} ({})", delta.operation_label_properties.label,
// ss.str()); if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
// std::set<PropertyId> properties;
// for (const auto &prop : delta.operation_label_properties.properties) {
// properties.emplace(shard_->NameToProperty(prop));
// }
// auto ret = shard_->CreateUniqueConstraint(shard_->NameToLabel(delta.operation_label_properties.label),
// properties, timestamp);
// if (!ret.HasValue() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS)
// throw utils::BasicException("Invalid transaction!");
// break;
// }
// case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: {
// std::stringstream ss;
// utils::PrintIterable(ss, delta.operation_label_properties.properties);
// spdlog::trace(" Drop unique constraint on :{} ({})", delta.operation_label_properties.label,
// ss.str()); if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
// std::set<PropertyId> properties;
// for (const auto &prop : delta.operation_label_properties.properties) {
// properties.emplace(shard_->NameToProperty(prop));
// }
// auto ret = shard_->DropUniqueConstraint(shard_->NameToLabel(delta.operation_label_properties.label),
// properties, timestamp);
// if (ret != UniqueConstraints::DeletionStatus::SUCCESS) throw utils::BasicException("Invalid transaction!");
// break;
// }
// }
}
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid data!");
shard_->last_commit_timestamp_ = max_commit_timestamp;
return applied_deltas;
}
} // namespace memgraph::storage::v3

View File

@@ -0,0 +1,47 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "storage/v3/shard.hpp"
namespace memgraph::storage::v3 {
class Shard::ReplicationServer {
public:
explicit ReplicationServer(Shard *shard, io::network::Endpoint endpoint,
const replication::ReplicationServerConfig &config);
ReplicationServer(const ReplicationServer &) = delete;
ReplicationServer(ReplicationServer &&) = delete;
ReplicationServer &operator=(const ReplicationServer &) = delete;
ReplicationServer &operator=(ReplicationServer &&) = delete;
~ReplicationServer();
private:
// RPC handlers
void HeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder);
static void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder);
void AppendDeltasHandler(slk::Reader *req_reader, slk::Builder *res_builder);
void SnapshotHandler(slk::Reader *req_reader, slk::Builder *res_builder);
void WalFilesHandler(slk::Reader *req_reader, slk::Builder *res_builder);
void CurrentWalHandler(slk::Reader *req_reader, slk::Builder *res_builder);
void LoadWal(replication::Decoder *decoder);
uint64_t ReadAndApplyDelta(durability::BaseDecoder *decoder);
std::optional<communication::ServerContext> rpc_server_context_;
std::optional<rpc::Server> rpc_server_;
Shard *shard_;
};
} // namespace memgraph::storage::v3

View File

@@ -0,0 +1,74 @@
;; Copyright 2022 Memgraph Ltd.
;;
;; Use of this software is governed by the Business Source License
;; included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
;; License, and you may not use this file except in compliance with the Business Source License.
;;
;; As of the Change Date specified in that file, in accordance with
;; the Business Source License, use of this software will be governed
;; by the Apache License, Version 2.0, included in the file
;; licenses/APL.txt.
#>cpp
#pragma once
#include <cstdint>
#include <cstring>
#include <string>
#include "rpc/messages.hpp"
#include "slk/serialization.hpp"
#include "slk/streams.hpp"
cpp<#
(lcp:namespace memgraph)
(lcp:namespace storage)
(lcp:namespace v3)
(lcp:namespace replication)
(lcp:define-rpc append-deltas
;; The actual deltas are sent as additional data using the RPC client's
;; streaming API for additional data.
(:request
((previous-commit-timestamp :uint64_t)
(seq-num :uint64_t)))
(:response
((success :bool)
(current-commit-timestamp :uint64_t))))
(lcp:define-rpc heartbeat
(:request
((main-commit-timestamp :uint64_t)
(epoch-id "std::string")))
(:response
((success :bool)
(current-commit-timestamp :uint64_t)
(epoch-id "std::string"))))
;; FrequentHearthbeat is required because calling Heartbeat takes the storage lock.
;; Configured by `replication_replica_check_delay`.
(lcp:define-rpc frequent-heartbeat
(:request ())
(:response ((success :bool))))
(lcp:define-rpc snapshot
(:request ())
(:response
((success :bool)
(current-commit-timestamp :uint64_t))))
(lcp:define-rpc wal-files
(:request ((file-number :uint64_t)))
(:response
((success :bool)
(current-commit-timestamp :uint64_t))))
(lcp:define-rpc current-wal
(:request ())
(:response
((success :bool)
(current-commit-timestamp :uint64_t))))
(lcp:pop-namespace) ;; replication
(lcp:pop-namespace) ;; v3
(lcp:pop-namespace) ;; storage
(lcp:pop-namespace) ;; memgraph

View File

@@ -0,0 +1,149 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "storage/v3/replication/serialization.hpp"
namespace memgraph::storage::v3::replication {
////// Encoder //////
void Encoder::WriteMarker(durability::Marker marker) { slk::Save(marker, builder_); }
void Encoder::WriteBool(bool value) {
WriteMarker(durability::Marker::TYPE_BOOL);
slk::Save(value, builder_);
}
void Encoder::WriteUint(uint64_t value) {
WriteMarker(durability::Marker::TYPE_INT);
slk::Save(value, builder_);
}
void Encoder::WriteDouble(double value) {
WriteMarker(durability::Marker::TYPE_DOUBLE);
slk::Save(value, builder_);
}
void Encoder::WriteString(const std::string_view &value) {
WriteMarker(durability::Marker::TYPE_STRING);
slk::Save(value, builder_);
}
void Encoder::WritePropertyValue(const PropertyValue &value) {
WriteMarker(durability::Marker::TYPE_PROPERTY_VALUE);
slk::Save(value, builder_);
}
void Encoder::WriteBuffer(const uint8_t *buffer, const size_t buffer_size) { builder_->Save(buffer, buffer_size); }
void Encoder::WriteFileData(utils::InputFile *file) {
auto file_size = file->GetSize();
uint8_t buffer[utils::kFileBufferSize];
while (file_size > 0) {
const auto chunk_size = std::min(file_size, utils::kFileBufferSize);
file->Read(buffer, chunk_size);
WriteBuffer(buffer, chunk_size);
file_size -= chunk_size;
}
}
void Encoder::WriteFile(const std::filesystem::path &path) {
utils::InputFile file;
MG_ASSERT(file.Open(path), "Failed to open file {}", path);
MG_ASSERT(path.has_filename(), "Path does not have a filename!");
const auto &filename = path.filename().generic_string();
WriteString(filename);
auto file_size = file.GetSize();
WriteUint(file_size);
WriteFileData(&file);
file.Close();
}
////// Decoder //////
std::optional<durability::Marker> Decoder::ReadMarker() {
durability::Marker marker{durability::Marker::TYPE_NULL};
slk::Load(&marker, reader_);
return marker;
}
std::optional<bool> Decoder::ReadBool() {
if (const auto marker = ReadMarker(); !marker || marker != durability::Marker::TYPE_BOOL) return std::nullopt;
bool value{false};
slk::Load(&value, reader_);
return value;
}
std::optional<uint64_t> Decoder::ReadUint() {
if (const auto marker = ReadMarker(); !marker || marker != durability::Marker::TYPE_INT) return std::nullopt;
uint64_t value{0};
slk::Load(&value, reader_);
return value;
}
std::optional<double> Decoder::ReadDouble() {
if (const auto marker = ReadMarker(); !marker || marker != durability::Marker::TYPE_DOUBLE) return std::nullopt;
double value{0.0};
slk::Load(&value, reader_);
return value;
}
std::optional<std::string> Decoder::ReadString() {
if (const auto marker = ReadMarker(); !marker || marker != durability::Marker::TYPE_STRING) return std::nullopt;
std::string value;
slk::Load(&value, reader_);
return std::move(value);
}
std::optional<PropertyValue> Decoder::ReadPropertyValue() {
if (const auto marker = ReadMarker(); !marker || marker != durability::Marker::TYPE_PROPERTY_VALUE)
return std::nullopt;
PropertyValue value;
slk::Load(&value, reader_);
return std::move(value);
}
bool Decoder::SkipString() {
if (const auto marker = ReadMarker(); !marker || marker != durability::Marker::TYPE_STRING) return false;
std::string value;
slk::Load(&value, reader_);
return true;
}
bool Decoder::SkipPropertyValue() {
if (const auto marker = ReadMarker(); !marker || marker != durability::Marker::TYPE_PROPERTY_VALUE) return false;
PropertyValue value;
slk::Load(&value, reader_);
return true;
}
std::optional<std::filesystem::path> Decoder::ReadFile(const std::filesystem::path &directory,
const std::string &suffix) {
MG_ASSERT(std::filesystem::exists(directory) && std::filesystem::is_directory(directory),
"Sent path for streamed files should be a valid directory!");
utils::OutputFile file;
const auto maybe_filename = ReadString();
MG_ASSERT(maybe_filename, "Filename missing for the file");
const auto filename = *maybe_filename + suffix;
auto path = directory / filename;
file.Open(path, utils::OutputFile::Mode::OVERWRITE_EXISTING);
std::optional<size_t> maybe_file_size = ReadUint();
MG_ASSERT(maybe_file_size, "File size missing");
auto file_size = *maybe_file_size;
uint8_t buffer[utils::kFileBufferSize];
while (file_size > 0) {
const auto chunk_size = std::min(file_size, utils::kFileBufferSize);
reader_->Load(buffer, chunk_size);
file.Write(buffer, chunk_size);
file_size -= chunk_size;
}
file.Close();
return std::move(path);
}
} // namespace memgraph::storage::v3::replication

View File

@@ -0,0 +1,80 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <filesystem>
#include "slk/streams.hpp"
#include "storage/v3/durability/serialization.hpp"
#include "storage/v3/replication/slk.hpp"
#include "utils/cast.hpp"
#include "utils/file.hpp"
namespace memgraph::storage::v3::replication {
class Encoder final : public durability::BaseEncoder {
public:
explicit Encoder(slk::Builder *builder) : builder_(builder) {}
void WriteMarker(durability::Marker marker) override;
void WriteBool(bool value) override;
void WriteUint(uint64_t value) override;
void WriteDouble(double value) override;
void WriteString(const std::string_view &value) override;
void WritePropertyValue(const PropertyValue &value) override;
void WriteBuffer(const uint8_t *buffer, size_t buffer_size);
void WriteFileData(utils::InputFile *file);
void WriteFile(const std::filesystem::path &path);
private:
slk::Builder *builder_;
};
class Decoder final : public durability::BaseDecoder {
public:
explicit Decoder(slk::Reader *reader) : reader_(reader) {}
std::optional<durability::Marker> ReadMarker() override;
std::optional<bool> ReadBool() override;
std::optional<uint64_t> ReadUint() override;
std::optional<double> ReadDouble() override;
std::optional<std::string> ReadString() override;
std::optional<PropertyValue> ReadPropertyValue() override;
bool SkipString() override;
bool SkipPropertyValue() override;
/// Read the file and save it inside the specified directory.
/// @param directory Directory which will contain the read file.
/// @param suffix Suffix to be added to the received file's filename.
/// @return If the read was successful, path to the read file.
std::optional<std::filesystem::path> ReadFile(const std::filesystem::path &directory, const std::string &suffix = "");
private:
slk::Reader *reader_;
};
} // namespace memgraph::storage::v3::replication

View File

@@ -0,0 +1,169 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "storage/v3/replication/slk.hpp"
#include <type_traits>
#include "storage/v3/property_value.hpp"
#include "storage/v3/temporal.hpp"
#include "utils/cast.hpp"
namespace memgraph::slk {
void Save(const storage::v3::Gid &gid, slk::Builder *builder) { slk::Save(gid.AsUint(), builder); }
void Load(storage::v3::Gid *gid, slk::Reader *reader) {
uint64_t value{0};
slk::Load(&value, reader);
*gid = storage::v3::Gid::FromUint(value);
}
void Load(storage::v3::PropertyValue::Type *type, slk::Reader *reader) {
using PVTypeUnderlyingType = std::underlying_type_t<storage::v3::PropertyValue::Type>;
PVTypeUnderlyingType value{};
slk::Load(&value, reader);
bool valid{false};
switch (value) {
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::Null):
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::Bool):
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::Int):
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::Double):
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::String):
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::List):
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::Map):
case utils::UnderlyingCast(storage::v3::PropertyValue::Type::TemporalData):
valid = true;
break;
default:
valid = false;
break;
}
if (!valid) throw slk::SlkDecodeException("Trying to load unknown storage::v3::PropertyValue!");
*type = static_cast<storage::v3::PropertyValue::Type>(value);
}
void Save(const storage::v3::PropertyValue &value, slk::Builder *builder) {
switch (value.type()) {
case storage::v3::PropertyValue::Type::Null:
slk::Save(storage::v3::PropertyValue::Type::Null, builder);
return;
case storage::v3::PropertyValue::Type::Bool:
slk::Save(storage::v3::PropertyValue::Type::Bool, builder);
slk::Save(value.ValueBool(), builder);
return;
case storage::v3::PropertyValue::Type::Int:
slk::Save(storage::v3::PropertyValue::Type::Int, builder);
slk::Save(value.ValueInt(), builder);
return;
case storage::v3::PropertyValue::Type::Double:
slk::Save(storage::v3::PropertyValue::Type::Double, builder);
slk::Save(value.ValueDouble(), builder);
return;
case storage::v3::PropertyValue::Type::String:
slk::Save(storage::v3::PropertyValue::Type::String, builder);
slk::Save(value.ValueString(), builder);
return;
case storage::v3::PropertyValue::Type::List: {
slk::Save(storage::v3::PropertyValue::Type::List, builder);
const auto &values = value.ValueList();
size_t size = values.size();
slk::Save(size, builder);
for (const auto &v : values) {
slk::Save(v, builder);
}
return;
}
case storage::v3::PropertyValue::Type::Map: {
slk::Save(storage::v3::PropertyValue::Type::Map, builder);
const auto &map = value.ValueMap();
size_t size = map.size();
slk::Save(size, builder);
for (const auto &kv : map) {
slk::Save(kv, builder);
}
return;
}
case storage::v3::PropertyValue::Type::TemporalData: {
slk::Save(storage::v3::PropertyValue::Type::TemporalData, builder);
const auto temporal_data = value.ValueTemporalData();
slk::Save(temporal_data.type, builder);
slk::Save(temporal_data.microseconds, builder);
return;
}
}
}
void Load(storage::v3::PropertyValue *value, slk::Reader *reader) {
storage::v3::PropertyValue::Type type{};
slk::Load(&type, reader);
switch (type) {
case storage::v3::PropertyValue::Type::Null:
*value = storage::v3::PropertyValue();
return;
case storage::v3::PropertyValue::Type::Bool: {
bool v{false};
slk::Load(&v, reader);
*value = storage::v3::PropertyValue(v);
return;
}
case storage::v3::PropertyValue::Type::Int: {
int64_t v{0};
slk::Load(&v, reader);
*value = storage::v3::PropertyValue(v);
return;
}
case storage::v3::PropertyValue::Type::Double: {
double v{0.0};
slk::Load(&v, reader);
*value = storage::v3::PropertyValue(v);
return;
}
case storage::v3::PropertyValue::Type::String: {
std::string v;
slk::Load(&v, reader);
*value = storage::v3::PropertyValue(std::move(v));
return;
}
case storage::v3::PropertyValue::Type::List: {
size_t size{0};
slk::Load(&size, reader);
std::vector<storage::v3::PropertyValue> list(size);
for (size_t i = 0; i < size; ++i) {
slk::Load(&list[i], reader);
}
*value = storage::v3::PropertyValue(std::move(list));
return;
}
case storage::v3::PropertyValue::Type::Map: {
size_t size{0};
slk::Load(&size, reader);
std::map<std::string, storage::v3::PropertyValue> map;
for (size_t i = 0; i < size; ++i) {
std::pair<std::string, storage::v3::PropertyValue> kv;
slk::Load(&kv, reader);
map.insert(kv);
}
*value = storage::v3::PropertyValue(std::move(map));
return;
}
case storage::v3::PropertyValue::Type::TemporalData: {
storage::v3::TemporalType temporal_type{};
slk::Load(&temporal_type, reader);
int64_t microseconds{0};
slk::Load(&microseconds, reader);
*value = storage::v3::PropertyValue(storage::v3::TemporalData{temporal_type, microseconds});
return;
}
}
}
} // namespace memgraph::slk

View File

@@ -0,0 +1,41 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "slk/serialization.hpp"
#include "storage/v3/durability/marker.hpp"
#include "storage/v3/id_types.hpp"
#include "storage/v3/property_value.hpp"
#include "utils/concepts.hpp"
namespace memgraph::slk {
void Save(const storage::v3::Gid &gid, slk::Builder *builder);
void Load(storage::v3::Gid *gid, slk::Reader *reader);
void Save(const storage::v3::PropertyValue &value, slk::Builder *builder);
void Load(storage::v3::PropertyValue *value, slk::Reader *reader);
template <utils::Enum T>
void Save(const T &enum_value, slk::Builder *builder) {
slk::Save(utils::UnderlyingCast(enum_value), builder);
}
template <utils::Enum T>
void Load(T *enum_value, slk::Reader *reader) {
using UnderlyingType = std::underlying_type_t<T>;
UnderlyingType value;
slk::Load(&value, reader);
*enum_value = static_cast<T>(value);
}
} // namespace memgraph::slk

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
#pragma once
#include <atomic>
#include <cstdint>
#include <filesystem>
#include <map>
@@ -20,11 +21,13 @@
#include <variant>
#include <vector>
#include "coordinator/hybrid_logical_clock.hpp"
#include "io/network/endpoint.hpp"
#include "io/time.hpp"
#include "kvstore/kvstore.hpp"
#include "storage/v3/commit_log.hpp"
#include "storage/v3/config.hpp"
#include "storage/v3/constraints.hpp"
#include "storage/v3/durability/metadata.hpp"
#include "storage/v3/durability/wal.hpp"
#include "storage/v3/edge.hpp"
#include "storage/v3/edge_accessor.hpp"
#include "storage/v3/id_types.hpp"
@@ -43,7 +46,6 @@
#include "storage/v3/vertex_accessor.hpp"
#include "storage/v3/vertex_id.hpp"
#include "storage/v3/vertices_skip_list.hpp"
#include "storage/v3/view.hpp"
#include "utils/exceptions.hpp"
#include "utils/file_locker.hpp"
#include "utils/on_scope_exit.hpp"
@@ -53,6 +55,13 @@
#include "utils/synchronized.hpp"
#include "utils/uuid.hpp"
/// REPLICATION ///
#include "rpc/server.hpp"
#include "storage/v3/replication/config.hpp"
#include "storage/v3/replication/enums.hpp"
#include "storage/v3/replication/rpc.hpp"
#include "storage/v3/replication/serialization.hpp"
namespace memgraph::storage::v3 {
// The storage is based on this paper:
@@ -69,6 +78,7 @@ class AllVerticesIterable final {
Transaction *transaction_;
View view_;
Indices *indices_;
Constraints *constraints_;
Config::Items config_;
const VertexValidator *vertex_validator_;
const Schemas *schemas_;
@@ -92,11 +102,13 @@ class AllVerticesIterable final {
};
AllVerticesIterable(VerticesSkipList::Accessor vertices_accessor, Transaction *transaction, View view,
Indices *indices, Config::Items config, const VertexValidator &vertex_validator)
Indices *indices, Constraints *constraints, Config::Items config,
const VertexValidator &vertex_validator)
: vertices_accessor_(std::move(vertices_accessor)),
transaction_(transaction),
view_(view),
indices_(indices),
constraints_(constraints),
config_(config),
vertex_validator_{&vertex_validator} {}
@@ -172,6 +184,13 @@ struct IndicesInfo {
std::vector<std::pair<LabelId, PropertyId>> label_property;
};
/// Structure used to return information about existing constraints in the
/// storage.
struct ConstraintsInfo {
std::vector<std::pair<LabelId, PropertyId>> existence;
std::vector<std::pair<LabelId, std::set<PropertyId>>> unique;
};
/// Structure used to return information about existing schemas in the storage
struct SchemasInfo {
Schemas::SchemasList schemas;
@@ -183,8 +202,11 @@ struct StorageInfo {
uint64_t edge_count;
double average_degree;
uint64_t memory_usage;
uint64_t disk_usage;
};
enum class ReplicationRole : uint8_t { MAIN, REPLICA };
class Shard final {
public:
/// @throw std::system_error
@@ -202,9 +224,19 @@ class Shard final {
private:
friend class Shard;
Accessor(Shard &shard, Transaction &transaction);
explicit Accessor(Shard *shard, IsolationLevel isolation_level);
public:
Accessor(const Accessor &) = delete;
Accessor &operator=(const Accessor &) = delete;
Accessor &operator=(Accessor &&other) = delete;
// NOTE: After the accessor is moved, all objects derived from it (accessors
// and iterators) are *invalid*. You have to get all derived objects again.
Accessor(Accessor &&other) noexcept;
~Accessor();
// TODO(gvolfing) this is just a workaround for stitching remove this later.
LabelId GetPrimaryLabel() const noexcept { return shard_->primary_label_; }
@@ -221,8 +253,9 @@ class Shard final {
std::optional<VertexAccessor> FindVertex(std::vector<PropertyValue> primary_key, View view);
VerticesIterable Vertices(View view) {
return VerticesIterable(AllVerticesIterable(shard_->vertices_.access(), transaction_, view, &shard_->indices_,
shard_->config_.items, shard_->vertex_validator_));
return VerticesIterable(AllVerticesIterable(shard_->vertices_.access(), &transaction_, view, &shard_->indices_,
&shard_->constraints_, shard_->config_.items,
shard_->vertex_validator_));
}
VerticesIterable Vertices(LabelId label, View view);
@@ -283,16 +316,8 @@ class Shard final {
/// @throw std::bad_alloc
Result<std::optional<EdgeAccessor>> DeleteEdge(VertexId from_vertex_id, VertexId to_vertex_id, Gid edge_id);
LabelId NameToLabel(std::string_view name) const;
PropertyId NameToProperty(std::string_view name) const;
EdgeTypeId NameToEdgeType(std::string_view name) const;
const std::string &LabelToName(LabelId label) const;
const std::string &PropertyToName(PropertyId property) const;
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const;
bool LabelIndexExists(LabelId label) const { return shard_->indices_.label_index.IndexExists(label); }
@@ -305,42 +330,56 @@ class Shard final {
return {shard_->indices_.label_index.ListIndices(), shard_->indices_.label_property_index.ListIndices()};
}
ConstraintsInfo ListAllConstraints() const {
return {ListExistenceConstraints(shard_->constraints_),
shard_->constraints_.unique_constraints.ListConstraints()};
}
const SchemaValidator &GetSchemaValidator() const;
SchemasInfo ListAllSchemas() const { return {shard_->schemas_.ListSchemas()}; }
void AdvanceCommand();
void Commit(coordinator::Hlc commit_timestamp);
/// Commit returns `ConstraintViolation` if the changes made by this
/// transaction violate an existence or unique constraint. In that case the
/// transaction is automatically aborted. Otherwise, void is returned.
/// @throw std::bad_alloc
utils::BasicResult<ConstraintViolation, void> Commit(std::optional<uint64_t> desired_commit_timestamp = {});
/// @throw std::bad_alloc
void Abort();
void FinalizeTransaction();
private:
/// @throw std::bad_alloc
VertexAccessor CreateVertex(Gid gid, LabelId primary_label);
Shard *shard_;
Transaction *transaction_;
Transaction transaction_;
std::optional<uint64_t> commit_timestamp_;
bool is_transaction_active_;
Config::Items config_;
};
Accessor Access(coordinator::Hlc start_timestamp, std::optional<IsolationLevel> override_isolation_level = {}) {
return Accessor{*this, GetTransaction(start_timestamp, override_isolation_level.value_or(isolation_level_))};
Accessor Access(std::optional<IsolationLevel> override_isolation_level = {}) {
return Accessor{this, override_isolation_level.value_or(isolation_level_)};
}
LabelId NameToLabel(std::string_view name) const;
PropertyId NameToProperty(std::string_view name) const;
EdgeTypeId NameToEdgeType(std::string_view name) const;
const std::string &LabelToName(LabelId label) const;
const std::string &PropertyToName(PropertyId property) const;
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const;
/// @throw std::bad_alloc if unable to insert a new mapping
LabelId NameToLabel(std::string_view name);
/// @throw std::bad_alloc if unable to insert a new mapping
PropertyId NameToProperty(std::string_view name);
/// @throw std::bad_alloc if unable to insert a new mapping
EdgeTypeId NameToEdgeType(std::string_view name);
/// @throw std::bad_alloc
bool CreateIndex(LabelId label, std::optional<uint64_t> desired_commit_timestamp = {});
@@ -353,6 +392,45 @@ class Shard final {
IndicesInfo ListAllIndices() const;
/// Creates an existence constraint. Returns true if the constraint was
/// successfully added, false if it already exists and a `ConstraintViolation`
/// if there is an existing vertex violating the constraint.
///
/// @throw std::bad_alloc
/// @throw std::length_error
utils::BasicResult<ConstraintViolation, bool> CreateExistenceConstraint(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
/// Removes an existence constraint. Returns true if the constraint was
/// removed, and false if it doesn't exist.
bool DropExistenceConstraint(LabelId label, PropertyId property,
std::optional<uint64_t> desired_commit_timestamp = {});
/// Creates a unique constraint. In the case of two vertices violating the
/// constraint, it returns `ConstraintViolation`. Otherwise returns a
/// `UniqueConstraints::CreationStatus` enum with the following possibilities:
/// * `SUCCESS` if the constraint was successfully created,
/// * `ALREADY_EXISTS` if the constraint already existed,
/// * `EMPTY_PROPERTIES` if the property set is empty, or
// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the
// limit of maximum number of properties.
///
/// @throw std::bad_alloc
utils::BasicResult<ConstraintViolation, UniqueConstraints::CreationStatus> CreateUniqueConstraint(
LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp = {});
/// Removes a unique constraint. Returns `UniqueConstraints::DeletionStatus`
/// enum with the following possibilities:
/// * `SUCCESS` if constraint was successfully removed,
/// * `NOT_FOUND` if the specified constraint was not found,
/// * `EMPTY_PROPERTIES` if the property set is empty, or
/// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the
// limit of maximum number of properties.
UniqueConstraints::DeletionStatus DropUniqueConstraint(LabelId label, const std::set<PropertyId> &properties,
std::optional<uint64_t> desired_commit_timestamp = {});
ConstraintsInfo ListAllConstraints() const;
SchemasInfo ListAllSchemas() const;
const Schemas::Schema *GetSchema(LabelId primary_label) const;
@@ -363,15 +441,73 @@ class Shard final {
StorageInfo GetInfo() const;
bool LockPath();
bool UnlockPath();
bool SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config = {});
bool SetMainReplicationRole();
enum class RegisterReplicaError : uint8_t {
NAME_EXISTS,
END_POINT_EXISTS,
CONNECTION_FAILED,
COULD_NOT_BE_PERSISTED
};
/// @pre The instance should have a MAIN role
/// @pre Timeout can only be set for SYNC replication
utils::BasicResult<RegisterReplicaError, void> RegisterReplica(
std::string name, io::network::Endpoint endpoint, replication::ReplicationMode replication_mode,
const replication::ReplicationClientConfig &config = {});
/// @pre The instance should have a MAIN role
bool UnregisterReplica(std::string_view name);
std::optional<replication::ReplicaState> GetReplicaState(std::string_view name);
ReplicationRole GetReplicationRole() const;
struct ReplicaInfo {
std::string name;
replication::ReplicationMode mode;
std::optional<double> timeout;
io::network::Endpoint endpoint;
replication::ReplicaState state;
};
std::vector<ReplicaInfo> ReplicasInfo();
void FreeMemory();
void SetIsolationLevel(IsolationLevel isolation_level);
// Might invalidate accessors
void CollectGarbage(io::Time current_time);
enum class CreateSnapshotError : uint8_t { DisabledForReplica };
void StoreMapping(std::unordered_map<uint64_t, std::string> id_to_name);
utils::BasicResult<CreateSnapshotError> CreateSnapshot();
private:
Transaction &GetTransaction(coordinator::Hlc start_timestamp, IsolationLevel isolation_level);
Transaction CreateTransaction(IsolationLevel isolation_level);
/// The force parameter determines the behaviour of the garbage collector.
/// If it's set to true, it will behave as a global operation, i.e. it can't
/// be part of a transaction, and no other transaction can be active at the same time.
/// This allows it to delete immediately vertices without worrying that some other
/// transaction is possibly using it. If there are active transactions when this method
/// is called with force set to true, it will fallback to the same method with the force
/// set to false.
/// If it's set to false, it will execute in parallel with other transactions, ensuring
/// that no object in use can be deleted.
/// @throw std::system_error
/// @throw std::bad_alloc
template <bool force>
void CollectGarbage();
bool InitializeWalFile();
void FinalizeWalFile();
void AppendToWal(const Transaction &transaction, uint64_t final_commit_timestamp);
void AppendToWal(durability::StorageGlobalOperation operation, LabelId label, const std::set<PropertyId> &properties,
uint64_t final_commit_timestamp);
uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {});
@@ -392,22 +528,45 @@ class Shard final {
SchemaValidator schema_validator_;
VertexValidator vertex_validator_;
Constraints constraints_;
Indices indices_;
Schemas schemas_;
std::list<Transaction *> committed_transactions_;
// Transaction engine
uint64_t timestamp_{kTimestampInitialId};
uint64_t transaction_id_{kTransactionInitialId};
// TODO: This isn't really a commit log, it doesn't even care if a
// transaction commited or aborted. We could probably combine this with
// `timestamp_` in a sensible unit, something like TransactionClock or
// whatever.
std::optional<CommitLog> commit_log_;
std::list<Transaction> committed_transactions_;
IsolationLevel isolation_level_;
Config config_;
// Undo buffers that were unlinked and now are waiting to be freed.
std::list<std::pair<uint64_t, std::list<Delta>>> garbage_undo_buffers_;
// Vertices that are logically deleted but still have to be removed from
// indices before removing them from the main storage.
std::list<PrimaryKey> deleted_vertices_;
// Vertices that are logically deleted and removed from indices and now wait
// to be removed from the main storage.
std::list<std::pair<uint64_t, PrimaryKey>> garbage_vertices_;
// Edges that are logically deleted and wait to be removed from the main
// storage.
std::list<Gid> deleted_edges_;
// Durability
std::filesystem::path snapshot_directory_;
std::filesystem::path wal_directory_;
std::filesystem::path lock_file_path_;
utils::OutputFile lock_file_handle_;
// UUID used to distinguish snapshots and to link snapshots to WALs
std::string uuid_;
// Sequence number used to keep track of the chain of WALs.
@@ -433,6 +592,7 @@ class Shard final {
// epoch.
std::deque<std::pair<std::string, uint64_t>> epoch_history_;
std::optional<durability::WalFile> wal_file_;
uint64_t wal_unsynced_transactions_{0};
utils::FileRetainer file_retainer_;
@@ -440,10 +600,27 @@ class Shard final {
// Global locker that is used for clients file locking
utils::FileRetainer::FileLocker global_locker_;
// Holds all of the (in progress, committed and aborted) transactions that are read or write to this shard, but
// haven't been cleaned up yet
std::map<uint64_t, std::unique_ptr<Transaction>> start_logical_id_to_transaction_{};
bool has_any_transaction_aborted_since_last_gc{false};
// Last commited timestamp
uint64_t last_commit_timestamp_{kTimestampInitialId};
class ReplicationServer;
std::unique_ptr<ReplicationServer> replication_server_{nullptr};
class ReplicationClient;
// We create ReplicationClient using unique_ptr so we can move
// newly created client into the vector.
// We cannot move the client directly because it contains ThreadPool
// which cannot be moved. Also, the move is necessary because
// we don't want to create the client directly inside the vector
// because that would require the lock on the list putting all
// commits (they iterate list of clients) to halt.
// This way we can initialize client in main thread which means
// that we can immediately notify the user if the initialization
// failed.
using ReplicationClientList = utils::Synchronized<std::vector<std::unique_ptr<ReplicationClient>>, utils::SpinLock>;
ReplicationClientList replication_clients_;
ReplicationRole replication_role_{ReplicationRole::MAIN};
};
} // namespace memgraph::storage::v3

View File

@@ -1,226 +0,0 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <queue>
#include <set>
#include <boost/uuid/uuid.hpp>
#include <coordinator/coordinator.hpp>
#include <io/address.hpp>
#include <io/message_conversion.hpp>
#include <io/messages.hpp>
#include <io/rsm/raft.hpp>
#include <io/time.hpp>
#include <io/transport.hpp>
#include <query/v2/requests.hpp>
#include <storage/v3/shard.hpp>
#include <storage/v3/shard_rsm.hpp>
#include "storage/v3/config.hpp"
namespace memgraph::storage::v3 {
using boost::uuids::uuid;
using memgraph::coordinator::CoordinatorWriteRequests;
using memgraph::coordinator::CoordinatorWriteResponses;
using memgraph::coordinator::HeartbeatRequest;
using memgraph::coordinator::HeartbeatResponse;
using memgraph::io::Address;
using memgraph::io::Duration;
using memgraph::io::Message;
using memgraph::io::RequestId;
using memgraph::io::ResponseFuture;
using memgraph::io::Time;
using memgraph::io::messages::CoordinatorMessages;
using memgraph::io::messages::ShardManagerMessages;
using memgraph::io::messages::ShardMessages;
using memgraph::io::rsm::Raft;
using memgraph::io::rsm::WriteRequest;
using memgraph::io::rsm::WriteResponse;
using memgraph::msgs::ReadRequests;
using memgraph::msgs::ReadResponses;
using memgraph::msgs::WriteRequests;
using memgraph::msgs::WriteResponses;
using memgraph::storage::v3::ShardRsm;
using ShardManagerOrRsmMessage = std::variant<ShardMessages, ShardManagerMessages>;
using TimeUuidPair = std::pair<Time, uuid>;
template <typename IoImpl>
using ShardRaft = Raft<IoImpl, ShardRsm, WriteRequests, WriteResponses, ReadRequests, ReadResponses>;
using namespace std::chrono_literals;
static constexpr Duration kMinimumCronInterval = 1000ms;
static constexpr Duration kMaximumCronInterval = 2000ms;
static_assert(kMinimumCronInterval < kMaximumCronInterval,
"The minimum cron interval has to be smaller than the maximum cron interval!");
/// The ShardManager is responsible for:
/// * reconciling the storage engine's local configuration with the Coordinator's
/// intentions for how it should participate in multiple raft clusters
/// * replying to heartbeat requests to the Coordinator
/// * routing incoming messages to the appropriate sRSM
///
/// Every storage engine has exactly one RsmEngine.
template <typename IoImpl>
class ShardManager {
public:
ShardManager(io::Io<IoImpl> io, Address coordinator_leader) : io_(io), coordinator_leader_(coordinator_leader) {}
/// Periodic protocol maintenance. Returns the time that Cron should be called again
/// in the future.
Time Cron() {
spdlog::info("running ShardManager::Cron, address {}", io_.GetAddress().ToString());
Time now = io_.Now();
if (now >= next_cron_) {
Reconciliation();
std::uniform_int_distribution time_distrib(kMinimumCronInterval.count(), kMaximumCronInterval.count());
const auto rand = io_.Rand(time_distrib);
next_cron_ = now + Duration{rand};
}
if (!cron_schedule_.empty()) {
const auto &[time, uuid] = cron_schedule_.top();
if (time <= now) {
auto &rsm = rsm_map_.at(uuid);
Time next_for_uuid = rsm.Cron();
cron_schedule_.pop();
cron_schedule_.push(std::make_pair(next_for_uuid, uuid));
const auto &[next_time, _uuid] = cron_schedule_.top();
return std::min(next_cron_, next_time);
}
}
return next_cron_;
}
/// Returns the Address for our underlying Io implementation
Address GetAddress() { return io_.GetAddress(); }
void Receive(ShardManagerMessages &&smm, RequestId request_id, Address from) {}
void Route(ShardMessages &&sm, RequestId request_id, Address to, Address from) {
Address address = io_.GetAddress();
MG_ASSERT(address.last_known_port == to.last_known_port);
MG_ASSERT(address.last_known_ip == to.last_known_ip);
auto &rsm = rsm_map_.at(to.unique_id);
rsm.Handle(std::forward<ShardMessages>(sm), request_id, from);
}
private:
io::Io<IoImpl> io_;
std::map<uuid, ShardRaft<IoImpl>> rsm_map_;
std::priority_queue<std::pair<Time, uuid>, std::vector<std::pair<Time, uuid>>, std::greater<>> cron_schedule_;
Time next_cron_;
Address coordinator_leader_;
std::optional<ResponseFuture<WriteResponse<CoordinatorWriteResponses>>> heartbeat_res_;
// TODO(tyler) over time remove items from initialized_but_not_confirmed_rsm_
// after the Coordinator is clearly aware of them
std::set<boost::uuids::uuid> initialized_but_not_confirmed_rsm_;
void Reconciliation() {
if (heartbeat_res_.has_value()) {
if (heartbeat_res_->IsReady()) {
io::ResponseResult<WriteResponse<CoordinatorWriteResponses>> response_result =
std::move(heartbeat_res_).value().Wait();
heartbeat_res_.reset();
if (response_result.HasError()) {
spdlog::error("SM timed out while trying to reach C");
} else {
auto response_envelope = response_result.GetValue();
WriteResponse<CoordinatorWriteResponses> wr = response_envelope.message;
if (wr.retry_leader.has_value()) {
spdlog::info("SM redirected to new C leader");
coordinator_leader_ = wr.retry_leader.value();
} else if (wr.success) {
CoordinatorWriteResponses cwr = wr.write_return;
HeartbeatResponse hr = std::get<HeartbeatResponse>(cwr);
spdlog::info("SM received heartbeat response from C");
EnsureShardsInitialized(hr);
}
}
} else {
return;
}
}
HeartbeatRequest req{
.from_storage_manager = GetAddress(),
.initialized_rsms = initialized_but_not_confirmed_rsm_,
};
CoordinatorWriteRequests cwr = req;
WriteRequest<CoordinatorWriteRequests> ww;
ww.operation = cwr;
spdlog::info("SM sending heartbeat to coordinator {}", coordinator_leader_.ToString());
heartbeat_res_.emplace(std::move(
io_.template Request<WriteRequest<CoordinatorWriteRequests>, WriteResponse<CoordinatorWriteResponses>>(
coordinator_leader_, ww)));
spdlog::info("SM sent heartbeat");
}
void EnsureShardsInitialized(HeartbeatResponse hr) {
for (const auto &shard_to_initialize : hr.shards_to_initialize) {
InitializeRsm(shard_to_initialize);
initialized_but_not_confirmed_rsm_.emplace(shard_to_initialize.uuid);
}
}
/// Returns true if the RSM was able to be initialized, and false if it was already initialized
void InitializeRsm(coordinator::ShardToInitialize to_init) {
if (rsm_map_.contains(to_init.uuid)) {
// it's not a bug for the coordinator to send us UUIDs that we have
// already created, because there may have been lag that caused
// the coordinator not to hear back from us.
return;
}
auto rsm_io = io_.ForkLocal();
auto io_addr = rsm_io.GetAddress();
io_addr.unique_id = to_init.uuid;
rsm_io.SetAddress(io_addr);
// TODO(tyler) get geers from Coordinator in HeartbeatResponse
std::vector<Address> rsm_peers = {};
std::unique_ptr<Shard> shard =
std::make_unique<Shard>(to_init.label_id, to_init.min_key, to_init.max_key, to_init.config);
ShardRsm rsm_state{std::move(shard)};
ShardRaft<IoImpl> rsm{std::move(rsm_io), rsm_peers, std::move(rsm_state)};
spdlog::info("SM created a new shard with UUID {}", to_init.uuid);
rsm_map_.emplace(to_init.uuid, std::move(rsm));
}
};
} // namespace memgraph::storage::v3

View File

@@ -9,12 +9,12 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include <functional>
#include <iterator>
#include <utility>
#include "query/v2/requests.hpp"
#include "storage/v3/shard_rsm.hpp"
#include "storage/v3/value_conversions.hpp"
#include "storage/v3/vertex_accessor.hpp"
using memgraph::msgs::Label;
@@ -22,12 +22,84 @@ using memgraph::msgs::PropertyId;
using memgraph::msgs::Value;
using memgraph::msgs::VertexId;
using memgraph::storage::conversions::ConvertPropertyVector;
using memgraph::storage::conversions::ConvertValueVector;
using memgraph::storage::conversions::ToPropertyValue;
using memgraph::storage::conversions::ToValue;
namespace {
// TODO(gvolfing) use come algorithm instead of explicit for loops
// TODO(gvolfing) make this use rvalue& again instead of copy!!!!
memgraph::storage::v3::PropertyValue ToPropertyValue(Value &&value) {
using PV = memgraph::storage::v3::PropertyValue;
PV ret;
switch (value.type) {
case Value::Type::Null:
return PV{};
case Value::Type::Bool:
return PV(value.bool_v);
case Value::Type::Int64:
return PV(static_cast<int64_t>(value.int_v));
case Value::Type::Double:
return PV(value.double_v);
case Value::Type::String:
return PV(value.string_v);
case Value::Type::List: {
std::vector<PV> list;
for (auto &elem : value.list_v) {
list.emplace_back(ToPropertyValue(std::move(elem)));
}
return PV(list);
}
case Value::Type::Map: {
std::map<std::string, PV> map;
for (auto &[key, value] : value.map_v) {
map.emplace(std::make_pair(key, ToPropertyValue(std::move(value))));
}
return PV(map);
}
// These are not PropertyValues
case Value::Type::Vertex:
case Value::Type::Edge:
case Value::Type::Path:
MG_ASSERT(false, "Not PropertyValue");
}
return ret;
}
Value ToValue(const memgraph::storage::v3::PropertyValue &pv) {
using memgraph::storage::v3::PropertyValue;
switch (pv.type()) {
case PropertyValue::Type::Bool:
return Value(pv.ValueBool());
case PropertyValue::Type::Double:
return Value(pv.ValueDouble());
case PropertyValue::Type::Int:
return Value(pv.ValueInt());
case PropertyValue::Type::List: {
std::vector<Value> list(pv.ValueList().size());
for (const auto &elem : pv.ValueList()) {
list.emplace_back(ToValue(elem));
}
return Value(list);
}
case PropertyValue::Type::Map: {
std::map<std::string, Value> map;
for (const auto &[key, val] : pv.ValueMap()) {
// maybe use std::make_pair once the && issue is resolved.
map.emplace(std::make_pair(key, ToValue(val)));
}
return Value(map);
}
case PropertyValue::Type::Null:
return Value{};
case PropertyValue::Type::String:
return Value(pv.ValueString());
case PropertyValue::Type::TemporalData: {
// TBD -> we need to specify this in the messages, not a priority.
MG_ASSERT(false, "Temporal datatypes are not yet implemented on Value!");
return Value{};
}
}
}
std::vector<std::pair<memgraph::storage::v3::PropertyId, memgraph::storage::v3::PropertyValue>> ConvertPropertyMap(
std::vector<std::pair<PropertyId, Value>> &&properties) {
@@ -41,13 +113,23 @@ std::vector<std::pair<memgraph::storage::v3::PropertyId, memgraph::storage::v3::
return ret;
}
std::vector<std::pair<memgraph::storage::v3::PropertyId, Value>> FromMap(
const std::map<PropertyId, Value> &properties) {
std::vector<std::pair<memgraph::storage::v3::PropertyId, Value>> ret;
ret.reserve(properties.size());
std::vector<memgraph::storage::v3::PropertyValue> ConvertPropertyVector(std::vector<Value> &&vec) {
std::vector<memgraph::storage::v3::PropertyValue> ret;
ret.reserve(vec.size());
for (const auto &[key, value] : properties) {
ret.emplace_back(std::make_pair(key, value));
for (auto &elem : vec) {
ret.push_back(ToPropertyValue(std::move(elem)));
}
return ret;
}
std::vector<Value> ConvertValueVector(const std::vector<memgraph::storage::v3::PropertyValue> &vec) {
std::vector<Value> ret;
ret.reserve(vec.size());
for (const auto &elem : vec) {
ret.push_back(ToValue(elem));
}
return ret;
@@ -69,7 +151,7 @@ std::optional<std::map<PropertyId, Value>> CollectPropertiesFromAccessor(
spdlog::debug("The specified property does not exist but it should");
continue;
}
ret.emplace(prop, ToValue(value));
ret.emplace(std::make_pair(prop, ToValue(value)));
}
return ret;
@@ -109,12 +191,21 @@ Value ConstructValueVertex(const memgraph::storage::v3::VertexAccessor &acc, mem
return Value({.id = vertex_id, .labels = value_labels});
}
bool DoesEdgeTypeMatch(const memgraph::msgs::ExpandOneRequest &req, const memgraph::storage::v3::EdgeAccessor &edge) {
for (const auto &edge_type : req.edge_types) {
if (memgraph::storage::v3::EdgeTypeId::FromUint(edge_type.id) == edge.EdgeType()) {
return true;
}
}
return false;
}
} // namespace
namespace memgraph::storage::v3 {
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CreateVerticesRequest &&req) {
auto acc = shard_->Access(req.transaction_id);
auto acc = shard_->Access();
// Workaround untill we have access to CreateVertexAndValidate()
// with the new signature that does not require the primary label.
@@ -164,12 +255,86 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CreateVerticesRequest &&req) {
}
}
return msgs::CreateVerticesResponse{action_successful};
msgs::CreateVerticesResponse resp{};
resp.success = action_successful;
if (action_successful) {
auto result = acc.Commit(req.transaction_id.logical_id);
if (result.HasError()) {
resp.success = false;
spdlog::debug(&"ConstraintViolation, commiting vertices was unsuccesfull with transaction id: "[req.transaction_id
.logical_id]);
}
}
return resp;
}
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::UpdateVerticesRequest &&req) {
auto acc = shard_->Access();
bool action_successful = true;
for (auto &vertex : req.new_properties) {
if (!action_successful) {
break;
}
auto vertex_to_update = acc.FindVertex(ConvertPropertyVector(std::move(vertex.primary_key)), View::OLD);
if (!vertex_to_update) {
action_successful = false;
spdlog::debug(
&"Vertex could not be found while trying to update its properties. Transaction id: "[req.transaction_id
.logical_id]);
continue;
}
for (auto &update_prop : vertex.property_updates) {
// TODO(gvolfing) Maybe check if the setting is valid if SetPropertyAndValidate()
// does not do that alreaedy.
auto result_schema =
vertex_to_update->SetPropertyAndValidate(update_prop.first, ToPropertyValue(std::move(update_prop.second)));
if (result_schema.HasError()) {
auto &error = result_schema.GetError();
std::visit(
[&action_successful]<typename T>(T &&) {
using ErrorType = std::remove_cvref_t<T>;
if constexpr (std::is_same_v<ErrorType, SchemaViolation>) {
action_successful = false;
spdlog::debug("Updating vertex failed with error: SchemaViolation");
} else if constexpr (std::is_same_v<ErrorType, Error>) {
action_successful = false;
spdlog::debug("Updating vertex failed with error: Error");
} else {
static_assert(kAlwaysFalse<T>, "Missing type from variant visitor");
}
},
error);
break;
}
}
}
msgs::UpdateVerticesResponse resp{};
resp.success = action_successful;
if (action_successful) {
auto result = acc.Commit(req.transaction_id.logical_id);
if (result.HasError()) {
resp.success = false;
spdlog::debug(&"ConstraintViolation, commiting vertices was unsuccesfull with transaction id:"[req.transaction_id
.logical_id]);
}
}
return resp;
}
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteVerticesRequest &&req) {
bool action_successful = true;
auto acc = shard_->Access(req.transaction_id);
auto acc = shard_->Access();
for (auto &propval : req.primary_keys) {
if (!action_successful) {
@@ -179,8 +344,9 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteVerticesRequest &&req) {
auto vertex_acc = acc.FindVertex(ConvertPropertyVector(std::move(propval)), View::OLD);
if (!vertex_acc) {
spdlog::debug("Error while trying to delete vertex. Vertex to delete does not exist. Transaction id: {}",
req.transaction_id.logical_id);
spdlog::debug(
&"Error while trying to delete vertex. Vertex to delete does not exist. Transaction id: "[req.transaction_id
.logical_id]);
action_successful = false;
} else {
// TODO(gvolfing)
@@ -191,7 +357,7 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteVerticesRequest &&req) {
auto result = acc.DeleteVertex(&vertex_acc.value());
if (result.HasError() || !(result.GetValue().has_value())) {
action_successful = false;
spdlog::debug("Error while trying to delete vertex. Transaction id: {}", req.transaction_id.logical_id);
spdlog::debug(&"Error while trying to delete vertex. Transaction id: "[req.transaction_id.logical_id]);
}
break;
@@ -200,8 +366,8 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteVerticesRequest &&req) {
auto result = acc.DetachDeleteVertex(&vertex_acc.value());
if (result.HasError() || !(result.GetValue().has_value())) {
action_successful = false;
spdlog::debug("Error while trying to detach and delete vertex. Transaction id: {}",
req.transaction_id.logical_id);
spdlog::debug(
&"Error while trying to detach and delete vertex. Transaction id: "[req.transaction_id.logical_id]);
}
break;
@@ -210,11 +376,23 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteVerticesRequest &&req) {
}
}
return msgs::DeleteVerticesResponse{action_successful};
msgs::DeleteVerticesResponse resp{};
resp.success = action_successful;
if (action_successful) {
auto result = acc.Commit(req.transaction_id.logical_id);
if (result.HasError()) {
resp.success = false;
spdlog::debug(&"ConstraintViolation, commiting vertices was unsuccesfull with transaction id: "[req.transaction_id
.logical_id]);
}
}
return resp;
}
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CreateEdgesRequest &&req) {
auto acc = shard_->Access(req.transaction_id);
auto acc = shard_->Access();
bool action_successful = true;
for (auto &edge : req.edges) {
@@ -226,8 +404,8 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CreateEdgesRequest &&req) {
if (!vertex_from_acc || !vertex_to_acc) {
action_successful = false;
spdlog::debug("Error while trying to insert edge, vertex does not exist. Transaction id: {}",
req.transaction_id.logical_id);
spdlog::debug(
&"Error while trying to insert edge, vertex does not exist. Transaction id: "[req.transaction_id.logical_id]);
break;
}
@@ -238,16 +416,151 @@ msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CreateEdgesRequest &&req) {
if (edge_acc.HasError()) {
action_successful = false;
spdlog::debug("Creating edge was not successful. Transaction id: {}", req.transaction_id.logical_id);
spdlog::debug(&"Creating edge was not successful. Transaction id: "[req.transaction_id.logical_id]);
break;
}
// Add properties to the edge if there is any
if (edge.properties) {
for (auto &[edge_prop_key, edge_prop_val] : edge.properties.value()) {
auto set_result = edge_acc->SetProperty(edge_prop_key, ToPropertyValue(std::move(edge_prop_val)));
if (set_result.HasError()) {
action_successful = false;
spdlog::debug(&"Adding property to edge was not successful. Transaction id: "[req.transaction_id.logical_id]);
break;
}
}
}
}
return msgs::CreateEdgesResponse{action_successful};
msgs::CreateEdgesResponse resp{};
resp.success = action_successful;
if (action_successful) {
auto result = acc.Commit(req.transaction_id.logical_id);
if (result.HasError()) {
resp.success = false;
spdlog::debug(
&"ConstraintViolation, commiting edge creation was unsuccesfull with transaction id: "[req.transaction_id
.logical_id]);
}
}
return resp;
}
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteEdgesRequest &&req) {
bool action_successful = true;
auto acc = shard_->Access();
for (auto &edge : req.edges) {
if (!action_successful) {
break;
}
auto edge_acc = acc.DeleteEdge(VertexId(edge.src.first.id, ConvertPropertyVector(std::move(edge.src.second))),
VertexId(edge.dst.first.id, ConvertPropertyVector(std::move(edge.dst.second))),
Gid::FromUint(edge.id.gid));
if (edge_acc.HasError() || !edge_acc.HasValue()) {
spdlog::debug(&"Error while trying to delete edge. Transaction id: "[req.transaction_id.logical_id]);
action_successful = false;
continue;
}
}
msgs::DeleteEdgesResponse resp{};
resp.success = action_successful;
if (action_successful) {
auto result = acc.Commit(req.transaction_id.logical_id);
if (result.HasError()) {
resp.success = false;
spdlog::debug(
&"ConstraintViolation, commiting edge creation was unsuccesfull with transaction id: "[req.transaction_id
.logical_id]);
}
}
return resp;
}
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::UpdateEdgesRequest &&req) {
auto acc = shard_->Access();
bool action_successful = true;
for (auto &edge : req.new_properties) {
if (!action_successful) {
break;
}
auto vertex_acc = acc.FindVertex(ConvertPropertyVector(std::move(edge.src.second)), View::OLD);
if (!vertex_acc) {
action_successful = false;
spdlog::debug(
&"Encountered an error while trying to acquire VertexAccessor with transaction id: "[req.transaction_id
.logical_id]);
continue;
}
// Since we are using the source vertex of the edge we are only intrested
// in the vertex's out-going edges
auto edges_res = vertex_acc->OutEdges(View::OLD);
if (edges_res.HasError()) {
action_successful = false;
spdlog::debug(
&"Encountered an error while trying to acquire EdgeAccessor with transaction id: "[req.transaction_id
.logical_id]);
continue;
}
auto &edge_accessors = edges_res.GetValue();
// Look for the appropriate edge accessor
bool edge_accessor_did_match = false;
for (auto &edge_accessor : edge_accessors) {
if (edge_accessor.Gid().AsUint() == edge.edge_id.gid) { // Found the appropriate accessor
edge_accessor_did_match = true;
for (auto &[key, value] : edge.property_updates) {
// TODO(gvolfing)
// Check if the property was set if SetProperty does not do that itself.
auto res = edge_accessor.SetProperty(key, ToPropertyValue(std::move(value)));
if (res.HasError()) {
spdlog::debug(&"Encountered an error while trying to set the property of an Edge with transaction id: "
[req.transaction_id.logical_id]);
}
}
}
}
if (!edge_accessor_did_match) {
action_successful = false;
spdlog::debug(&"Could not find the Edge with the specified Gid. Transaction id: "[req.transaction_id.logical_id]);
continue;
}
}
msgs::UpdateEdgesResponse resp{};
resp.success = action_successful;
if (action_successful) {
auto result = acc.Commit(req.transaction_id.logical_id);
if (result.HasError()) {
resp.success = false;
spdlog::debug(
&"ConstraintViolation, commiting edge update was unsuccesfull with transaction id: "[req.transaction_id
.logical_id]);
}
}
return resp;
}
msgs::ReadResponses ShardRsm::HandleRead(msgs::ScanVerticesRequest &&req) {
auto acc = shard_->Access(req.transaction_id);
auto acc = shard_->Access();
bool action_successful = true;
std::vector<msgs::ScanResultRow> results;
@@ -258,12 +571,10 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::ScanVerticesRequest &&req) {
bool did_reach_starting_point = false;
uint64_t sample_counter = 0;
const auto start_ids = ConvertPropertyVector(std::move(req.start_id.second));
for (auto it = vertex_iterable.begin(); it != vertex_iterable.end(); ++it) {
const auto &vertex = *it;
if (start_ids == vertex.PrimaryKey(View(req.storage_view)).GetValue()) {
if (ConvertPropertyVector(std::move(req.start_id.second)) == vertex.PrimaryKey(View(req.storage_view)).GetValue()) {
did_reach_starting_point = true;
}
@@ -280,8 +591,8 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::ScanVerticesRequest &&req) {
continue;
}
results.emplace_back(msgs::ScanResultRow{.vertex = ConstructValueVertex(vertex, view).vertex_v,
.props = FromMap(found_props.value())});
results.emplace_back(
msgs::ScanResultRow{.vertex = ConstructValueVertex(vertex, view), .props = found_props.value()});
++sample_counter;
if (sample_counter == req.batch_limit) {
@@ -306,22 +617,316 @@ msgs::ReadResponses ShardRsm::HandleRead(msgs::ScanVerticesRequest &&req) {
return resp;
}
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::CommitRequest &&req) {
shard_->Access(req.transaction_id).Commit(req.commit_timestamp);
return msgs::CommitResponse{true};
};
msgs::ReadResponses ShardRsm::HandleRead(msgs::ExpandOneRequest &&req) {
auto acc = shard_->Access();
bool action_successful = true;
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::UpdateVerticesRequest && /*req*/) {
return msgs::UpdateVerticesResponse{};
using EdgeProperties = std::variant<std::map<PropertyId, msgs::Value>, std::vector<msgs::Value>>;
std::function<EdgeProperties(const EdgeAccessor &)> get_edge_properties;
if (!req.edge_properties) {
get_edge_properties = [&req, &action_successful](const EdgeAccessor &edge) {
std::map<PropertyId, msgs::Value> ret;
auto property_results = edge.Properties(View::OLD);
if (property_results.HasError()) {
spdlog::debug(
&"Encountered an error while trying to get out-going EdgeAccessors. Transaction id: "[req.transaction_id
.logical_id]);
action_successful = false;
return ret;
}
for (const auto &[prop_key, prop_val] : property_results.GetValue()) {
ret.insert(std::make_pair(prop_key, ToValue(prop_val)));
}
return ret;
};
} else {
// TODO(gvolfing) - do we want to set the action_successful here?
get_edge_properties = [&req, &action_successful](const EdgeAccessor &edge) {
std::vector<msgs::Value> ret;
ret.reserve(req.edge_properties.value().size());
for (const auto &edge_prop : req.edge_properties.value()) {
// TODO(gvolfing) maybe check for the absence of certain properties
ret.push_back(ToValue(edge.GetProperty(edge_prop, View::OLD).GetValue()));
}
return ret;
};
}
std::vector<msgs::ExpandOneResultRow> results;
for (auto &src_vertex : req.src_vertices) {
msgs::ExpandOneResultRow current_row;
msgs::Vertex source_vertex;
/// The empty optional means return all of the properties, while an empty
/// list means do not return any properties.
std::optional<std::map<PropertyId, Value>> src_vertex_properties_opt;
std::map<PropertyId, Value> src_vertex_properties;
auto v_acc = acc.FindVertex(ConvertPropertyVector(std::move(src_vertex.second)), View::OLD);
/// Fill up source vertex
auto secondary_labels = v_acc->Labels(View::OLD);
if (secondary_labels.HasError()) {
spdlog::debug(&"Encountered an error while trying to get the secondary labels of a vertex. Transaction id: "
[req.transaction_id.logical_id]);
action_successful = false;
break;
}
source_vertex.id = src_vertex;
source_vertex.labels.reserve(secondary_labels.GetValue().size());
for (auto label_id : secondary_labels.GetValue()) {
source_vertex.labels.push_back({.id = label_id});
}
/// Fill up source vertex properties
if (!req.src_vertex_properties) {
auto props = v_acc->Properties(View::OLD);
if (props.HasError()) {
spdlog::debug(
&"Encountered an error while trying to access vertex properties. Transaction id: "[req.transaction_id
.logical_id]);
action_successful = false;
break;
}
for (auto &[key, val] : props.GetValue()) {
src_vertex_properties.insert(std::make_pair(key, ToValue(val)));
}
src_vertex_properties_opt = src_vertex_properties;
} else if (req.src_vertex_properties.value().empty()) {
src_vertex_properties_opt = {};
} else {
for (const auto &prop : req.src_vertex_properties.value()) {
const auto &prop_val = v_acc->GetProperty(prop, View::OLD);
src_vertex_properties.insert(std::make_pair(prop, ToValue(prop_val.GetValue())));
}
src_vertex_properties_opt = src_vertex_properties;
}
/// Fill up connecting edges
std::vector<EdgeAccessor> in_edges;
std::vector<EdgeAccessor> out_edges;
switch (req.direction) {
case msgs::EdgeDirection::OUT: {
auto out_edges_result = v_acc->OutEdges(View::OLD);
if (out_edges_result.HasError()) {
spdlog::debug(
&"Encountered an error while trying to get out-going EdgeAccessors. Transaction id: "[req.transaction_id
.logical_id]);
action_successful = false;
break;
}
out_edges = out_edges_result.GetValue();
break;
}
case msgs::EdgeDirection::IN: {
auto in_edges_result = v_acc->InEdges(View::OLD);
if (in_edges_result.HasError()) {
spdlog::debug(
&"Encountered an error while trying to get in-going EdgeAccessors. Transaction id: "[req.transaction_id
.logical_id]);
action_successful = false;
break;
}
in_edges = in_edges_result.GetValue();
break;
}
case msgs::EdgeDirection::BOTH: {
auto in_edges_result = v_acc->InEdges(View::OLD);
if (in_edges_result.HasError()) {
spdlog::debug(
&"Encountered an error while trying to get in-going EdgeAccessors. Transaction id: "[req.transaction_id
.logical_id]);
action_successful = false;
break;
}
in_edges = in_edges_result.GetValue();
auto out_edges_result = v_acc->InEdges(View::OLD);
if (out_edges_result.HasError()) {
spdlog::debug(
&"Encountered an error while trying to get out-going EdgeAccessors. Transaction id: "[req.transaction_id
.logical_id]);
action_successful = false;
break;
}
out_edges = out_edges_result.GetValue();
break;
}
}
// Check for stoppage here because of the switch case
if (!action_successful) {
break;
}
/// Assemble the edge properties
std::optional<std::vector<std::tuple<msgs::VertexId, msgs::Gid, std::map<PropertyId, msgs::Value>>>>
edges_with_all_properties;
std::optional<std::vector<std::tuple<msgs::VertexId, msgs::Gid, std::vector<msgs::Value>>>>
edges_with_specific_properties;
if (!req.edge_properties) {
std::vector<std::tuple<msgs::VertexId, msgs::Gid, std::map<PropertyId, msgs::Value>>> ret_in;
// ret_in.reserve(in_edges.size());
std::vector<std::tuple<msgs::VertexId, msgs::Gid, std::map<PropertyId, msgs::Value>>> ret_out;
// ret_out.reserve(out_edges.size());
for (const auto &edge : in_edges) {
if (!DoesEdgeTypeMatch(req, edge)) {
continue;
}
std::tuple<msgs::VertexId, msgs::Gid, std::map<PropertyId, msgs::Value>> ret_tuple;
msgs::Label label;
label.id = edge.FromVertex().primary_label;
msgs::VertexId other_vertex = std::make_pair(label, ConvertValueVector(edge.FromVertex().primary_key));
const auto &edge_props_var = get_edge_properties(edge);
auto edge_props = std::get<std::map<PropertyId, msgs::Value>>(edge_props_var);
msgs::Gid gid = edge.Gid().AsUint();
ret_tuple = {other_vertex, gid, edge_props};
ret_in.push_back(ret_tuple);
}
for (const auto &edge : out_edges) {
if (!DoesEdgeTypeMatch(req, edge)) {
continue;
}
std::tuple<msgs::VertexId, msgs::Gid, std::map<PropertyId, msgs::Value>> ret_tuple;
msgs::Label label;
label.id = edge.FromVertex().primary_label;
msgs::VertexId other_vertex = std::make_pair(label, ConvertValueVector(edge.ToVertex().primary_key));
const auto &edge_props_var = get_edge_properties(edge);
auto edge_props = std::get<std::map<PropertyId, msgs::Value>>(edge_props_var);
msgs::Gid gid = edge.Gid().AsUint();
ret_tuple = {other_vertex, gid, edge_props};
ret_out.push_back(ret_tuple);
}
// Set one of the options to the actual datastructure and the otherone to nullopt
switch (req.direction) {
case msgs::EdgeDirection::OUT: {
edges_with_all_properties = ret_out;
break;
}
case msgs::EdgeDirection::IN: {
edges_with_all_properties = ret_in;
break;
}
case msgs::EdgeDirection::BOTH: {
std::vector<std::tuple<msgs::VertexId, msgs::Gid, std::map<PropertyId, msgs::Value>>> ret;
ret.resize(ret_out.size() + ret_in.size());
ret.insert(ret.end(), ret_in.begin(), ret_in.end());
ret.insert(ret.end(), ret_out.begin(), ret_out.end());
edges_with_all_properties = ret;
break;
}
}
edges_with_specific_properties = {};
} else {
// when user specifies specific properties, its enough to return just a vector
std::vector<std::tuple<msgs::VertexId, msgs::Gid, std::vector<msgs::Value>>> ret_in;
// ret_in.reserve(in_edges.size());
std::vector<std::tuple<msgs::VertexId, msgs::Gid, std::vector<msgs::Value>>> ret_out;
// ret_out.reserve(out_edges.size());
for (const auto &edge : in_edges) {
if (!DoesEdgeTypeMatch(req, edge)) {
continue;
}
std::tuple<msgs::VertexId, msgs::Gid, std::vector<msgs::Value>> ret_tuple;
msgs::Label label;
label.id = edge.FromVertex().primary_label;
msgs::VertexId other_vertex = std::make_pair(label, ConvertValueVector(edge.FromVertex().primary_key));
const auto &edge_props_var = get_edge_properties(edge);
auto edge_props = std::get<std::vector<msgs::Value>>(edge_props_var);
msgs::Gid gid = edge.Gid().AsUint();
ret_tuple = {other_vertex, gid, edge_props};
ret_in.push_back(ret_tuple);
}
for (const auto &edge : out_edges) {
if (!DoesEdgeTypeMatch(req, edge)) {
continue;
}
std::tuple<msgs::VertexId, msgs::Gid, std::vector<msgs::Value>> ret_tuple;
msgs::Label label;
label.id = edge.FromVertex().primary_label;
msgs::VertexId other_vertex = std::make_pair(label, ConvertValueVector(edge.ToVertex().primary_key));
const auto &edge_props_var = get_edge_properties(edge);
auto edge_props = std::get<std::vector<msgs::Value>>(edge_props_var);
msgs::Gid gid = edge.Gid().AsUint();
ret_tuple = {other_vertex, gid, edge_props};
ret_out.push_back(ret_tuple);
}
// Set one of the options to the actual datastructure and the otherone to nullopt
switch (req.direction) {
case msgs::EdgeDirection::OUT: {
edges_with_specific_properties = ret_out;
break;
}
case msgs::EdgeDirection::IN: {
edges_with_specific_properties = ret_in;
break;
}
case msgs::EdgeDirection::BOTH: {
std::vector<std::tuple<msgs::VertexId, msgs::Gid, std::vector<msgs::Value>>> ret;
ret.resize(ret_out.size() + ret_in.size());
ret.insert(ret.end(), ret_in.begin(), ret_in.end());
ret.insert(ret.end(), ret_out.begin(), ret_out.end());
edges_with_specific_properties = ret;
break;
}
}
edges_with_all_properties = {};
}
results.emplace_back(msgs::ExpandOneResultRow{.src_vertex = src_vertex,
.src_vertex_properties = src_vertex_properties,
.edges_with_all_properties = edges_with_all_properties,
.edges_with_specific_properties = edges_with_specific_properties});
}
msgs::ExpandOneResponse resp{};
if (action_successful) {
resp.result = std::move(results);
}
return resp;
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteEdgesRequest && /*req*/) { return msgs::DeleteEdgesResponse{}; }
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
msgs::WriteResponses ShardRsm::ApplyWrite(msgs::UpdateEdgesRequest && /*req*/) { return msgs::UpdateEdgesResponse{}; }
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
msgs::ReadResponses ShardRsm::HandleRead(msgs::ExpandOneRequest && /*req*/) { return msgs::ExpandOneResponse{}; }
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
// msgs::WriteResponses ShardRsm::ApplyWrite(msgs::UpdateVerticesRequest && /*req*/) {
// return msgs::UpdateVerticesResponse{};
// }
// msgs::WriteResponses ShardRsm::ApplyWrite(msgs::DeleteEdgesRequest && /*req*/) { return msgs::DeleteEdgesResponse{};
// } msgs::WriteResponses ShardRsm::ApplyWrite(msgs::UpdateEdgesRequest && /*req*/) { return
// msgs::UpdateEdgesResponse{}; } msgs::ReadResponses ShardRsm::HandleRead(msgs::ExpandOneRequest && /*req*/) { return
// msgs::ExpandOneResponse{}; } NOLINTNEXTLINE(readability-convert-member-functions-to-static)
msgs::ReadResponses ShardRsm::HandleRead(msgs::GetPropertiesRequest && /*req*/) {
return msgs::GetPropertiesResponse{};
}

Some files were not shown because too many files have changed in this diff Show More