Skip to content

Commit 787cc4e

Browse files
committed
Fix batch cutting for incremental refresh
Currently, incremental refresh uses 30days as the length for the monthly bucket, which results in batches misaligned with bucket boundary. Circumscription extends the batch to match bucket boundary to ensure correctness, but leads to overlapping between adjacent batches. This patch fixes the batch cutting to use the exact number of days in a calendar month. The patch also makes the following modifications: - Generated batches will start right at the latest bucket that overlaps both a chunk and invalidations. Before, we generated batches first and picked those with overlapping, resulting in batches that started earlier than needed. For example, if invalidations is (-inf , +inf) and chunks are [10,30) , [50,70). With refresh windows [0,90), bucket = 10, and bucket_per_batch = 2, previously we would enumerate batches of [0,20), [20,40], [40,60), [60,80), [80,100), then select batches [0,20), [20,40), [40,60), [60,80) because these overlap with chunk slices and invalidations. With the new logic, we skip forward to the next max(dimension slice, invalidations), so we only generate batches [10,30) and [50,70). This explains some test diffs on existing tests, because we will have fewer batches in some cases. - Bug fix: Previously, when the refresh window had null start/end, we capped it using the min/max dimension slice, then inscribed the window. So if the chunk boundary is not at the bucket boundary, we will generate batches based on a window range that is smaller than the original window. We restored null at the end so it's still correct with respect to the data range being refreshed, but the batches at the two ends could be a bucket bigger. Fix by capping to the start/end of the bucket that contains the boundary.
1 parent 0273d49 commit 787cc4e

13 files changed

Lines changed: 1106 additions & 356 deletions

File tree

.unreleased/pr_9890

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixes: #9890 Fix incremental refresh batch boundaries to align with variable-width buckets and start only where a chunk and an invalidation overlap

src/dimension_slice.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ ts_dimension_slice_scan_list(int32 dimension_id, int64 coordinate, List **matchi
514514
CurrentMemoryContext);
515515
}
516516

517-
int
517+
TSDLLEXPORT int
518518
ts_dimension_slice_scan_iterator_set_range(ScanIterator *it, int32 dimension_id,
519519
StrategyNumber start_strategy, int64 start_value,
520520
StrategyNumber end_strategy, int64 end_value)
@@ -970,7 +970,7 @@ ts_dimension_slice_scan_by_id_and_lock(int32 dimension_slice_id, const ScanTupLo
970970
return slice;
971971
}
972972

973-
ScanIterator
973+
TSDLLEXPORT ScanIterator
974974
ts_dimension_slice_scan_iterator_create(const ScanTupLock *tuplock, MemoryContext result_mcxt)
975975
{
976976
ScanIterator it = ts_scan_iterator_create(DIMENSION_SLICE, AccessShareLock, result_mcxt);

src/dimension_slice.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,15 @@ extern TSDLLEXPORT List *ts_dimension_slice_get_chunkids_to_compress(
9898
StrategyNumber end_strategy, int64 end_value, bool compress, bool recompress, int32 numchunks);
9999

100100
extern DimensionSlice *ts_dimension_slice_from_tuple(TupleInfo *ti);
101-
extern ScanIterator ts_dimension_slice_scan_iterator_create(const ScanTupLock *tuplock,
102-
MemoryContext result_mcxt);
101+
extern TSDLLEXPORT ScanIterator ts_dimension_slice_scan_iterator_create(const ScanTupLock *tuplock,
102+
MemoryContext result_mcxt);
103103
extern void ts_dimension_slice_scan_iterator_set_slice_id(ScanIterator *it, int32 slice_id);
104104
extern DimensionSlice *ts_dimension_slice_scan_iterator_get_by_id(ScanIterator *it, int32 slice_id);
105105

106-
extern int ts_dimension_slice_scan_iterator_set_range(ScanIterator *it, int32 dimension_id,
107-
StrategyNumber start_strategy,
108-
int64 start_value,
109-
StrategyNumber end_strategy, int64 end_value);
106+
extern TSDLLEXPORT int
107+
ts_dimension_slice_scan_iterator_set_range(ScanIterator *it, int32 dimension_id,
108+
StrategyNumber start_strategy, int64 start_value,
109+
StrategyNumber end_strategy, int64 end_value);
110110

111111
extern bool ts_osm_chunk_range_overlaps(int32 osm_dimension_slice_id, int32 dimension_id,
112112
int64 range_start, int64 range_end);

tsl/src/bgw_policy/job.c

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -445,11 +445,9 @@ policy_refresh_cagg_execute(int32 job_id, Jsonb *config)
445445
ContinuousAggRefreshContext context = { .callctx = CAGG_REFRESH_POLICY, .job_id = job_id };
446446

447447
/* Try to split window range into a list of ranges */
448-
List *refresh_window_list =
449-
continuous_agg_split_refresh_window(policy_data.cagg,
450-
&policy_data.refresh_window,
451-
policy_data.buckets_per_batch,
452-
policy_data.refresh_newest_first);
448+
List *refresh_window_list = continuous_agg_split_refresh_window(policy_data.cagg,
449+
&policy_data.refresh_window,
450+
policy_data.buckets_per_batch);
453451
if (refresh_window_list == NIL)
454452
{
455453
refresh_window_list = lappend(refresh_window_list, &policy_data.refresh_window);
@@ -461,11 +459,20 @@ policy_refresh_cagg_execute(int32 job_id, Jsonb *config)
461459

462460
context.number_of_batches = list_length(refresh_window_list);
463461

464-
ListCell *lc;
462+
/*
463+
* The list is always built oldest-first. When refresh_newest_first is true we
464+
* iterate from the last element down to the first using index-based access so
465+
* that no reversal copy of the list is needed.
466+
*/
465467
int32 processing_batch = 0;
466-
foreach (lc, refresh_window_list)
467-
{
468-
InternalTimeRange *refresh_window = (InternalTimeRange *) lfirst(lc);
468+
int32 nbatches = list_length(refresh_window_list);
469+
int32 batch_start = policy_data.refresh_newest_first ? nbatches - 1 : 0;
470+
int32 batch_end = policy_data.refresh_newest_first ? -1 : nbatches;
471+
int32 batch_step = policy_data.refresh_newest_first ? -1 : 1;
472+
for (int32 batch_idx = batch_start; batch_idx != batch_end; batch_idx += batch_step)
473+
{
474+
InternalTimeRange *refresh_window =
475+
(InternalTimeRange *) list_nth(refresh_window_list, batch_idx);
469476
elog(DEBUG1,
470477
"refreshing continuous aggregate \"%s\" from %s to %s",
471478
NameStr(policy_data.cagg->data.user_view_name),
@@ -553,6 +560,7 @@ policy_refresh_cagg_execute(int32 job_id, Jsonb *config)
553560
}
554561

555562
/* Process each chunk in its own transaction */
563+
ListCell *lc;
556564
foreach (lc, chunkid_lst)
557565
{
558566
PushActiveSnapshot(GetTransactionSnapshot());

0 commit comments

Comments
 (0)