diff --git a/src/storage/v2/CMakeLists.txt b/src/storage/v2/CMakeLists.txt index ba1e7d3b2..6264db977 100644 --- a/src/storage/v2/CMakeLists.txt +++ b/src/storage/v2/CMakeLists.txt @@ -1,6 +1,9 @@ set(storage_v2_src_files constraints.cpp - durability.cpp + durability/durability.cpp + durability/serialization.cpp + durability/snapshot.cpp + durability/wal.cpp edge_accessor.cpp indices.cpp property_store.cpp diff --git a/src/storage/v2/durability.hpp b/src/storage/v2/durability.hpp deleted file mode 100644 index 4fc1a06e7..000000000 --- a/src/storage/v2/durability.hpp +++ /dev/null @@ -1,446 +0,0 @@ -#pragma once - -#include <cstdint> -#include <filesystem> -#include <functional> -#include <list> -#include <optional> -#include <string> -#include <string_view> -#include <type_traits> -#include <utility> - -#include "storage/v2/config.hpp" -#include "storage/v2/constraints.hpp" -#include "storage/v2/delta.hpp" -#include "storage/v2/edge.hpp" -#include "storage/v2/indices.hpp" -#include "storage/v2/name_id_mapper.hpp" -#include "storage/v2/property_value.hpp" -#include "storage/v2/transaction.hpp" -#include "storage/v2/vertex.hpp" -#include "utils/exceptions.hpp" -#include "utils/file.hpp" -#include "utils/scheduler.hpp" -#include "utils/skip_list.hpp" - -namespace storage { - -static const std::string kSnapshotDirectory{"snapshots"}; -static const std::string kWalDirectory{"wal"}; -static const std::string kBackupDirectory{".backup"}; - -// 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>); - -/// 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, - - SECTION_VERTEX = 0x20, - SECTION_EDGE = 0x21, - SECTION_MAPPER = 0x22, - SECTION_METADATA = 0x23, - SECTION_INDICES = 0x24, - SECTION_CONSTRAINTS = 0x25, - SECTION_DELTA = 0x26, - 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_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_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, -}; - -/// Encoder that is used to generate a snapshot/WAL. -class Encoder final { - public: - void Initialize(const std::filesystem::path &path, - const std::string_view &magic, uint64_t version); - - // 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); - void WriteBool(bool value); - void WriteUint(uint64_t value); - void WriteDouble(double value); - void WriteString(const std::string_view &value); - void WritePropertyValue(const PropertyValue &value); - - uint64_t GetPosition(); - void SetPosition(uint64_t position); - - void Sync(); - - void Finalize(); - - private: - utils::OutputFile file_; -}; - -/// Decoder that is used to read a generated snapshot/WAL. -class Decoder final { - 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(); - std::optional<bool> ReadBool(); - std::optional<uint64_t> ReadUint(); - std::optional<double> ReadDouble(); - std::optional<std::string> ReadString(); - std::optional<PropertyValue> ReadPropertyValue(); - - bool SkipString(); - bool SkipPropertyValue(); - - std::optional<uint64_t> GetSize(); - std::optional<uint64_t> GetPosition(); - bool SetPosition(uint64_t position); - - private: - utils::InputFile file_; -}; - -/// Exception used to handle errors during recovery. -class RecoveryFailure : public utils::BasicException { - using utils::BasicException::BasicException; -}; - -/// 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_metadata; - - std::string uuid; - uint64_t start_timestamp; - uint64_t edges_count; - uint64_t vertices_count; -}; - -/// Function used to read information about the snapshot file. -/// @throw RecoveryFailure -SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path); - -/// Structure used to hold information about a WAL. -struct WalInfo { - uint64_t offset_metadata; - uint64_t offset_deltas; - - std::string uuid; - uint64_t seq_num; - uint64_t from_timestamp; - uint64_t to_timestamp; - uint64_t num_deltas; -}; - -/// Function used to read information about the WAL file. -/// @throw RecoveryFailure -WalInfo ReadWalInfo(const std::filesystem::path &path); - -/// 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); - -/// Function used to read the WAL delta header. The function returns the delta -/// timestamp. -/// @throw RecoveryFailure -uint64_t ReadWalDeltaHeader(Decoder *wal); - -/// 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(Decoder *wal); - -/// 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(Decoder *wal); - -/// 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, -}; - -/// 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; -}; - -/// WalFile class used to append deltas and operations to the WAL file. -class WalFile { - public: - WalFile(const std::filesystem::path &wal_directory, const std::string &uuid, - Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num); - - 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(); - - 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_; -}; - -/// Durability class that is used to provide full durability functionality to -/// the storage. -class Durability final { - public: - struct RecoveryInfo { - uint64_t next_vertex_id{0}; - uint64_t next_edge_id{0}; - uint64_t next_timestamp{0}; - }; - - struct RecoveredSnapshot { - SnapshotInfo snapshot_info; - RecoveryInfo recovery_info; - RecoveredIndicesAndConstraints indices_constraints; - }; - - Durability(Config::Durability config, utils::SkipList<Vertex> *vertices, - utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, - std::atomic<uint64_t> *edge_count, Indices *indices, - Constraints *constraints, Config::Items items); - - std::optional<RecoveryInfo> Initialize( - std::function<void(std::function<void(Transaction *)>)> - execute_with_transaction); - - void Finalize(); - - void AppendToWal(const Transaction &transaction, - uint64_t final_commit_timestamp); - - void AppendToWal(StorageGlobalOperation operation, LabelId label, - const std::set<PropertyId> &properties, - uint64_t final_commit_timestamp); - - private: - void CreateSnapshot(Transaction *transaction); - - std::optional<RecoveryInfo> RecoverData(); - - RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path); - - RecoveryInfo LoadWal(const std::filesystem::path &path, - RecoveredIndicesAndConstraints *indices_constraints, - std::optional<uint64_t> snapshot_timestamp); - - bool InitializeWalFile(); - - void FinalizeWalFile(); - - Config::Durability config_; - - utils::SkipList<Vertex> *vertices_; - utils::SkipList<Edge> *edges_; - NameIdMapper *name_id_mapper_; - std::atomic<uint64_t> *edge_count_; - Indices *indices_; - Constraints *constraints_; - Config::Items items_; - - std::function<void(std::function<void(Transaction *)>)> - execute_with_transaction_; - - std::filesystem::path storage_directory_; - std::filesystem::path snapshot_directory_; - std::filesystem::path wal_directory_; - std::filesystem::path lock_file_path_; - utils::OutputFile lock_file_handle_; - - utils::Scheduler snapshot_runner_; - - // 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. - uint64_t wal_seq_num_{0}; - - std::optional<WalFile> wal_file_; - uint64_t wal_unsynced_transactions_{0}; -}; - -} // namespace storage diff --git a/src/storage/v2/durability.cpp b/src/storage/v2/durability/durability.cpp similarity index 58% rename from src/storage/v2/durability.cpp rename to src/storage/v2/durability/durability.cpp index a9b996a62..2cfa8b242 100644 --- a/src/storage/v2/durability.cpp +++ b/src/storage/v2/durability/durability.cpp @@ -1,6 +1,5 @@ -#include "storage/v2/durability.hpp" +#include "storage/v2/durability/durability.hpp" -#include <fcntl.h> #include <pwd.h> #include <sys/stat.h> #include <sys/types.h> @@ -10,893 +9,23 @@ #include <cstring> #include <algorithm> -#include <filesystem> -#include <string> #include <tuple> #include <unordered_map> #include <unordered_set> +#include <utility> #include <vector> +#include "storage/v2/durability/paths.hpp" +#include "storage/v2/durability/version.hpp" #include "storage/v2/edge_accessor.hpp" #include "storage/v2/edge_ref.hpp" #include "storage/v2/mvcc.hpp" #include "storage/v2/vertex_accessor.hpp" -#include "utils/endian.hpp" -#include "utils/file.hpp" -#include "utils/timestamp.hpp" #include "utils/uuid.hpp" -namespace storage { - -////////////////////////// -// Encoder implementation. -////////////////////////// +namespace storage::durability { 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::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; - } - } -} - -uint64_t Encoder::GetPosition() { return file_.GetPosition(); } - -void Encoder::SetPosition(uint64_t position) { - file_.SetPosition(utils::OutputFile::Position::SET, position); -} - -void Encoder::Sync() { file_.Sync(); } - -void Encoder::Finalize() { - file_.Sync(); - file_.Close(); -} - -////////////////////////// -// 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; - 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; - 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; - 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; - 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; - 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; - 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; -} - -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_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_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_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_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, position); -} - -///////////////////////////// -// Durability implementation. -///////////////////////////// - -namespace { -// 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{13}; - -const uint64_t kOldestSupportedVersion{12}; -const uint64_t kUniqueConstraintVersion{13}; - -// Snapshot format: -// -// 1) Magic string (non-encoded) -// -// 2) Snapshot version (non-encoded, little-endian) -// -// 3) Section offsets: -// * offset to the first edge in the snapshot (`0` if properties on edges -// are disabled) -// * offset to the first vertex in the snapshot -// * offset to the indices section -// * offset to the constraints section -// * offset to the mapper section -// * offset to the metadata section -// -// 4) Encoded edges (if properties on edges are enabled); each edge is written -// in the following format: -// * gid -// * properties -// -// 5) Encoded vertices; each vertex is written in the following format: -// * gid -// * labels -// * properties -// * in edges -// * edge gid -// * from vertex gid -// * edge type -// * out edges -// * edge gid -// * to vertex gid -// * edge type -// -// 6) Indices -// * label indices -// * label -// * label+property indices -// * label -// * property -// -// 7) Constraints -// * existence constraints -// * label -// * property -// * unique constraints (from version 13) -// * label -// * properties -// -// 8) Name to ID mapper data -// * id to name mappings -// * id -// * name -// -// 9) Metadata -// * storage UUID -// * snapshot transaction start timestamp (required when recovering -// from snapshot combined with WAL to determine what deltas need to be -// applied) -// * number of edges -// * number of vertices - -// WAL format: -// -// 1) Magic string (non-encoded) -// -// 2) WAL version (non-encoded, little-endian) -// -// 3) Section offsets: -// * offset to the metadata section -// * offset to the first delta in the WAL -// -// 4) Metadata -// * storage UUID -// * sequence number (number indicating the sequence position of this WAL -// file) -// -// 5) Encoded deltas; each delta is written in the following format: -// * commit timestamp -// * action (only one of the actions below are encoded) -// * vertex create, vertex delete -// * gid -// * vertex add label, vertex remove label -// * gid -// * label name -// * vertex set property -// * gid -// * property name -// * property value -// * edge create, edge delete -// * gid -// * edge type name -// * from vertex gid -// * to vertex gid -// * edge set property -// * gid -// * property name -// * property value -// * transaction end (marks that the whole transaction is -// stored in the WAL file) -// * label index create, label index drop -// * label name -// * label property index create, label property index drop, -// existence constraint create, existence constraint drop -// * label name -// * property name -// * unique constraint create, unique constraint drop -// * label name -// * property names - -// 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. -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. -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. -std::string RemakeWalName(const std::string ¤t_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); -} - -Marker OperationToMarker(StorageGlobalOperation operation) { - switch (operation) { - case StorageGlobalOperation::LABEL_INDEX_CREATE: - return Marker::DELTA_LABEL_INDEX_CREATE; - case StorageGlobalOperation::LABEL_INDEX_DROP: - return Marker::DELTA_LABEL_INDEX_DROP; - case StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE: - return Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE; - case StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP: - return Marker::DELTA_LABEL_PROPERTY_INDEX_DROP; - case StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE: - return Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE; - case StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP: - return Marker::DELTA_EXISTENCE_CONSTRAINT_DROP; - case StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE: - return Marker::DELTA_UNIQUE_CONSTRAINT_CREATE; - case StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP: - return Marker::DELTA_UNIQUE_CONSTRAINT_DROP; - } -} - -Marker VertexActionToMarker(Delta::Action action) { - // When converting a Delta to a WAL delta the logic is inverted. That is - // because the Delta's represent undo actions and we want to store redo - // actions. - switch (action) { - case Delta::Action::DELETE_OBJECT: - return Marker::DELTA_VERTEX_CREATE; - case Delta::Action::RECREATE_OBJECT: - return Marker::DELTA_VERTEX_DELETE; - case Delta::Action::SET_PROPERTY: - return Marker::DELTA_VERTEX_SET_PROPERTY; - case Delta::Action::ADD_LABEL: - return Marker::DELTA_VERTEX_REMOVE_LABEL; - case Delta::Action::REMOVE_LABEL: - return Marker::DELTA_VERTEX_ADD_LABEL; - case Delta::Action::ADD_IN_EDGE: - return Marker::DELTA_EDGE_DELETE; - case Delta::Action::ADD_OUT_EDGE: - return Marker::DELTA_EDGE_DELETE; - case Delta::Action::REMOVE_IN_EDGE: - return Marker::DELTA_EDGE_CREATE; - case Delta::Action::REMOVE_OUT_EDGE: - return Marker::DELTA_EDGE_CREATE; - } -} - -// This function convertes a Marker to a WalDeltaData::Type. It checks for the -// validity of the marker and throws if an invalid marker is specified. -// @throw RecoveryFailure -WalDeltaData::Type MarkerToWalDeltaDataType(Marker marker) { - switch (marker) { - case Marker::DELTA_VERTEX_CREATE: - return WalDeltaData::Type::VERTEX_CREATE; - case Marker::DELTA_VERTEX_DELETE: - return WalDeltaData::Type::VERTEX_DELETE; - case Marker::DELTA_VERTEX_ADD_LABEL: - return WalDeltaData::Type::VERTEX_ADD_LABEL; - case Marker::DELTA_VERTEX_REMOVE_LABEL: - return WalDeltaData::Type::VERTEX_REMOVE_LABEL; - case Marker::DELTA_EDGE_CREATE: - return WalDeltaData::Type::EDGE_CREATE; - case Marker::DELTA_EDGE_DELETE: - return WalDeltaData::Type::EDGE_DELETE; - case Marker::DELTA_VERTEX_SET_PROPERTY: - return WalDeltaData::Type::VERTEX_SET_PROPERTY; - case Marker::DELTA_EDGE_SET_PROPERTY: - return WalDeltaData::Type::EDGE_SET_PROPERTY; - case Marker::DELTA_TRANSACTION_END: - return WalDeltaData::Type::TRANSACTION_END; - case Marker::DELTA_LABEL_INDEX_CREATE: - return WalDeltaData::Type::LABEL_INDEX_CREATE; - case Marker::DELTA_LABEL_INDEX_DROP: - return WalDeltaData::Type::LABEL_INDEX_DROP; - case Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE: - return WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE; - case Marker::DELTA_LABEL_PROPERTY_INDEX_DROP: - return WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP; - case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE: - return WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE; - case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP: - return WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP; - case Marker::DELTA_UNIQUE_CONSTRAINT_CREATE: - return WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE; - case Marker::DELTA_UNIQUE_CONSTRAINT_DROP: - return WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP; - - case Marker::TYPE_NULL: - case Marker::TYPE_BOOL: - case Marker::TYPE_INT: - case Marker::TYPE_DOUBLE: - case Marker::TYPE_STRING: - case Marker::TYPE_LIST: - case Marker::TYPE_MAP: - 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_OFFSETS: - case Marker::VALUE_FALSE: - case Marker::VALUE_TRUE: - throw RecoveryFailure("Invalid WAL data!"); - } -} - -bool IsWalDeltaDataTypeTransactionEnd(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: - return true; - - // 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 either read or skip the current WAL delta data. The WAL -// delta header must be read before calling this function. If the delta data is -// read then the data returned is valid, if the delta data is skipped then the -// returned data is not guaranteed to be set (it could be empty) and shouldn't -// be used. -// @throw RecoveryFailure -template <bool read_data> -WalDeltaData ReadSkipWalDeltaData(Decoder *wal) { - WalDeltaData delta; - - auto action = wal->ReadMarker(); - if (!action) throw RecoveryFailure("Invalid WAL data!"); - delta.type = MarkerToWalDeltaDataType(*action); - - switch (delta.type) { - case WalDeltaData::Type::VERTEX_CREATE: - case WalDeltaData::Type::VERTEX_DELETE: { - auto gid = wal->ReadUint(); - if (!gid) throw RecoveryFailure("Invalid WAL data!"); - delta.vertex_create_delete.gid = Gid::FromUint(*gid); - break; - } - case WalDeltaData::Type::VERTEX_ADD_LABEL: - case WalDeltaData::Type::VERTEX_REMOVE_LABEL: { - auto gid = wal->ReadUint(); - if (!gid) throw RecoveryFailure("Invalid WAL data!"); - delta.vertex_add_remove_label.gid = Gid::FromUint(*gid); - if constexpr (read_data) { - auto label = wal->ReadString(); - if (!label) throw RecoveryFailure("Invalid WAL data!"); - delta.vertex_add_remove_label.label = std::move(*label); - } else { - if (!wal->SkipString()) throw RecoveryFailure("Invalid WAL data!"); - } - break; - } - case WalDeltaData::Type::VERTEX_SET_PROPERTY: - case WalDeltaData::Type::EDGE_SET_PROPERTY: { - auto gid = wal->ReadUint(); - if (!gid) throw RecoveryFailure("Invalid WAL data!"); - delta.vertex_edge_set_property.gid = Gid::FromUint(*gid); - if constexpr (read_data) { - auto property = wal->ReadString(); - if (!property) throw RecoveryFailure("Invalid WAL data!"); - delta.vertex_edge_set_property.property = std::move(*property); - auto value = wal->ReadPropertyValue(); - if (!value) throw RecoveryFailure("Invalid WAL data!"); - delta.vertex_edge_set_property.value = std::move(*value); - } else { - if (!wal->SkipString() || !wal->SkipPropertyValue()) - throw RecoveryFailure("Invalid WAL data!"); - } - break; - } - case WalDeltaData::Type::EDGE_CREATE: - case WalDeltaData::Type::EDGE_DELETE: { - auto gid = wal->ReadUint(); - if (!gid) throw RecoveryFailure("Invalid WAL data!"); - delta.edge_create_delete.gid = Gid::FromUint(*gid); - if constexpr (read_data) { - auto edge_type = wal->ReadString(); - if (!edge_type) throw RecoveryFailure("Invalid WAL data!"); - delta.edge_create_delete.edge_type = std::move(*edge_type); - } else { - if (!wal->SkipString()) throw RecoveryFailure("Invalid WAL data!"); - } - auto from_gid = wal->ReadUint(); - if (!from_gid) throw RecoveryFailure("Invalid WAL data!"); - delta.edge_create_delete.from_vertex = Gid::FromUint(*from_gid); - auto to_gid = wal->ReadUint(); - if (!to_gid) throw RecoveryFailure("Invalid WAL data!"); - delta.edge_create_delete.to_vertex = Gid::FromUint(*to_gid); - break; - } - case WalDeltaData::Type::TRANSACTION_END: - break; - case WalDeltaData::Type::LABEL_INDEX_CREATE: - case WalDeltaData::Type::LABEL_INDEX_DROP: { - if constexpr (read_data) { - auto label = wal->ReadString(); - if (!label) throw RecoveryFailure("Invalid WAL data!"); - delta.operation_label.label = std::move(*label); - } else { - if (!wal->SkipString()) throw RecoveryFailure("Invalid WAL data!"); - } - break; - } - 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: { - if constexpr (read_data) { - auto label = wal->ReadString(); - if (!label) throw RecoveryFailure("Invalid WAL data!"); - delta.operation_label_property.label = std::move(*label); - auto property = wal->ReadString(); - if (!property) throw RecoveryFailure("Invalid WAL data!"); - delta.operation_label_property.property = std::move(*property); - } else { - if (!wal->SkipString() || !wal->SkipString()) - throw RecoveryFailure("Invalid WAL data!"); - } - break; - } - case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: - case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: { - if constexpr (read_data) { - auto label = wal->ReadString(); - if (!label) throw RecoveryFailure("Invalid WAL data!"); - delta.operation_label_properties.label = std::move(*label); - auto properties_count = wal->ReadUint(); - if (!properties_count) throw RecoveryFailure("Invalid WAL data!"); - for (uint64_t i = 0; i < *properties_count; ++i) { - auto property = wal->ReadString(); - if (!property) throw RecoveryFailure("Invalid WAL data!"); - delta.operation_label_properties.properties.emplace( - std::move(*property)); - } - } else { - if (!wal->SkipString()) throw RecoveryFailure("Invalid WAL data!"); - auto properties_count = wal->ReadUint(); - if (!properties_count) throw RecoveryFailure("Invalid WAL data!"); - for (uint64_t i = 0; i < *properties_count; ++i) { - if (!wal->SkipString()) throw RecoveryFailure("Invalid WAL data!"); - } - } - } - } - - return delta; -} - -// 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); - } -} - -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); - } -} - -bool IsVersionSupported(uint64_t version) { - return version >= kOldestSupportedVersion && version <= kVersion; -} /// Verifies that the owner of the storage directory is the same user that /// started the current process. @@ -933,467 +62,6 @@ void VerifyStorageDirectoryOwnerAndProcessUserOrDie( } // namespace -// Function used to read information about the snapshot file. -SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path) { - // Check magic and version. - Decoder snapshot; - auto version = snapshot.Initialize(path, kSnapshotMagic); - if (!version) - throw RecoveryFailure("Couldn't read snapshot magic and/or version!"); - if (!IsVersionSupported(*version)) - throw RecoveryFailure("Invalid snapshot version!"); - - // Prepare return value. - SnapshotInfo info; - - // Read offsets. - { - auto marker = snapshot.ReadMarker(); - if (!marker || *marker != Marker::SECTION_OFFSETS) - throw RecoveryFailure("Invalid snapshot data!"); - - auto snapshot_size = snapshot.GetSize(); - if (!snapshot_size) - throw RecoveryFailure("Couldn't read data from snapshot!"); - - auto read_offset = [&snapshot, snapshot_size] { - auto maybe_offset = snapshot.ReadUint(); - if (!maybe_offset) throw RecoveryFailure("Invalid snapshot format!"); - auto offset = *maybe_offset; - if (offset > *snapshot_size) - throw RecoveryFailure("Invalid snapshot format!"); - return offset; - }; - - info.offset_edges = read_offset(); - info.offset_vertices = read_offset(); - info.offset_indices = read_offset(); - info.offset_constraints = read_offset(); - info.offset_mapper = read_offset(); - info.offset_metadata = read_offset(); - } - - // Read metadata. - { - if (!snapshot.SetPosition(info.offset_metadata)) - throw RecoveryFailure("Couldn't read data from snapshot!"); - - auto marker = snapshot.ReadMarker(); - if (!marker || *marker != Marker::SECTION_METADATA) - throw RecoveryFailure("Invalid snapshot data!"); - - auto maybe_uuid = snapshot.ReadString(); - if (!maybe_uuid) throw RecoveryFailure("Invalid snapshot data!"); - info.uuid = std::move(*maybe_uuid); - - auto maybe_timestamp = snapshot.ReadUint(); - if (!maybe_timestamp) throw RecoveryFailure("Invalid snapshot data!"); - info.start_timestamp = *maybe_timestamp; - - auto maybe_edges = snapshot.ReadUint(); - if (!maybe_edges) throw RecoveryFailure("Invalid snapshot data!"); - info.edges_count = *maybe_edges; - - auto maybe_vertices = snapshot.ReadUint(); - if (!maybe_vertices) throw RecoveryFailure("Invalid snapshot data!"); - info.vertices_count = *maybe_vertices; - } - - return info; -} - -// Function used to read information about the WAL file. -WalInfo ReadWalInfo(const std::filesystem::path &path) { - // Check magic and version. - Decoder wal; - auto version = wal.Initialize(path, kWalMagic); - if (!version) - throw RecoveryFailure("Couldn't read WAL magic and/or version!"); - if (!IsVersionSupported(*version)) - throw RecoveryFailure("Invalid WAL version!"); - - // Prepare return value. - WalInfo info; - - // Read offsets. - { - auto marker = wal.ReadMarker(); - if (!marker || *marker != Marker::SECTION_OFFSETS) - throw RecoveryFailure("Invalid WAL data!"); - - auto wal_size = wal.GetSize(); - if (!wal_size) throw RecoveryFailure("Invalid WAL data!"); - - auto read_offset = [&wal, wal_size] { - auto maybe_offset = wal.ReadUint(); - if (!maybe_offset) throw RecoveryFailure("Invalid WAL format!"); - auto offset = *maybe_offset; - if (offset > *wal_size) throw RecoveryFailure("Invalid WAL format!"); - return offset; - }; - - info.offset_metadata = read_offset(); - info.offset_deltas = read_offset(); - } - - // Read metadata. - { - wal.SetPosition(info.offset_metadata); - - auto marker = wal.ReadMarker(); - if (!marker || *marker != Marker::SECTION_METADATA) - throw RecoveryFailure("Invalid WAL data!"); - - auto maybe_uuid = wal.ReadString(); - if (!maybe_uuid) throw RecoveryFailure("Invalid WAL data!"); - info.uuid = std::move(*maybe_uuid); - - auto maybe_seq_num = wal.ReadUint(); - if (!maybe_seq_num) throw RecoveryFailure("Invalid WAL data!"); - info.seq_num = *maybe_seq_num; - } - - // Read deltas. - info.num_deltas = 0; - auto validate_delta = [&wal]() -> std::optional<std::pair<uint64_t, bool>> { - try { - auto timestamp = ReadWalDeltaHeader(&wal); - auto type = SkipWalDeltaData(&wal); - return {{timestamp, IsWalDeltaDataTypeTransactionEnd(type)}}; - } catch (const RecoveryFailure &) { - return std::nullopt; - } - }; - auto size = wal.GetSize(); - // Here we read the whole file and determine the number of valid deltas. A - // delta is valid only if all of its data can be successfully read. This - // allows us to recover data from WAL files that are corrupt at the end (eg. - // because of power loss) but are still valid at the beginning. While reading - // the deltas we only count deltas which are a part of a fully valid - // transaction (indicated by a TRANSACTION_END delta or any other - // non-transactional operation). - std::optional<uint64_t> current_timestamp; - uint64_t num_deltas = 0; - while (wal.GetPosition() != size) { - auto ret = validate_delta(); - if (!ret) break; - auto [timestamp, is_end_of_transaction] = *ret; - if (!current_timestamp) current_timestamp = timestamp; - if (*current_timestamp != timestamp) break; - ++num_deltas; - if (is_end_of_transaction) { - if (info.num_deltas == 0) { - info.from_timestamp = timestamp; - info.to_timestamp = timestamp; - } - if (timestamp < info.from_timestamp || timestamp < info.to_timestamp) - break; - info.to_timestamp = timestamp; - info.num_deltas += num_deltas; - current_timestamp = std::nullopt; - num_deltas = 0; - } - } - - if (info.num_deltas == 0) throw RecoveryFailure("Invalid WAL data!"); - - return info; -} - -bool operator==(const WalDeltaData &a, const WalDeltaData &b) { - if (a.type != b.type) return false; - switch (a.type) { - case WalDeltaData::Type::VERTEX_CREATE: - case WalDeltaData::Type::VERTEX_DELETE: - return a.vertex_create_delete.gid == b.vertex_create_delete.gid; - - case WalDeltaData::Type::VERTEX_ADD_LABEL: - case WalDeltaData::Type::VERTEX_REMOVE_LABEL: - return a.vertex_add_remove_label.gid == b.vertex_add_remove_label.gid && - a.vertex_add_remove_label.label == b.vertex_add_remove_label.label; - - case WalDeltaData::Type::VERTEX_SET_PROPERTY: - case WalDeltaData::Type::EDGE_SET_PROPERTY: - return a.vertex_edge_set_property.gid == b.vertex_edge_set_property.gid && - a.vertex_edge_set_property.property == - b.vertex_edge_set_property.property && - a.vertex_edge_set_property.value == - b.vertex_edge_set_property.value; - - case WalDeltaData::Type::EDGE_CREATE: - case WalDeltaData::Type::EDGE_DELETE: - return a.edge_create_delete.gid == b.edge_create_delete.gid && - a.edge_create_delete.edge_type == b.edge_create_delete.edge_type && - a.edge_create_delete.from_vertex == - b.edge_create_delete.from_vertex && - a.edge_create_delete.to_vertex == b.edge_create_delete.to_vertex; - - case WalDeltaData::Type::TRANSACTION_END: - return true; - - case WalDeltaData::Type::LABEL_INDEX_CREATE: - case WalDeltaData::Type::LABEL_INDEX_DROP: - return a.operation_label.label == b.operation_label.label; - - 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: - return a.operation_label_property.label == - b.operation_label_property.label && - a.operation_label_property.property == - b.operation_label_property.property; - case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: - case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: - return a.operation_label_properties.label == - b.operation_label_properties.label && - a.operation_label_properties.properties == - b.operation_label_properties.properties; - } -} -bool operator!=(const WalDeltaData &a, const WalDeltaData &b) { - return !(a == b); -} - -// Function used to read the WAL delta header. The function returns the delta -// timestamp. -uint64_t ReadWalDeltaHeader(Decoder *wal) { - auto marker = wal->ReadMarker(); - if (!marker || *marker != Marker::SECTION_DELTA) - throw RecoveryFailure("Invalid WAL data!"); - - auto timestamp = wal->ReadUint(); - if (!timestamp) throw RecoveryFailure("Invalid WAL data!"); - return *timestamp; -} - -// Function used to read the current WAL delta data. The WAL delta header must -// be read before calling this function. -WalDeltaData ReadWalDeltaData(Decoder *wal) { - return ReadSkipWalDeltaData<true>(wal); -} - -// Function used to skip the current WAL delta data. The WAL delta header must -// be read before calling this function. -WalDeltaData::Type SkipWalDeltaData(Decoder *wal) { - auto delta = ReadSkipWalDeltaData<false>(wal); - return delta.type; -} - -WalFile::WalFile(const std::filesystem::path &wal_directory, - const std::string &uuid, Config::Items items, - NameIdMapper *name_id_mapper, uint64_t seq_num) - : items_(items), - name_id_mapper_(name_id_mapper), - path_(wal_directory / MakeWalName()), - from_timestamp_(0), - to_timestamp_(0), - count_(0) { - // Ensure that the storage directory exists. - utils::EnsureDirOrDie(wal_directory); - - // Initialize the WAL file. - wal_.Initialize(path_, kWalMagic, kVersion); - - // Write placeholder offsets. - uint64_t offset_offsets = 0; - uint64_t offset_metadata = 0; - uint64_t offset_deltas = 0; - wal_.WriteMarker(Marker::SECTION_OFFSETS); - offset_offsets = wal_.GetPosition(); - wal_.WriteUint(offset_metadata); - wal_.WriteUint(offset_deltas); - - // Write metadata. - offset_metadata = wal_.GetPosition(); - wal_.WriteMarker(Marker::SECTION_METADATA); - wal_.WriteString(uuid); - wal_.WriteUint(seq_num); - - // Write final offsets. - offset_deltas = wal_.GetPosition(); - wal_.SetPosition(offset_offsets); - wal_.WriteUint(offset_metadata); - wal_.WriteUint(offset_deltas); - wal_.SetPosition(offset_deltas); - - // Sync the initial data. - wal_.Sync(); -} - -WalFile::~WalFile() { - if (count_ != 0) { - // Finalize file. - wal_.Finalize(); - - // Rename file. - std::filesystem::path new_path(path_); - new_path.replace_filename( - RemakeWalName(path_.filename(), from_timestamp_, to_timestamp_)); - // If the rename fails it isn't a crucial situation. The renaming is done - // only to make the directory structure of the WAL files easier to read - // manually. - utils::RenamePath(path_, new_path); - } else { - // Remove empty WAL file. - utils::DeleteFile(path_); - } -} - -void WalFile::AppendDelta(const Delta &delta, const Vertex &vertex, - uint64_t timestamp) { - // When converting a Delta to a WAL delta the logic is inverted. That is - // because the Delta's represent undo actions and we want to store redo - // actions. - wal_.WriteMarker(Marker::SECTION_DELTA); - wal_.WriteUint(timestamp); - std::lock_guard<utils::SpinLock> guard(vertex.lock); - switch (delta.action) { - case Delta::Action::DELETE_OBJECT: - case Delta::Action::RECREATE_OBJECT: { - wal_.WriteMarker(VertexActionToMarker(delta.action)); - wal_.WriteUint(vertex.gid.AsUint()); - break; - } - case Delta::Action::SET_PROPERTY: { - wal_.WriteMarker(Marker::DELTA_VERTEX_SET_PROPERTY); - wal_.WriteUint(vertex.gid.AsUint()); - wal_.WriteString(name_id_mapper_->IdToName(delta.property.key.AsUint())); - // The property value is the value that is currently stored in the - // vertex. - // TODO (mferencevic): Mitigate the memory allocation introduced here - // (with the `GetProperty` call). It is the only memory allocation in the - // entire WAL file writing logic. - wal_.WritePropertyValue( - vertex.properties.GetProperty(delta.property.key)); - break; - } - case Delta::Action::ADD_LABEL: - case Delta::Action::REMOVE_LABEL: { - wal_.WriteMarker(VertexActionToMarker(delta.action)); - wal_.WriteUint(vertex.gid.AsUint()); - wal_.WriteString(name_id_mapper_->IdToName(delta.label.AsUint())); - break; - } - case Delta::Action::ADD_OUT_EDGE: - case Delta::Action::REMOVE_OUT_EDGE: { - wal_.WriteMarker(VertexActionToMarker(delta.action)); - if (items_.properties_on_edges) { - wal_.WriteUint(delta.vertex_edge.edge.ptr->gid.AsUint()); - } else { - wal_.WriteUint(delta.vertex_edge.edge.gid.AsUint()); - } - wal_.WriteString( - name_id_mapper_->IdToName(delta.vertex_edge.edge_type.AsUint())); - wal_.WriteUint(vertex.gid.AsUint()); - wal_.WriteUint(delta.vertex_edge.vertex->gid.AsUint()); - break; - } - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - // These actions are already encoded in the *_OUT_EDGE actions. This - // function should never be called for this type of deltas. - LOG(FATAL) << "Invalid delta action!"; - } - UpdateStats(timestamp); -} - -void WalFile::AppendDelta(const Delta &delta, const Edge &edge, - uint64_t timestamp) { - // When converting a Delta to a WAL delta the logic is inverted. That is - // because the Delta's represent undo actions and we want to store redo - // actions. - wal_.WriteMarker(Marker::SECTION_DELTA); - wal_.WriteUint(timestamp); - std::lock_guard<utils::SpinLock> guard(edge.lock); - switch (delta.action) { - case Delta::Action::SET_PROPERTY: { - wal_.WriteMarker(Marker::DELTA_EDGE_SET_PROPERTY); - wal_.WriteUint(edge.gid.AsUint()); - wal_.WriteString(name_id_mapper_->IdToName(delta.property.key.AsUint())); - // The property value is the value that is currently stored in the - // edge. - // TODO (mferencevic): Mitigate the memory allocation introduced here - // (with the `GetProperty` call). It is the only memory allocation in the - // entire WAL file writing logic. - wal_.WritePropertyValue(edge.properties.GetProperty(delta.property.key)); - break; - } - case Delta::Action::DELETE_OBJECT: - case Delta::Action::RECREATE_OBJECT: - // These actions are already encoded in vertex *_OUT_EDGE actions. Also, - // these deltas don't contain any information about the from vertex, to - // vertex or edge type so they are useless. This function should never - // be called for this type of deltas. - LOG(FATAL) << "Invalid delta action!"; - case Delta::Action::ADD_LABEL: - case Delta::Action::REMOVE_LABEL: - case Delta::Action::ADD_OUT_EDGE: - case Delta::Action::REMOVE_OUT_EDGE: - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - // These deltas shouldn't appear for edges. - LOG(FATAL) << "Invalid database state!"; - } - UpdateStats(timestamp); -} - -void WalFile::AppendTransactionEnd(uint64_t timestamp) { - wal_.WriteMarker(Marker::SECTION_DELTA); - wal_.WriteUint(timestamp); - wal_.WriteMarker(Marker::DELTA_TRANSACTION_END); - UpdateStats(timestamp); -} - -void WalFile::AppendOperation(StorageGlobalOperation operation, LabelId label, - const std::set<PropertyId> &properties, - uint64_t timestamp) { - wal_.WriteMarker(Marker::SECTION_DELTA); - wal_.WriteUint(timestamp); - switch (operation) { - case StorageGlobalOperation::LABEL_INDEX_CREATE: - case StorageGlobalOperation::LABEL_INDEX_DROP: { - CHECK(properties.empty()) << "Invalid function call!"; - wal_.WriteMarker(OperationToMarker(operation)); - wal_.WriteString(name_id_mapper_->IdToName(label.AsUint())); - break; - } - case StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE: - case StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP: - case StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE: - case StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP: { - CHECK(properties.size() == 1) << "Invalid function call!"; - wal_.WriteMarker(OperationToMarker(operation)); - wal_.WriteString(name_id_mapper_->IdToName(label.AsUint())); - wal_.WriteString( - name_id_mapper_->IdToName((*properties.begin()).AsUint())); - break; - } - case StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE: - case StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP: { - CHECK(!properties.empty()) << "Invalid function call!"; - wal_.WriteMarker(OperationToMarker(operation)); - wal_.WriteString(name_id_mapper_->IdToName(label.AsUint())); - wal_.WriteUint(properties.size()); - for (const auto &property : properties) { - wal_.WriteString(name_id_mapper_->IdToName(property.AsUint())); - } - break; - } - } - UpdateStats(timestamp); -} - -void WalFile::Sync() { wal_.Sync(); } - -uint64_t WalFile::GetSize() { return wal_.GetPosition(); } - -void WalFile::UpdateStats(uint64_t timestamp) { - if (count_ == 0) from_timestamp_ = timestamp; - to_timestamp_ = timestamp; - count_ += 1; -} - Durability::Durability(Config::Durability config, utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges, @@ -2965,4 +1633,4 @@ void Durability::FinalizeWalFile() { } } -} // namespace storage +} // namespace storage::durability diff --git a/src/storage/v2/durability/durability.hpp b/src/storage/v2/durability/durability.hpp new file mode 100644 index 000000000..a931a7883 --- /dev/null +++ b/src/storage/v2/durability/durability.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include <atomic> +#include <cstdint> +#include <filesystem> +#include <functional> +#include <optional> +#include <string> + +#include "storage/v2/durability/metadata.hpp" +#include "storage/v2/durability/snapshot.hpp" +#include "storage/v2/durability/wal.hpp" + +#include "storage/v2/config.hpp" +#include "storage/v2/constraints.hpp" +#include "storage/v2/edge.hpp" +#include "storage/v2/indices.hpp" +#include "storage/v2/name_id_mapper.hpp" +#include "storage/v2/transaction.hpp" +#include "storage/v2/vertex.hpp" +#include "utils/scheduler.hpp" +#include "utils/skip_list.hpp" + +namespace storage::durability { + +/// Durability class that is used to provide full durability functionality to +/// the storage. +class Durability final { + public: + struct RecoveryInfo { + uint64_t next_vertex_id{0}; + uint64_t next_edge_id{0}; + uint64_t next_timestamp{0}; + }; + + struct RecoveredSnapshot { + SnapshotInfo snapshot_info; + RecoveryInfo recovery_info; + RecoveredIndicesAndConstraints indices_constraints; + }; + + Durability(Config::Durability config, utils::SkipList<Vertex> *vertices, + utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, + std::atomic<uint64_t> *edge_count, Indices *indices, + Constraints *constraints, Config::Items items); + + std::optional<RecoveryInfo> Initialize( + std::function<void(std::function<void(Transaction *)>)> + execute_with_transaction); + + void Finalize(); + + void AppendToWal(const Transaction &transaction, + uint64_t final_commit_timestamp); + + void AppendToWal(StorageGlobalOperation operation, LabelId label, + const std::set<PropertyId> &properties, + uint64_t final_commit_timestamp); + + private: + void CreateSnapshot(Transaction *transaction); + + std::optional<RecoveryInfo> RecoverData(); + + RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path); + + RecoveryInfo LoadWal(const std::filesystem::path &path, + RecoveredIndicesAndConstraints *indices_constraints, + std::optional<uint64_t> snapshot_timestamp); + + bool InitializeWalFile(); + + void FinalizeWalFile(); + + Config::Durability config_; + + utils::SkipList<Vertex> *vertices_; + utils::SkipList<Edge> *edges_; + NameIdMapper *name_id_mapper_; + std::atomic<uint64_t> *edge_count_; + Indices *indices_; + Constraints *constraints_; + Config::Items items_; + + std::function<void(std::function<void(Transaction *)>)> + execute_with_transaction_; + + std::filesystem::path storage_directory_; + std::filesystem::path snapshot_directory_; + std::filesystem::path wal_directory_; + std::filesystem::path lock_file_path_; + utils::OutputFile lock_file_handle_; + + utils::Scheduler snapshot_runner_; + + // 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. + uint64_t wal_seq_num_{0}; + + std::optional<WalFile> wal_file_; + uint64_t wal_unsynced_transactions_{0}; +}; + +} // namespace storage::durability diff --git a/src/storage/v2/durability/exceptions.hpp b/src/storage/v2/durability/exceptions.hpp new file mode 100644 index 000000000..8ef42f9f2 --- /dev/null +++ b/src/storage/v2/durability/exceptions.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "utils/exceptions.hpp" + +namespace storage::durability { + +/// Exception used to handle errors during recovery. +class RecoveryFailure : public utils::BasicException { + using utils::BasicException::BasicException; +}; + +} // namespace storage::durability diff --git a/src/storage/v2/durability/marker.hpp b/src/storage/v2/durability/marker.hpp new file mode 100644 index 000000000..ccb7ca8f1 --- /dev/null +++ b/src/storage/v2/durability/marker.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include <cstdint> + +namespace storage::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, + + SECTION_VERTEX = 0x20, + SECTION_EDGE = 0x21, + SECTION_MAPPER = 0x22, + SECTION_METADATA = 0x23, + SECTION_INDICES = 0x24, + SECTION_CONSTRAINTS = 0x25, + SECTION_DELTA = 0x26, + 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_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_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 storage::durability diff --git a/src/storage/v2/durability/metadata.hpp b/src/storage/v2/durability/metadata.hpp new file mode 100644 index 000000000..253d41a6c --- /dev/null +++ b/src/storage/v2/durability/metadata.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include <algorithm> +#include <set> +#include <utility> +#include <vector> + +#include "storage/v2/durability/exceptions.hpp" +#include "storage/v2/id_types.hpp" + +namespace storage::durability { + +/// 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 storage::durability diff --git a/src/storage/v2/durability/paths.hpp b/src/storage/v2/durability/paths.hpp new file mode 100644 index 000000000..98dac2789 --- /dev/null +++ b/src/storage/v2/durability/paths.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include <cstdint> +#include <string> + +#include "utils/timestamp.hpp" + +namespace storage::durability { + +static const std::string kSnapshotDirectory{"snapshots"}; +static const std::string kWalDirectory{"wal"}; +static const std::string kBackupDirectory{".backup"}; + +// 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 ¤t_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 storage::durability diff --git a/src/storage/v2/durability/serialization.cpp b/src/storage/v2/durability/serialization.cpp new file mode 100644 index 000000000..0c3764003 --- /dev/null +++ b/src/storage/v2/durability/serialization.cpp @@ -0,0 +1,425 @@ +#include "storage/v2/durability/serialization.hpp" + +#include "utils/endian.hpp" + +namespace storage::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::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; + } + } +} + +uint64_t Encoder::GetPosition() { return file_.GetPosition(); } + +void Encoder::SetPosition(uint64_t position) { + file_.SetPosition(utils::OutputFile::Position::SET, position); +} + +void Encoder::Sync() { file_.Sync(); } + +void Encoder::Finalize() { + file_.Sync(); + file_.Close(); +} + +////////////////////////// +// 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; + 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; + 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; + 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; + 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; + 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; + 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; +} + +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_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_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_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_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, position); +} + +} // namespace storage::durability diff --git a/src/storage/v2/durability/serialization.hpp b/src/storage/v2/durability/serialization.hpp new file mode 100644 index 000000000..e0d8a9e59 --- /dev/null +++ b/src/storage/v2/durability/serialization.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include <cstdint> +#include <filesystem> +#include <string_view> + +#include "storage/v2/config.hpp" +#include "storage/v2/durability/marker.hpp" +#include "storage/v2/name_id_mapper.hpp" +#include "storage/v2/property_value.hpp" +#include "utils/file.hpp" + +namespace storage::durability { + +/// Encoder that is used to generate a snapshot/WAL. +class Encoder final { + public: + void Initialize(const std::filesystem::path &path, + const std::string_view &magic, uint64_t version); + + // 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); + void WriteBool(bool value); + void WriteUint(uint64_t value); + void WriteDouble(double value); + void WriteString(const std::string_view &value); + void WritePropertyValue(const PropertyValue &value); + + uint64_t GetPosition(); + void SetPosition(uint64_t position); + + void Sync(); + + void Finalize(); + + private: + utils::OutputFile file_; +}; + +/// Decoder that is used to read a generated snapshot/WAL. +class Decoder final { + 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(); + std::optional<bool> ReadBool(); + std::optional<uint64_t> ReadUint(); + std::optional<double> ReadDouble(); + std::optional<std::string> ReadString(); + std::optional<PropertyValue> ReadPropertyValue(); + + bool SkipString(); + bool SkipPropertyValue(); + + std::optional<uint64_t> GetSize(); + std::optional<uint64_t> GetPosition(); + bool SetPosition(uint64_t position); + + private: + utils::InputFile file_; +}; + +} // namespace storage::durability diff --git a/src/storage/v2/durability/snapshot.cpp b/src/storage/v2/durability/snapshot.cpp new file mode 100644 index 000000000..cc6e4df8b --- /dev/null +++ b/src/storage/v2/durability/snapshot.cpp @@ -0,0 +1,142 @@ +#include "storage/v2/durability/snapshot.hpp" + +#include "storage/v2/durability/exceptions.hpp" +#include "storage/v2/durability/serialization.hpp" +#include "storage/v2/durability/version.hpp" + +namespace storage::durability { + +// Snapshot format: +// +// 1) Magic string (non-encoded) +// +// 2) Snapshot version (non-encoded, little-endian) +// +// 3) Section offsets: +// * offset to the first edge in the snapshot (`0` if properties on edges +// are disabled) +// * offset to the first vertex in the snapshot +// * offset to the indices section +// * offset to the constraints section +// * offset to the mapper section +// * offset to the metadata section +// +// 4) Encoded edges (if properties on edges are enabled); each edge is written +// in the following format: +// * gid +// * properties +// +// 5) Encoded vertices; each vertex is written in the following format: +// * gid +// * labels +// * properties +// * in edges +// * edge gid +// * from vertex gid +// * edge type +// * out edges +// * edge gid +// * to vertex gid +// * edge type +// +// 6) Indices +// * label indices +// * label +// * label+property indices +// * label +// * property +// +// 7) Constraints +// * existence constraints +// * label +// * property +// * unique constraints (from version 13) +// * label +// * properties +// +// 8) Name to ID mapper data +// * id to name mappings +// * id +// * name +// +// 9) Metadata +// * storage UUID +// * snapshot transaction start timestamp (required when recovering +// from snapshot combined with WAL to determine what deltas need to be +// applied) +// * number of edges +// * number of vertices +// +// IMPORTANT: When changing snapshot encoding/decoding bump the snapshot/WAL +// version in `version.hpp`. + +// Function used to read information about the snapshot file. +SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path) { + // Check magic and version. + Decoder snapshot; + auto version = snapshot.Initialize(path, kSnapshotMagic); + if (!version) + throw RecoveryFailure("Couldn't read snapshot magic and/or version!"); + if (!IsVersionSupported(*version)) + throw RecoveryFailure("Invalid snapshot version!"); + + // Prepare return value. + SnapshotInfo info; + + // Read offsets. + { + auto marker = snapshot.ReadMarker(); + if (!marker || *marker != Marker::SECTION_OFFSETS) + throw RecoveryFailure("Invalid snapshot data!"); + + auto snapshot_size = snapshot.GetSize(); + if (!snapshot_size) + throw RecoveryFailure("Couldn't read data from snapshot!"); + + auto read_offset = [&snapshot, snapshot_size] { + auto maybe_offset = snapshot.ReadUint(); + if (!maybe_offset) throw RecoveryFailure("Invalid snapshot format!"); + auto offset = *maybe_offset; + if (offset > *snapshot_size) + throw RecoveryFailure("Invalid snapshot format!"); + return offset; + }; + + info.offset_edges = read_offset(); + info.offset_vertices = read_offset(); + info.offset_indices = read_offset(); + info.offset_constraints = read_offset(); + info.offset_mapper = read_offset(); + info.offset_metadata = read_offset(); + } + + // Read metadata. + { + if (!snapshot.SetPosition(info.offset_metadata)) + throw RecoveryFailure("Couldn't read data from snapshot!"); + + auto marker = snapshot.ReadMarker(); + if (!marker || *marker != Marker::SECTION_METADATA) + throw RecoveryFailure("Invalid snapshot data!"); + + auto maybe_uuid = snapshot.ReadString(); + if (!maybe_uuid) throw RecoveryFailure("Invalid snapshot data!"); + info.uuid = std::move(*maybe_uuid); + + auto maybe_timestamp = snapshot.ReadUint(); + if (!maybe_timestamp) throw RecoveryFailure("Invalid snapshot data!"); + info.start_timestamp = *maybe_timestamp; + + auto maybe_edges = snapshot.ReadUint(); + if (!maybe_edges) throw RecoveryFailure("Invalid snapshot data!"); + info.edges_count = *maybe_edges; + + auto maybe_vertices = snapshot.ReadUint(); + if (!maybe_vertices) throw RecoveryFailure("Invalid snapshot data!"); + info.vertices_count = *maybe_vertices; + } + + return info; +} + +} // namespace storage::durability diff --git a/src/storage/v2/durability/snapshot.hpp b/src/storage/v2/durability/snapshot.hpp new file mode 100644 index 000000000..fc9fa4d3c --- /dev/null +++ b/src/storage/v2/durability/snapshot.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include <cstdint> +#include <filesystem> +#include <string> + +namespace storage::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_metadata; + + std::string uuid; + uint64_t start_timestamp; + uint64_t edges_count; + uint64_t vertices_count; +}; + +/// Function used to read information about the snapshot file. +/// @throw RecoveryFailure +SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path); + +} // namespace storage::durability diff --git a/src/storage/v2/durability/version.hpp b/src/storage/v2/durability/version.hpp new file mode 100644 index 000000000..044522c22 --- /dev/null +++ b/src/storage/v2/durability/version.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include <cstdint> +#include <string> +#include <type_traits> + +namespace storage::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{13}; + +const uint64_t kOldestSupportedVersion{12}; +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 storage::durability diff --git a/src/storage/v2/durability/wal.cpp b/src/storage/v2/durability/wal.cpp new file mode 100644 index 000000000..011445164 --- /dev/null +++ b/src/storage/v2/durability/wal.cpp @@ -0,0 +1,733 @@ +#include "storage/v2/durability/wal.hpp" + +#include "storage/v2/delta.hpp" +#include "storage/v2/durability/exceptions.hpp" +#include "storage/v2/durability/paths.hpp" +#include "storage/v2/durability/version.hpp" +#include "storage/v2/edge.hpp" +#include "storage/v2/vertex.hpp" + +namespace storage::durability { + +// WAL format: +// +// 1) Magic string (non-encoded) +// +// 2) WAL version (non-encoded, little-endian) +// +// 3) Section offsets: +// * offset to the metadata section +// * offset to the first delta in the WAL +// +// 4) Metadata +// * storage UUID +// * sequence number (number indicating the sequence position of this WAL +// file) +// +// 5) Encoded deltas; each delta is written in the following format: +// * commit timestamp +// * action (only one of the actions below are encoded) +// * vertex create, vertex delete +// * gid +// * vertex add label, vertex remove label +// * gid +// * label name +// * vertex set property +// * gid +// * property name +// * property value +// * edge create, edge delete +// * gid +// * edge type name +// * from vertex gid +// * to vertex gid +// * edge set property +// * gid +// * property name +// * property value +// * transaction end (marks that the whole transaction is +// stored in the WAL file) +// * label index create, label index drop +// * label name +// * label property index create, label property index drop, +// existence constraint create, existence constraint drop +// * label name +// * property name +// * unique constraint create, unique constraint drop +// * label name +// * property names +// +// IMPORTANT: When changing WAL encoding/decoding bump the snapshot/WAL version +// in `version.hpp`. + +namespace { + +Marker OperationToMarker(StorageGlobalOperation operation) { + switch (operation) { + case StorageGlobalOperation::LABEL_INDEX_CREATE: + return Marker::DELTA_LABEL_INDEX_CREATE; + case StorageGlobalOperation::LABEL_INDEX_DROP: + return Marker::DELTA_LABEL_INDEX_DROP; + case StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE: + return Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE; + case StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP: + return Marker::DELTA_LABEL_PROPERTY_INDEX_DROP; + case StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE: + return Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE; + case StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP: + return Marker::DELTA_EXISTENCE_CONSTRAINT_DROP; + case StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE: + return Marker::DELTA_UNIQUE_CONSTRAINT_CREATE; + case StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP: + return Marker::DELTA_UNIQUE_CONSTRAINT_DROP; + } +} + +Marker VertexActionToMarker(Delta::Action action) { + // When converting a Delta to a WAL delta the logic is inverted. That is + // because the Delta's represent undo actions and we want to store redo + // actions. + switch (action) { + case Delta::Action::DELETE_OBJECT: + return Marker::DELTA_VERTEX_CREATE; + case Delta::Action::RECREATE_OBJECT: + return Marker::DELTA_VERTEX_DELETE; + case Delta::Action::SET_PROPERTY: + return Marker::DELTA_VERTEX_SET_PROPERTY; + case Delta::Action::ADD_LABEL: + return Marker::DELTA_VERTEX_REMOVE_LABEL; + case Delta::Action::REMOVE_LABEL: + return Marker::DELTA_VERTEX_ADD_LABEL; + case Delta::Action::ADD_IN_EDGE: + return Marker::DELTA_EDGE_DELETE; + case Delta::Action::ADD_OUT_EDGE: + return Marker::DELTA_EDGE_DELETE; + case Delta::Action::REMOVE_IN_EDGE: + return Marker::DELTA_EDGE_CREATE; + case Delta::Action::REMOVE_OUT_EDGE: + return Marker::DELTA_EDGE_CREATE; + } +} + +// This function convertes a Marker to a WalDeltaData::Type. It checks for the +// validity of the marker and throws if an invalid marker is specified. +// @throw RecoveryFailure +WalDeltaData::Type MarkerToWalDeltaDataType(Marker marker) { + switch (marker) { + case Marker::DELTA_VERTEX_CREATE: + return WalDeltaData::Type::VERTEX_CREATE; + case Marker::DELTA_VERTEX_DELETE: + return WalDeltaData::Type::VERTEX_DELETE; + case Marker::DELTA_VERTEX_ADD_LABEL: + return WalDeltaData::Type::VERTEX_ADD_LABEL; + case Marker::DELTA_VERTEX_REMOVE_LABEL: + return WalDeltaData::Type::VERTEX_REMOVE_LABEL; + case Marker::DELTA_EDGE_CREATE: + return WalDeltaData::Type::EDGE_CREATE; + case Marker::DELTA_EDGE_DELETE: + return WalDeltaData::Type::EDGE_DELETE; + case Marker::DELTA_VERTEX_SET_PROPERTY: + return WalDeltaData::Type::VERTEX_SET_PROPERTY; + case Marker::DELTA_EDGE_SET_PROPERTY: + return WalDeltaData::Type::EDGE_SET_PROPERTY; + case Marker::DELTA_TRANSACTION_END: + return WalDeltaData::Type::TRANSACTION_END; + case Marker::DELTA_LABEL_INDEX_CREATE: + return WalDeltaData::Type::LABEL_INDEX_CREATE; + case Marker::DELTA_LABEL_INDEX_DROP: + return WalDeltaData::Type::LABEL_INDEX_DROP; + case Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE: + return WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE; + case Marker::DELTA_LABEL_PROPERTY_INDEX_DROP: + return WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP; + case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE: + return WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE; + case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP: + return WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP; + case Marker::DELTA_UNIQUE_CONSTRAINT_CREATE: + return WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE; + case Marker::DELTA_UNIQUE_CONSTRAINT_DROP: + return WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP; + + case Marker::TYPE_NULL: + case Marker::TYPE_BOOL: + case Marker::TYPE_INT: + case Marker::TYPE_DOUBLE: + case Marker::TYPE_STRING: + case Marker::TYPE_LIST: + case Marker::TYPE_MAP: + 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_OFFSETS: + case Marker::VALUE_FALSE: + case Marker::VALUE_TRUE: + throw RecoveryFailure("Invalid WAL data!"); + } +} + +bool IsWalDeltaDataTypeTransactionEnd(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: + return true; + + // 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 either read or skip the current WAL delta data. The WAL +// delta header must be read before calling this function. If the delta data is +// read then the data returned is valid, if the delta data is skipped then the +// returned data is not guaranteed to be set (it could be empty) and shouldn't +// be used. +// @throw RecoveryFailure +template <bool read_data> +WalDeltaData ReadSkipWalDeltaData(Decoder *wal) { + WalDeltaData delta; + + auto action = wal->ReadMarker(); + if (!action) throw RecoveryFailure("Invalid WAL data!"); + delta.type = MarkerToWalDeltaDataType(*action); + + switch (delta.type) { + case WalDeltaData::Type::VERTEX_CREATE: + case WalDeltaData::Type::VERTEX_DELETE: { + auto gid = wal->ReadUint(); + if (!gid) throw RecoveryFailure("Invalid WAL data!"); + delta.vertex_create_delete.gid = Gid::FromUint(*gid); + break; + } + case WalDeltaData::Type::VERTEX_ADD_LABEL: + case WalDeltaData::Type::VERTEX_REMOVE_LABEL: { + auto gid = wal->ReadUint(); + if (!gid) throw RecoveryFailure("Invalid WAL data!"); + delta.vertex_add_remove_label.gid = Gid::FromUint(*gid); + if constexpr (read_data) { + auto label = wal->ReadString(); + if (!label) throw RecoveryFailure("Invalid WAL data!"); + delta.vertex_add_remove_label.label = std::move(*label); + } else { + if (!wal->SkipString()) throw RecoveryFailure("Invalid WAL data!"); + } + break; + } + case WalDeltaData::Type::VERTEX_SET_PROPERTY: + case WalDeltaData::Type::EDGE_SET_PROPERTY: { + auto gid = wal->ReadUint(); + if (!gid) throw RecoveryFailure("Invalid WAL data!"); + delta.vertex_edge_set_property.gid = Gid::FromUint(*gid); + if constexpr (read_data) { + auto property = wal->ReadString(); + if (!property) throw RecoveryFailure("Invalid WAL data!"); + delta.vertex_edge_set_property.property = std::move(*property); + auto value = wal->ReadPropertyValue(); + if (!value) throw RecoveryFailure("Invalid WAL data!"); + delta.vertex_edge_set_property.value = std::move(*value); + } else { + if (!wal->SkipString() || !wal->SkipPropertyValue()) + throw RecoveryFailure("Invalid WAL data!"); + } + break; + } + case WalDeltaData::Type::EDGE_CREATE: + case WalDeltaData::Type::EDGE_DELETE: { + auto gid = wal->ReadUint(); + if (!gid) throw RecoveryFailure("Invalid WAL data!"); + delta.edge_create_delete.gid = Gid::FromUint(*gid); + if constexpr (read_data) { + auto edge_type = wal->ReadString(); + if (!edge_type) throw RecoveryFailure("Invalid WAL data!"); + delta.edge_create_delete.edge_type = std::move(*edge_type); + } else { + if (!wal->SkipString()) throw RecoveryFailure("Invalid WAL data!"); + } + auto from_gid = wal->ReadUint(); + if (!from_gid) throw RecoveryFailure("Invalid WAL data!"); + delta.edge_create_delete.from_vertex = Gid::FromUint(*from_gid); + auto to_gid = wal->ReadUint(); + if (!to_gid) throw RecoveryFailure("Invalid WAL data!"); + delta.edge_create_delete.to_vertex = Gid::FromUint(*to_gid); + break; + } + case WalDeltaData::Type::TRANSACTION_END: + break; + case WalDeltaData::Type::LABEL_INDEX_CREATE: + case WalDeltaData::Type::LABEL_INDEX_DROP: { + if constexpr (read_data) { + auto label = wal->ReadString(); + if (!label) throw RecoveryFailure("Invalid WAL data!"); + delta.operation_label.label = std::move(*label); + } else { + if (!wal->SkipString()) throw RecoveryFailure("Invalid WAL data!"); + } + break; + } + 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: { + if constexpr (read_data) { + auto label = wal->ReadString(); + if (!label) throw RecoveryFailure("Invalid WAL data!"); + delta.operation_label_property.label = std::move(*label); + auto property = wal->ReadString(); + if (!property) throw RecoveryFailure("Invalid WAL data!"); + delta.operation_label_property.property = std::move(*property); + } else { + if (!wal->SkipString() || !wal->SkipString()) + throw RecoveryFailure("Invalid WAL data!"); + } + break; + } + case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: + case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: { + if constexpr (read_data) { + auto label = wal->ReadString(); + if (!label) throw RecoveryFailure("Invalid WAL data!"); + delta.operation_label_properties.label = std::move(*label); + auto properties_count = wal->ReadUint(); + if (!properties_count) throw RecoveryFailure("Invalid WAL data!"); + for (uint64_t i = 0; i < *properties_count; ++i) { + auto property = wal->ReadString(); + if (!property) throw RecoveryFailure("Invalid WAL data!"); + delta.operation_label_properties.properties.emplace( + std::move(*property)); + } + } else { + if (!wal->SkipString()) throw RecoveryFailure("Invalid WAL data!"); + auto properties_count = wal->ReadUint(); + if (!properties_count) throw RecoveryFailure("Invalid WAL data!"); + for (uint64_t i = 0; i < *properties_count; ++i) { + if (!wal->SkipString()) throw RecoveryFailure("Invalid WAL data!"); + } + } + } + } + + return delta; +} + +} // namespace + +// Function used to read information about the WAL file. +WalInfo ReadWalInfo(const std::filesystem::path &path) { + // Check magic and version. + Decoder wal; + auto version = wal.Initialize(path, kWalMagic); + if (!version) + throw RecoveryFailure("Couldn't read WAL magic and/or version!"); + if (!IsVersionSupported(*version)) + throw RecoveryFailure("Invalid WAL version!"); + + // Prepare return value. + WalInfo info; + + // Read offsets. + { + auto marker = wal.ReadMarker(); + if (!marker || *marker != Marker::SECTION_OFFSETS) + throw RecoveryFailure("Invalid WAL data!"); + + auto wal_size = wal.GetSize(); + if (!wal_size) throw RecoveryFailure("Invalid WAL data!"); + + auto read_offset = [&wal, wal_size] { + auto maybe_offset = wal.ReadUint(); + if (!maybe_offset) throw RecoveryFailure("Invalid WAL format!"); + auto offset = *maybe_offset; + if (offset > *wal_size) throw RecoveryFailure("Invalid WAL format!"); + return offset; + }; + + info.offset_metadata = read_offset(); + info.offset_deltas = read_offset(); + } + + // Read metadata. + { + wal.SetPosition(info.offset_metadata); + + auto marker = wal.ReadMarker(); + if (!marker || *marker != Marker::SECTION_METADATA) + throw RecoveryFailure("Invalid WAL data!"); + + auto maybe_uuid = wal.ReadString(); + if (!maybe_uuid) throw RecoveryFailure("Invalid WAL data!"); + info.uuid = std::move(*maybe_uuid); + + auto maybe_seq_num = wal.ReadUint(); + if (!maybe_seq_num) throw RecoveryFailure("Invalid WAL data!"); + info.seq_num = *maybe_seq_num; + } + + // Read deltas. + info.num_deltas = 0; + auto validate_delta = [&wal]() -> std::optional<std::pair<uint64_t, bool>> { + try { + auto timestamp = ReadWalDeltaHeader(&wal); + auto type = SkipWalDeltaData(&wal); + return {{timestamp, IsWalDeltaDataTypeTransactionEnd(type)}}; + } catch (const RecoveryFailure &) { + return std::nullopt; + } + }; + auto size = wal.GetSize(); + // Here we read the whole file and determine the number of valid deltas. A + // delta is valid only if all of its data can be successfully read. This + // allows us to recover data from WAL files that are corrupt at the end (eg. + // because of power loss) but are still valid at the beginning. While reading + // the deltas we only count deltas which are a part of a fully valid + // transaction (indicated by a TRANSACTION_END delta or any other + // non-transactional operation). + std::optional<uint64_t> current_timestamp; + uint64_t num_deltas = 0; + while (wal.GetPosition() != size) { + auto ret = validate_delta(); + if (!ret) break; + auto [timestamp, is_end_of_transaction] = *ret; + if (!current_timestamp) current_timestamp = timestamp; + if (*current_timestamp != timestamp) break; + ++num_deltas; + if (is_end_of_transaction) { + if (info.num_deltas == 0) { + info.from_timestamp = timestamp; + info.to_timestamp = timestamp; + } + if (timestamp < info.from_timestamp || timestamp < info.to_timestamp) + break; + info.to_timestamp = timestamp; + info.num_deltas += num_deltas; + current_timestamp = std::nullopt; + num_deltas = 0; + } + } + + if (info.num_deltas == 0) throw RecoveryFailure("Invalid WAL data!"); + + return info; +} + +bool operator==(const WalDeltaData &a, const WalDeltaData &b) { + if (a.type != b.type) return false; + switch (a.type) { + case WalDeltaData::Type::VERTEX_CREATE: + case WalDeltaData::Type::VERTEX_DELETE: + return a.vertex_create_delete.gid == b.vertex_create_delete.gid; + + case WalDeltaData::Type::VERTEX_ADD_LABEL: + case WalDeltaData::Type::VERTEX_REMOVE_LABEL: + return a.vertex_add_remove_label.gid == b.vertex_add_remove_label.gid && + a.vertex_add_remove_label.label == b.vertex_add_remove_label.label; + + case WalDeltaData::Type::VERTEX_SET_PROPERTY: + case WalDeltaData::Type::EDGE_SET_PROPERTY: + return a.vertex_edge_set_property.gid == b.vertex_edge_set_property.gid && + a.vertex_edge_set_property.property == + b.vertex_edge_set_property.property && + a.vertex_edge_set_property.value == + b.vertex_edge_set_property.value; + + case WalDeltaData::Type::EDGE_CREATE: + case WalDeltaData::Type::EDGE_DELETE: + return a.edge_create_delete.gid == b.edge_create_delete.gid && + a.edge_create_delete.edge_type == b.edge_create_delete.edge_type && + a.edge_create_delete.from_vertex == + b.edge_create_delete.from_vertex && + a.edge_create_delete.to_vertex == b.edge_create_delete.to_vertex; + + case WalDeltaData::Type::TRANSACTION_END: + return true; + + case WalDeltaData::Type::LABEL_INDEX_CREATE: + case WalDeltaData::Type::LABEL_INDEX_DROP: + return a.operation_label.label == b.operation_label.label; + + 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: + return a.operation_label_property.label == + b.operation_label_property.label && + a.operation_label_property.property == + b.operation_label_property.property; + case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: + case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: + return a.operation_label_properties.label == + b.operation_label_properties.label && + a.operation_label_properties.properties == + b.operation_label_properties.properties; + } +} +bool operator!=(const WalDeltaData &a, const WalDeltaData &b) { + return !(a == b); +} + +// Function used to read the WAL delta header. The function returns the delta +// timestamp. +uint64_t ReadWalDeltaHeader(Decoder *wal) { + auto marker = wal->ReadMarker(); + if (!marker || *marker != Marker::SECTION_DELTA) + throw RecoveryFailure("Invalid WAL data!"); + + auto timestamp = wal->ReadUint(); + if (!timestamp) throw RecoveryFailure("Invalid WAL data!"); + return *timestamp; +} + +// Function used to read the current WAL delta data. The WAL delta header must +// be read before calling this function. +WalDeltaData ReadWalDeltaData(Decoder *wal) { + return ReadSkipWalDeltaData<true>(wal); +} + +// Function used to skip the current WAL delta data. The WAL delta header must +// be read before calling this function. +WalDeltaData::Type SkipWalDeltaData(Decoder *wal) { + auto delta = ReadSkipWalDeltaData<false>(wal); + return delta.type; +} + +WalFile::WalFile(const std::filesystem::path &wal_directory, + const std::string &uuid, Config::Items items, + NameIdMapper *name_id_mapper, uint64_t seq_num) + : items_(items), + name_id_mapper_(name_id_mapper), + path_(wal_directory / MakeWalName()), + from_timestamp_(0), + to_timestamp_(0), + count_(0) { + // Ensure that the storage directory exists. + utils::EnsureDirOrDie(wal_directory); + + // Initialize the WAL file. + wal_.Initialize(path_, kWalMagic, kVersion); + + // Write placeholder offsets. + uint64_t offset_offsets = 0; + uint64_t offset_metadata = 0; + uint64_t offset_deltas = 0; + wal_.WriteMarker(Marker::SECTION_OFFSETS); + offset_offsets = wal_.GetPosition(); + wal_.WriteUint(offset_metadata); + wal_.WriteUint(offset_deltas); + + // Write metadata. + offset_metadata = wal_.GetPosition(); + wal_.WriteMarker(Marker::SECTION_METADATA); + wal_.WriteString(uuid); + wal_.WriteUint(seq_num); + + // Write final offsets. + offset_deltas = wal_.GetPosition(); + wal_.SetPosition(offset_offsets); + wal_.WriteUint(offset_metadata); + wal_.WriteUint(offset_deltas); + wal_.SetPosition(offset_deltas); + + // Sync the initial data. + wal_.Sync(); +} + +WalFile::~WalFile() { + if (count_ != 0) { + // Finalize file. + wal_.Finalize(); + + // Rename file. + std::filesystem::path new_path(path_); + new_path.replace_filename( + RemakeWalName(path_.filename(), from_timestamp_, to_timestamp_)); + // If the rename fails it isn't a crucial situation. The renaming is done + // only to make the directory structure of the WAL files easier to read + // manually. + utils::RenamePath(path_, new_path); + } else { + // Remove empty WAL file. + utils::DeleteFile(path_); + } +} + +void WalFile::AppendDelta(const Delta &delta, const Vertex &vertex, + uint64_t timestamp) { + // When converting a Delta to a WAL delta the logic is inverted. That is + // because the Delta's represent undo actions and we want to store redo + // actions. + wal_.WriteMarker(Marker::SECTION_DELTA); + wal_.WriteUint(timestamp); + std::lock_guard<utils::SpinLock> guard(vertex.lock); + switch (delta.action) { + case Delta::Action::DELETE_OBJECT: + case Delta::Action::RECREATE_OBJECT: { + wal_.WriteMarker(VertexActionToMarker(delta.action)); + wal_.WriteUint(vertex.gid.AsUint()); + break; + } + case Delta::Action::SET_PROPERTY: { + wal_.WriteMarker(Marker::DELTA_VERTEX_SET_PROPERTY); + wal_.WriteUint(vertex.gid.AsUint()); + wal_.WriteString(name_id_mapper_->IdToName(delta.property.key.AsUint())); + // The property value is the value that is currently stored in the + // vertex. + // TODO (mferencevic): Mitigate the memory allocation introduced here + // (with the `GetProperty` call). It is the only memory allocation in the + // entire WAL file writing logic. + wal_.WritePropertyValue( + vertex.properties.GetProperty(delta.property.key)); + break; + } + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: { + wal_.WriteMarker(VertexActionToMarker(delta.action)); + wal_.WriteUint(vertex.gid.AsUint()); + wal_.WriteString(name_id_mapper_->IdToName(delta.label.AsUint())); + break; + } + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: { + wal_.WriteMarker(VertexActionToMarker(delta.action)); + if (items_.properties_on_edges) { + wal_.WriteUint(delta.vertex_edge.edge.ptr->gid.AsUint()); + } else { + wal_.WriteUint(delta.vertex_edge.edge.gid.AsUint()); + } + wal_.WriteString( + name_id_mapper_->IdToName(delta.vertex_edge.edge_type.AsUint())); + wal_.WriteUint(vertex.gid.AsUint()); + wal_.WriteUint(delta.vertex_edge.vertex->gid.AsUint()); + break; + } + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + // These actions are already encoded in the *_OUT_EDGE actions. This + // function should never be called for this type of deltas. + LOG(FATAL) << "Invalid delta action!"; + } + UpdateStats(timestamp); +} + +void WalFile::AppendDelta(const Delta &delta, const Edge &edge, + uint64_t timestamp) { + // When converting a Delta to a WAL delta the logic is inverted. That is + // because the Delta's represent undo actions and we want to store redo + // actions. + wal_.WriteMarker(Marker::SECTION_DELTA); + wal_.WriteUint(timestamp); + std::lock_guard<utils::SpinLock> guard(edge.lock); + switch (delta.action) { + case Delta::Action::SET_PROPERTY: { + wal_.WriteMarker(Marker::DELTA_EDGE_SET_PROPERTY); + wal_.WriteUint(edge.gid.AsUint()); + wal_.WriteString(name_id_mapper_->IdToName(delta.property.key.AsUint())); + // The property value is the value that is currently stored in the + // edge. + // TODO (mferencevic): Mitigate the memory allocation introduced here + // (with the `GetProperty` call). It is the only memory allocation in the + // entire WAL file writing logic. + wal_.WritePropertyValue(edge.properties.GetProperty(delta.property.key)); + break; + } + case Delta::Action::DELETE_OBJECT: + case Delta::Action::RECREATE_OBJECT: + // These actions are already encoded in vertex *_OUT_EDGE actions. Also, + // these deltas don't contain any information about the from vertex, to + // vertex or edge type so they are useless. This function should never + // be called for this type of deltas. + LOG(FATAL) << "Invalid delta action!"; + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + // These deltas shouldn't appear for edges. + LOG(FATAL) << "Invalid database state!"; + } + UpdateStats(timestamp); +} + +void WalFile::AppendTransactionEnd(uint64_t timestamp) { + wal_.WriteMarker(Marker::SECTION_DELTA); + wal_.WriteUint(timestamp); + wal_.WriteMarker(Marker::DELTA_TRANSACTION_END); + UpdateStats(timestamp); +} + +void WalFile::AppendOperation(StorageGlobalOperation operation, LabelId label, + const std::set<PropertyId> &properties, + uint64_t timestamp) { + wal_.WriteMarker(Marker::SECTION_DELTA); + wal_.WriteUint(timestamp); + switch (operation) { + case StorageGlobalOperation::LABEL_INDEX_CREATE: + case StorageGlobalOperation::LABEL_INDEX_DROP: { + CHECK(properties.empty()) << "Invalid function call!"; + wal_.WriteMarker(OperationToMarker(operation)); + wal_.WriteString(name_id_mapper_->IdToName(label.AsUint())); + break; + } + case StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE: + case StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP: + case StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE: + case StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP: { + CHECK(properties.size() == 1) << "Invalid function call!"; + wal_.WriteMarker(OperationToMarker(operation)); + wal_.WriteString(name_id_mapper_->IdToName(label.AsUint())); + wal_.WriteString( + name_id_mapper_->IdToName((*properties.begin()).AsUint())); + break; + } + case StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE: + case StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP: { + CHECK(!properties.empty()) << "Invalid function call!"; + wal_.WriteMarker(OperationToMarker(operation)); + wal_.WriteString(name_id_mapper_->IdToName(label.AsUint())); + wal_.WriteUint(properties.size()); + for (const auto &property : properties) { + wal_.WriteString(name_id_mapper_->IdToName(property.AsUint())); + } + break; + } + } + UpdateStats(timestamp); +} + +void WalFile::Sync() { wal_.Sync(); } + +uint64_t WalFile::GetSize() { return wal_.GetPosition(); } + +void WalFile::UpdateStats(uint64_t timestamp) { + if (count_ == 0) from_timestamp_ = timestamp; + to_timestamp_ = timestamp; + count_ += 1; +} + +} // namespace storage::durability diff --git a/src/storage/v2/durability/wal.hpp b/src/storage/v2/durability/wal.hpp new file mode 100644 index 000000000..27819c741 --- /dev/null +++ b/src/storage/v2/durability/wal.hpp @@ -0,0 +1,163 @@ +#pragma once + +#include <cstdint> +#include <filesystem> +#include <set> +#include <string> + +#include "storage/v2/delta.hpp" +#include "storage/v2/durability/serialization.hpp" +#include "storage/v2/id_types.hpp" +#include "storage/v2/property_value.hpp" + +namespace storage::durability { + +/// Structure used to hold information about a WAL. +struct WalInfo { + uint64_t offset_metadata; + uint64_t offset_deltas; + + std::string uuid; + 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, +}; + +/// 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(Decoder *wal); + +/// 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(Decoder *wal); + +/// 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(Decoder *wal); + +/// WalFile class used to append deltas and operations to the WAL file. +class WalFile { + public: + WalFile(const std::filesystem::path &wal_directory, const std::string &uuid, + Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num); + + 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(); + + 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_; +}; + +} // namespace storage::durability diff --git a/src/storage/v2/storage.cpp b/src/storage/v2/storage.cpp index e29530812..02d7f3b5e 100644 --- a/src/storage/v2/storage.cpp +++ b/src/storage/v2/storage.cpp @@ -968,8 +968,9 @@ bool Storage::CreateIndex(LabelId label) { // next regular transaction after this operation. This prevents collisions of // commit timestamps between non-transactional operations and transactional // operations. - durability_.AppendToWal(StorageGlobalOperation::LABEL_INDEX_CREATE, label, {}, - timestamp_); + durability_.AppendToWal( + durability::StorageGlobalOperation::LABEL_INDEX_CREATE, label, {}, + timestamp_); return true; } @@ -980,8 +981,9 @@ bool Storage::CreateIndex(LabelId label, PropertyId property) { return false; // For a description why using `timestamp_` is correct, see // `CreateIndex(LabelId label)`. - durability_.AppendToWal(StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE, - label, {property}, timestamp_); + durability_.AppendToWal( + durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE, label, + {property}, timestamp_); return true; } @@ -990,8 +992,8 @@ bool Storage::DropIndex(LabelId label) { if (!indices_.label_index.DropIndex(label)) return false; // For a description why using `timestamp_` is correct, see // `CreateIndex(LabelId label)`. - durability_.AppendToWal(StorageGlobalOperation::LABEL_INDEX_DROP, label, {}, - timestamp_); + durability_.AppendToWal(durability::StorageGlobalOperation::LABEL_INDEX_DROP, + label, {}, timestamp_); return true; } @@ -1000,8 +1002,9 @@ bool Storage::DropIndex(LabelId label, PropertyId property) { if (!indices_.label_property_index.DropIndex(label, property)) return false; // For a description why using `timestamp_` is correct, see // `CreateIndex(LabelId label)`. - durability_.AppendToWal(StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP, - label, {property}, timestamp_); + durability_.AppendToWal( + durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP, label, + {property}, timestamp_); return true; } @@ -1019,8 +1022,9 @@ Storage::CreateExistenceConstraint(LabelId label, PropertyId property) { if (ret.HasError() || !ret.GetValue()) return ret; // For a description why using `timestamp_` is correct, see // `CreateIndex(LabelId label)`. - durability_.AppendToWal(StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE, - label, {property}, timestamp_); + durability_.AppendToWal( + durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE, label, + {property}, timestamp_); return true; } @@ -1030,8 +1034,9 @@ bool Storage::DropExistenceConstraint(LabelId label, PropertyId property) { return false; // For a description why using `timestamp_` is correct, see // `CreateIndex(LabelId label)`. - durability_.AppendToWal(StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP, - label, {property}, timestamp_); + durability_.AppendToWal( + durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP, label, + {property}, timestamp_); return true; } @@ -1047,8 +1052,9 @@ Storage::CreateUniqueConstraint(LabelId label, } // For a description why using `timestamp_` is correct, see // `CreateIndex(LabelId label)`. - durability_.AppendToWal(StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE, - label, properties, timestamp_); + durability_.AppendToWal( + durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE, label, + properties, timestamp_); return UniqueConstraints::CreationStatus::SUCCESS; } @@ -1061,8 +1067,9 @@ UniqueConstraints::DeletionStatus Storage::DropUniqueConstraint( } // For a description why using `timestamp_` is correct, see // `CreateIndex(LabelId label)`. - durability_.AppendToWal(StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP, label, - properties, timestamp_); + durability_.AppendToWal( + durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP, label, + properties, timestamp_); return UniqueConstraints::DeletionStatus::SUCCESS; } diff --git a/src/storage/v2/storage.hpp b/src/storage/v2/storage.hpp index 2b9e73161..ea516c600 100644 --- a/src/storage/v2/storage.hpp +++ b/src/storage/v2/storage.hpp @@ -6,7 +6,7 @@ #include "storage/v2/commit_log.hpp" #include "storage/v2/config.hpp" #include "storage/v2/constraints.hpp" -#include "storage/v2/durability.hpp" +#include "storage/v2/durability/durability.hpp" #include "storage/v2/edge.hpp" #include "storage/v2/edge_accessor.hpp" #include "storage/v2/indices.hpp" @@ -442,7 +442,7 @@ class Storage final { // storage. utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_edges_; - Durability durability_; + durability::Durability durability_; }; } // namespace storage diff --git a/tests/unit/storage_v2_decoder_encoder.cpp b/tests/unit/storage_v2_decoder_encoder.cpp index d4a658e72..a20714664 100644 --- a/tests/unit/storage_v2_decoder_encoder.cpp +++ b/tests/unit/storage_v2_decoder_encoder.cpp @@ -3,7 +3,7 @@ #include <filesystem> #include <limits> -#include "storage/v2/durability.hpp" +#include "storage/v2/durability/serialization.hpp" static const std::string kTestMagic{"MGtest"}; static const uint64_t kTestVersion{1}; @@ -36,9 +36,9 @@ class DecoderEncoderTest : public ::testing::Test { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(DecoderEncoderTest, ReadMarker) { { - storage::Encoder encoder; + storage::durability::Encoder encoder; encoder.Initialize(storage_file, kTestMagic, kTestVersion); - for (const auto &item : storage::kMarkersAll) { + for (const auto &item : storage::durability::kMarkersAll) { encoder.WriteMarker(item); } { @@ -48,11 +48,11 @@ TEST_F(DecoderEncoderTest, ReadMarker) { encoder.Finalize(); } { - storage::Decoder decoder; + storage::durability::Decoder decoder; auto version = decoder.Initialize(storage_file, kTestMagic); ASSERT_TRUE(version); ASSERT_EQ(*version, kTestVersion); - for (const auto &item : storage::kMarkersAll) { + for (const auto &item : storage::durability::kMarkersAll) { auto decoded = decoder.ReadMarker(); ASSERT_TRUE(decoded); ASSERT_EQ(*decoded, item); @@ -70,7 +70,7 @@ TEST_F(DecoderEncoderTest, ReadMarker) { TEST_F(DecoderEncoderTest, Read##name) { \ std::vector<type> dataset{__VA_ARGS__}; \ { \ - storage::Encoder encoder; \ + storage::durability::Encoder encoder; \ encoder.Initialize(storage_file, kTestMagic, kTestVersion); \ for (const auto &item : dataset) { \ encoder.Write##name(item); \ @@ -82,7 +82,7 @@ TEST_F(DecoderEncoderTest, ReadMarker) { encoder.Finalize(); \ } \ { \ - storage::Decoder decoder; \ + storage::durability::Decoder decoder; \ auto version = decoder.Initialize(storage_file, kTestMagic); \ ASSERT_TRUE(version); \ ASSERT_EQ(*version, kTestVersion); \ @@ -131,7 +131,7 @@ GENERATE_READ_TEST( TEST_F(DecoderEncoderTest, Skip##name) { \ std::vector<type> dataset{__VA_ARGS__}; \ { \ - storage::Encoder encoder; \ + storage::durability::Encoder encoder; \ encoder.Initialize(storage_file, kTestMagic, kTestVersion); \ for (const auto &item : dataset) { \ encoder.Write##name(item); \ @@ -143,7 +143,7 @@ GENERATE_READ_TEST( encoder.Finalize(); \ } \ { \ - storage::Decoder decoder; \ + storage::durability::Decoder decoder; \ auto version = decoder.Initialize(storage_file, kTestMagic); \ ASSERT_TRUE(version); \ ASSERT_EQ(*version, kTestVersion); \ @@ -177,7 +177,7 @@ GENERATE_SKIP_TEST( #define GENERATE_PARTIAL_READ_TEST(name, value) \ TEST_F(DecoderEncoderTest, PartialRead##name) { \ { \ - storage::Encoder encoder; \ + storage::durability::Encoder encoder; \ encoder.Initialize(storage_file, kTestMagic, kTestVersion); \ encoder.Write##name(value); \ encoder.Finalize(); \ @@ -195,7 +195,7 @@ GENERATE_SKIP_TEST( ofile.Write(&byte, sizeof(byte)); \ ofile.Sync(); \ } \ - storage::Decoder decoder; \ + storage::durability::Decoder decoder; \ auto version = decoder.Initialize(alternate_file, kTestMagic); \ if (i < kTestMagic.size() + sizeof(kTestVersion)) { \ ASSERT_FALSE(version); \ @@ -215,7 +215,7 @@ GENERATE_SKIP_TEST( } // NOLINTNEXTLINE(hicpp-special-member-functions) -GENERATE_PARTIAL_READ_TEST(Marker, storage::Marker::SECTION_VERTEX); +GENERATE_PARTIAL_READ_TEST(Marker, storage::durability::Marker::SECTION_VERTEX); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_PARTIAL_READ_TEST(Bool, false); @@ -243,7 +243,7 @@ GENERATE_PARTIAL_READ_TEST( #define GENERATE_PARTIAL_SKIP_TEST(name, value) \ TEST_F(DecoderEncoderTest, PartialSkip##name) { \ { \ - storage::Encoder encoder; \ + storage::durability::Encoder encoder; \ encoder.Initialize(storage_file, kTestMagic, kTestVersion); \ encoder.Write##name(value); \ encoder.Finalize(); \ @@ -261,7 +261,7 @@ GENERATE_PARTIAL_READ_TEST( ofile.Write(&byte, sizeof(byte)); \ ofile.Sync(); \ } \ - storage::Decoder decoder; \ + storage::durability::Decoder decoder; \ auto version = decoder.Initialize(alternate_file, kTestMagic); \ if (i < kTestMagic.size() + sizeof(kTestVersion)) { \ ASSERT_FALSE(version); \ @@ -294,7 +294,7 @@ GENERATE_PARTIAL_SKIP_TEST( // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(DecoderEncoderTest, PropertyValueInvalidMarker) { { - storage::Encoder encoder; + storage::durability::Encoder encoder; encoder.Initialize(storage_file, kTestMagic, kTestVersion); encoder.WritePropertyValue(storage::PropertyValue(123L)); encoder.Finalize(); @@ -302,68 +302,69 @@ TEST_F(DecoderEncoderTest, PropertyValueInvalidMarker) { { utils::OutputFile file; file.Open(storage_file, utils::OutputFile::Mode::OVERWRITE_EXISTING); - for (auto marker : storage::kMarkersAll) { + for (auto marker : storage::durability::kMarkersAll) { bool valid_marker; switch (marker) { - case storage::Marker::TYPE_NULL: - case storage::Marker::TYPE_BOOL: - case storage::Marker::TYPE_INT: - case storage::Marker::TYPE_DOUBLE: - case storage::Marker::TYPE_STRING: - case storage::Marker::TYPE_LIST: - case storage::Marker::TYPE_MAP: - case storage::Marker::TYPE_PROPERTY_VALUE: + case storage::durability::Marker::TYPE_NULL: + case storage::durability::Marker::TYPE_BOOL: + case storage::durability::Marker::TYPE_INT: + case storage::durability::Marker::TYPE_DOUBLE: + case storage::durability::Marker::TYPE_STRING: + case storage::durability::Marker::TYPE_LIST: + case storage::durability::Marker::TYPE_MAP: + case storage::durability::Marker::TYPE_PROPERTY_VALUE: valid_marker = true; break; - case storage::Marker::SECTION_VERTEX: - case storage::Marker::SECTION_EDGE: - case storage::Marker::SECTION_MAPPER: - case storage::Marker::SECTION_METADATA: - case storage::Marker::SECTION_INDICES: - case storage::Marker::SECTION_CONSTRAINTS: - case storage::Marker::SECTION_DELTA: - case storage::Marker::SECTION_OFFSETS: - case storage::Marker::DELTA_VERTEX_CREATE: - case storage::Marker::DELTA_VERTEX_DELETE: - case storage::Marker::DELTA_VERTEX_ADD_LABEL: - case storage::Marker::DELTA_VERTEX_REMOVE_LABEL: - case storage::Marker::DELTA_VERTEX_SET_PROPERTY: - case storage::Marker::DELTA_EDGE_CREATE: - case storage::Marker::DELTA_EDGE_DELETE: - case storage::Marker::DELTA_EDGE_SET_PROPERTY: - case storage::Marker::DELTA_TRANSACTION_END: - case storage::Marker::DELTA_LABEL_INDEX_CREATE: - case storage::Marker::DELTA_LABEL_INDEX_DROP: - case storage::Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE: - case storage::Marker::DELTA_LABEL_PROPERTY_INDEX_DROP: - case storage::Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE: - case storage::Marker::DELTA_EXISTENCE_CONSTRAINT_DROP: - case storage::Marker::DELTA_UNIQUE_CONSTRAINT_CREATE: - case storage::Marker::DELTA_UNIQUE_CONSTRAINT_DROP: - case storage::Marker::VALUE_FALSE: - case storage::Marker::VALUE_TRUE: + case storage::durability::Marker::SECTION_VERTEX: + case storage::durability::Marker::SECTION_EDGE: + case storage::durability::Marker::SECTION_MAPPER: + case storage::durability::Marker::SECTION_METADATA: + case storage::durability::Marker::SECTION_INDICES: + case storage::durability::Marker::SECTION_CONSTRAINTS: + case storage::durability::Marker::SECTION_DELTA: + case storage::durability::Marker::SECTION_OFFSETS: + case storage::durability::Marker::DELTA_VERTEX_CREATE: + case storage::durability::Marker::DELTA_VERTEX_DELETE: + case storage::durability::Marker::DELTA_VERTEX_ADD_LABEL: + case storage::durability::Marker::DELTA_VERTEX_REMOVE_LABEL: + case storage::durability::Marker::DELTA_VERTEX_SET_PROPERTY: + case storage::durability::Marker::DELTA_EDGE_CREATE: + case storage::durability::Marker::DELTA_EDGE_DELETE: + case storage::durability::Marker::DELTA_EDGE_SET_PROPERTY: + case storage::durability::Marker::DELTA_TRANSACTION_END: + case storage::durability::Marker::DELTA_LABEL_INDEX_CREATE: + case storage::durability::Marker::DELTA_LABEL_INDEX_DROP: + case storage::durability::Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE: + case storage::durability::Marker::DELTA_LABEL_PROPERTY_INDEX_DROP: + case storage::durability::Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE: + case storage::durability::Marker::DELTA_EXISTENCE_CONSTRAINT_DROP: + case storage::durability::Marker::DELTA_UNIQUE_CONSTRAINT_CREATE: + case storage::durability::Marker::DELTA_UNIQUE_CONSTRAINT_DROP: + case storage::durability::Marker::VALUE_FALSE: + case storage::durability::Marker::VALUE_TRUE: valid_marker = false; break; } // We only run this test with invalid markers. if (valid_marker) continue; { - file.SetPosition(utils::OutputFile::Position::RELATIVE_TO_END, - -(sizeof(uint64_t) + sizeof(storage::Marker))); + file.SetPosition( + utils::OutputFile::Position::RELATIVE_TO_END, + -(sizeof(uint64_t) + sizeof(storage::durability::Marker))); auto byte = static_cast<uint8_t>(marker); file.Write(&byte, sizeof(byte)); file.Sync(); } { - storage::Decoder decoder; + storage::durability::Decoder decoder; auto version = decoder.Initialize(storage_file, kTestMagic); ASSERT_TRUE(version); ASSERT_EQ(*version, kTestVersion); ASSERT_FALSE(decoder.SkipPropertyValue()); } { - storage::Decoder decoder; + storage::durability::Decoder decoder; auto version = decoder.Initialize(storage_file, kTestMagic); ASSERT_TRUE(version); ASSERT_EQ(*version, kTestVersion); @@ -372,21 +373,22 @@ TEST_F(DecoderEncoderTest, PropertyValueInvalidMarker) { } { { - file.SetPosition(utils::OutputFile::Position::RELATIVE_TO_END, - -(sizeof(uint64_t) + sizeof(storage::Marker))); + file.SetPosition( + utils::OutputFile::Position::RELATIVE_TO_END, + -(sizeof(uint64_t) + sizeof(storage::durability::Marker))); uint8_t byte = 1; file.Write(&byte, sizeof(byte)); file.Sync(); } { - storage::Decoder decoder; + storage::durability::Decoder decoder; auto version = decoder.Initialize(storage_file, kTestMagic); ASSERT_TRUE(version); ASSERT_EQ(*version, kTestVersion); ASSERT_FALSE(decoder.SkipPropertyValue()); } { - storage::Decoder decoder; + storage::durability::Decoder decoder; auto version = decoder.Initialize(storage_file, kTestMagic); ASSERT_TRUE(version); ASSERT_EQ(*version, kTestVersion); @@ -399,13 +401,13 @@ TEST_F(DecoderEncoderTest, PropertyValueInvalidMarker) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(DecoderEncoderTest, DecoderPosition) { { - storage::Encoder encoder; + storage::durability::Encoder encoder; encoder.Initialize(storage_file, kTestMagic, kTestVersion); encoder.WriteBool(true); encoder.Finalize(); } { - storage::Decoder decoder; + storage::durability::Decoder decoder; auto version = decoder.Initialize(storage_file, kTestMagic); ASSERT_TRUE(version); ASSERT_EQ(*version, kTestVersion); @@ -425,7 +427,7 @@ TEST_F(DecoderEncoderTest, DecoderPosition) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(DecoderEncoderTest, EncoderPosition) { { - storage::Encoder encoder; + storage::durability::Encoder encoder; encoder.Initialize(storage_file, kTestMagic, kTestVersion); encoder.WriteBool(false); encoder.SetPosition(kTestMagic.size() + sizeof(kTestVersion)); @@ -434,7 +436,7 @@ TEST_F(DecoderEncoderTest, EncoderPosition) { encoder.Finalize(); } { - storage::Decoder decoder; + storage::durability::Decoder decoder; auto version = decoder.Initialize(storage_file, kTestMagic); ASSERT_TRUE(version); ASSERT_EQ(*version, kTestVersion); diff --git a/tests/unit/storage_v2_durability.cpp b/tests/unit/storage_v2_durability.cpp index 4a8817f30..a1531d1b1 100644 --- a/tests/unit/storage_v2_durability.cpp +++ b/tests/unit/storage_v2_durability.cpp @@ -13,7 +13,8 @@ #include <iostream> #include <thread> -#include "storage/v2/durability.hpp" +#include "storage/v2/durability/paths.hpp" +#include "storage/v2/durability/version.hpp" #include "storage/v2/storage.hpp" #include "utils/file.hpp" #include "utils/timer.hpp" @@ -621,21 +622,24 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { } std::vector<std::filesystem::path> GetSnapshotsList() { - return GetFilesList(storage_directory / storage::kSnapshotDirectory); + return GetFilesList(storage_directory / + storage::durability::kSnapshotDirectory); } std::vector<std::filesystem::path> GetBackupSnapshotsList() { - return GetFilesList(storage_directory / storage::kBackupDirectory / - storage::kSnapshotDirectory); + return GetFilesList(storage_directory / + storage::durability::kBackupDirectory / + storage::durability::kSnapshotDirectory); } std::vector<std::filesystem::path> GetWalsList() { - return GetFilesList(storage_directory / storage::kWalDirectory); + return GetFilesList(storage_directory / storage::durability::kWalDirectory); } std::vector<std::filesystem::path> GetBackupWalsList() { - return GetFilesList(storage_directory / storage::kBackupDirectory / - storage::kWalDirectory); + return GetFilesList(storage_directory / + storage::durability::kBackupDirectory / + storage::durability::kWalDirectory); } void RestoreBackups() { @@ -643,15 +647,16 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { auto backup_snapshots = GetBackupSnapshotsList(); for (const auto &item : backup_snapshots) { std::filesystem::rename( - item, - storage_directory / storage::kSnapshotDirectory / item.filename()); + item, storage_directory / storage::durability::kSnapshotDirectory / + item.filename()); } } { auto backup_wals = GetBackupWalsList(); for (const auto &item : backup_wals) { - std::filesystem::rename( - item, storage_directory / storage::kWalDirectory / item.filename()); + std::filesystem::rename(item, storage_directory / + storage::durability::kWalDirectory / + item.filename()); } } } @@ -685,31 +690,31 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { }; void DestroySnapshot(const std::filesystem::path &path) { - auto info = storage::ReadSnapshotInfo(path); + auto info = storage::durability::ReadSnapshotInfo(path); LOG(INFO) << "Destroying snapshot " << path; utils::OutputFile file; file.Open(path, utils::OutputFile::Mode::OVERWRITE_EXISTING); file.SetPosition(utils::OutputFile::Position::SET, info.offset_vertices); - auto value = static_cast<uint8_t>(storage::Marker::TYPE_MAP); + auto value = static_cast<uint8_t>(storage::durability::Marker::TYPE_MAP); file.Write(&value, sizeof(value)); file.Sync(); file.Close(); } void DestroyWalFirstDelta(const std::filesystem::path &path) { - auto info = storage::ReadWalInfo(path); + auto info = storage::durability::ReadWalInfo(path); LOG(INFO) << "Destroying WAL " << path; utils::OutputFile file; file.Open(path, utils::OutputFile::Mode::OVERWRITE_EXISTING); file.SetPosition(utils::OutputFile::Position::SET, info.offset_deltas); - auto value = static_cast<uint8_t>(storage::Marker::TYPE_MAP); + auto value = static_cast<uint8_t>(storage::durability::Marker::TYPE_MAP); file.Write(&value, sizeof(value)); file.Sync(); file.Close(); } void DestroyWalSuffix(const std::filesystem::path &path) { - auto info = storage::ReadWalInfo(path); + auto info = storage::durability::ReadWalInfo(path); LOG(INFO) << "Destroying WAL " << path; utils::OutputFile file; file.Open(path, utils::OutputFile::Mode::OVERWRITE_EXISTING); @@ -868,7 +873,7 @@ TEST_P(DurabilityTest, SnapshotEverythingCorrupt) { { auto snapshots = GetSnapshotsList(); ASSERT_EQ(snapshots.size(), 1); - auto info = storage::ReadSnapshotInfo(*snapshots.begin()); + auto info = storage::durability::ReadSnapshotInfo(*snapshots.begin()); unrelated_uuid = info.uuid; } @@ -904,7 +909,7 @@ TEST_P(DurabilityTest, SnapshotEverythingCorrupt) { auto snapshots = GetSnapshotsList(); ASSERT_GE(snapshots.size(), 2); for (const auto &snapshot : snapshots) { - auto info = storage::ReadSnapshotInfo(snapshot); + auto info = storage::durability::ReadSnapshotInfo(snapshot); if (info.uuid == unrelated_uuid) { LOG(INFO) << "Skipping snapshot " << snapshot; continue; @@ -973,7 +978,7 @@ TEST_P(DurabilityTest, SnapshotRetention) { for (size_t i = 0; i < snapshots.size(); ++i) { const auto &path = snapshots[i]; // This shouldn't throw. - auto info = storage::ReadSnapshotInfo(path); + auto info = storage::durability::ReadSnapshotInfo(path); if (i == 0) uuid = info.uuid; if (i < snapshots.size() - 1) { ASSERT_EQ(info.uuid, uuid); @@ -1655,15 +1660,15 @@ TEST_P(DurabilityTest, WalTransactionOrdering) { // Verify WAL data. { auto path = GetWalsList().front(); - auto info = storage::ReadWalInfo(path); - storage::Decoder wal; - wal.Initialize(path, storage::kWalMagic); + auto info = storage::durability::ReadWalInfo(path); + storage::durability::Decoder wal; + wal.Initialize(path, storage::durability::kWalMagic); wal.SetPosition(info.offset_deltas); ASSERT_EQ(info.num_deltas, 9); - std::vector<std::pair<uint64_t, storage::WalDeltaData>> data; + std::vector<std::pair<uint64_t, storage::durability::WalDeltaData>> data; for (uint64_t i = 0; i < info.num_deltas; ++i) { - auto timestamp = storage::ReadWalDeltaHeader(&wal); - data.emplace_back(timestamp, storage::ReadWalDeltaData(&wal)); + auto timestamp = storage::durability::ReadWalDeltaHeader(&wal); + data.emplace_back(timestamp, storage::durability::ReadWalDeltaData(&wal)); } // Verify timestamps. ASSERT_EQ(data[1].first, data[0].first); @@ -1675,38 +1680,41 @@ TEST_P(DurabilityTest, WalTransactionOrdering) { ASSERT_EQ(data[7].first, data[6].first); ASSERT_EQ(data[8].first, data[7].first); // Verify transaction 3. - ASSERT_EQ(data[0].second.type, storage::WalDeltaData::Type::VERTEX_CREATE); + ASSERT_EQ(data[0].second.type, + storage::durability::WalDeltaData::Type::VERTEX_CREATE); ASSERT_EQ(data[0].second.vertex_create_delete.gid, gid3); ASSERT_EQ(data[1].second.type, - storage::WalDeltaData::Type::VERTEX_SET_PROPERTY); + storage::durability::WalDeltaData::Type::VERTEX_SET_PROPERTY); ASSERT_EQ(data[1].second.vertex_edge_set_property.gid, gid3); ASSERT_EQ(data[1].second.vertex_edge_set_property.property, "id"); ASSERT_EQ(data[1].second.vertex_edge_set_property.value, storage::PropertyValue(3)); ASSERT_EQ(data[2].second.type, - storage::WalDeltaData::Type::TRANSACTION_END); + storage::durability::WalDeltaData::Type::TRANSACTION_END); // Verify transaction 1. - ASSERT_EQ(data[3].second.type, storage::WalDeltaData::Type::VERTEX_CREATE); + ASSERT_EQ(data[3].second.type, + storage::durability::WalDeltaData::Type::VERTEX_CREATE); ASSERT_EQ(data[3].second.vertex_create_delete.gid, gid1); ASSERT_EQ(data[4].second.type, - storage::WalDeltaData::Type::VERTEX_SET_PROPERTY); + storage::durability::WalDeltaData::Type::VERTEX_SET_PROPERTY); ASSERT_EQ(data[4].second.vertex_edge_set_property.gid, gid1); ASSERT_EQ(data[4].second.vertex_edge_set_property.property, "id"); ASSERT_EQ(data[4].second.vertex_edge_set_property.value, storage::PropertyValue(1)); ASSERT_EQ(data[5].second.type, - storage::WalDeltaData::Type::TRANSACTION_END); + storage::durability::WalDeltaData::Type::TRANSACTION_END); // Verify transaction 2. - ASSERT_EQ(data[6].second.type, storage::WalDeltaData::Type::VERTEX_CREATE); + ASSERT_EQ(data[6].second.type, + storage::durability::WalDeltaData::Type::VERTEX_CREATE); ASSERT_EQ(data[6].second.vertex_create_delete.gid, gid2); ASSERT_EQ(data[7].second.type, - storage::WalDeltaData::Type::VERTEX_SET_PROPERTY); + storage::durability::WalDeltaData::Type::VERTEX_SET_PROPERTY); ASSERT_EQ(data[7].second.vertex_edge_set_property.gid, gid2); ASSERT_EQ(data[7].second.vertex_edge_set_property.property, "id"); ASSERT_EQ(data[7].second.vertex_edge_set_property.value, storage::PropertyValue(2)); ASSERT_EQ(data[8].second.type, - storage::WalDeltaData::Type::TRANSACTION_END); + storage::durability::WalDeltaData::Type::TRANSACTION_END); } // Recover WALs. diff --git a/tests/unit/storage_v2_wal_file.cpp b/tests/unit/storage_v2_wal_file.cpp index d129e01ad..2773853b3 100644 --- a/tests/unit/storage_v2_wal_file.cpp +++ b/tests/unit/storage_v2_wal_file.cpp @@ -6,32 +6,39 @@ #include <filesystem> #include <string_view> -#include "storage/v2/durability.hpp" +#include "storage/v2/durability/exceptions.hpp" +#include "storage/v2/durability/version.hpp" +#include "storage/v2/durability/wal.hpp" #include "storage/v2/mvcc.hpp" #include "storage/v2/name_id_mapper.hpp" #include "utils/file.hpp" #include "utils/uuid.hpp" // Helper function used to convert between enum types. -storage::WalDeltaData::Type StorageGlobalOperationToWalDeltaDataType( - storage::StorageGlobalOperation operation) { +storage::durability::WalDeltaData::Type +StorageGlobalOperationToWalDeltaDataType( + storage::durability::StorageGlobalOperation operation) { switch (operation) { - case storage::StorageGlobalOperation::LABEL_INDEX_CREATE: - return storage::WalDeltaData::Type::LABEL_INDEX_CREATE; - case storage::StorageGlobalOperation::LABEL_INDEX_DROP: - return storage::WalDeltaData::Type::LABEL_INDEX_DROP; - case storage::StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE: - return storage::WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE; - case storage::StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP: - return storage::WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP; - case storage::StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE: - return storage::WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE; - case storage::StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP: - return storage::WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP; - case storage::StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE: - return storage::WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE; - case storage::StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP: - return storage::WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP; + case storage::durability::StorageGlobalOperation::LABEL_INDEX_CREATE: + return storage::durability::WalDeltaData::Type::LABEL_INDEX_CREATE; + case storage::durability::StorageGlobalOperation::LABEL_INDEX_DROP: + return storage::durability::WalDeltaData::Type::LABEL_INDEX_DROP; + case storage::durability::StorageGlobalOperation:: + LABEL_PROPERTY_INDEX_CREATE: + return storage::durability::WalDeltaData::Type:: + LABEL_PROPERTY_INDEX_CREATE; + case storage::durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP: + return storage::durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP; + case storage::durability::StorageGlobalOperation:: + EXISTENCE_CONSTRAINT_CREATE: + return storage::durability::WalDeltaData::Type:: + EXISTENCE_CONSTRAINT_CREATE; + case storage::durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP: + return storage::durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP; + case storage::durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE: + return storage::durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE; + case storage::durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP: + return storage::durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP; } } @@ -52,8 +59,8 @@ class DeltaGenerator final { auto &it = gen_->vertices_.emplace_back(gid, delta); delta->prev.Set(&it); { - storage::WalDeltaData data; - data.type = storage::WalDeltaData::Type::VERTEX_CREATE; + storage::durability::WalDeltaData data; + data.type = storage::durability::WalDeltaData::Type::VERTEX_CREATE; data.vertex_create_delete.gid = gid; data_.push_back(data); } @@ -64,8 +71,8 @@ class DeltaGenerator final { storage::CreateAndLinkDelta(&transaction_, &*vertex, storage::Delta::RecreateObjectTag()); { - storage::WalDeltaData data; - data.type = storage::WalDeltaData::Type::VERTEX_DELETE; + storage::durability::WalDeltaData data; + data.type = storage::durability::WalDeltaData::Type::VERTEX_DELETE; data.vertex_create_delete.gid = vertex->gid; data_.push_back(data); } @@ -77,8 +84,8 @@ class DeltaGenerator final { storage::CreateAndLinkDelta(&transaction_, &*vertex, storage::Delta::RemoveLabelTag(), label_id); { - storage::WalDeltaData data; - data.type = storage::WalDeltaData::Type::VERTEX_ADD_LABEL; + storage::durability::WalDeltaData data; + data.type = storage::durability::WalDeltaData::Type::VERTEX_ADD_LABEL; data.vertex_add_remove_label.gid = vertex->gid; data.vertex_add_remove_label.label = label; data_.push_back(data); @@ -92,8 +99,9 @@ class DeltaGenerator final { storage::CreateAndLinkDelta(&transaction_, &*vertex, storage::Delta::AddLabelTag(), label_id); { - storage::WalDeltaData data; - data.type = storage::WalDeltaData::Type::VERTEX_REMOVE_LABEL; + storage::durability::WalDeltaData data; + data.type = + storage::durability::WalDeltaData::Type::VERTEX_REMOVE_LABEL; data.vertex_add_remove_label.gid = vertex->gid; data.vertex_add_remove_label.label = label; data_.push_back(data); @@ -111,8 +119,9 @@ class DeltaGenerator final { old_value); props.SetProperty(property_id, value); { - storage::WalDeltaData data; - data.type = storage::WalDeltaData::Type::VERTEX_SET_PROPERTY; + storage::durability::WalDeltaData data; + data.type = + storage::durability::WalDeltaData::Type::VERTEX_SET_PROPERTY; data.vertex_edge_set_property.gid = vertex->gid; data.vertex_edge_set_property.property = property; // We don't store the property value here. That is because the storage @@ -143,7 +152,8 @@ class DeltaGenerator final { if (gen_->valid_) { gen_->UpdateStats(commit_timestamp, transaction_.deltas.size() + 1); for (auto &data : data_) { - if (data.type == storage::WalDeltaData::Type::VERTEX_SET_PROPERTY) { + if (data.type == + storage::durability::WalDeltaData::Type::VERTEX_SET_PROPERTY) { // We need to put the final property value into the SET_PROPERTY // delta. auto vertex = @@ -158,8 +168,8 @@ class DeltaGenerator final { } gen_->data_.emplace_back(commit_timestamp, data); } - storage::WalDeltaData data{ - .type = storage::WalDeltaData::Type::TRANSACTION_END}; + storage::durability::WalDeltaData data{ + .type = storage::durability::WalDeltaData::Type::TRANSACTION_END}; gen_->data_.emplace_back(commit_timestamp, data); } } else { @@ -170,10 +180,11 @@ class DeltaGenerator final { private: DeltaGenerator *gen_; storage::Transaction transaction_; - std::vector<storage::WalDeltaData> data_; + std::vector<storage::durability::WalDeltaData> data_; }; - using DataT = std::vector<std::pair<uint64_t, storage::WalDeltaData>>; + using DataT = + std::vector<std::pair<uint64_t, storage::durability::WalDeltaData>>; DeltaGenerator(const std::filesystem::path &data_directory, bool properties_on_edges, uint64_t seq_num) @@ -191,7 +202,7 @@ class DeltaGenerator final { valid_ = false; } - void AppendOperation(storage::StorageGlobalOperation operation, + void AppendOperation(storage::durability::StorageGlobalOperation operation, const std::string &label, const std::set<std::string> properties = {}) { auto label_id = storage::LabelId::FromUint(mapper_.NameToId(label)); @@ -203,21 +214,27 @@ class DeltaGenerator final { wal_file_.AppendOperation(operation, label_id, property_ids, timestamp_); if (valid_) { UpdateStats(timestamp_, 1); - storage::WalDeltaData data; + storage::durability::WalDeltaData data; data.type = StorageGlobalOperationToWalDeltaDataType(operation); switch (operation) { - case storage::StorageGlobalOperation::LABEL_INDEX_CREATE: - case storage::StorageGlobalOperation::LABEL_INDEX_DROP: + case storage::durability::StorageGlobalOperation::LABEL_INDEX_CREATE: + case storage::durability::StorageGlobalOperation::LABEL_INDEX_DROP: data.operation_label.label = label; break; - case storage::StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE: - case storage::StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP: - case storage::StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE: - case storage::StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP: + case storage::durability::StorageGlobalOperation:: + LABEL_PROPERTY_INDEX_CREATE: + case storage::durability::StorageGlobalOperation:: + LABEL_PROPERTY_INDEX_DROP: + case storage::durability::StorageGlobalOperation:: + EXISTENCE_CONSTRAINT_CREATE: + case storage::durability::StorageGlobalOperation:: + EXISTENCE_CONSTRAINT_DROP: data.operation_label_property.label = label; data.operation_label_property.property = *properties.begin(); - case storage::StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE: - case storage::StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP: + case storage::durability::StorageGlobalOperation:: + UNIQUE_CONSTRAINT_CREATE: + case storage::durability::StorageGlobalOperation:: + UNIQUE_CONSTRAINT_DROP: data.operation_label_properties.label = label; data.operation_label_properties.properties = properties; } @@ -227,7 +244,7 @@ class DeltaGenerator final { uint64_t GetPosition() { return wal_file_.GetSize(); } - storage::WalInfo GetInfo() { + storage::durability::WalInfo GetInfo() { return {.offset_metadata = 0, .offset_deltas = 0, .uuid = uuid_, @@ -257,7 +274,7 @@ class DeltaGenerator final { std::list<storage::Vertex> vertices_; storage::NameIdMapper mapper_; - storage::WalFile wal_file_; + storage::durability::WalFile wal_file_; DataT data_; @@ -276,10 +293,12 @@ class DeltaGenerator final { } // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define OPERATION(op, ...) \ - gen.AppendOperation(storage::StorageGlobalOperation::op, __VA_ARGS__) +#define OPERATION(op, ...) \ + gen.AppendOperation(storage::durability::StorageGlobalOperation::op, \ + __VA_ARGS__) -void AssertWalInfoEqual(const storage::WalInfo &a, const storage::WalInfo &b) { +void AssertWalInfoEqual(const storage::durability::WalInfo &a, + const storage::durability::WalInfo &b) { ASSERT_EQ(a.uuid, b.uuid); ASSERT_EQ(a.seq_num, b.seq_num); ASSERT_EQ(a.from_timestamp, b.from_timestamp); @@ -289,14 +308,15 @@ void AssertWalInfoEqual(const storage::WalInfo &a, const storage::WalInfo &b) { void AssertWalDataEqual(const DeltaGenerator::DataT &data, const std::filesystem::path &path) { - auto info = storage::ReadWalInfo(path); - storage::Decoder wal; - wal.Initialize(path, storage::kWalMagic); + auto info = storage::durability::ReadWalInfo(path); + storage::durability::Decoder wal; + wal.Initialize(path, storage::durability::kWalMagic); wal.SetPosition(info.offset_deltas); DeltaGenerator::DataT current; for (uint64_t i = 0; i < info.num_deltas; ++i) { - auto timestamp = storage::ReadWalDeltaHeader(&wal); - current.emplace_back(timestamp, storage::ReadWalDeltaData(&wal)); + auto timestamp = storage::durability::ReadWalDeltaHeader(&wal); + current.emplace_back(timestamp, + storage::durability::ReadWalDeltaData(&wal)); } ASSERT_EQ(data.size(), current.size()); ASSERT_EQ(data, current); @@ -344,28 +364,29 @@ TEST_P(WalFileTest, EmptyFile) { } // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define GENERATE_SIMPLE_TEST(name, ops) \ - TEST_P(WalFileTest, name) { \ - storage::WalInfo info; \ - DeltaGenerator::DataT data; \ - \ - { \ - DeltaGenerator gen(storage_directory, GetParam(), 5); \ - ops; \ - info = gen.GetInfo(); \ - data = gen.GetData(); \ - } \ - \ - auto wal_files = GetFilesList(); \ - ASSERT_EQ(wal_files.size(), 1); \ - \ - if (info.num_deltas == 0) { \ - ASSERT_THROW(storage::ReadWalInfo(wal_files.front()), \ - storage::RecoveryFailure); \ - } else { \ - AssertWalInfoEqual(info, storage::ReadWalInfo(wal_files.front())); \ - AssertWalDataEqual(data, wal_files.front()); \ - } \ +#define GENERATE_SIMPLE_TEST(name, ops) \ + TEST_P(WalFileTest, name) { \ + storage::durability::WalInfo info; \ + DeltaGenerator::DataT data; \ + \ + { \ + DeltaGenerator gen(storage_directory, GetParam(), 5); \ + ops; \ + info = gen.GetInfo(); \ + data = gen.GetData(); \ + } \ + \ + auto wal_files = GetFilesList(); \ + ASSERT_EQ(wal_files.size(), 1); \ + \ + if (info.num_deltas == 0) { \ + ASSERT_THROW(storage::durability::ReadWalInfo(wal_files.front()), \ + storage::durability::RecoveryFailure); \ + } else { \ + AssertWalInfoEqual(info, \ + storage::durability::ReadWalInfo(wal_files.front())); \ + AssertWalDataEqual(data, wal_files.front()); \ + } \ } // NOLINTNEXTLINE(hicpp-special-member-functions) @@ -525,7 +546,7 @@ GENERATE_SIMPLE_TEST(InvalidTransactionOrdering, { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(WalFileTest, InvalidMarker) { - storage::WalInfo info; + storage::durability::WalInfo info; { DeltaGenerator gen(storage_directory, GetParam(), 5); @@ -537,12 +558,12 @@ TEST_P(WalFileTest, InvalidMarker) { ASSERT_EQ(wal_files.size(), 1); const auto &wal_file = wal_files.front(); - auto final_info = storage::ReadWalInfo(wal_file); + auto final_info = storage::durability::ReadWalInfo(wal_file); AssertWalInfoEqual(info, final_info); size_t i = 0; - for (auto marker : storage::kMarkersAll) { - if (marker == storage::Marker::SECTION_DELTA) continue; + for (auto marker : storage::durability::kMarkersAll) { + if (marker == storage::durability::Marker::SECTION_DELTA) continue; auto current_file = storage_directory / fmt::format("temporary_{}", i); ASSERT_TRUE(std::filesystem::copy_file(wal_file, current_file)); utils::OutputFile file; @@ -553,14 +574,15 @@ TEST_P(WalFileTest, InvalidMarker) { file.Write(&value, sizeof(value)); file.Sync(); file.Close(); - ASSERT_THROW(storage::ReadWalInfo(current_file), storage::RecoveryFailure); + ASSERT_THROW(storage::durability::ReadWalInfo(current_file), + storage::durability::RecoveryFailure); ++i; } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(WalFileTest, PartialData) { - std::vector<std::pair<uint64_t, storage::WalInfo>> infos; + std::vector<std::pair<uint64_t, storage::durability::WalInfo>> infos; { DeltaGenerator gen(storage_directory, GetParam(), 5); @@ -591,7 +613,8 @@ TEST_P(WalFileTest, PartialData) { ASSERT_EQ(wal_files.size(), 1); const auto &wal_file = wal_files.front(); - AssertWalInfoEqual(infos.back().second, storage::ReadWalInfo(wal_file)); + AssertWalInfoEqual(infos.back().second, + storage::durability::ReadWalInfo(wal_file)); auto current_file = storage_directory / "temporary"; utils::InputFile infile; @@ -600,11 +623,12 @@ TEST_P(WalFileTest, PartialData) { uint64_t pos = 0; for (size_t i = 0; i < infile.GetSize(); ++i) { if (i < infos.front().first) { - ASSERT_THROW(storage::ReadWalInfo(current_file), - storage::RecoveryFailure); + ASSERT_THROW(storage::durability::ReadWalInfo(current_file), + storage::durability::RecoveryFailure); } else { if (i >= infos[pos + 1].first) ++pos; - AssertWalInfoEqual(infos[pos].second, storage::ReadWalInfo(current_file)); + AssertWalInfoEqual(infos[pos].second, + storage::durability::ReadWalInfo(current_file)); } { utils::OutputFile outfile; @@ -618,5 +642,5 @@ TEST_P(WalFileTest, PartialData) { } ASSERT_EQ(pos, infos.size() - 2); AssertWalInfoEqual(infos[infos.size() - 1].second, - storage::ReadWalInfo(current_file)); + storage::durability::ReadWalInfo(current_file)); }