From 4dc6400681a80847cf29af647514e7eb5bc58e1e Mon Sep 17 00:00:00 2001 From: Josip Mrden <josip.mrden@memgraph.io> Date: Fri, 9 Feb 2024 11:02:34 +0100 Subject: [PATCH] Add tests for flags --- src/flags/run_time_configurable.cpp | 20 +++++ src/flags/run_time_configurable.hpp | 7 ++ src/storage/v2/inmemory/storage.cpp | 9 ++- src/storage/v2/mvcc.hpp | 40 +++++----- src/storage/v2/transaction.hpp | 8 +- tests/e2e/CMakeLists.txt | 1 + tests/e2e/configuration/default_config.py | 2 + .../maximum_deltas_restriction/CMakeLists.txt | 8 ++ .../e2e/maximum_deltas_restriction/common.py | 27 +++++++ .../maximum_deltas_restriction.py | 74 +++++++++++++++++++ .../maximum_deltas_restriction/workloads.yaml | 14 ++++ 11 files changed, 188 insertions(+), 22 deletions(-) create mode 100644 tests/e2e/maximum_deltas_restriction/CMakeLists.txt create mode 100644 tests/e2e/maximum_deltas_restriction/common.py create mode 100644 tests/e2e/maximum_deltas_restriction/maximum_deltas_restriction.py create mode 100644 tests/e2e/maximum_deltas_restriction/workloads.yaml diff --git a/src/flags/run_time_configurable.cpp b/src/flags/run_time_configurable.cpp index a52426b5c..68bf0bc7d 100644 --- a/src/flags/run_time_configurable.cpp +++ b/src/flags/run_time_configurable.cpp @@ -57,6 +57,10 @@ DEFINE_bool(cartesian_product_enabled, true, "Enable cartesian product expansion // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) DEFINE_int64(maximum_deltas_per_transaction, -1, "Limit of deltas per transaction, default -1 (no limit)"); +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DEFINE_int64(maximum_delete_deltas_per_transaction, -1, + "Limit of delete deltas per transaction, default -1 (no limit)"); + namespace { // Bolt server name constexpr auto kServerNameSettingKey = "server.name"; @@ -79,6 +83,9 @@ constexpr auto kCartesianProductEnabledGFlagsKey = "cartesian-product-enabled"; constexpr auto kMaximumDeltasPerTransactionSettingKey = "maximum-deltas-per-transaction"; constexpr auto kMaximumDeltasPerTransactionGFlagsKey = "maximum-deltas-per-transaction"; +constexpr auto kMaximumDeleteDeltasPerTransactionSettingKey = "maximum-delete-deltas-per-transaction"; +constexpr auto kMaximumDeleteDeltasPerTransactionGFlagsKey = "maximum-delete-deltas-per-transaction"; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) std::atomic<double> execution_timeout_sec_; // Local cache-like thing @@ -88,6 +95,9 @@ std::atomic<bool> cartesian_product_enabled_{true}; // Local cache-like thing // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) std::atomic<int64_t> maximum_deltas_per_transaction_; // Local cache-like thing +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +std::atomic<int64_t> maximum_delete_deltas_per_transaction_; // Local cache-like thing + auto ToLLEnum(std::string_view val) { const auto ll_enum = memgraph::flags::LogLevelToEnum(val); if (!ll_enum) { @@ -203,6 +213,14 @@ void Initialize() { [&](const std::string &val) { maximum_deltas_per_transaction_ = std::stoll(val); // Cache for faster reads }); + + /* + * Register maximum delete deltas per transaction + */ + register_flag(kMaximumDeleteDeltasPerTransactionGFlagsKey, kMaximumDeleteDeltasPerTransactionSettingKey, !kRestore, + [&](const std::string &val) { + maximum_delete_deltas_per_transaction_ = std::stoll(val); // Cache for faster reads + }); } std::string GetServerName() { @@ -218,4 +236,6 @@ bool GetCartesianProductEnabled() { return cartesian_product_enabled_; } int64_t GetMaximumDeltasPerTransaction() { return maximum_deltas_per_transaction_; } +int64_t GetMaximumDeleteDeltasPerTransaction() { return maximum_delete_deltas_per_transaction_; } + } // namespace memgraph::flags::run_time diff --git a/src/flags/run_time_configurable.hpp b/src/flags/run_time_configurable.hpp index 0aaebbcd4..c32154081 100644 --- a/src/flags/run_time_configurable.hpp +++ b/src/flags/run_time_configurable.hpp @@ -49,4 +49,11 @@ bool GetCartesianProductEnabled(); */ int64_t GetMaximumDeltasPerTransaction(); +/** + * @brief Get the maximum amount of delete deltas per transaction + * + * @return int64_t + */ +int64_t GetMaximumDeleteDeltasPerTransaction(); + } // namespace memgraph::flags::run_time diff --git a/src/storage/v2/inmemory/storage.cpp b/src/storage/v2/inmemory/storage.cpp index 5180b9cb9..87222bade 100644 --- a/src/storage/v2/inmemory/storage.cpp +++ b/src/storage/v2/inmemory/storage.cpp @@ -1308,8 +1308,15 @@ Transaction InMemoryStorage::CreateTransaction( } auto maximum_deltas_per_transaction = flags::run_time::GetMaximumDeltasPerTransaction(); + auto maximum_delete_deltas_per_transaction = flags::run_time::GetMaximumDeleteDeltasPerTransaction(); - return {transaction_id, start_timestamp, isolation_level, storage_mode, false, maximum_deltas_per_transaction}; + return {transaction_id, + start_timestamp, + isolation_level, + storage_mode, + false, + maximum_deltas_per_transaction, + maximum_delete_deltas_per_transaction}; } void InMemoryStorage::SetStorageMode(StorageMode new_storage_mode) { diff --git a/src/storage/v2/mvcc.hpp b/src/storage/v2/mvcc.hpp index 28b19e3e5..8eea55bea 100644 --- a/src/storage/v2/mvcc.hpp +++ b/src/storage/v2/mvcc.hpp @@ -14,6 +14,7 @@ #include <atomic> #include <cstdint> #include <optional> +#include <unordered_set> #include "storage/v2/property_value.hpp" #include "storage/v2/transaction.hpp" @@ -24,6 +25,9 @@ namespace memgraph::storage { +const std::unordered_set<Delta::Action> delete_delta_actions{Delta::Action::ADD_IN_EDGE, Delta::Action::ADD_OUT_EDGE, + Delta::Action::RECREATE_OBJECT}; + /// 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 @@ -105,11 +109,18 @@ inline bool PrepareForWrite(Transaction *transaction, TObj *object) { return false; } -inline void IncrementDeltasChanged(Transaction *transaction) { - transaction->deltas_changed++; - if (transaction->max_deltas > -1 && transaction->deltas_changed > transaction->max_deltas) { +inline void IncrementDeltasChanged(Transaction *transaction, Delta *delta) { + if (transaction->max_deltas > -1) { + transaction->deltas_changed++; + if (transaction->deltas_changed > transaction->max_deltas) { + throw utils::BasicException( + "You have reached the maximum number of deltas for a transaction, transaction will be rollbacked!"); + } + } + + if (transaction->max_delete_deltas > -1 && delete_delta_actions.contains(delta->action)) { throw utils::BasicException( - "You have reached the maximum number of deltas for a transaction, transaction will be rollbacked!"); + "You have reached the maximum number of delete deltas for a transaction, transaction will be rollbacked!"); } } @@ -124,21 +135,12 @@ inline Delta *CreateDeleteObjectDelta(Transaction *transaction) { } transaction->EnsureCommitTimestampExists(); - IncrementDeltasChanged(transaction); + auto *delta = &transaction->deltas.use().emplace_back(Delta::DeleteObjectTag(), transaction->commit_timestamp.get(), + transaction->command_id); - return &transaction->deltas.use().emplace_back(Delta::DeleteObjectTag(), transaction->commit_timestamp.get(), - transaction->command_id); -} + IncrementDeltasChanged(transaction, delta); -inline Delta *CreateDeleteObjectDelta(Transaction *transaction, std::list<Delta> *deltas) { - if (transaction->storage_mode == StorageMode::IN_MEMORY_ANALYTICAL) { - return nullptr; - } - transaction->EnsureCommitTimestampExists(); - - IncrementDeltasChanged(transaction); - - return &deltas->emplace_back(Delta::DeleteObjectTag(), transaction->commit_timestamp.get(), transaction->command_id); + return delta; } /// TODO: what if in-memory analytical @@ -181,11 +183,11 @@ inline void CreateAndLinkDelta(Transaction *transaction, TObj *object, Args &&.. } transaction->EnsureCommitTimestampExists(); - IncrementDeltasChanged(transaction); - auto delta = &transaction->deltas.use().emplace_back(std::forward<Args>(args)..., transaction->commit_timestamp.get(), transaction->command_id); + IncrementDeltasChanged(transaction, delta); + // 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 diff --git a/src/storage/v2/transaction.hpp b/src/storage/v2/transaction.hpp index e9e4c599a..1d4e7ac1c 100644 --- a/src/storage/v2/transaction.hpp +++ b/src/storage/v2/transaction.hpp @@ -43,13 +43,15 @@ using PmrListDelta = utils::pmr::list<Delta>; struct Transaction { Transaction(uint64_t transaction_id, uint64_t start_timestamp, IsolationLevel isolation_level, - StorageMode storage_mode, bool edge_import_mode_active, int64_t max_deltas = -1) + StorageMode storage_mode, bool edge_import_mode_active, int64_t max_deltas = -1, + int64_t max_delete_deltas = -1) : transaction_id(transaction_id), start_timestamp(start_timestamp), command_id(0), deltas(0), md_deltas(utils::NewDeleteResource()), max_deltas(max_deltas), + max_delete_deltas(max_delete_deltas), must_abort(false), isolation_level(isolation_level), storage_mode(storage_mode), @@ -94,8 +96,10 @@ struct Transaction { Bond<PmrListDelta> deltas; utils::pmr::list<MetadataDelta> md_deltas; - int64_t max_deltas{0}; + int64_t max_deltas{-1}; + int64_t max_delete_deltas{-1}; uint64_t deltas_changed{0}; + uint64_t delete_deltas_changed{0}; bool must_abort{}; IsolationLevel isolation_level{}; StorageMode storage_mode{}; diff --git a/tests/e2e/CMakeLists.txt b/tests/e2e/CMakeLists.txt index a95297301..f4eaad838 100644 --- a/tests/e2e/CMakeLists.txt +++ b/tests/e2e/CMakeLists.txt @@ -76,6 +76,7 @@ add_subdirectory(queries) add_subdirectory(query_modules_storage_modes) add_subdirectory(garbage_collection) add_subdirectory(query_planning) +add_subdirectory(maximum_deltas_restriction) if (MG_EXPERIMENTAL_HIGH_AVAILABILITY) add_subdirectory(high_availability_experimental) diff --git a/tests/e2e/configuration/default_config.py b/tests/e2e/configuration/default_config.py index 915a14d14..5940d0778 100644 --- a/tests/e2e/configuration/default_config.py +++ b/tests/e2e/configuration/default_config.py @@ -90,6 +90,8 @@ startup_config_dict = { "TRACE", "Minimum log level. Allowed values: TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL", ), + "maximum_deltas_per_transaction": ("-1", "-1", "Limit of deltas per transaction, default -1 (no limit)"), + "maximum_delete_deltas_per_transaction": ("-1", "-1", "Limit of deltas per transaction, default -1 (no limit)"), "memory_limit": ( "0", "0", diff --git a/tests/e2e/maximum_deltas_restriction/CMakeLists.txt b/tests/e2e/maximum_deltas_restriction/CMakeLists.txt new file mode 100644 index 000000000..2750905da --- /dev/null +++ b/tests/e2e/maximum_deltas_restriction/CMakeLists.txt @@ -0,0 +1,8 @@ +function(copy_maximum_deltas_restriction_e2e_python_files FILE_NAME) + copy_e2e_python_files(maximum_deltas_restriction ${FILE_NAME}) +endfunction() + +copy_maximum_deltas_restriction_e2e_python_files(common.py) +copy_maximum_deltas_restriction_e2e_python_files(maximum_deltas_restriction.py) + +copy_e2e_files(maximum_deltas_restriction workloads.yaml) diff --git a/tests/e2e/maximum_deltas_restriction/common.py b/tests/e2e/maximum_deltas_restriction/common.py new file mode 100644 index 000000000..976b649d7 --- /dev/null +++ b/tests/e2e/maximum_deltas_restriction/common.py @@ -0,0 +1,27 @@ +# 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. + +import pytest +from gqlalchemy import Memgraph + +MAXIMUM_DELTAS_RESTRICTION_PLACEHOLDER = "SET DATABASE SETTING 'maximum-deltas-per-transaction' TO '$0';" +MAXIMUM_DELETE_DELTAS_RESTRICTION_PLACEHOLDER = "SET DATABASE SETTING 'maximum-delete-deltas-per-transaction' TO '$0';" + + +@pytest.fixture +def memgraph(**kwargs) -> Memgraph: + memgraph = Memgraph() + + yield memgraph + + memgraph.execute(MAXIMUM_DELTAS_RESTRICTION_PLACEHOLDER.replace("$0", "-1")) + memgraph.execute(MAXIMUM_DELETE_DELTAS_RESTRICTION_PLACEHOLDER.replace("$0", "-1")) + memgraph.drop_database() diff --git a/tests/e2e/maximum_deltas_restriction/maximum_deltas_restriction.py b/tests/e2e/maximum_deltas_restriction/maximum_deltas_restriction.py new file mode 100644 index 000000000..c8d7dad33 --- /dev/null +++ b/tests/e2e/maximum_deltas_restriction/maximum_deltas_restriction.py @@ -0,0 +1,74 @@ +# 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. + +import sys + +import pytest +from common import ( + MAXIMUM_DELETE_DELTAS_RESTRICTION_PLACEHOLDER, + MAXIMUM_DELTAS_RESTRICTION_PLACEHOLDER, + memgraph, +) +from gqlalchemy import GQLAlchemyError + +CREATE_EMPTY_NODES_PLACEHOLDER = "FOREACH (i in range(1, $0) | CREATE ());" +CREATE_FULL_NODES_PLACEHOLDER = "FOREACH (i in range(1, $0) | CREATE (:Node {id: i}));" +DELETE_EVERYTHING_QUERY = "MATCH (n) DETACH DELETE n;" + + +def test_given_no_restrictions_on_the_database_when_executing_commands_then_everything_should_pass(memgraph): + memgraph.execute(CREATE_EMPTY_NODES_PLACEHOLDER.replace("$0", "100")) + memgraph.execute(CREATE_FULL_NODES_PLACEHOLDER.replace("$0", "100")) + memgraph.execute(DELETE_EVERYTHING_QUERY) + + +def test_given_maximum_restriction_ingestion_fails_when_inserting_more_nodes(memgraph): + memgraph.execute(MAXIMUM_DELTAS_RESTRICTION_PLACEHOLDER.replace("$0", "100")) + + memgraph.execute(CREATE_EMPTY_NODES_PLACEHOLDER.replace("$0", "100")) + memgraph.execute(DELETE_EVERYTHING_QUERY) + + with pytest.raises(GQLAlchemyError): + memgraph.execute(CREATE_EMPTY_NODES_PLACEHOLDER.replace("$0", "101")) + + +def test_given_maximum_delete_restriction_deletion_fails_when_deleting_more_nodes(memgraph): + memgraph.execute(MAXIMUM_DELETE_DELTAS_RESTRICTION_PLACEHOLDER.replace("$0", "100")) + + memgraph.execute(CREATE_EMPTY_NODES_PLACEHOLDER.replace("$0", "2000")) + + with pytest.raises(GQLAlchemyError): + memgraph.execute(DELETE_EVERYTHING_QUERY) + + +def test_given_maximum_delete_restriction_deletion_fails_when_deleting_more_edges(memgraph): + memgraph.execute(MAXIMUM_DELETE_DELTAS_RESTRICTION_PLACEHOLDER.replace("$0", "100")) + + memgraph.execute(CREATE_EMPTY_NODES_PLACEHOLDER.replace("$0", "2000")) + memgraph.execute("CREATE (s:Supernode)") + memgraph.execute("MATCH (s:Supernode) MATCH (n) CREATE (s)-[:HAS]->(n)") + + with pytest.raises(GQLAlchemyError): + memgraph.execute(DELETE_EVERYTHING_QUERY) + + +def test_given_maximum_delta_restriction_fails_when_deleting_everything_on_batch_ingested_nodes(memgraph): + memgraph.execute(MAXIMUM_DELTAS_RESTRICTION_PLACEHOLDER.replace("$0", "100")) + + memgraph.execute(CREATE_EMPTY_NODES_PLACEHOLDER.replace("$0", "90")) + memgraph.execute(CREATE_EMPTY_NODES_PLACEHOLDER.replace("$0", "90")) + + with pytest.raises(GQLAlchemyError): + memgraph.execute(DELETE_EVERYTHING_QUERY) + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/maximum_deltas_restriction/workloads.yaml b/tests/e2e/maximum_deltas_restriction/workloads.yaml new file mode 100644 index 000000000..cedea9de4 --- /dev/null +++ b/tests/e2e/maximum_deltas_restriction/workloads.yaml @@ -0,0 +1,14 @@ +maximum_deltas_restriction_cluster: &maximum_deltas_restriction_cluster + cluster: + main: + args: ["--bolt-port", "7687", "--log-level=TRACE"] + log_file: "maximum_deltas_restriction.log" + setup_queries: [] + validation_queries: [] + + +workloads: + - name: "Maximum deltas restriction" + binary: "tests/e2e/pytest_runner.sh" + args: ["maximum_deltas_restriction/maximum_deltas_restriction.py"] + <<: *maximum_deltas_restriction_cluster