Integrate property store

Reviewers: buda

Reviewed By: buda

Subscribers: buda, pullbot

Differential Revision: https://phabricator.memgraph.io/D2764
This commit is contained in:
Matej Ferencevic 2020-05-20 16:44:41 +02:00
parent 3932376301
commit b923d2bc36
10 changed files with 82 additions and 260 deletions

View File

@ -30,9 +30,9 @@ std::optional<size_t> FindPropertyPosition(
/// active.
bool LastCommittedVersionHasLabelProperty(
const Vertex &vertex, LabelId label, const std::set<PropertyId> &properties,
const PropertyValueArray &value_array, const Transaction &transaction,
uint64_t commit_timestamp) {
CHECK(properties.size() == value_array.size) << "Invalid database state!";
const std::vector<PropertyValue> &value_array,
const Transaction &transaction, uint64_t commit_timestamp) {
CHECK(properties.size() == value_array.size()) << "Invalid database state!";
PropertyIdArray property_array(properties.size());
bool current_value_equal_to_value[kUniqueConstraintsMaxProperties];
@ -55,11 +55,8 @@ bool LastCommittedVersionHasLabelProperty(
size_t i = 0;
for (const auto &property : properties) {
auto it = vertex.properties.find(property);
current_value_equal_to_value[i] = value_array.values[i]->IsNull();
if (it != vertex.properties.end()) {
current_value_equal_to_value[i] = it->second == *value_array.values[i];
}
current_value_equal_to_value[i] =
vertex.properties.IsPropertyEqual(property, value_array[i]);
property_array.values[i] = property;
i++;
}
@ -76,7 +73,7 @@ bool LastCommittedVersionHasLabelProperty(
auto pos = FindPropertyPosition(property_array, delta->property.key);
if (pos) {
current_value_equal_to_value[*pos] =
delta->property.value == *value_array.values[*pos];
delta->property.value == value_array[*pos];
}
break;
}
@ -147,11 +144,8 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label,
size_t i = 0;
for (const auto &property : properties) {
auto it = vertex.properties.find(property);
current_value_equal_to_value[i] = values[i].IsNull();
if (it != vertex.properties.end()) {
current_value_equal_to_value[i] = it->second == values[i];
}
current_value_equal_to_value[i] =
vertex.properties.IsPropertyEqual(property, values[i]);
property_array.values[i] = property;
i++;
}
@ -228,79 +222,21 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label,
return false;
}
bool operator<(const PropertyValueArray &lhs,
const std::vector<PropertyValue> &rhs) {
size_t n = std::min(lhs.size, rhs.size());
for (size_t i = 0; i < n; ++i) {
if (*lhs.values[i] < rhs[i]) {
return true;
}
if (rhs[i] < *lhs.values[i]) {
return false;
}
}
return lhs.size < rhs.size();
}
bool operator<(const std::vector<PropertyValue> &lhs,
const PropertyValueArray &rhs) {
size_t n = std::min(lhs.size(), rhs.size);
for (size_t i = 0; i < n; ++i) {
if (lhs[i] < *rhs.values[i]) {
return true;
}
if (*rhs.values[i] < lhs[i]) {
return false;
}
}
return lhs.size() < rhs.size;
}
bool operator==(const std::vector<PropertyValue> &lhs,
const PropertyValueArray &rhs) {
if (lhs.size() != rhs.size) {
return false;
}
for (size_t i = 0; i < rhs.size; ++i) {
if (lhs[i] == *rhs.values[i]) {
continue;
}
return false;
}
return true;
}
/// Helper function that, given the set of `properties`, extracts corresponding
/// property values from the `vertex`. `PropertyValueArray` is returned instead
/// of `std::vector` to avoid `std::bad_alloc` exception.
std::optional<PropertyValueArray> ExtractPropertyValues(
/// property values from the `vertex`.
/// @throw std::bad_alloc
std::optional<std::vector<PropertyValue>> ExtractPropertyValues(
const Vertex &vertex, const std::set<PropertyId> &properties) {
PropertyValueArray value_array(properties.size());
size_t i = 0;
std::vector<PropertyValue> value_array;
value_array.reserve(properties.size());
for (const auto &prop : properties) {
auto it = vertex.properties.find(prop);
if (it == vertex.properties.end() || it->second.IsNull()) {
auto value = vertex.properties.GetProperty(prop);
if (value.IsNull()) {
return std::nullopt;
}
value_array.values[i++] = &it->second;
value_array.emplace_back(std::move(value));
}
return value_array;
}
/// Helper function that converts optional property value array to optional
/// `std::vector` of property values.
/// @throw std::bad_alloc
std::optional<std::vector<PropertyValue>> OptionalPropertyValueArrayToVector(
const std::optional<PropertyValueArray> &value_array) {
if (!value_array) {
return std::nullopt;
}
std::vector<PropertyValue> values;
values.reserve(value_array->size);
for (size_t i = 0; i < value_array->size; ++i) {
values.push_back(*value_array->values[i]);
}
return std::move(values);
return std::move(value_array);
}
} // namespace
@ -337,22 +273,13 @@ bool UniqueConstraints::Entry::operator==(
return values == rhs;
}
bool UniqueConstraints::Entry::operator<(const PropertyValueArray &rhs) {
return values < rhs;
}
bool UniqueConstraints::Entry::operator==(const PropertyValueArray &rhs) {
return values == rhs;
}
void UniqueConstraints::UpdateBeforeCommit(const Vertex *vertex,
const Transaction &tx) {
for (auto &[label_props, storage] : constraints_) {
if (!utils::Contains(vertex->labels, label_props.first)) {
continue;
}
auto values = OptionalPropertyValueArrayToVector(
ExtractPropertyValues(*vertex, label_props.second));
auto values = ExtractPropertyValues(*vertex, label_props.second);
if (values) {
auto acc = storage.access();
acc.insert(Entry{std::move(*values), vertex, tx.start_timestamp});
@ -389,8 +316,7 @@ UniqueConstraints::CreateConstraint(
if (vertex.deleted || !utils::Contains(vertex.labels, label)) {
continue;
}
auto values = OptionalPropertyValueArrayToVector(
ExtractPropertyValues(vertex, properties));
auto values = ExtractPropertyValues(vertex, properties);
if (!values) {
continue;
}

View File

@ -28,7 +28,6 @@ struct FixedCapacityArray {
}
};
using PropertyValueArray = FixedCapacityArray<const PropertyValue *>;
using PropertyIdArray = FixedCapacityArray<PropertyId>;
struct ConstraintViolation {
@ -60,9 +59,6 @@ class UniqueConstraints {
bool operator<(const std::vector<PropertyValue> &rhs);
bool operator==(const std::vector<PropertyValue> &rhs);
bool operator<(const PropertyValueArray &rhs);
bool operator==(const PropertyValueArray &rhs);
};
public:
@ -118,6 +114,7 @@ class UniqueConstraints {
/// Validates the given vertex against unique constraints before committing.
/// This method should be called while commit lock is active with
/// `commit_timestamp` being a potential commit timestamp of the transaction.
/// @throw std::bad_alloc
std::optional<ConstraintViolation> Validate(const Vertex &vertex,
const Transaction &tx,
uint64_t commit_timestamp) const;
@ -155,7 +152,7 @@ inline utils::BasicResult<ConstraintViolation, bool> CreateExistenceConstraint(
}
for (const auto &vertex : vertices) {
if (!vertex.deleted && utils::Contains(vertex.labels, label) &&
vertex.properties.find(property) == vertex.properties.end()) {
!vertex.properties.HasProperty(property)) {
return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label,
std::set<PropertyId>{property}};
}
@ -186,7 +183,7 @@ ValidateExistenceConstraints(const Vertex &vertex,
const Constraints &constraints) {
for (const auto &[label, property] : constraints.existence_constraints) {
if (!vertex.deleted && utils::Contains(vertex.labels, label) &&
vertex.properties.find(property) == vertex.properties.end()) {
!vertex.properties.HasProperty(property)) {
return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label,
std::set<PropertyId>{property}};
}

View File

@ -1261,12 +1261,11 @@ void WalFile::AppendDelta(const Delta &delta, const Vertex &vertex,
wal_.WriteString(name_id_mapper_->IdToName(delta.property.key.AsUint()));
// The property value is the value that is currently stored in the
// vertex.
auto it = vertex.properties.find(delta.property.key);
if (it != vertex.properties.end()) {
wal_.WritePropertyValue(it->second);
} else {
wal_.WritePropertyValue(PropertyValue());
}
// 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:
@ -1314,12 +1313,10 @@ void WalFile::AppendDelta(const Delta &delta, const Edge &edge,
wal_.WriteString(name_id_mapper_->IdToName(delta.property.key.AsUint()));
// The property value is the value that is currently stored in the
// edge.
auto it = edge.properties.find(delta.property.key);
if (it != edge.properties.end()) {
wal_.WritePropertyValue(it->second);
} else {
wal_.WritePropertyValue(PropertyValue());
}
// 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:
@ -2322,7 +2319,7 @@ Durability::RecoveredSnapshot Durability::LoadSnapshot(
if (!key) throw RecoveryFailure("Invalid snapshot data!");
auto value = snapshot.ReadPropertyValue();
if (!value) throw RecoveryFailure("Invalid snapshot data!");
props.emplace(get_property_from_id(*key), std::move(*value));
props.SetProperty(get_property_from_id(*key), *value);
}
}
} else {
@ -2392,7 +2389,7 @@ Durability::RecoveredSnapshot Durability::LoadSnapshot(
if (!key) throw RecoveryFailure("Invalid snapshot data!");
auto value = snapshot.ReadPropertyValue();
if (!value) throw RecoveryFailure("Invalid snapshot data!");
props.emplace(get_property_from_id(*key), std::move(*value));
props.SetProperty(get_property_from_id(*key), *value);
}
}
@ -2731,18 +2728,7 @@ Durability::RecoveryInfo Durability::LoadWal(
delta.vertex_edge_set_property.property));
auto &property_value = delta.vertex_edge_set_property.value;
auto it = vertex->properties.find(property_id);
if (it != vertex->properties.end()) {
if (property_value.IsNull()) {
// remove the property
vertex->properties.erase(it);
} else {
// set the value
it->second = std::move(property_value);
}
} else if (!property_value.IsNull()) {
vertex->properties.emplace(property_id, std::move(property_value));
}
vertex->properties.SetProperty(property_id, property_value);
break;
}
@ -2851,18 +2837,7 @@ Durability::RecoveryInfo Durability::LoadWal(
auto property_id = PropertyId::FromUint(name_id_mapper_->NameToId(
delta.vertex_edge_set_property.property));
auto &property_value = delta.vertex_edge_set_property.value;
auto it = edge->properties.find(property_id);
if (it != edge->properties.end()) {
if (property_value.IsNull()) {
// remove the property
edge->properties.erase(it);
} else {
// set the value
it->second = std::move(property_value);
}
} else if (!property_value.IsNull()) {
edge->properties.emplace(property_id, std::move(property_value));
}
edge->properties.SetProperty(property_id, property_value);
break;
}
case WalDeltaData::Type::TRANSACTION_END:

View File

@ -1,12 +1,12 @@
#pragma once
#include <limits>
#include <map>
#include "utils/spin_lock.hpp"
#include "storage/v2/delta.hpp"
#include "storage/v2/id_types.hpp"
#include "storage/v2/property_store.hpp"
namespace storage {
@ -20,7 +20,7 @@ struct Edge {
Gid gid;
std::map<PropertyId, storage::PropertyValue> properties;
PropertyStore properties;
mutable utils::SpinLock lock;
bool deleted;

View File

@ -26,31 +26,17 @@ Result<bool> EdgeAccessor::SetProperty(PropertyId property,
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
auto it = edge_.ptr->properties.find(property);
bool existed = it != edge_.ptr->properties.end();
auto current_value = edge_.ptr->properties.GetProperty(property);
bool existed = !current_value.IsNull();
// We could skip setting the value if the previous one is the same to the new
// one. This would save some memory as a delta would not be created as well as
// avoid copying the value. The reason we are not doing that is because the
// current code always follows the logical pattern of "create a delta" and
// "modify in-place". Additionally, the created delta will make other
// transactions get a SERIALIZATION_ERROR.
if (it != edge_.ptr->properties.end()) {
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(),
property, it->second);
if (value.IsNull()) {
// remove the property
edge_.ptr->properties.erase(it);
} else {
// set the value
it->second = value;
}
} else {
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(),
property, PropertyValue());
if (!value.IsNull()) {
edge_.ptr->properties.emplace(property, value);
}
}
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property,
std::move(current_value));
edge_.ptr->properties.SetProperty(property, value);
return !existed;
}
@ -65,13 +51,14 @@ Result<bool> EdgeAccessor::ClearProperties() {
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
bool removed = !edge_.ptr->properties.empty();
for (const auto &property : edge_.ptr->properties) {
auto properties = edge_.ptr->properties.Properties();
bool removed = !properties.empty();
for (const auto &property : properties) {
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(),
property.first, property.second);
}
edge_.ptr->properties.clear();
edge_.ptr->properties.ClearProperties();
return removed;
}
@ -86,10 +73,7 @@ Result<PropertyValue> EdgeAccessor::GetProperty(PropertyId property,
{
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
deleted = edge_.ptr->deleted;
auto it = edge_.ptr->properties.find(property);
if (it != edge_.ptr->properties.end()) {
value = it->second;
}
value = edge_.ptr->properties.GetProperty(property);
delta = edge_.ptr->delta;
}
ApplyDeltasForRead(transaction_, delta, view,
@ -134,7 +118,7 @@ Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::Properties(
{
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
deleted = edge_.ptr->deleted;
properties = edge_.ptr->properties;
properties = edge_.ptr->properties.Properties();
delta = edge_.ptr->delta;
}
ApplyDeltasForRead(transaction_, delta, view,

View File

@ -93,10 +93,8 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label,
{
std::lock_guard<utils::SpinLock> guard(vertex.lock);
has_label = utils::Contains(vertex.labels, label);
auto it = vertex.properties.find(key);
if (it != vertex.properties.end()) {
current_value_equal_to_value = it->second == value;
}
current_value_equal_to_value =
vertex.properties.IsPropertyEqual(key, value);
deleted = vertex.deleted;
delta = vertex.delta;
}
@ -213,10 +211,8 @@ bool CurrentVersionHasLabelProperty(const Vertex &vertex, LabelId label,
std::lock_guard<utils::SpinLock> guard(vertex.lock);
deleted = vertex.deleted;
has_label = utils::Contains(vertex.labels, label);
auto it = vertex.properties.find(key);
if (it != vertex.properties.end()) {
current_value_equal_to_value = it->second == value;
}
current_value_equal_to_value =
vertex.properties.IsPropertyEqual(key, value);
delta = vertex.delta;
}
ApplyDeltasForRead(transaction, delta, view,
@ -397,10 +393,10 @@ void LabelPropertyIndex::UpdateOnAddLabel(LabelId label, Vertex *vertex,
if (label_prop.first != label) {
continue;
}
auto it = vertex->properties.find(label_prop.second);
if (it != vertex->properties.end() && !it->second.IsNull()) {
auto prop_value = vertex->properties.GetProperty(label_prop.second);
if (!prop_value.IsNull()) {
auto acc = storage.access();
acc.insert(Entry{it->second, vertex, tx.start_timestamp});
acc.insert(Entry{std::move(prop_value), vertex, tx.start_timestamp});
}
}
}
@ -438,11 +434,11 @@ bool LabelPropertyIndex::CreateIndex(
if (vertex.deleted || !utils::Contains(vertex.labels, label)) {
continue;
}
auto it = vertex.properties.find(property);
if (it == vertex.properties.end()) {
auto value = vertex.properties.GetProperty(property);
if (value.IsNull()) {
continue;
}
acc.insert(Entry{it->second, &vertex, 0});
acc.insert(Entry{std::move(value), &vertex, 0});
}
return true;
}

View File

@ -787,19 +787,8 @@ void Storage::Accessor::Abort() {
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);
}
vertex->properties.SetProperty(current->property.key,
current->property.value);
break;
}
case Delta::Action::ADD_IN_EDGE: {
@ -882,19 +871,8 @@ void Storage::Accessor::Abort() {
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);
}
edge->properties.SetProperty(current->property.key,
current->property.value);
break;
}
case Delta::Action::DELETE_OBJECT: {

View File

@ -1,7 +1,6 @@
#pragma once
#include <limits>
#include <map>
#include <tuple>
#include <vector>
@ -10,6 +9,7 @@
#include "storage/v2/delta.hpp"
#include "storage/v2/edge_ref.hpp"
#include "storage/v2/id_types.hpp"
#include "storage/v2/property_store.hpp"
namespace storage {
@ -22,7 +22,7 @@ struct Vertex {
Gid gid;
std::vector<LabelId> labels;
std::map<PropertyId, storage::PropertyValue> properties;
PropertyStore properties;
std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> in_edges;
std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> out_edges;

View File

@ -197,31 +197,17 @@ Result<bool> VertexAccessor::SetProperty(PropertyId property,
if (vertex_->deleted) return Error::DELETED_OBJECT;
auto it = vertex_->properties.find(property);
bool existed = it != vertex_->properties.end();
auto current_value = vertex_->properties.GetProperty(property);
bool existed = !current_value.IsNull();
// We could skip setting the value if the previous one is the same to the new
// one. This would save some memory as a delta would not be created as well as
// avoid copying the value. The reason we are not doing that is because the
// current code always follows the logical pattern of "create a delta" and
// "modify in-place". Additionally, the created delta will make other
// transactions get a SERIALIZATION_ERROR.
if (it != vertex_->properties.end()) {
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property,
it->second);
if (value.IsNull()) {
// remove the property
vertex_->properties.erase(it);
} else {
// set the value
it->second = value;
}
} else {
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property,
PropertyValue());
if (!value.IsNull()) {
vertex_->properties.emplace(property, value);
}
}
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property,
std::move(current_value));
vertex_->properties.SetProperty(property, value);
UpdateOnSetProperty(indices_, property, value, vertex_, *transaction_);
@ -236,15 +222,16 @@ Result<bool> VertexAccessor::ClearProperties() {
if (vertex_->deleted) return Error::DELETED_OBJECT;
bool removed = !vertex_->properties.empty();
for (const auto &property : vertex_->properties) {
auto properties = vertex_->properties.Properties();
bool removed = !properties.empty();
for (const auto &property : properties) {
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(),
property.first, property.second);
UpdateOnSetProperty(indices_, property.first, PropertyValue(), vertex_,
*transaction_);
}
vertex_->properties.clear();
vertex_->properties.ClearProperties();
return removed;
}
@ -258,10 +245,7 @@ Result<PropertyValue> VertexAccessor::GetProperty(PropertyId property,
{
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
deleted = vertex_->deleted;
auto it = vertex_->properties.find(property);
if (it != vertex_->properties.end()) {
value = it->second;
}
value = vertex_->properties.GetProperty(property);
delta = vertex_->delta;
}
ApplyDeltasForRead(transaction_, delta, view,
@ -304,7 +288,7 @@ Result<std::map<PropertyId, PropertyValue>> VertexAccessor::Properties(
{
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
deleted = vertex_->deleted;
properties = vertex_->properties;
properties = vertex_->properties.Properties();
delta = vertex_->delta;
}
ApplyDeltasForRead(transaction_, delta, view,

View File

@ -105,24 +105,11 @@ class DeltaGenerator final {
auto property_id =
storage::PropertyId::FromUint(gen_->mapper_.NameToId(property));
auto &props = vertex->properties;
auto it = props.find(property_id);
if (it == props.end()) {
storage::CreateAndLinkDelta(&transaction_, &*vertex,
storage::Delta::SetPropertyTag(),
property_id, storage::PropertyValue());
if (!value.IsNull()) {
props.emplace(property_id, value);
}
} else {
storage::CreateAndLinkDelta(&transaction_, &*vertex,
storage::Delta::SetPropertyTag(),
property_id, it->second);
if (!value.IsNull()) {
it->second = value;
} else {
props.erase(it);
}
}
auto old_value = props.GetProperty(property_id);
storage::CreateAndLinkDelta(&transaction_, &*vertex,
storage::Delta::SetPropertyTag(), property_id,
old_value);
props.SetProperty(property_id, value);
{
storage::WalDeltaData data;
data.type = storage::WalDeltaData::Type::VERTEX_SET_PROPERTY;
@ -166,13 +153,8 @@ class DeltaGenerator final {
auto property_id =
storage::PropertyId::FromUint(gen_->mapper_.NameToId(
data.vertex_edge_set_property.property));
auto &props = vertex->properties;
auto it = props.find(property_id);
if (it == props.end()) {
data.vertex_edge_set_property.value = storage::PropertyValue();
} else {
data.vertex_edge_set_property.value = it->second;
}
data.vertex_edge_set_property.value =
vertex->properties.GetProperty(property_id);
}
gen_->data_.emplace_back(commit_timestamp, data);
}