-
Notifications
You must be signed in to change notification settings - Fork 21
CNDB-10302: Tombstone-capable trie memtable #2005
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
base: main
Are you sure you want to change the base?
Conversation
e358a60 to
6914cba
Compare
This also changes the behaviour of subtries to always include boundaries, their prefixes and their descendant branches. This is necessary for well-defined reverse walks and helps present metadata on the path of queried ranges, and is not a real limitation for the prefix-free keys that we use.
Range tries are tries made of ranges of coverage, which track applicable ranges and are mainly to be used to store deletions and deletion ranges.
Deletion-aware tries combine data and deletion tries. The cursor of a deletion-aware trie walks the data part of the trie, but also provides a `deletionBranchCursor` that can return a deletion/ tombstone branch covering the current position and the branch below it as a range trie. Such a branch can be given only once for any path in the trie (i.e. there cannot be a deletion branch covering another deletion branch). Deletion-aware merges and updates to in-memory tries take deletion branches into account when merging data so that deleted data is not produced in the resulting merge.
Implements a row-level trie memtable that uses deletion-aware tries to store deletions separately from live data, together with the associated TrieBackedPartition and TriePartitionUpdate. Every deletion is first converted to its range version (e.g. deleted rows are now represented as a WHERE ck <= x AND ck >= x, deleted partitions -- as deletions covering from LT_EXCLUDED to GT_NEXT_COMPONENT to include static and all normal rows) and then stored in the deletion path of the trie. To make tests work, all such ranges are converted back to rows and partition deletion times on conversion to UnfiteredPartitionIterator.
Adds a new method to UnfilteredRowIterator that is implemented by the new trie-backed partitions to ask them to stop issuing tombstones. This is done on filtering (i.e. conversion from UnfilteredRowIterator to RowIterator) where tombstones have already done their job and are no longer needed. Adds JMH tests of tombstones that demonstrate the improvement.
In the initial implementation row deletions were mapped to range tombstones, which works but isn't compatible with the multitude of tests, which require deletions to be returned in the form they were made. This commit changes the representation of deleted rows to use point tombstones. In addition to making the tests pass, this improves the memory usage of memtables with row deletions. Although they only add complexity at this stage, point tombstones (expanded to apply to the covered branch) will be needed in the next stage of development.
|
|
Some benchmark results demonstrating the effect: In the table above, the first 99.7% of all rows in each partition are deleted with one of the following operations: and then a An example with hundreds of thousands of tombstones per read partition: (this throws Here is the table of the build time and memory usage: Unlike the previous memtables, the new implementation will delete data from the trie when it receives a range tombstone, resulting in some cases in longer build time but lower memory usage. Full benchmark run to be posted soon. |
test/unit/org/apache/cassandra/db/tries/DeletionAwareTestBase.java
Outdated
Show resolved
Hide resolved
test/unit/org/apache/cassandra/db/tries/DeletionBranchConsistencyTest.java
Outdated
Show resolved
Hide resolved
test/unit/org/apache/cassandra/db/tries/InMemoryDeletionAwareTrieConsistencyTest.java
Outdated
Show resolved
Hide resolved
test/unit/org/apache/cassandra/db/tries/InMemoryDeletionAwareTrieThreadedTest.java
Outdated
Show resolved
Hide resolved
test/unit/org/apache/cassandra/db/tries/DeletionAwareRandomizedTest.java
Outdated
Show resolved
Hide resolved
test/unit/org/apache/cassandra/db/tries/DeletionAwareRandomizedTest.java
Outdated
Show resolved
Hide resolved
test/unit/org/apache/cassandra/db/tries/DeletionAwareRandomizedTest.java
Outdated
Show resolved
Hide resolved
test/unit/org/apache/cassandra/db/tries/DeletionAwareRandomizedTest.java
Outdated
Show resolved
Hide resolved
src/java/org/apache/cassandra/db/rows/TrieTombstoneMarkerImpl.java
Outdated
Show resolved
Hide resolved
src/java/org/apache/cassandra/db/rows/TrieTombstoneMarkerImpl.java
Outdated
Show resolved
Hide resolved
test/unit/org/apache/cassandra/db/partition/PartitionImplementationTest.java
Outdated
Show resolved
Hide resolved
|
Impressive work @blambov ! |
… using Range operations
and use it to avoid a couple of intermediate objects in set union
Fix Cursor.skipToWhenAhead for reverse iteration Add Cursor.dumpBranch for debugging Fix various methods to return Preencoded byte-comparables Fix deletion-aware collection merge cursor's reporting of deletion branch at tail points Fix deletion-aware collection merge cursor's failure on one deletion branch
… relevant deletions
|
✔️ Build ds-cassandra-pr-gate/PR-2005 approved by ButlerApproved by Butler |
pkolaczk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a full review yet.
So far I have two general remarks (I will not flag them individually in all places):
- License header should be DataStax license, not Apache License
- Some more complex asserts would benefit from messages including the actual values that failed the assertion - that would make it easier to debug
| return true; | ||
| } | ||
|
|
||
| /// Returns a tail trie, i.e. a trie whose root is the current position. Walking a tail trie will list all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method now returns a cursor for the trie, not a trie itself. Please update the javadoc.
| /// Returns a tail trie, i.e. a trie whose root is the current position. Walking a tail trie will list all | ||
| /// descendants of the current position with depth adjusted by the current depth. | ||
| /// | ||
| /// It is an error to call `tailTrie` on an exhausted cursor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tailCursor
| /// `(0, -1), (1, t), (2, r), (3, e), (4, e)*, (3, i), (4, e)*, (4, p)*, (1, w), (2, i), (3, n)*` | ||
| /// | ||
| /// Because we exhaust transitions on bigger depths before we go the next transition on the smaller ones, when | ||
| /// cursors are advanced together their positions can be easily compared using only the [#depth] and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it needs highlighting that cursors "advanced together" means that if they are not at the same position, we always advance the one that is lagging behind until it catches up or jumps over. Otherwise, if we let the higher cursor advance more times, or skipTo arbitrary points, this comparison logic would not work.
Anyway, I'm impressed by how smart this algorithm is!
| int incomingTransition(); | ||
|
|
||
| /// @return the content associated with the current node. This may be non-null for any presented node, including | ||
| /// the root. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but it also can be null, right?
Can we add @Nullable if this is the case?
| /// child. | ||
| /// | ||
| /// It is an error to call this after the trie has already been exhausted (i.e. when `depth() == -1`); | ||
| /// for performance reasons we won't always check this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if we don't check this and some code calls it in that state?
Can we at least narrow down the list of very bad things that could happen or explicitly state it's undefined? Noop, exception, returning duplicate last entry, ... ?
It does look to me like a good candidate for an assert depth >= 0 - cheap enough that won't make much difference when assertions are enabled and could increase the likelyhood we fail fast in the tests, but also zero cost in prod where we run with assertions turned off
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use DataStax license for new files.
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please replace with DataStax license
| assert c1.depth() == c2.depth(); | ||
| assert c1.incomingTransition() == c2.incomingTransition(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: a message with the actual values might be helpful when something goes wrong
| { | ||
| return new TrieEntriesIterator.AsEntriesFilteredByType<>(cursor(direction), clazz); | ||
| return dir -> new SingletonCursor<>(dir, b.asComparableBytes(byteComparableVersion), byteComparableVersion, v); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is quite cool to use a SAM here to implement a Trie by providing the implementation of makeCursor, but I am slightly on the fence with the readablity aspect of that. I mean, I like that it's terse and looks nice once understood, but because Trie has sooo many methods it wasn't immediately apparent to me how it actually works ;) It requires noticing that one unimplemented method among plenty of defaults.
I would slightly prefer to be a bit more verbose and explicit here and implement the Trie directly by using an anonymous class. The previous version of this code was more readable to me. Or, if we decide to keep this short version, because it is a prevalent abstraction everywhere, maybe let's have at least some hint in the comment / javadoc on the Trie explaining this pattern.
// Edit: the more look at it, the more the short version makes sense; because it seems that you deliberately want Tries to be defined in terms of the traversal (which is very smart as we don't need a physical node structure). So indeed maybe just add a paragraph on that in the top-level description.
| { | ||
| return direction; | ||
| } | ||
| Cursor<T> makeCursor(Direction direction); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we shift that up to the beginning of the class?
And also add a Javadoc as this seems to be the central way of building Tries.
This is too important to be hiding here.
BTW: Trie is public and Cursor is package private. There is a compiler warning that Cursor is exposed outside of its visibility scope. I don't think it's a problem, maybe we should suppress that warning. I guess your intention was that we cannot implement Tries outside of this package?



What is the issue
https://github.com/riptano/cndb/issues/10302
What does this PR fix and why was it fixed
Implements the necessary trie machinery to work with trie sets, range and deletion-aware tries, and a memtable that uses it to store deletions in separate per-partition branches of the memtable trie.
Implements a method of skipping over tombstones when converting
UnfilteredRowIteratorto the filteredRowIterator, which has the effect of ignoring all tombstones when looking for data and speeds up next-live lookups dramatically. Adds a test to demonstrate this effect with the new memtable.The changes are described in further detail with each commit.