materialized view: fix bug in some large modifications to base partitions
Sometimes a single modification to a base partition requires updates to a large number of view rows. A common example is deletion of a base partition containing many rows. A large BATCH is also possible. To avoid large allocations, we split the large amount of work into batch of 100 (max_rows_for_view_updates) rows each. The existing code assumed an empty result from one of these batches meant that we are done. But this assumption was incorrect: There are several cases when a base-table update may not need a view update to be generated (see can_skip_view_updates()) so if all 100 rows in a batch were skipped, the view update stopped prematurely. This patch includes two tests showing when this bug can happen - one test using a partition deletion with a USING TIMESTAMP causing the deletion to not affect the first 100 rows, and a second test using a specially-crafed large BATCH. These use cases are fairly esoteric, but in fact hit a user in the wild, which led to the discovery of this bug. The fix is fairly simple: To detect when build_some() is done it is no longer enough to check if it returned zero view-update rows; Rather, it explicitly returns whether or not it is done as an std::optional. The patch includes several tests for this bug, which pass on Cassandra, failed on Scylla before this patch, and pass with this patch. Fixes #12297. Signed-off-by: Nadav Har'El <nyh@scylladb.com> Closes #12305
This commit is contained in:
committed by
Botond Dénes
parent
e7d8855675
commit
92d03be37b
@@ -1219,8 +1219,12 @@ future<stop_iteration> view_update_builder::stop() const {
|
||||
return make_ready_future<stop_iteration>(stop_iteration::yes);
|
||||
}
|
||||
|
||||
future<utils::chunked_vector<frozen_mutation_and_schema>> view_update_builder::build_some() {
|
||||
future<std::optional<utils::chunked_vector<frozen_mutation_and_schema>>> view_update_builder::build_some() {
|
||||
(void)co_await advance_all();
|
||||
if (!_update && !_existing) {
|
||||
// Tell the caller there is no more data to build.
|
||||
co_return std::nullopt;
|
||||
}
|
||||
bool do_advance_updates = false;
|
||||
bool do_advance_existings = false;
|
||||
if (_update && _update->is_partition_start()) {
|
||||
|
||||
@@ -275,7 +275,15 @@ public:
|
||||
}
|
||||
view_update_builder(view_update_builder&& other) noexcept = default;
|
||||
|
||||
future<utils::chunked_vector<frozen_mutation_and_schema>> build_some();
|
||||
|
||||
// build_some() works on batches of 100 (max_rows_for_view_updates)
|
||||
// updated rows, but can_skip_view_updates() can decide that some of
|
||||
// these rows do not effect the view, and as a result build_some() can
|
||||
// fewer than 100 rows - in extreme cases even zero (see issue #12297).
|
||||
// So we can't use an empty returned vector to signify that the view
|
||||
// update building is done - and we wrap the return value in an
|
||||
// std::optional, which is disengaged when the iteration is done.
|
||||
future<std::optional<utils::chunked_vector<frozen_mutation_and_schema>>> build_some();
|
||||
|
||||
future<> close() noexcept;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user