MV: fix handling of view update which reassign the same key value
When a materialized view has a key (in Alternator, this can be two keys) which was a regular column in the base table, and a base update modifies that regular column, there are two distinct cases: 1. If the old and new key values are different, we need to delete the old view row, and create a new view row (with the different key). 2. If the old and new key values are the same, we just need to update the pre-existing row. It's important not to confuse the two cases: If we try to delete and create the *same* view row in the same timestamp, the result will be that the row will be deleted (a tombstone wins over data if they have the same timestamp) instead of updated. This is what we saw in issue #11801. We had a bug that was seen when an update set the view key column to the old value it already had: To compare the old and new key values we used the function compare_atomic_cell_for_merge(), but this compared not just they values but also incorrectly compared the metadata such as a the timestamp. Because setting a column to the same value changes its timestamp, we wrongly concluded that these to be different view keys and used the delete-and-create code for this case, resulting in the view row being deleted (as explained above). The simple fix is to compare just the key values - not looking at the metadata. See tests reproducing this bug and confirming its fix in the next patch. Fixes #11801 Signed-off-by: Nadav Har'El <nyh@scylladb.com>
This commit is contained in:
@@ -1064,13 +1064,18 @@ void view_updates::generate_update(
|
||||
bool same_row = true;
|
||||
for (auto col_id : col_ids) {
|
||||
auto* after = update.cells().find_cell(col_id);
|
||||
// Note: multi-cell columns can't be part of the primary key.
|
||||
auto& cdef = _base->regular_column_at(col_id);
|
||||
if (existing) {
|
||||
auto* before = existing->cells().find_cell(col_id);
|
||||
// Note that this cell is necessarily atomic, because col_ids are
|
||||
// view key columns, and keys must be atomic.
|
||||
if (before && before->as_atomic_cell(cdef).is_live()) {
|
||||
if (after && after->as_atomic_cell(cdef).is_live()) {
|
||||
auto cmp = compare_atomic_cell_for_merge(before->as_atomic_cell(cdef), after->as_atomic_cell(cdef));
|
||||
// We need to compare just the values of the keys, not
|
||||
// metadata like the timestamp. This is because below,
|
||||
// if the old and new view row have the same key, we need
|
||||
// to be sure to reach the update_entry() case.
|
||||
auto cmp = compare_unsigned(before->as_atomic_cell(cdef).value(), after->as_atomic_cell(cdef).value());
|
||||
if (cmp != 0) {
|
||||
same_row = false;
|
||||
}
|
||||
@@ -1090,6 +1095,11 @@ void view_updates::generate_update(
|
||||
if (same_row) {
|
||||
update_entry(base_key, update, *existing, now);
|
||||
} else {
|
||||
// This code doesn't work if the old and new view row have the
|
||||
// same key, because if they do we get both data and tombstone
|
||||
// for the same timestamp (now) and the tombstone wins. This
|
||||
// is why we need the "same_row" case above - it's not just a
|
||||
// performance optimization.
|
||||
delete_old_entry(base_key, *existing, update, now);
|
||||
create_entry(base_key, update, now);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user