memgraph/src/storage/v3/mvcc.hpp

152 lines
7.1 KiB
C++
Raw Normal View History

2023-01-26 21:26:24 +08:00
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
2022-12-12 19:39:49 +08:00
#include <type_traits>
2022-12-15 18:25:37 +08:00
2022-12-12 19:39:49 +08:00
#include "storage/v3/edge.hpp"
#include "storage/v3/property_value.hpp"
#include "storage/v3/transaction.hpp"
2022-12-12 19:39:49 +08:00
#include "storage/v3/vertex.hpp"
#include "storage/v3/view.hpp"
2022-12-12 19:39:49 +08:00
#include "utils/concepts.hpp"
namespace memgraph::storage::v3 {
2022-12-15 18:25:37 +08:00
inline VertexData *GetDeltaHolder(Vertex *vertex) { return &vertex->second; }
inline Edge *GetDeltaHolder(Edge *edge) { return edge; }
/// This function iterates through the undo buffers from an object (starting
/// from the supplied delta) and determines what deltas should be applied to get
/// the currently visible version of the object. When the function finds a delta
/// that should be applied it calls the callback function with the delta that
/// should be applied passed as a parameter to the callback. It is up to the
/// caller to apply the deltas.
template <typename TCallback>
inline void ApplyDeltasForRead(Transaction *transaction, const Delta *delta, View view, const TCallback &callback) {
// if the transaction is not committed, then its deltas have transaction_id for the timestamp, otherwise they have
// its commit timestamp set.
// This allows the transaction to see its changes even though it's committed.
2022-09-22 00:25:51 +08:00
const auto &commit_info = *transaction->commit_info;
while (delta != nullptr) {
2022-09-22 00:25:51 +08:00
const auto &delta_commit_info = *delta->commit_info;
auto cid = delta->command_id;
// For SNAPSHOT ISOLATION -> we can only see the changes which were committed before the start of the current
// transaction
//
// For READ COMMITTED -> we can only see the changes which are committed. Commit timestamps of
// uncommitted changes are set to the transaction id of the transaction that made the change. Transaction id is
// always higher than start or commit timestamps so we know if the timestamp is lower than the initial transaction
// id value, that the change is committed.
//
// For READ UNCOMMITTED -> we accept any change.
2022-09-22 00:25:51 +08:00
if ((transaction->isolation_level == IsolationLevel::SNAPSHOT_ISOLATION && delta_commit_info.is_locally_committed &&
delta_commit_info.start_or_commit_timestamp.logical_id < transaction->start_timestamp.logical_id) ||
(transaction->isolation_level == IsolationLevel::READ_COMMITTED && delta_commit_info.is_locally_committed) ||
(transaction->isolation_level == IsolationLevel::READ_UNCOMMITTED)) {
break;
}
// We shouldn't undo our newest changes because the user requested a NEW
// view of the database.
2022-09-22 00:25:51 +08:00
if (view == View::NEW && delta_commit_info.start_or_commit_timestamp == commit_info.start_or_commit_timestamp &&
cid <= transaction->command_id) {
break;
}
// We shouldn't undo our older changes because the user requested a OLD view
// of the database.
2022-09-22 00:25:51 +08:00
if (view == View::OLD && delta_commit_info.start_or_commit_timestamp == commit_info.start_or_commit_timestamp &&
delta->command_id < transaction->command_id) {
break;
}
// This delta must be applied, call the callback.
callback(*delta);
// Move to the next delta.
2022-09-22 00:25:51 +08:00
delta = delta->next;
}
}
/// This function prepares the object for a write. It checks whether there are
/// any serialization errors in the process (eg. the object can't be written to
/// from this transaction because it is being written to from another
/// transaction) and returns a `bool` value indicating whether the caller can
/// proceed with a write operation.
template <typename TObj>
2022-12-12 19:39:49 +08:00
requires utils::SameAsAnyOf<TObj, Edge, Vertex>
inline bool PrepareForWrite(Transaction *transaction, TObj *object) {
2022-12-15 18:25:37 +08:00
auto *delta_holder = GetDeltaHolder(object);
2022-12-12 19:39:49 +08:00
if (delta_holder->delta == nullptr) return true;
2022-12-12 19:39:49 +08:00
const auto &delta_commit_info = *delta_holder->delta->commit_info;
2022-09-22 00:25:51 +08:00
if (delta_commit_info.start_or_commit_timestamp == transaction->commit_info->start_or_commit_timestamp ||
(delta_commit_info.is_locally_committed &&
delta_commit_info.start_or_commit_timestamp < transaction->start_timestamp)) {
return true;
}
transaction->must_abort = true;
return false;
}
/// This function creates a `DELETE_OBJECT` delta in the transaction and returns
/// a pointer to the created delta. It doesn't perform any linking of the delta
/// and is primarily used to create the first delta for an object (that must be
/// a `DELETE_OBJECT` delta).
/// @throw std::bad_alloc
inline Delta *CreateDeleteObjectDelta(Transaction *transaction) {
2023-02-03 20:42:28 +08:00
return &transaction->deltas.emplace_back(Delta::DeleteObjectTag(), transaction->commit_info.get(), GetNextDeltaId(),
transaction->command_id);
}
/// This function creates a delta in the transaction for the object and links
/// the delta into the object's delta list.
/// @throw std::bad_alloc
template <typename TObj, class... Args>
2022-12-12 19:39:49 +08:00
requires utils::SameAsAnyOf<TObj, Edge, Vertex>
inline void CreateAndLinkDelta(Transaction *transaction, TObj *object, Args &&...args) {
2022-09-22 00:25:51 +08:00
auto delta = &transaction->deltas.emplace_back(std::forward<Args>(args)..., transaction->commit_info.get(),
2023-02-03 20:42:28 +08:00
GetNextDeltaId(), transaction->command_id);
2022-12-15 18:25:37 +08:00
auto *delta_holder = GetDeltaHolder(object);
// The operations are written in such order so that both `next` and `prev`
// chains are valid at all times. The chains must be valid at all times
// because garbage collection (which traverses the chains) is done
// concurrently (as well as other execution threads).
// 1. We need to set the next delta of the new delta to the existing delta.
// TODO(antaljanosbenjamin): clang-tidy detects (in my opinion a false positive) issue in
// `Shard::Accessor::CreateEdge`.
// NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
2022-12-12 19:39:49 +08:00
delta->next = delta_holder->delta;
// 2. We need to set the previous delta of the new delta to the object.
delta->prev.Set(object);
// 3. We need to set the previous delta of the existing delta to the new
// delta. After this point the garbage collector will be able to see the new
// delta but won't modify it until we are done with all of our modifications.
2022-12-12 19:39:49 +08:00
if (delta_holder->delta) {
delta_holder->delta->prev.Set(delta);
}
// 4. Finally, we need to set the object's delta to the new delta. The garbage
// collector and other transactions will acquire the object lock to read the
// delta from the object. Because the lock is held during the whole time this
// modification is being done, everybody else will wait until we are fully
// done with our modification before they read the object's delta value.
2022-12-12 19:39:49 +08:00
delta_holder->delta = delta;
}
} // namespace memgraph::storage::v3