diff --git a/src/storage/v2/delta.hpp b/src/storage/v2/delta.hpp index 7c7a8cab1..0bf12d1ef 100644 --- a/src/storage/v2/delta.hpp +++ b/src/storage/v2/delta.hpp @@ -2,6 +2,8 @@ #include <atomic> +#include <glog/logging.h> + #include "storage/v2/property_value.hpp" namespace storage { @@ -9,6 +11,80 @@ namespace storage { // Forward declarations because we only store pointers here. struct Vertex; struct Edge; +struct Delta; + +// This class stores one of three pointers (`Delta`, `Vertex` and `Edge`) +// without using additional memory for storing the type. The type is stored in +// the pointer itself in the lower bits. All of those structures contain large +// items in themselves (e.g. `uint64_t`) that require the pointer to be aligned +// to their size (for `uint64_t` it is 8). That means that the pointer will +// always be a multiple of 8 which implies that the lower 3 bits of the pointer +// will always be 0. We can use those 3 bits to store information about the type +// of the pointer stored (2 bits). +class PreviousPtr { + private: + static constexpr uintptr_t kDelta = 0b01UL; + static constexpr uintptr_t kVertex = 0b10UL; + static constexpr uintptr_t kEdge = 0b11UL; + + static constexpr uintptr_t kMask = 0b11UL; + + public: + enum class Type { + DELTA, + VERTEX, + EDGE, + }; + + void Set(Delta *delta) { + uintptr_t value = reinterpret_cast<uintptr_t>(delta); + CHECK((value & kMask) == 0) << "Invalid pointer!"; + storage_ = value | kDelta; + } + + void Set(Vertex *vertex) { + uintptr_t value = reinterpret_cast<uintptr_t>(vertex); + CHECK((value & kMask) == 0) << "Invalid pointer!"; + storage_ = value | kVertex; + } + + void Set(Edge *edge) { + uintptr_t value = reinterpret_cast<uintptr_t>(edge); + CHECK((value & kMask) == 0) << "Invalid pointer!"; + storage_ = value | kEdge; + } + + Type GetType() const { + uintptr_t type = storage_ & kMask; + if (type == kDelta) { + return Type::DELTA; + } else if (type == kVertex) { + return Type::VERTEX; + } else if (type == kEdge) { + return Type::EDGE; + } else { + LOG(FATAL) << "Invalid pointer type!"; + } + } + + Delta *GetDelta() const { + CHECK((storage_ & kMask) == kDelta) << "Can't convert pointer to delta!"; + return reinterpret_cast<Delta *>(storage_ & ~kMask); + } + + Vertex *GetVertex() const { + CHECK((storage_ & kMask) == kVertex) << "Can't convert pointer to vertex!"; + return reinterpret_cast<Vertex *>(storage_ & ~kMask); + } + + Edge *GetEdge() const { + CHECK((storage_ & kMask) == kEdge) << "Can't convert pointer to edge!"; + return reinterpret_cast<Edge *>(storage_ & ~kMask); + } + + private: + uintptr_t storage_{0}; +}; struct Delta { enum class Action { @@ -141,7 +217,7 @@ struct Delta { // TODO: optimize with in-place copy std::atomic<uint64_t> *timestamp; uint64_t command_id; - Delta *prev{nullptr}; + PreviousPtr prev; std::atomic<Delta *> next{nullptr}; union { @@ -176,4 +252,7 @@ struct Delta { } }; +static_assert(alignof(Delta) >= 8, + "The Delta should be aligned to at least 8!"); + } // namespace storage diff --git a/src/storage/v2/edge.hpp b/src/storage/v2/edge.hpp index 85a2da1d3..96b20c956 100644 --- a/src/storage/v2/edge.hpp +++ b/src/storage/v2/edge.hpp @@ -30,6 +30,8 @@ struct Edge { Delta *delta; }; +static_assert(alignof(Edge) >= 8, "The Edge should be aligned to at least 8!"); + inline bool operator==(const Edge &first, const Edge &second) { return first.gid == second.gid; } diff --git a/src/storage/v2/mvcc.hpp b/src/storage/v2/mvcc.hpp index 1f75c8092..855c156ee 100644 --- a/src/storage/v2/mvcc.hpp +++ b/src/storage/v2/mvcc.hpp @@ -1,12 +1,8 @@ #pragma once -#include <type_traits> - #include "storage/v2/delta.hpp" -#include "storage/v2/edge.hpp" #include "storage/v2/property_value.hpp" #include "storage/v2/transaction.hpp" -#include "storage/v2/vertex.hpp" #include "storage/v2/view.hpp" namespace storage { @@ -80,8 +76,7 @@ inline Delta *CreateDeleteObjectDelta(Transaction *transaction) { } /// This function creates a delta in the transaction for the object and links -/// the delta into the object's delta list. It also adds the object to the -/// transaction's modified objects list. +/// the delta into the object's delta list. template <typename TObj, class... Args> inline void CreateAndLinkDelta(Transaction *transaction, TObj *object, Args &&... args) { @@ -89,24 +84,14 @@ inline void CreateAndLinkDelta(Transaction *transaction, TObj *object, &transaction->commit_timestamp, transaction->command_id); + // The operations are written in such order so that both `next` and `prev` + // chains are valid at all times. + delta->prev.Set(object); if (object->delta) { - object->delta->prev = delta; + object->delta->prev.Set(delta); } delta->next.store(object->delta, std::memory_order_release); object->delta = delta; - - if constexpr (std::is_same_v<TObj, Vertex>) { - if (transaction->modified_vertices.empty() || - transaction->modified_vertices.back() != object) { - transaction->modified_vertices.push_back(object); - } - } - if constexpr (std::is_same_v<TObj, Edge>) { - if (transaction->modified_edges.empty() || - transaction->modified_edges.back() != object) { - transaction->modified_edges.push_back(object); - } - } } } // namespace storage diff --git a/src/storage/v2/storage.cpp b/src/storage/v2/storage.cpp index e5969b190..32f49fca9 100644 --- a/src/storage/v2/storage.cpp +++ b/src/storage/v2/storage.cpp @@ -63,7 +63,7 @@ VertexAccessor Storage::Accessor::CreateVertex() { auto [it, inserted] = acc.insert(Vertex{storage::Gid::FromUint(gid), delta}); CHECK(inserted) << "The vertex must be inserted here!"; CHECK(it != acc.end()) << "Invalid Vertex accessor!"; - transaction_->modified_vertices.push_back(&*it); + delta->prev.Set(&*it); return VertexAccessor{&*it, transaction_}; } @@ -201,7 +201,7 @@ Result<EdgeAccessor> Storage::Accessor::CreateEdge(VertexAccessor *from, CHECK(inserted) << "The edge must be inserted here!"; CHECK(it != acc.end()) << "Invalid Edge accessor!"; auto edge = &*it; - transaction_->modified_edges.push_back(edge); + delta->prev.Set(&*it); CreateAndLinkDelta(transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); @@ -306,145 +306,163 @@ void Storage::Accessor::Commit() { void Storage::Accessor::Abort() { CHECK(transaction_->is_active) << "The transaction is already terminated!"; - for (Vertex *vertex : transaction_->modified_vertices) { - std::lock_guard<utils::SpinLock> guard(vertex->lock); - Delta *current = vertex->delta; - while (current != nullptr && - current->timestamp->load(std::memory_order_acquire) == - transaction_->transaction_id) { - switch (current->action) { - case Delta::Action::REMOVE_LABEL: { - auto it = std::find(vertex->labels.begin(), vertex->labels.end(), - current->label); - CHECK(it != vertex->labels.end()) << "Invalid database state!"; - std::swap(*it, *vertex->labels.rbegin()); - vertex->labels.pop_back(); - break; - } - case Delta::Action::ADD_LABEL: { - auto it = std::find(vertex->labels.begin(), vertex->labels.end(), - current->label); - CHECK(it == vertex->labels.end()) << "Invalid database state!"; - vertex->labels.push_back(current->label); - break; - } - case Delta::Action::SET_PROPERTY: { - auto it = vertex->properties.find(current->property.key); - if (it != vertex->properties.end()) { - if (current->property.value.IsNull()) { - // remove the property - vertex->properties.erase(it); - } else { - // set the value - it->second = current->property.value; + for (const auto &delta : transaction_->deltas) { + switch (delta.prev.GetType()) { + case PreviousPtr::Type::VERTEX: { + auto vertex = delta.prev.GetVertex(); + std::lock_guard<utils::SpinLock> guard(vertex->lock); + Delta *current = vertex->delta; + while (current != nullptr && + current->timestamp->load(std::memory_order_acquire) == + transaction_->transaction_id) { + switch (current->action) { + case Delta::Action::REMOVE_LABEL: { + auto it = std::find(vertex->labels.begin(), vertex->labels.end(), + current->label); + CHECK(it != vertex->labels.end()) << "Invalid database state!"; + std::swap(*it, *vertex->labels.rbegin()); + vertex->labels.pop_back(); + break; } - } else if (!current->property.value.IsNull()) { - vertex->properties.emplace(current->property.key, - current->property.value); - } - break; - } - case Delta::Action::ADD_IN_EDGE: { - std::tuple<uint64_t, Vertex *, Edge *> link{ - current->vertex_edge.edge_type, current->vertex_edge.vertex, - current->vertex_edge.edge}; - auto it = - std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); - CHECK(it == vertex->in_edges.end()) << "Invalid database state!"; - vertex->in_edges.push_back(link); - break; - } - case Delta::Action::ADD_OUT_EDGE: { - std::tuple<uint64_t, Vertex *, Edge *> link{ - current->vertex_edge.edge_type, current->vertex_edge.vertex, - current->vertex_edge.edge}; - auto it = std::find(vertex->out_edges.begin(), - vertex->out_edges.end(), link); - CHECK(it == vertex->out_edges.end()) << "Invalid database state!"; - vertex->out_edges.push_back(link); - break; - } - case Delta::Action::REMOVE_IN_EDGE: { - std::tuple<uint64_t, Vertex *, Edge *> link{ - current->vertex_edge.edge_type, current->vertex_edge.vertex, - current->vertex_edge.edge}; - auto it = - std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); - CHECK(it != vertex->in_edges.end()) << "Invalid database state!"; - std::swap(*it, *vertex->in_edges.rbegin()); - vertex->in_edges.pop_back(); - break; - } - case Delta::Action::REMOVE_OUT_EDGE: { - std::tuple<uint64_t, Vertex *, Edge *> link{ - current->vertex_edge.edge_type, current->vertex_edge.vertex, - current->vertex_edge.edge}; - auto it = std::find(vertex->out_edges.begin(), - vertex->out_edges.end(), link); - CHECK(it != vertex->out_edges.end()) << "Invalid database state!"; - std::swap(*it, *vertex->out_edges.rbegin()); - vertex->out_edges.pop_back(); - break; - } - case Delta::Action::DELETE_OBJECT: { - auto acc = storage_->vertices_.access(); - CHECK(acc.remove(vertex->gid)) << "Invalid database state!"; - break; - } - case Delta::Action::RECREATE_OBJECT: { - vertex->deleted = false; - break; - } - } - current = current->next.load(std::memory_order_acquire); - } - vertex->delta = current; - } - for (Edge *edge : transaction_->modified_edges) { - std::lock_guard<utils::SpinLock> guard(edge->lock); - Delta *current = edge->delta; - while (current != nullptr && - current->timestamp->load(std::memory_order_acquire) == - transaction_->transaction_id) { - switch (current->action) { - case Delta::Action::SET_PROPERTY: { - auto it = edge->properties.find(current->property.key); - if (it != edge->properties.end()) { - if (current->property.value.IsNull()) { - // remove the property - edge->properties.erase(it); - } else { - // set the value - it->second = current->property.value; + case Delta::Action::ADD_LABEL: { + auto it = std::find(vertex->labels.begin(), vertex->labels.end(), + current->label); + CHECK(it == vertex->labels.end()) << "Invalid database state!"; + vertex->labels.push_back(current->label); + break; + } + case Delta::Action::SET_PROPERTY: { + auto it = vertex->properties.find(current->property.key); + if (it != vertex->properties.end()) { + if (current->property.value.IsNull()) { + // remove the property + vertex->properties.erase(it); + } else { + // set the value + it->second = current->property.value; + } + } else if (!current->property.value.IsNull()) { + vertex->properties.emplace(current->property.key, + current->property.value); + } + break; + } + case Delta::Action::ADD_IN_EDGE: { + std::tuple<uint64_t, Vertex *, Edge *> link{ + current->vertex_edge.edge_type, current->vertex_edge.vertex, + current->vertex_edge.edge}; + auto it = std::find(vertex->in_edges.begin(), + vertex->in_edges.end(), link); + CHECK(it == vertex->in_edges.end()) << "Invalid database state!"; + vertex->in_edges.push_back(link); + break; + } + case Delta::Action::ADD_OUT_EDGE: { + std::tuple<uint64_t, Vertex *, Edge *> link{ + current->vertex_edge.edge_type, current->vertex_edge.vertex, + current->vertex_edge.edge}; + auto it = std::find(vertex->out_edges.begin(), + vertex->out_edges.end(), link); + CHECK(it == vertex->out_edges.end()) << "Invalid database state!"; + vertex->out_edges.push_back(link); + break; + } + case Delta::Action::REMOVE_IN_EDGE: { + std::tuple<uint64_t, Vertex *, Edge *> link{ + current->vertex_edge.edge_type, current->vertex_edge.vertex, + current->vertex_edge.edge}; + auto it = std::find(vertex->in_edges.begin(), + vertex->in_edges.end(), link); + CHECK(it != vertex->in_edges.end()) << "Invalid database state!"; + std::swap(*it, *vertex->in_edges.rbegin()); + vertex->in_edges.pop_back(); + break; + } + case Delta::Action::REMOVE_OUT_EDGE: { + std::tuple<uint64_t, Vertex *, Edge *> link{ + current->vertex_edge.edge_type, current->vertex_edge.vertex, + current->vertex_edge.edge}; + auto it = std::find(vertex->out_edges.begin(), + vertex->out_edges.end(), link); + CHECK(it != vertex->out_edges.end()) << "Invalid database state!"; + std::swap(*it, *vertex->out_edges.rbegin()); + vertex->out_edges.pop_back(); + break; + } + case Delta::Action::DELETE_OBJECT: { + auto acc = storage_->vertices_.access(); + CHECK(acc.remove(vertex->gid)) << "Invalid database state!"; + break; + } + case Delta::Action::RECREATE_OBJECT: { + vertex->deleted = false; + break; } - } else if (!current->property.value.IsNull()) { - edge->properties.emplace(current->property.key, - current->property.value); } - break; + current = current->next.load(std::memory_order_acquire); } - case Delta::Action::DELETE_OBJECT: { - auto acc = storage_->edges_.access(); - CHECK(acc.remove(edge->gid)) << "Invalid database state!"; - break; - } - case Delta::Action::RECREATE_OBJECT: { - edge->deleted = false; - break; - } - case Delta::Action::REMOVE_LABEL: - case Delta::Action::ADD_LABEL: - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::ADD_OUT_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - case Delta::Action::REMOVE_OUT_EDGE: { - LOG(FATAL) << "Invalid database state!"; - break; + vertex->delta = current; + if (current != nullptr) { + current->prev.Set(vertex); } + + break; } - current = current->next.load(std::memory_order_acquire); + case PreviousPtr::Type::EDGE: { + auto edge = delta.prev.GetEdge(); + std::lock_guard<utils::SpinLock> guard(edge->lock); + Delta *current = edge->delta; + while (current != nullptr && + current->timestamp->load(std::memory_order_acquire) == + transaction_->transaction_id) { + switch (current->action) { + case Delta::Action::SET_PROPERTY: { + auto it = edge->properties.find(current->property.key); + if (it != edge->properties.end()) { + if (current->property.value.IsNull()) { + // remove the property + edge->properties.erase(it); + } else { + // set the value + it->second = current->property.value; + } + } else if (!current->property.value.IsNull()) { + edge->properties.emplace(current->property.key, + current->property.value); + } + break; + } + case Delta::Action::DELETE_OBJECT: { + auto acc = storage_->edges_.access(); + CHECK(acc.remove(edge->gid)) << "Invalid database state!"; + break; + } + case Delta::Action::RECREATE_OBJECT: { + edge->deleted = false; + break; + } + case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_LABEL: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: { + LOG(FATAL) << "Invalid database state!"; + break; + } + } + current = current->next.load(std::memory_order_acquire); + } + edge->delta = current; + if (current != nullptr) { + current->prev.Set(edge); + } + + break; + } + case PreviousPtr::Type::DELTA: + break; } - edge->delta = current; } transaction_->is_active = false; } diff --git a/src/storage/v2/transaction.hpp b/src/storage/v2/transaction.hpp index 963deba3b..c7089e5cb 100644 --- a/src/storage/v2/transaction.hpp +++ b/src/storage/v2/transaction.hpp @@ -29,7 +29,6 @@ struct Transaction { commit_timestamp(other.commit_timestamp.load()), command_id(other.command_id), deltas(std::move(other.deltas)), - modified_vertices(std::move(other.modified_vertices)), is_active(other.is_active), must_abort(other.must_abort) {} @@ -44,8 +43,6 @@ struct Transaction { std::atomic<uint64_t> commit_timestamp; uint64_t command_id; std::list<Delta> deltas; - std::list<Vertex *> modified_vertices; - std::list<Edge *> modified_edges; bool is_active; bool must_abort; }; diff --git a/src/storage/v2/vertex.hpp b/src/storage/v2/vertex.hpp index 9cb4b18a8..5fc80c0ec 100644 --- a/src/storage/v2/vertex.hpp +++ b/src/storage/v2/vertex.hpp @@ -35,6 +35,9 @@ struct Vertex { Delta *delta; }; +static_assert(alignof(Vertex) >= 8, + "The Vertex should be aligned to at least 8!"); + inline bool operator==(const Vertex &first, const Vertex &second) { return first.gid == second.gid; }