Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make modification of snapshot atomic and corresponding tombstone handling changes #1650

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

phoebusm
Copy link
Collaborator

@phoebusm phoebusm commented Jun 26, 2024

Reference Issues/PRs

Originally only for forward porting https://mangit.maninvestments.com/projects/DATA/repos/arcticc/pull-requests/1095/overview
But corresponding test requires https://github.com/man-group/arcticdb-enterprise/issues/32 so part of changes is included too.

What does this implement or fix?

  1. Modification of snapshot will not longer delete the original SNAPSHOT_REF key. It will be simply overwritten so atomicity can be achieved
  2. As the changes will affect op logs as well and the test cannot be made without https://github.com/man-group/arcticdb-enterprise/issues/32 so some corresponding changes for that ticket as well. More details can be found there.

Any other comments?

Checklist

Checklist for code changes...
  • Have you updated the relevant docstrings, documentation and copyright notice?
  • Is this contribution tested against all ArcticDB's features?
  • Do all exceptions introduced raise appropriate error messages?
  • Are API changes highlighted in the PR description?
  • Is the PR labelled as enhancement or bug so it appears in autogenerated release notes?

-Supplymentary work for tombstone handling
@phoebusm phoebusm changed the title -Make modification of snapshot atomic Make modification of snapshot atomic and corresponding tombstone handling changes Jun 26, 2024
@phoebusm
Copy link
Collaborator Author

@poodlewars
Copy link
Collaborator

This change requires a test in this repo. I think that I did something similar a while ago for vref atomicity - https://github.com/man-group/ArcticDB/blob/master/python/tests/stress/arcticdb/version_store/test_concurrent_read_and_write.py

typename=std::enable_if_t< // Make obvious if iterator& is captured
is_same_cvref_removed<T, entity::VariantKey>::value
|| is_iter_of<T, entity::VariantKey>::value
|| is_pointer_of<T, entity::VariantKey>::value>>
struct KeyExistsTask : BaseTask {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old implementation was going to great lengths to ensure that we are never copying the key in to the KeyExistsTask, why do we need to relax that constraint now??

Copy link
Collaborator Author

@phoebusm phoebusm Jul 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the old implementation, the task itself doesn't hold the memory during construction. This could be a ticking timebomb (at least I hit it) as the memory could no longer be valid by the time the task is run.
IMO in the worker-pool model, every task should be self-sufficient - not depending on memory resides outside the object

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still dangerous, it is allowing iterators into vectors that can go out of scope and raw pointers. To ensure liveness of the key memory, only accept rvalue references and shared pointers

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am happy to do so if there is no concern for performance. It can simplify the mad template parameter checking in this class.
I thought it is designed to accept iterator and shared pointer for a reason.
My changes is only for making the class store the key locally if a lvalue key is passed

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, yes, copying lvalues is also acceptable from a correctness perspective. I haven't seen this level of madness in any similar methods though, how do they solve this problem? I've definitely seen overloaded constructors that copy lvalues or move rvalues, are there other approaches?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The template has been removed as KeyExistsTask now store copy of key, per discussed

@@ -400,6 +394,7 @@ class LocalVersionedEngine : public VersionedEngine {
}
void _test_set_store(std::shared_ptr<Store> store);
std::shared_ptr<VersionMap> _test_get_version_map();
SymbolList& _test_get_symbol_list();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return by value to avoid confusion if this is just for testing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the testing only in the other repo

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with Alex, returning by reference is unnecessarily dangerous for test code

Copy link
Collaborator Author

@phoebusm phoebusm Jul 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, why do we need this at all? Just call symbol_list() directly from the test?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dependency is removed by turning off the symbol_list in the test

@@ -37,6 +37,16 @@ enum class BatchGetVersionOption {
COUNT
};

inline std::optional<std::string> collect_futures_exceptions(auto futures) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the futures in?

tombstone_snapshot(store(), to_ref(snap_key), std::move(snap_segment), version_map()->log_changes());
} else {
delete_trees_responsibly(deleted_keys, get_master_snapshots_map(store()), snap_name).get();
if(variant_key_type(snap_key) != KeyType::SNAPSHOT_REF || !cfg().write_options().delayed_deletes()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The arcticc version with bool is_delete_keys_immediately was clearer

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm got it.... Let me re-align my style

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old is_delete_keys_immediately is kinda a relic of a sepearate implementation

tombstone_snapshot(store(), to_ref(snap_key), std::move(snap_segment), version_map()->log_changes());
} else {
delete_trees_responsibly(deleted_keys, get_master_snapshots_map(store()), snap_name).get();
if(variant_key_type(snap_key) != KeyType::SNAPSHOT_REF || !cfg().write_options().delayed_deletes()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it was here already, but this check,

variant_key_type(snap_key) != KeyType::SNAPSHOT_REF

is really confusing. Don't we already know what type snap_key is??

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the same question when I was working on it. My guess is for backward compatibility with KeyType::SNAPSHOT:

store->iterate_type(KeyType::SNAPSHOT, [&ret, &snap_name](VariantKey &&vk) {

@phoebusm phoebusm force-pushed the fix/make_mod_snapshot_atomic branch from 5021c50 to 1ef07c7 Compare July 2, 2024 09:09
@phoebusm phoebusm force-pushed the fix/make_mod_snapshot_atomic branch from 0f803d3 to bc0bc7c Compare July 2, 2024 09:15
cpp/arcticdb/version/version_store_api.cpp Show resolved Hide resolved
cpp/arcticdb/version/version_store_api.cpp Show resolved Hide resolved
cpp/arcticdb/version/version_store_api.cpp Show resolved Hide resolved
@@ -400,6 +394,7 @@ class LocalVersionedEngine : public VersionedEngine {
}
void _test_set_store(std::shared_ptr<Store> store);
std::shared_ptr<VersionMap> _test_get_version_map();
SymbolList& _test_get_symbol_list();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with Alex, returning by reference is unnecessarily dangerous for test code

@@ -895,11 +897,11 @@ folly::Future<folly::Unit> LocalVersionedEngine::delete_trees_responsibly(

storage::ReadKeyOpts read_opts;
read_opts.ignores_missing_key_ = true;
auto data_keys_to_be_deleted = get_data_keys_set(store(), *keys_to_delete, read_opts);
auto data_keys_to_be_deleted = get_data_keys_set(store, *keys_to_delete, read_opts);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've renamed this function in #1657, whoever merges second will need to rebase

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll rebase it once the PR is approved

cpp/arcticdb/async/tasks.hpp Outdated Show resolved Hide resolved
typename=std::enable_if_t< // Make obvious if iterator& is captured
is_same_cvref_removed<T, entity::VariantKey>::value
|| is_iter_of<T, entity::VariantKey>::value
|| is_pointer_of<T, entity::VariantKey>::value>>
struct KeyExistsTask : BaseTask {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still dangerous, it is allowing iterators into vectors that can go out of scope and raw pointers. To ensure liveness of the key memory, only accept rvalue references and shared pointers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants