mvcc::Hints reduction
Summary: It's a sketch. I can tidy it up if you like this approach. Reviewers: mislav.bradac, mferencevic Reviewed By: mislav.bradac Subscribers: buda, pullbot Differential Revision: https://phabricator.memgraph.io/D841
This commit is contained in:
parent
3ca9acb90a
commit
0370e83b4d
@ -1,103 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
#include <atomic>
|
||||
|
||||
#include "transactions/commit_log.hpp"
|
||||
#include "utils/assert.hpp"
|
||||
|
||||
namespace mvcc {
|
||||
|
||||
// known committed and known aborted for both cre and exp
|
||||
// this hints are used to quickly check the commit/abort status of the
|
||||
// transaction that created this record. if these are not set, one should
|
||||
// consult the commit log to find the status and update the status here
|
||||
// more info https://wiki.postgresql.org/wiki/Hint_Bits
|
||||
class Hints {
|
||||
public:
|
||||
union HintBits;
|
||||
|
||||
private:
|
||||
enum Flags : uint8_t {
|
||||
CRE_CMT = 0x01, // __01
|
||||
CRE_ABT = 0x02, // __10
|
||||
EXP_CMT = 0x04, // 01__
|
||||
EXP_ABT = 0x08 // 10__
|
||||
};
|
||||
|
||||
template <Flags COMMITTED, Flags ABORTED>
|
||||
class TxHints {
|
||||
using type = TxHints<COMMITTED, ABORTED>;
|
||||
|
||||
public:
|
||||
TxHints(std::atomic<uint8_t> &bits) : bits(bits) {}
|
||||
|
||||
struct Value {
|
||||
bool is_committed() const { return bits & COMMITTED; }
|
||||
|
||||
bool is_aborted() const { return bits & ABORTED; }
|
||||
|
||||
bool is_unknown() const { return !(is_committed() || is_aborted()); }
|
||||
|
||||
uint8_t bits;
|
||||
};
|
||||
|
||||
Value load(std::memory_order order = std::memory_order_seq_cst) {
|
||||
return Value{bits.load(order)};
|
||||
}
|
||||
|
||||
void set_committed(std::memory_order order = std::memory_order_seq_cst) {
|
||||
bits.fetch_or(COMMITTED, order);
|
||||
}
|
||||
|
||||
void set_aborted(std::memory_order order = std::memory_order_seq_cst) {
|
||||
bits.fetch_or(ABORTED, order);
|
||||
}
|
||||
|
||||
void clear_commited(std::memory_order order = std::memory_order_seq_cst) {
|
||||
bits.fetch_and(~COMMITTED, order);
|
||||
}
|
||||
|
||||
void clear_aborted(std::memory_order order = std::memory_order_seq_cst) {
|
||||
bits.fetch_and(~ABORTED, order);
|
||||
}
|
||||
|
||||
void clear(std::memory_order order = std::memory_order_seq_cst) {
|
||||
clear_aborted(order);
|
||||
clear_commited(order);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<uint8_t> &bits;
|
||||
};
|
||||
|
||||
struct Cre : public TxHints<CRE_CMT, CRE_ABT> {
|
||||
using TxHints::TxHints;
|
||||
};
|
||||
|
||||
struct Exp : public TxHints<EXP_CMT, EXP_ABT> {
|
||||
using TxHints::TxHints;
|
||||
};
|
||||
|
||||
public:
|
||||
Hints() : cre(bits), exp(bits) {
|
||||
debug_assert(bits.is_lock_free(), "Bits are not lock free.");
|
||||
}
|
||||
|
||||
union HintBits {
|
||||
uint8_t bits;
|
||||
|
||||
Cre::Value cre;
|
||||
Exp::Value exp;
|
||||
};
|
||||
|
||||
HintBits load(std::memory_order order = std::memory_order_seq_cst) {
|
||||
return HintBits{bits.load(order)};
|
||||
}
|
||||
|
||||
Cre cre;
|
||||
Exp exp;
|
||||
|
||||
std::atomic<uint8_t> bits{0};
|
||||
};
|
||||
}
|
@ -7,7 +7,6 @@
|
||||
#include "transactions/engine.hpp"
|
||||
#include "transactions/transaction.hpp"
|
||||
|
||||
#include "mvcc/hints.hpp"
|
||||
#include "mvcc/version.hpp"
|
||||
#include "storage/locking/record_lock.hpp"
|
||||
|
||||
@ -25,35 +24,6 @@ class Record : public Version<T> {
|
||||
Record(Record &&) = delete;
|
||||
Record &operator=(Record &&) = delete;
|
||||
|
||||
private:
|
||||
template <typename TId>
|
||||
struct CreExp {
|
||||
std::atomic<TId> cre{0};
|
||||
std::atomic<TId> exp{0};
|
||||
};
|
||||
|
||||
// tx.cre is the id of the transaction that created the record
|
||||
// and tx.exp is the id of the transaction that deleted the record
|
||||
// These values are used to determine the visibility of the record
|
||||
// to the current transaction.
|
||||
CreExp<tx::transaction_id_t> tx_;
|
||||
|
||||
// cmd.cre is the id of the command in this transaction that created the
|
||||
// record and cmd.exp is the id of the command in this transaction that
|
||||
// deleted the record. These values are used to determine the visibility
|
||||
// of the record to the current command in the running transaction.
|
||||
CreExp<tx::command_id_t> cmd_;
|
||||
|
||||
Hints hints_;
|
||||
|
||||
public:
|
||||
inline const auto &tx() const { return tx_; }
|
||||
inline const auto &cmd() const { return cmd_; }
|
||||
|
||||
// NOTE: Wasn't used.
|
||||
// this lock is used by write queries when they update or delete records
|
||||
// RecordLock lock;
|
||||
|
||||
// check if this record is visible to the transaction t
|
||||
bool visible(const tx::Transaction &t) {
|
||||
// Mike Olson says 17 march 1993: the tests in this routine are correct;
|
||||
@ -65,23 +35,24 @@ class Record : public Version<T> {
|
||||
tx::command_id_t cmd_exp;
|
||||
std::tie(tx_exp, cmd_exp) = fetch_exp();
|
||||
|
||||
return ((tx_.cre == t.id_ && // inserted by the current transaction
|
||||
cmd_.cre < t.cid() && // before this command, and
|
||||
(tx_exp == 0 || // the row has not been deleted, or
|
||||
(tx_exp == t.id_ && // it was deleted by the current
|
||||
// transaction
|
||||
cmd_exp >= t.cid()))) // but not before this command,
|
||||
|| // or
|
||||
(cre_committed(tx_.cre, t) && // the record was inserted by a
|
||||
// committed transaction, and
|
||||
(tx_exp == 0 || // the record has not been deleted, or
|
||||
(tx_exp == t.id_ && // the row is being deleted by this
|
||||
// transaction
|
||||
cmd_exp >= t.cid()) || // but it's not deleted "yet", or
|
||||
(tx_exp != t.id_ && // the row was deleted by another
|
||||
// transaction
|
||||
!exp_committed(tx_exp, t) // that has not been committed
|
||||
))));
|
||||
return (
|
||||
(tx_.cre == t.id_ && // inserted by the current transaction
|
||||
cmd_.cre < t.cid() && // before this command, and
|
||||
(tx_exp == 0 || // the row has not been deleted, or
|
||||
(tx_exp == t.id_ && // it was deleted by the current
|
||||
// transaction
|
||||
cmd_exp >= t.cid()))) // but not before this command,
|
||||
|| // or
|
||||
(committed(Hints::kCre, tx_.cre, t) && // the record was inserted by a
|
||||
// committed transaction, and
|
||||
(tx_exp == 0 || // the record has not been deleted, or
|
||||
(tx_exp == t.id_ && // the row is being deleted by this
|
||||
// transaction
|
||||
cmd_exp >= t.cid()) || // but it's not deleted "yet", or
|
||||
(tx_exp != t.id_ && // the row was deleted by another
|
||||
// transaction
|
||||
!committed(Hints::kExp, tx_exp, t) // that has not been committed
|
||||
))));
|
||||
}
|
||||
|
||||
void mark_created(const tx::Transaction &t) {
|
||||
@ -91,21 +62,13 @@ class Record : public Version<T> {
|
||||
}
|
||||
|
||||
void mark_expired(const tx::Transaction &t) {
|
||||
if (tx_.exp != 0) hints_.exp.clear();
|
||||
if (tx_.exp != 0) hints_.Clear(Hints::kExp);
|
||||
tx_.exp = t.id_;
|
||||
cmd_.exp = t.cid();
|
||||
}
|
||||
|
||||
bool exp_committed(tx::transaction_id_t id, const tx::Transaction &t) {
|
||||
return committed(hints_.exp, id, t);
|
||||
}
|
||||
|
||||
bool exp_committed(tx::Engine &engine) {
|
||||
return committed(hints_.exp, tx_.exp, engine);
|
||||
}
|
||||
|
||||
bool cre_committed(tx::transaction_id_t id, const tx::Transaction &t) {
|
||||
return committed(hints_.cre, id, t);
|
||||
return committed(Hints::kExp, tx_.exp, engine);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -174,9 +137,56 @@ class Record : public Version<T> {
|
||||
return tx_.exp == t.id_ && cmd_.exp == t.cid();
|
||||
}
|
||||
|
||||
const auto &tx() const { return tx_; }
|
||||
const auto &cmd() const { return cmd_; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* Fetch the (transaction, command) expiration before the check
|
||||
* Fast indicators if a transaction has committed or aborted. It is possible
|
||||
* the hints do not have that information, in which case the commit log needs
|
||||
* to be consulted (a slower operation).
|
||||
*/
|
||||
class Hints {
|
||||
public:
|
||||
/// Masks for the creation/expration and commit/abort positions.
|
||||
static constexpr uint8_t kCre = 0b0011;
|
||||
static constexpr uint8_t kExp = 0b1100;
|
||||
static constexpr uint8_t kCmt = 0b0101;
|
||||
static constexpr uint8_t kAbt = 0b1010;
|
||||
|
||||
/** Retruns true if any bit under the given mask is set. */
|
||||
bool Get(uint8_t mask) const { return bits_ & mask; }
|
||||
|
||||
/** Sets all the bits under the given mask. */
|
||||
void Set(uint8_t mask) { bits_.fetch_or(mask); }
|
||||
|
||||
/** Clears all the bits under the given mask. */
|
||||
void Clear(uint8_t mask) { bits_.fetch_and(~mask); }
|
||||
|
||||
private:
|
||||
std::atomic<uint8_t> bits_{0};
|
||||
};
|
||||
|
||||
template <typename TId>
|
||||
struct CreExp {
|
||||
std::atomic<TId> cre{0};
|
||||
std::atomic<TId> exp{0};
|
||||
};
|
||||
|
||||
// tx.cre is the id of the transaction that created the record
|
||||
// and tx.exp is the id of the transaction that deleted the record
|
||||
// These values are used to determine the visibility of the record
|
||||
// to the current transaction.
|
||||
CreExp<tx::transaction_id_t> tx_;
|
||||
|
||||
// cmd.cre is the id of the command in this transaction that created the
|
||||
// record and cmd.exp is the id of the command in this transaction that
|
||||
// deleted the record. These values are used to determine the visibility
|
||||
// of the record to the current command in the running transaction.
|
||||
CreExp<tx::command_id_t> cmd_;
|
||||
|
||||
Hints hints_;
|
||||
/** Fetch the (transaction, command) expiration before the check
|
||||
* because they can be concurrently modified by multiple transactions.
|
||||
* Do it in a loop to ensure that command is consistent with transaction.
|
||||
*/
|
||||
@ -197,13 +207,15 @@ class Record : public Version<T> {
|
||||
* Evaluates to true if that transaction has committed,
|
||||
* it started before `t` and it's not in it's snapshot.
|
||||
*
|
||||
* @param hints - hints to use to determine commit/abort
|
||||
* about transactions commit/abort status
|
||||
* @param mask - Hint bits mask (either Hints::kCre or Hints::kExp).
|
||||
* @param id - id to check if it's commited and visible
|
||||
* @return true if the id is commited and visible for the transaction t.
|
||||
*/
|
||||
template <class U>
|
||||
bool committed(U &hints, tx::transaction_id_t id, const tx::Transaction &t) {
|
||||
bool committed(uint8_t mask, tx::transaction_id_t id,
|
||||
const tx::Transaction &t) {
|
||||
debug_assert(mask == Hints::kCre || mask == Hints::kExp,
|
||||
"Mask must be either kCre or kExp");
|
||||
// Dominik Gleich says 4 april 2017: the tests in this routine are correct;
|
||||
// if you think they're not, you're wrong, and you should think about it
|
||||
// again. I know, it happened to me (and also to Matej Gradicek).
|
||||
@ -216,30 +228,30 @@ class Record : public Version<T> {
|
||||
// The creating transaction is still in progress (examine snapshot)
|
||||
if (t.snapshot().contains(id)) return false;
|
||||
|
||||
return committed(hints, id, t.engine_);
|
||||
return committed(mask, id, t.engine_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief - Check if the transaction with the given `id`
|
||||
* is committed.
|
||||
*
|
||||
* @param hints - hints to use to determine commit/abort
|
||||
* @param mask - Hint bits mask (either Hints::kCre or Hints::kExp).
|
||||
* @param id - id to check if commited
|
||||
* @param engine - engine instance with information about transaction
|
||||
* statuses
|
||||
* @return true if it's commited, false otherwise
|
||||
*/
|
||||
template <class U>
|
||||
bool committed(U &hints, tx::transaction_id_t id, tx::Engine &engine) {
|
||||
auto hint_bits = hints.load();
|
||||
bool committed(uint8_t mask, tx::transaction_id_t id, tx::Engine &engine) {
|
||||
debug_assert(mask == Hints::kCre || mask == Hints::kExp,
|
||||
"Mask must be either kCre or kExp");
|
||||
// if hints are set, return if id is committed
|
||||
if (!hint_bits.is_unknown()) return hint_bits.is_committed();
|
||||
if (hints_.Get(mask)) return hints_.Get(Hints::kCmt & mask);
|
||||
|
||||
// if hints are not set consult the commit log
|
||||
auto is_commited = engine.clog().is_committed(id);
|
||||
|
||||
// committed
|
||||
if (is_commited) return hints.set_committed(), true;
|
||||
if (engine.clog().is_committed(id)) {
|
||||
hints_.Set(Hints::kCmt & mask);
|
||||
return true;
|
||||
}
|
||||
|
||||
// we can't set_aborted hints because of a race-condition that
|
||||
// can occurr when tx.exp gets changed by some transaction.
|
||||
|
Loading…
Reference in New Issue
Block a user