Storage fields refactoring

This commit is contained in:
Andi Skrgat
2023-05-17 09:59:35 +02:00
parent 7cb5a2e3b5
commit 3b4f949a52
6 changed files with 198 additions and 162 deletions

View File

@@ -230,22 +230,10 @@ DiskStorage::~DiskStorage() {
}
DiskStorage::DiskAccessor::DiskAccessor(DiskStorage *storage, IsolationLevel isolation_level, StorageMode storage_mode)
: storage_(storage),
// The lock must be acquired before creating the transaction object to
// prevent freshly created transactions from dangling in an active state
// during exclusive operations.
storage_guard_(storage_->main_lock_),
transaction_(storage->CreateTransaction(isolation_level, storage_mode)),
is_transaction_active_(true),
config_(storage->config_.items) {}
: Accessor(storage, isolation_level, storage_mode), config_(storage->config_.items) {}
DiskStorage::DiskAccessor::DiskAccessor(DiskAccessor &&other) noexcept
: storage_(other.storage_),
storage_guard_(std::move(other.storage_guard_)),
transaction_(std::move(other.transaction_)),
commit_timestamp_(other.commit_timestamp_),
is_transaction_active_(other.is_transaction_active_),
config_(other.config_) {
: Accessor(std::move(other)), config_(other.config_) {
// Don't allow the other accessor to abort our transaction in destructor.
other.is_transaction_active_ = false;
other.commit_timestamp_.reset();
@@ -322,23 +310,26 @@ std::optional<EdgeAccessor> DiskStorage::DiskAccessor::DeserializeEdge(const roc
}
VerticesIterable DiskStorage::DiskAccessor::Vertices(View view) {
auto *disk_storage = static_cast<DiskStorage *>(storage_);
rocksdb::ReadOptions ro;
rocksdb::Slice ts = utils::StringTimestamp(transaction_.start_timestamp);
ro.timestamp = &ts;
auto it =
std::unique_ptr<rocksdb::Iterator>(storage_->kvstore_->db_->NewIterator(ro, storage_->kvstore_->vertex_chandle));
auto it = std::unique_ptr<rocksdb::Iterator>(
disk_storage->kvstore_->db_->NewIterator(ro, disk_storage->kvstore_->vertex_chandle));
for (it->SeekToFirst(); it->Valid(); it->Next()) {
// When deserializing vertex, key size is set to user key-size
// To be able to extract timestamp, here a copy can be created
// with size explicitly added with sizeof(uint64_t)
DeserializeVertex(it->key(), it->value());
}
return VerticesIterable(AllVerticesIterable(storage_->vertices_.access(), &transaction_, view, &storage_->indices_,
&storage_->constraints_, storage_->config_.items));
return VerticesIterable(AllVerticesIterable(storage_->vertices_.access(), &transaction_, view,
&disk_storage->indices_, &disk_storage->constraints_,
disk_storage->config_.items));
}
VerticesIterable DiskStorage::DiskAccessor::Vertices(LabelId label, View view) {
return VerticesIterable(storage_->indices_.label_index.Vertices(label, view, &transaction_));
return VerticesIterable(
static_cast<DiskStorage *>(storage_)->indices_.label_index.Vertices(label, view, &transaction_));
}
VerticesIterable DiskStorage::DiskAccessor::Vertices(LabelId label, PropertyId property, View view) {
@@ -359,8 +350,10 @@ VerticesIterable DiskStorage::DiskAccessor::Vertices(LabelId label, PropertyId p
int64_t DiskStorage::DiskAccessor::ApproximateVertexCount() const {
uint64_t estimate_num_keys = 0;
storage_->kvstore_->db_->GetIntProperty(storage_->kvstore_->vertex_chandle, "rocksdb.estimate-num-keys",
&estimate_num_keys);
auto *disk_storage = static_cast<DiskStorage *>(storage_);
// TODO: This method should probably be organized.
disk_storage->kvstore_->db_->GetIntProperty(disk_storage->kvstore_->vertex_chandle, "rocksdb.estimate-num-keys",
&estimate_num_keys);
return static_cast<int64_t>(estimate_num_keys);
}
@@ -377,8 +370,9 @@ VertexAccessor DiskStorage::DiskAccessor::CreateVertex() {
if (delta) {
delta->prev.Set(&*it);
}
auto *disk_storage = static_cast<DiskStorage *>(storage_);
return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_};
return {&*it, &transaction_, &disk_storage->indices_, &disk_storage->constraints_, config_};
}
VertexAccessor DiskStorage::DiskAccessor::CreateVertex(storage::Gid gid) {
@@ -402,7 +396,8 @@ VertexAccessor DiskStorage::DiskAccessor::CreateVertex(storage::Gid gid) {
if (delta) {
delta->prev.Set(&*it);
}
return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_};
auto *disk_storage = static_cast<DiskStorage *>(storage_);
return {&*it, &transaction_, &disk_storage->indices_, &disk_storage->constraints_, config_};
}
/// TODO(andi): This method is the duplicate of CreateVertex(storage::Gid gid), the only thing that is different is
@@ -431,11 +426,13 @@ VertexAccessor DiskStorage::DiskAccessor::CreateVertex(storage::Gid gid, uint64_
if (delta) {
delta->prev.Set(&*it);
}
return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_};
auto *disk_storage = static_cast<DiskStorage *>(storage_);
return {&*it, &transaction_, &disk_storage->indices_, &disk_storage->constraints_, config_};
}
std::optional<VertexAccessor> DiskStorage::DiskAccessor::FindVertex(storage::Gid gid, View view) {
/// Check if the vertex is in the cache.
auto *disk_storage = static_cast<DiskStorage *>(storage_);
auto acc = storage_->vertices_.access();
auto vertex_it = acc.find(gid);
if (vertex_it != acc.end()) {
@@ -445,15 +442,15 @@ std::optional<VertexAccessor> DiskStorage::DiskAccessor::FindVertex(storage::Gid
} else {
spdlog::debug("Vertex with gid {} found in the cache! Delta is null.", gid.AsUint());
}
return VertexAccessor::Create(&*vertex_it, &transaction_, &storage_->indices_, &storage_->constraints_, config_,
view);
return VertexAccessor::Create(&*vertex_it, &transaction_, &disk_storage->indices_, &disk_storage->constraints_,
config_, view);
}
/// If not in the memory, check whether it exists in RocksDB.
rocksdb::ReadOptions read_opts;
rocksdb::Slice ts = utils::StringTimestamp(transaction_.start_timestamp);
read_opts.timestamp = &ts;
auto it = std::unique_ptr<rocksdb::Iterator>(
storage_->kvstore_->db_->NewIterator(read_opts, storage_->kvstore_->vertex_chandle));
disk_storage->kvstore_->db_->NewIterator(read_opts, disk_storage->kvstore_->vertex_chandle));
for (it->SeekToFirst(); it->Valid(); it->Next()) {
const auto &key = it->key();
// TODO(andi): If we change format of vertex serialization, change vertex_parts[1] to vertex_parts[0].
@@ -484,8 +481,9 @@ Result<std::optional<VertexAccessor>> DiskStorage::DiskAccessor::DeleteVertex(Ve
vertex_ptr->deleted = true;
vertices_to_delete_.emplace_back(utils::SerializeVertex(*vertex_ptr));
return std::make_optional<VertexAccessor>(vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_,
config_, true);
auto *disk_storage = static_cast<DiskStorage *>(storage_);
return std::make_optional<VertexAccessor>(vertex_ptr, &transaction_, &disk_storage->indices_,
&disk_storage->constraints_, config_, true);
}
Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>>
@@ -495,6 +493,7 @@ DiskStorage::DiskAccessor::DetachDeleteVertex(VertexAccessor *vertex) {
"VertexAccessor must be from the same transaction as the storage "
"accessor when deleting a vertex!");
auto *vertex_ptr = vertex->vertex_;
auto *disk_storage = static_cast<DiskStorage *>(storage_);
std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> in_edges;
std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> out_edges;
@@ -513,8 +512,8 @@ DiskStorage::DiskAccessor::DetachDeleteVertex(VertexAccessor *vertex) {
std::vector<EdgeAccessor> deleted_edges;
for (const auto &item : in_edges) {
auto [edge_type, from_vertex, edge] = item;
EdgeAccessor e(edge, edge_type, from_vertex, vertex_ptr, &transaction_, &storage_->indices_,
&storage_->constraints_, config_);
EdgeAccessor e(edge, edge_type, from_vertex, vertex_ptr, &transaction_, &disk_storage->indices_,
&disk_storage->constraints_, config_);
auto ret = DeleteEdge(&e);
if (ret.HasError()) {
MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!");
@@ -527,8 +526,8 @@ DiskStorage::DiskAccessor::DetachDeleteVertex(VertexAccessor *vertex) {
}
for (const auto &item : out_edges) {
auto [edge_type, to_vertex, edge] = item;
EdgeAccessor e(edge, edge_type, vertex_ptr, to_vertex, &transaction_, &storage_->indices_, &storage_->constraints_,
config_);
EdgeAccessor e(edge, edge_type, vertex_ptr, to_vertex, &transaction_, &disk_storage->indices_,
&disk_storage->constraints_, config_);
auto ret = DeleteEdge(&e);
if (ret.HasError()) {
MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!");
@@ -555,7 +554,7 @@ DiskStorage::DiskAccessor::DetachDeleteVertex(VertexAccessor *vertex) {
vertices_to_delete_.emplace_back(utils::SerializeVertex(*vertex_ptr));
return std::make_optional<ReturnType>(
VertexAccessor{vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_, config_, true},
VertexAccessor{vertex_ptr, &transaction_, &disk_storage->indices_, &disk_storage->constraints_, config_, true},
std::move(deleted_edges));
}
@@ -563,8 +562,9 @@ void DiskStorage::DiskAccessor::PrefetchEdges(const auto &prefetch_edge_filter)
rocksdb::ReadOptions read_opts;
rocksdb::Slice ts = utils::StringTimestamp(transaction_.start_timestamp);
read_opts.timestamp = &ts;
auto *disk_storage = static_cast<DiskStorage *>(storage_);
auto it = std::unique_ptr<rocksdb::Iterator>(
storage_->kvstore_->db_->NewIterator(read_opts, storage_->kvstore_->edge_chandle));
disk_storage->kvstore_->db_->NewIterator(read_opts, disk_storage->kvstore_->edge_chandle));
for (it->SeekToFirst(); it->Valid(); it->Next()) {
const rocksdb::Slice &key = it->key();
const auto edge_parts = utils::Split(key.ToStringView(), "|");
@@ -652,8 +652,9 @@ Result<EdgeAccessor> DiskStorage::DiskAccessor::CreateEdge(VertexAccessor *from,
// Increment edge count.
storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel);
return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_,
&storage_->constraints_, config_);
auto *disk_storage = static_cast<DiskStorage *>(storage_);
return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &disk_storage->indices_,
&disk_storage->constraints_, config_);
}
Result<EdgeAccessor> DiskStorage::DiskAccessor::CreateEdge(VertexAccessor *from, VertexAccessor *to,
@@ -720,8 +721,9 @@ Result<EdgeAccessor> DiskStorage::DiskAccessor::CreateEdge(VertexAccessor *from,
// Increment edge count.
storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel);
return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_,
&storage_->constraints_, config_);
auto *disk_storage = static_cast<DiskStorage *>(storage_);
return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &disk_storage->indices_,
&disk_storage->constraints_, config_);
}
Result<EdgeAccessor> DiskStorage::DiskAccessor::CreateEdge(VertexAccessor *from, VertexAccessor *to,
@@ -779,8 +781,9 @@ Result<EdgeAccessor> DiskStorage::DiskAccessor::CreateEdge(VertexAccessor *from,
// Increment edge count.
storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel);
return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_,
&storage_->constraints_, config_);
auto *disk_storage = static_cast<DiskStorage *>(storage_);
return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &disk_storage->indices_,
&disk_storage->constraints_, config_);
}
Result<std::optional<EdgeAccessor>> DiskStorage::DiskAccessor::DeleteEdge(EdgeAccessor *edge) {
@@ -868,8 +871,9 @@ Result<std::optional<EdgeAccessor>> DiskStorage::DiskAccessor::DeleteEdge(EdgeAc
// Decrement edge count.
storage_->edge_count_.fetch_add(-1, std::memory_order_acq_rel);
auto *disk_storage = static_cast<DiskStorage *>(storage_);
return std::make_optional<EdgeAccessor>(edge_ref, edge_type, from_vertex, to_vertex, &transaction_,
&storage_->indices_, &storage_->constraints_, config_, true);
&disk_storage->indices_, &disk_storage->constraints_, config_, true);
}
// this should be handled on an above level of abstraction
@@ -908,22 +912,23 @@ void DiskStorage::DiskAccessor::FlushCache() {
rocksdb::WriteOptions write_options;
rocksdb::Slice ts = utils::StringTimestamp(*commit_timestamp_);
write_options.timestamp = &ts;
auto *disk_storage = static_cast<DiskStorage *>(storage_);
for (Vertex &vertex : vertex_acc) {
logging::AssertRocksDBStatus(storage_->kvstore_->db_->Put(write_options, storage_->kvstore_->vertex_chandle,
utils::SerializeVertex(vertex),
utils::SerializeProperties(vertex.properties)));
logging::AssertRocksDBStatus(disk_storage->kvstore_->db_->Put(write_options, disk_storage->kvstore_->vertex_chandle,
utils::SerializeVertex(vertex),
utils::SerializeProperties(vertex.properties)));
spdlog::debug("rocksdb: Saved vertex with key {} and ts {}", utils::SerializeVertex(vertex), *commit_timestamp_);
spdlog::debug("Vertex {} has {} out edges", vertex.gid.AsUint(), vertex.out_edges.size());
for (auto &edge_entry : vertex.out_edges) {
Edge *edge_ptr = std::get<2>(edge_entry).ptr;
auto [src_dest_key, dest_src_key] =
utils::SerializeEdge(vertex.gid, std::get<1>(edge_entry)->gid, std::get<0>(edge_entry), edge_ptr);
logging::AssertRocksDBStatus(storage_->kvstore_->db_->Put(write_options, storage_->kvstore_->edge_chandle,
src_dest_key,
utils::SerializeProperties(edge_ptr->properties)));
logging::AssertRocksDBStatus(storage_->kvstore_->db_->Put(write_options, storage_->kvstore_->edge_chandle,
dest_src_key,
utils::SerializeProperties(edge_ptr->properties)));
logging::AssertRocksDBStatus(disk_storage->kvstore_->db_->Put(write_options, disk_storage->kvstore_->edge_chandle,
src_dest_key,
utils::SerializeProperties(edge_ptr->properties)));
logging::AssertRocksDBStatus(disk_storage->kvstore_->db_->Put(write_options, disk_storage->kvstore_->edge_chandle,
dest_src_key,
utils::SerializeProperties(edge_ptr->properties)));
spdlog::debug("rocksdb: Saved edge with key {} and ts {}", src_dest_key, *commit_timestamp_);
spdlog::debug("rocksdb: Saved edge with key {} and ts {}", dest_src_key, *commit_timestamp_);
num_ser_edges++;
@@ -933,14 +938,14 @@ void DiskStorage::DiskAccessor::FlushCache() {
for (const auto &vertex_to_delete : vertices_to_delete_) {
spdlog::debug("rocksdb: Deleted vertex with key {}", vertex_to_delete);
logging::AssertRocksDBStatus(
storage_->kvstore_->db_->Delete(write_options, storage_->kvstore_->vertex_chandle, vertex_to_delete));
disk_storage->kvstore_->db_->Delete(write_options, disk_storage->kvstore_->vertex_chandle, vertex_to_delete));
}
// Delete edges that were deleted in the current transaction.
for (const auto &edge_to_delete : edges_to_delete_) {
spdlog::debug("rocksdb: Deleted edge with key {}", edge_to_delete);
logging::AssertRocksDBStatus(
storage_->kvstore_->db_->Delete(write_options, storage_->kvstore_->edge_chandle, edge_to_delete));
disk_storage->kvstore_->db_->Delete(write_options, disk_storage->kvstore_->edge_chandle, edge_to_delete));
}
}
@@ -950,6 +955,7 @@ utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor
MG_ASSERT(is_transaction_active_, "The transaction is already terminated!");
MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!");
auto *disk_storage = static_cast<DiskStorage *>(storage_);
auto could_replicate_all_sync_replicas = true;
if (transaction_.deltas.empty() ||
@@ -958,7 +964,7 @@ utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor
// We don't have to update the commit timestamp here because no one reads
// it.
// If there are no deltas, then we don't have to serialize anything on the disk.
storage_->commit_log_->MarkFinished(transaction_.start_timestamp);
disk_storage->commit_log_->MarkFinished(transaction_.start_timestamp);
} else {
// Validate that existence constraints are satisfied for all modified
// vertices.
@@ -970,7 +976,7 @@ utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor
}
// No need to take any locks here because we modified this vertex and no
// one else can touch it until we commit.
auto validation_result = ValidateExistenceConstraints(*prev.vertex, storage_->constraints_);
auto validation_result = ValidateExistenceConstraints(*prev.vertex, disk_storage->constraints_);
if (validation_result) {
Abort();
return StorageDataManipulationError{*validation_result};
@@ -987,7 +993,7 @@ utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor
{
std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_);
commit_timestamp_.emplace(storage_->CommitTimestamp(desired_commit_timestamp));
commit_timestamp_.emplace(disk_storage->CommitTimestamp(desired_commit_timestamp));
// Before committing and validating vertices against unique constraints,
// we have to update unique constraints with the vertices that are going
@@ -998,7 +1004,7 @@ utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor
if (prev.type != PreviousPtr::Type::VERTEX) {
continue;
}
storage_->constraints_.unique_constraints.UpdateBeforeCommit(prev.vertex, transaction_);
disk_storage->constraints_.unique_constraints.UpdateBeforeCommit(prev.vertex, transaction_);
}
// Validate that unique constraints are satisfied for all modified
@@ -1013,7 +1019,7 @@ utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor
// No need to take any locks here because we modified this vertex and no
// one else can touch it until we commit.
unique_constraint_violation =
storage_->constraints_.unique_constraints.Validate(*prev.vertex, transaction_, *commit_timestamp_);
disk_storage->constraints_.unique_constraints.Validate(*prev.vertex, transaction_, *commit_timestamp_);
if (unique_constraint_violation) {
break;
}
@@ -1031,13 +1037,14 @@ utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor
// so the Wal files are consistent
// if (storage_->replication_role_ == ReplicationRole::MAIN || desired_commit_timestamp.has_value()) {
if (desired_commit_timestamp.has_value()) {
could_replicate_all_sync_replicas = storage_->AppendToWalDataManipulation(transaction_, *commit_timestamp_);
could_replicate_all_sync_replicas =
disk_storage->AppendToWalDataManipulation(transaction_, *commit_timestamp_);
}
// Take committed_transactions lock while holding the engine lock to
// make sure that committed transactions are sorted by the commit
// timestamp in the list.
storage_->committed_transactions_.WithLock([&](auto &committed_transactions) {
disk_storage->committed_transactions_.WithLock([&](auto &committed_transactions) {
// TODO: release lock, and update all deltas to have a local copy
// of the commit timestamp
MG_ASSERT(transaction_.commit_timestamp != nullptr, "Invalid database state!");
@@ -1054,7 +1061,7 @@ utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor
engine_guard.unlock();
});
storage_->commit_log_->MarkFinished(start_timestamp);
disk_storage->commit_log_->MarkFinished(start_timestamp);
}
}
@@ -1221,32 +1228,34 @@ void DiskStorage::DiskAccessor::Abort() {
}
}
auto *disk_storage = static_cast<DiskStorage *>(storage_);
{
std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_);
uint64_t mark_timestamp = storage_->timestamp_;
// Take garbage_undo_buffers lock while holding the engine lock to make
// sure that entries are sorted by mark timestamp in the list.
storage_->garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) {
disk_storage->garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) {
// Release engine lock because we don't have to hold it anymore and
// emplace back could take a long time.
engine_guard.unlock();
garbage_undo_buffers.emplace_back(mark_timestamp, std::move(transaction_.deltas));
});
storage_->deleted_vertices_.WithLock(
disk_storage->deleted_vertices_.WithLock(
[&](auto &deleted_vertices) { deleted_vertices.splice(deleted_vertices.begin(), my_deleted_vertices); });
storage_->deleted_edges_.WithLock(
disk_storage->deleted_edges_.WithLock(
[&](auto &deleted_edges) { deleted_edges.splice(deleted_edges.begin(), my_deleted_edges); });
}
storage_->commit_log_->MarkFinished(transaction_.start_timestamp);
disk_storage->commit_log_->MarkFinished(transaction_.start_timestamp);
is_transaction_active_ = false;
}
// maybe will need some usages here
void DiskStorage::DiskAccessor::FinalizeTransaction() {
auto *disk_storage = static_cast<DiskStorage *>(storage_);
if (commit_timestamp_) {
storage_->commit_log_->MarkFinished(*commit_timestamp_);
storage_->committed_transactions_.WithLock(
disk_storage->commit_log_->MarkFinished(*commit_timestamp_);
disk_storage->committed_transactions_.WithLock(
[&](auto &committed_transactions) { committed_transactions.emplace_back(std::move(transaction_)); });
commit_timestamp_.reset();
}

View File

@@ -114,7 +114,7 @@ class DiskStorage final : public Storage {
int64_t ApproximateVertexCount() const override;
int64_t ApproximateVertexCount(LabelId label) const override {
return storage_->indices_.label_index.ApproximateVertexCount(label);
throw utils::NotYetImplemented("ApproximateVertexCount(label) is not implemented for DiskStorage.");
}
int64_t ApproximateVertexCount(LabelId label, PropertyId property) const override {
@@ -181,14 +181,14 @@ class DiskStorage final : public Storage {
/// @throw std::bad_alloc if unable to insert a new mapping
EdgeTypeId NameToEdgeType(std::string_view name) override;
bool LabelIndexExists(LabelId label) const override { return storage_->indices_.label_index.IndexExists(label); }
bool LabelIndexExists(LabelId label) const override { throw utils::NotYetImplemented("LabelIndexExists()"); }
bool LabelPropertyIndexExists(LabelId label, PropertyId property) const override {
return storage_->indices_.label_property_index.IndexExists(label, property);
throw utils::NotYetImplemented("LabelPropertyIndexExists() is not implemented for DiskStorage.");
}
IndicesInfo ListAllIndices() const override {
return {storage_->indices_.label_index.ListIndices(), storage_->indices_.label_property_index.ListIndices()};
throw utils::NotYetImplemented("ListAllIndices() is not implemented for DiskStorage.");
}
ConstraintsInfo ListAllConstraints() const override {
@@ -244,15 +244,9 @@ class DiskStorage final : public Storage {
/// After this method, the vertex and edge caches are cleared.
void FlushCache();
DiskStorage *storage_;
std::shared_lock<utils::RWLock> storage_guard_;
Transaction transaction_;
Config::Items config_;
std::vector<std::string> edges_to_delete_;
std::vector<std::string> vertices_to_delete_;
std::optional<uint64_t> commit_timestamp_;
bool is_transaction_active_;
Config::Items config_;
};
std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level) override {
@@ -386,9 +380,9 @@ class DiskStorage final : public Storage {
utils::BasicResult<CreateSnapshotError> CreateSnapshot(std::optional<bool> is_periodic) override;
private:
Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode);
Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) override;
private:
/// The force parameter determines the behaviour of the garbage collector.
/// If it's set to true, it will behave as a global operation, i.e. it can't
/// be part of a transaction, and no other transaction can be active at the same time.

View File

@@ -26,6 +26,7 @@
#include "storage/v2/durability/snapshot.hpp"
#include "storage/v2/durability/wal.hpp"
#include "storage/v2/edge_accessor.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "storage/v2/mvcc.hpp"
#include "storage/v2/replication/config.hpp"
#include "storage/v2/replication/enums.hpp"
@@ -223,26 +224,9 @@ InMemoryStorage::~InMemoryStorage() {
InMemoryStorage::InMemoryAccessor::InMemoryAccessor(InMemoryStorage *storage, IsolationLevel isolation_level,
StorageMode storage_mode)
: storage_(storage),
// The lock must be acquired before creating the transaction object to
// prevent freshly created transactions from dangling in an active state
// during exclusive operations.
storage_guard_(storage_->main_lock_),
transaction_(storage->CreateTransaction(isolation_level, storage_mode)),
is_transaction_active_(true),
config_(storage->config_.items) {}
: Accessor(storage, isolation_level, storage_mode), config_(storage->config_.items) {}
InMemoryStorage::InMemoryAccessor::InMemoryAccessor(InMemoryAccessor &&other) noexcept
: storage_(other.storage_),
storage_guard_(std::move(other.storage_guard_)),
transaction_(std::move(other.transaction_)),
commit_timestamp_(other.commit_timestamp_),
is_transaction_active_(other.is_transaction_active_),
config_(other.config_) {
// Don't allow the other accessor to abort our transaction in destructor.
other.is_transaction_active_ = false;
other.commit_timestamp_.reset();
}
: Accessor(std::move(other)), config_(other.config_) {}
InMemoryStorage::InMemoryAccessor::~InMemoryAccessor() {
if (is_transaction_active_) {
@@ -266,7 +250,9 @@ VertexAccessor InMemoryStorage::InMemoryAccessor::CreateVertex() {
delta->prev.Set(&*it);
}
return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_};
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
return {&*it, &transaction_, &mem_storage->indices_, &mem_storage->constraints_, config_};
}
VertexAccessor InMemoryStorage::InMemoryAccessor::CreateVertex(storage::Gid gid) {
@@ -288,14 +274,16 @@ VertexAccessor InMemoryStorage::InMemoryAccessor::CreateVertex(storage::Gid gid)
if (delta) {
delta->prev.Set(&*it);
}
return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_};
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
return {&*it, &transaction_, &mem_storage->indices_, &mem_storage->constraints_, config_};
}
std::optional<VertexAccessor> InMemoryStorage::InMemoryAccessor::FindVertex(Gid gid, View view) {
auto acc = storage_->vertices_.access();
auto it = acc.find(gid);
if (it == acc.end()) return std::nullopt;
return VertexAccessor::Create(&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_, view);
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
return VertexAccessor::Create(&*it, &transaction_, &mem_storage->indices_, &mem_storage->constraints_, config_, view);
}
Result<std::optional<VertexAccessor>> InMemoryStorage::InMemoryAccessor::DeleteVertex(VertexAccessor *vertex) {
@@ -317,8 +305,9 @@ Result<std::optional<VertexAccessor>> InMemoryStorage::InMemoryAccessor::DeleteV
CreateAndLinkDelta(&transaction_, vertex_ptr, Delta::RecreateObjectTag());
vertex_ptr->deleted = true;
return std::make_optional<VertexAccessor>(vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_,
config_, true);
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
return std::make_optional<VertexAccessor>(vertex_ptr, &transaction_, &mem_storage->indices_,
&mem_storage->constraints_, config_, true);
}
Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>>
@@ -345,10 +334,11 @@ InMemoryStorage::InMemoryAccessor::DetachDeleteVertex(VertexAccessor *vertex) {
}
std::vector<EdgeAccessor> deleted_edges;
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
for (const auto &item : in_edges) {
auto [edge_type, from_vertex, edge] = item;
EdgeAccessor e(edge, edge_type, from_vertex, vertex_ptr, &transaction_, &storage_->indices_,
&storage_->constraints_, config_);
EdgeAccessor e(edge, edge_type, from_vertex, vertex_ptr, &transaction_, &mem_storage->indices_,
&mem_storage->constraints_, config_);
auto ret = DeleteEdge(&e);
if (ret.HasError()) {
MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!");
@@ -361,8 +351,8 @@ InMemoryStorage::InMemoryAccessor::DetachDeleteVertex(VertexAccessor *vertex) {
}
for (const auto &item : out_edges) {
auto [edge_type, to_vertex, edge] = item;
EdgeAccessor e(edge, edge_type, vertex_ptr, to_vertex, &transaction_, &storage_->indices_, &storage_->constraints_,
config_);
EdgeAccessor e(edge, edge_type, vertex_ptr, to_vertex, &transaction_, &mem_storage->indices_,
&mem_storage->constraints_, config_);
auto ret = DeleteEdge(&e);
if (ret.HasError()) {
MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!");
@@ -388,7 +378,7 @@ InMemoryStorage::InMemoryAccessor::DetachDeleteVertex(VertexAccessor *vertex) {
vertex_ptr->deleted = true;
return std::make_optional<ReturnType>(
VertexAccessor{vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_, config_, true},
VertexAccessor{vertex_ptr, &transaction_, &mem_storage->indices_, &mem_storage->constraints_, config_, true},
std::move(deleted_edges));
}
@@ -451,8 +441,9 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdge(VertexAccesso
// Increment edge count.
storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel);
return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_,
&storage_->constraints_, config_);
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &mem_storage->indices_,
&mem_storage->constraints_, config_);
}
Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdge(VertexAccessor *from, VertexAccessor *to,
@@ -522,8 +513,9 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdge(VertexAccesso
// Increment edge count.
storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel);
return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_,
&storage_->constraints_, config_);
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &mem_storage->indices_,
&mem_storage->constraints_, config_);
}
Result<std::optional<EdgeAccessor>> InMemoryStorage::InMemoryAccessor::DeleteEdge(EdgeAccessor *edge) {
@@ -606,8 +598,9 @@ Result<std::optional<EdgeAccessor>> InMemoryStorage::InMemoryAccessor::DeleteEdg
// Decrement edge count.
storage_->edge_count_.fetch_add(-1, std::memory_order_acq_rel);
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
return std::make_optional<EdgeAccessor>(edge_ref, edge_type, from_vertex, to_vertex, &transaction_,
&storage_->indices_, &storage_->constraints_, config_, true);
&mem_storage->indices_, &mem_storage->constraints_, config_, true);
}
const std::string &InMemoryStorage::InMemoryAccessor::LabelToName(LabelId label) const {
@@ -643,10 +636,12 @@ utils::BasicResult<StorageDataManipulationError, void> InMemoryStorage::InMemory
auto could_replicate_all_sync_replicas = true;
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
if (transaction_.deltas.empty()) {
// We don't have to update the commit timestamp here because no one reads
// it.
storage_->commit_log_->MarkFinished(transaction_.start_timestamp);
mem_storage->commit_log_->MarkFinished(transaction_.start_timestamp);
} else {
// Validate that existence constraints are satisfied for all modified
// vertices.
@@ -658,7 +653,7 @@ utils::BasicResult<StorageDataManipulationError, void> InMemoryStorage::InMemory
}
// No need to take any locks here because we modified this vertex and no
// one else can touch it until we commit.
auto validation_result = ValidateExistenceConstraints(*prev.vertex, storage_->constraints_);
auto validation_result = ValidateExistenceConstraints(*prev.vertex, mem_storage->constraints_);
if (validation_result) {
Abort();
return StorageDataManipulationError{*validation_result};
@@ -675,7 +670,7 @@ utils::BasicResult<StorageDataManipulationError, void> InMemoryStorage::InMemory
{
std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_);
commit_timestamp_.emplace(storage_->CommitTimestamp(desired_commit_timestamp));
commit_timestamp_.emplace(mem_storage->CommitTimestamp(desired_commit_timestamp));
// Before committing and validating vertices against unique constraints,
// we have to update unique constraints with the vertices that are going
@@ -686,7 +681,7 @@ utils::BasicResult<StorageDataManipulationError, void> InMemoryStorage::InMemory
if (prev.type != PreviousPtr::Type::VERTEX) {
continue;
}
storage_->constraints_.unique_constraints.UpdateBeforeCommit(prev.vertex, transaction_);
mem_storage->constraints_.unique_constraints.UpdateBeforeCommit(prev.vertex, transaction_);
}
// Validate that unique constraints are satisfied for all modified
@@ -701,7 +696,7 @@ utils::BasicResult<StorageDataManipulationError, void> InMemoryStorage::InMemory
// No need to take any locks here because we modified this vertex and no
// one else can touch it until we commit.
unique_constraint_violation =
storage_->constraints_.unique_constraints.Validate(*prev.vertex, transaction_, *commit_timestamp_);
mem_storage->constraints_.unique_constraints.Validate(*prev.vertex, transaction_, *commit_timestamp_);
if (unique_constraint_violation) {
break;
}
@@ -717,21 +712,22 @@ utils::BasicResult<StorageDataManipulationError, void> InMemoryStorage::InMemory
// modifications before they are written to disk.
// Replica can log only the write transaction received from Main
// so the Wal files are consistent
if (storage_->replication_role_ == ReplicationRole::MAIN || desired_commit_timestamp.has_value()) {
could_replicate_all_sync_replicas = storage_->AppendToWalDataManipulation(transaction_, *commit_timestamp_);
if (mem_storage->replication_role_ == ReplicationRole::MAIN || desired_commit_timestamp.has_value()) {
could_replicate_all_sync_replicas =
mem_storage->AppendToWalDataManipulation(transaction_, *commit_timestamp_);
}
// Take committed_transactions lock while holding the engine lock to
// make sure that committed transactions are sorted by the commit
// timestamp in the list.
storage_->committed_transactions_.WithLock([&](auto & /*committed_transactions*/) {
mem_storage->committed_transactions_.WithLock([&](auto & /*committed_transactions*/) {
// TODO: release lock, and update all deltas to have a local copy
// of the commit timestamp
MG_ASSERT(transaction_.commit_timestamp != nullptr, "Invalid database state!");
transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release);
// Replica can only update the last commit timestamp with
// the commits received from main.
if (storage_->replication_role_ == ReplicationRole::MAIN || desired_commit_timestamp.has_value()) {
if (mem_storage->replication_role_ == ReplicationRole::MAIN || desired_commit_timestamp.has_value()) {
// Update the last commit timestamp
storage_->last_commit_timestamp_.store(*commit_timestamp_);
}
@@ -740,7 +736,7 @@ utils::BasicResult<StorageDataManipulationError, void> InMemoryStorage::InMemory
engine_guard.unlock();
});
storage_->commit_log_->MarkFinished(start_timestamp);
mem_storage->commit_log_->MarkFinished(start_timestamp);
}
}
@@ -905,31 +901,33 @@ void InMemoryStorage::InMemoryAccessor::Abort() {
}
}
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
{
std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_);
uint64_t mark_timestamp = storage_->timestamp_;
// Take garbage_undo_buffers lock while holding the engine lock to make
// sure that entries are sorted by mark timestamp in the list.
storage_->garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) {
mem_storage->garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) {
// Release engine lock because we don't have to hold it anymore and
// emplace back could take a long time.
engine_guard.unlock();
garbage_undo_buffers.emplace_back(mark_timestamp, std::move(transaction_.deltas));
});
storage_->deleted_vertices_.WithLock(
mem_storage->deleted_vertices_.WithLock(
[&](auto &deleted_vertices) { deleted_vertices.splice(deleted_vertices.begin(), my_deleted_vertices); });
storage_->deleted_edges_.WithLock(
mem_storage->deleted_edges_.WithLock(
[&](auto &deleted_edges) { deleted_edges.splice(deleted_edges.begin(), my_deleted_edges); });
}
storage_->commit_log_->MarkFinished(transaction_.start_timestamp);
mem_storage->commit_log_->MarkFinished(transaction_.start_timestamp);
is_transaction_active_ = false;
}
void InMemoryStorage::InMemoryAccessor::FinalizeTransaction() {
if (commit_timestamp_) {
storage_->commit_log_->MarkFinished(*commit_timestamp_);
storage_->committed_transactions_.WithLock(
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
mem_storage->commit_log_->MarkFinished(*commit_timestamp_);
mem_storage->committed_transactions_.WithLock(
[&](auto &committed_transactions) { committed_transactions.emplace_back(std::move(transaction_)); });
commit_timestamp_.reset();
}
@@ -1155,25 +1153,26 @@ StorageInfo InMemoryStorage::GetInfo() const {
}
VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(LabelId label, View view) {
return VerticesIterable(storage_->indices_.label_index.Vertices(label, view, &transaction_));
return VerticesIterable(
static_cast<InMemoryStorage *>(storage_)->indices_.label_index.Vertices(label, view, &transaction_));
}
VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(LabelId label, PropertyId property, View view) {
return VerticesIterable(storage_->indices_.label_property_index.Vertices(label, property, std::nullopt, std::nullopt,
view, &transaction_));
return VerticesIterable(static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index.Vertices(
label, property, std::nullopt, std::nullopt, view, &transaction_));
}
VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(LabelId label, PropertyId property,
const PropertyValue &value, View view) {
return VerticesIterable(storage_->indices_.label_property_index.Vertices(
return VerticesIterable(static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index.Vertices(
label, property, utils::MakeBoundInclusive(value), utils::MakeBoundInclusive(value), view, &transaction_));
}
VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(
LabelId label, PropertyId property, const std::optional<utils::Bound<PropertyValue>> &lower_bound,
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view) {
return VerticesIterable(
storage_->indices_.label_property_index.Vertices(label, property, lower_bound, upper_bound, view, &transaction_));
return VerticesIterable(static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index.Vertices(
label, property, lower_bound, upper_bound, view, &transaction_));
}
Transaction InMemoryStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) {

View File

@@ -91,9 +91,10 @@ class InMemoryStorage final : public Storage {
std::optional<VertexAccessor> FindVertex(Gid gid, View view) override;
VerticesIterable Vertices(View view) override {
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
return VerticesIterable(AllVerticesIterable(storage_->vertices_.access(), &transaction_, view,
&storage_->indices_, &storage_->constraints_,
storage_->config_.items));
&mem_storage->indices_, &mem_storage->constraints_,
mem_storage->config_.items));
}
VerticesIterable Vertices(LabelId label, View view) override;
@@ -113,20 +114,22 @@ class InMemoryStorage final : public Storage {
/// Return approximate number of vertices with the given label.
/// Note that this is always an over-estimate and never an under-estimate.
int64_t ApproximateVertexCount(LabelId label) const override {
return storage_->indices_.label_index.ApproximateVertexCount(label);
return static_cast<InMemoryStorage *>(storage_)->indices_.label_index.ApproximateVertexCount(label);
}
/// Return approximate number of vertices with the given label and property.
/// Note that this is always an over-estimate and never an under-estimate.
int64_t ApproximateVertexCount(LabelId label, PropertyId property) const override {
return storage_->indices_.label_property_index.ApproximateVertexCount(label, property);
return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index.ApproximateVertexCount(label,
property);
}
/// Return approximate number of vertices with the given label and the given
/// value for the given property. Note that this is always an over-estimate
/// and never an under-estimate.
int64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const override {
return storage_->indices_.label_property_index.ApproximateVertexCount(label, property, value);
return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index.ApproximateVertexCount(
label, property, value);
}
/// Return approximate number of vertices with the given label and value for
@@ -135,16 +138,17 @@ class InMemoryStorage final : public Storage {
int64_t ApproximateVertexCount(LabelId label, PropertyId property,
const std::optional<utils::Bound<PropertyValue>> &lower,
const std::optional<utils::Bound<PropertyValue>> &upper) const override {
return storage_->indices_.label_property_index.ApproximateVertexCount(label, property, lower, upper);
return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index.ApproximateVertexCount(
label, property, lower, upper);
}
std::optional<storage::IndexStats> GetIndexStats(const storage::LabelId &label,
const storage::PropertyId &property) const override {
return storage_->indices_.label_property_index.GetIndexStats(label, property);
return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index.GetIndexStats(label, property);
}
std::vector<std::pair<LabelId, PropertyId>> ClearIndexStats() override {
return storage_->indices_.label_property_index.ClearIndexStats();
return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index.ClearIndexStats();
}
std::vector<std::pair<LabelId, PropertyId>> DeleteIndexStatsForLabels(
@@ -152,7 +156,8 @@ class InMemoryStorage final : public Storage {
std::vector<std::pair<LabelId, PropertyId>> deleted_indexes;
std::for_each(labels.begin(), labels.end(), [this, &deleted_indexes](const auto &label_str) {
std::vector<std::pair<LabelId, PropertyId>> loc_results =
storage_->indices_.label_property_index.DeleteIndexStatsForLabel(NameToLabel(label_str));
static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index.DeleteIndexStatsForLabel(
NameToLabel(label_str));
deleted_indexes.insert(deleted_indexes.end(), std::make_move_iterator(loc_results.begin()),
std::make_move_iterator(loc_results.end()));
});
@@ -161,7 +166,7 @@ class InMemoryStorage final : public Storage {
void SetIndexStats(const storage::LabelId &label, const storage::PropertyId &property,
const IndexStats &stats) override {
storage_->indices_.label_property_index.SetIndexStats(label, property, stats);
static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index.SetIndexStats(label, property, stats);
}
/// @return Accessor to the deleted vertex if a deletion took place, std::nullopt otherwise
@@ -197,19 +202,24 @@ class InMemoryStorage final : public Storage {
/// @throw std::bad_alloc if unable to insert a new mapping
EdgeTypeId NameToEdgeType(std::string_view name) override;
bool LabelIndexExists(LabelId label) const override { return storage_->indices_.label_index.IndexExists(label); }
bool LabelIndexExists(LabelId label) const override {
return static_cast<InMemoryStorage *>(storage_)->indices_.label_index.IndexExists(label);
}
bool LabelPropertyIndexExists(LabelId label, PropertyId property) const override {
return storage_->indices_.label_property_index.IndexExists(label, property);
return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index.IndexExists(label, property);
}
IndicesInfo ListAllIndices() const override {
return {storage_->indices_.label_index.ListIndices(), storage_->indices_.label_property_index.ListIndices()};
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
return {mem_storage->indices_.label_index.ListIndices(),
mem_storage->indices_.label_property_index.ListIndices()};
}
ConstraintsInfo ListAllConstraints() const override {
return {ListExistenceConstraints(storage_->constraints_),
storage_->constraints_.unique_constraints.ListConstraints()};
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
return {ListExistenceConstraints(mem_storage->constraints_),
mem_storage->constraints_.unique_constraints.ListConstraints()};
}
void AdvanceCommand() override;
@@ -237,11 +247,6 @@ class InMemoryStorage final : public Storage {
/// @throw std::bad_alloc
Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, storage::Gid gid);
InMemoryStorage *storage_;
std::shared_lock<utils::RWLock> storage_guard_;
Transaction transaction_;
std::optional<uint64_t> commit_timestamp_;
bool is_transaction_active_;
Config::Items config_;
};
@@ -375,9 +380,9 @@ class InMemoryStorage final : public Storage {
utils::BasicResult<CreateSnapshotError> CreateSnapshot(std::optional<bool> is_periodic) override;
private:
Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode);
Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) override;
private:
/// The force parameter determines the behaviour of the garbage collector.
/// If it's set to true, it will behave as a global operation, i.e. it can't
/// be part of a transaction, and no other transaction can be active at the same time.
@@ -408,10 +413,10 @@ class InMemoryStorage final : public Storage {
bool ShouldStoreAndRestoreReplicas() const;
// Specific per storage engine
Constraints constraints_;
Indices indices_;
IsolationLevel isolation_level_;
StorageMode storage_mode_;
Constraints constraints_;
Indices indices_;
Config config_;
// TODO: This isn't really a commit log, it doesn't even care if a

View File

@@ -259,4 +259,24 @@ bool VerticesIterable::Iterator::operator==(const Iterator &other) const {
}
}
Storage::Accessor::Accessor(Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode)
: storage_(storage),
// The lock must be acquired before creating the transaction object to
// prevent freshly created transactions from dangling in an active state
// during exclusive operations.
storage_guard_(storage_->main_lock_),
transaction_(storage->CreateTransaction(isolation_level, storage_mode)),
is_transaction_active_(true) {}
Storage::Accessor::Accessor(Accessor &&other) noexcept
: storage_(other.storage_),
storage_guard_(std::move(other.storage_guard_)),
transaction_(std::move(other.transaction_)),
commit_timestamp_(other.commit_timestamp_),
is_transaction_active_(other.is_transaction_active_) {
// Don't allow the other accessor to abort our transaction in destructor.
other.is_transaction_active_ = false;
other.commit_timestamp_.reset();
}
} // namespace memgraph::storage

View File

@@ -189,7 +189,7 @@ class Storage {
virtual ~Storage() {}
class Accessor {
public:
Accessor() {}
Accessor(Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode);
Accessor(const Accessor &) = delete;
Accessor &operator=(const Accessor &) = delete;
Accessor &operator=(Accessor &&other) = delete;
@@ -310,6 +310,13 @@ class Storage {
virtual void FinalizeTransaction() = 0;
virtual std::optional<uint64_t> GetTransactionId() const = 0;
protected:
Storage *storage_;
std::shared_lock<utils::RWLock> storage_guard_;
Transaction transaction_;
std::optional<uint64_t> commit_timestamp_;
bool is_transaction_active_;
};
virtual std::unique_ptr<Accessor> Access(std::optional<IsolationLevel> override_isolation_level) = 0;
@@ -518,7 +525,8 @@ class Storage {
virtual utils::BasicResult<CreateSnapshotError> CreateSnapshot(std::optional<bool> is_periodic) = 0;
protected:
virtual Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) = 0;
// Main storage lock.
// Accessors take a shared lock when starting, so it is possible to block
// creation of new accessors by taking a unique lock. This is used when doing
@@ -543,6 +551,7 @@ class Storage {
utils::SpinLock engine_lock_;
uint64_t timestamp_{kTimestampInitialId};
uint64_t transaction_id_{kTransactionInitialId};
// Durability
std::filesystem::path snapshot_directory_;
std::filesystem::path wal_directory_;