Skip to content

Commit 5586fdf

Browse files
committed
Incremental manual refresh support
refresh_continuous_aggregate() can now refresh incrementally in batches, matching the behavior the continuous aggregate policy already supports. Forced refresh is also incremental by default. Options available for incremental refresh with policies can be set for manual refresh via JSONB options: - `buckets_per_batch` (default 10, as policies) - `max_batches_per_execution` (default 0 = no limit) - `refresh_newest_first` Usage: Without options, it behaves similar to a refresh policy with default values: CALL refresh_continuous_aggregate('cagg', t1, t2);
1 parent 9041462 commit 5586fdf

47 files changed

Lines changed: 2345 additions & 617 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.unreleased/pr_9903

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implements: #9903 Incremental refresh for refresh_continuous_aggregate()

tsl/src/bgw_policy/continuous_aggregate_api.c

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "bgw_policy/job_api.h"
2222
#include "bgw_policy/policies_v2.h"
2323
#include "bgw_policy/policy_utils.h"
24+
#include "continuous_aggs/refresh.h"
2425
#include "dimension.h"
2526
#include "guc.h"
2627
#include "jsonb_utils.h"
@@ -32,12 +33,8 @@
3233
/* Default max runtime for a continuous aggregate jobs is unlimited for now */
3334
#define DEFAULT_MAX_RUNTIME \
3435
DatumGetIntervalP(DirectFunctionCall3(interval_in, CStringGetDatum("0"), InvalidOid, -1))
35-
/* Default buckets per batch is 1, which means that the job will refresh 1 bucket at a time */
36-
#define DEFAULT_BUCKETS_PER_BATCH 10
3736
/* Default max batches per execution is 0, which means no limit */
3837
#define DEFAULT_MAX_BATCHES_PER_EXECUTION 0
39-
/* Default refresh newest first is true, which means from newest data to the oldest */
40-
#define DEFAULT_REFRESH_NEWEST_FIRST true
4138
/* Default compress after refresh is false, which means compression does not run after refresh */
4239
#define DEFAULT_COMPRESS_AFTER_REFRESH false
4340

tsl/src/bgw_policy/job.c

Lines changed: 12 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -422,74 +422,18 @@ policy_refresh_cagg_execute(int32 job_id, Jsonb *config)
422422
PGC_S_SESSION);
423423
}
424424

425-
ContinuousAggRefreshContext context = { .callctx = CAGG_REFRESH_POLICY, .job_id = job_id };
426-
427-
/* Try to split window range into a list of ranges */
428-
List *refresh_window_list = continuous_agg_split_refresh_window(policy_data.cagg,
429-
&policy_data.refresh_window,
430-
policy_data.buckets_per_batch);
431-
if (refresh_window_list == NIL)
432-
{
433-
refresh_window_list = lappend(refresh_window_list, &policy_data.refresh_window);
434-
}
435-
else
436-
{
437-
context.callctx = CAGG_REFRESH_POLICY_BATCHED;
438-
}
439-
440-
context.number_of_batches = list_length(refresh_window_list);
441-
442-
/*
443-
* The list is always built oldest-first. When refresh_newest_first is true we
444-
* iterate from the last element down to the first using index-based access so
445-
* that no reversal copy of the list is needed.
446-
*/
447-
int32 processing_batch = 0;
448-
int32 nbatches = list_length(refresh_window_list);
449-
int32 batch_start = policy_data.refresh_newest_first ? nbatches - 1 : 0;
450-
int32 batch_end = policy_data.refresh_newest_first ? -1 : nbatches;
451-
int32 batch_step = policy_data.refresh_newest_first ? -1 : 1;
452-
for (int32 batch_idx = batch_start; batch_idx != batch_end; batch_idx += batch_step)
453-
{
454-
InternalTimeRange *refresh_window =
455-
(InternalTimeRange *) list_nth(refresh_window_list, batch_idx);
456-
elog(DEBUG1,
457-
"refreshing continuous aggregate \"%s\" from %s to %s",
458-
NameStr(policy_data.cagg->data.user_view_name),
459-
ts_internal_to_time_string(refresh_window->start, refresh_window->type),
460-
ts_internal_to_time_string(refresh_window->end, refresh_window->type));
461-
462-
context.processing_batch = ++processing_batch;
463-
464-
/* extend_last_bucket must only apply to the boundary batch — the one
465-
* whose window abuts the adjacent policy. For newest-first ordering
466-
* that is batch 1; for oldest-first it is the final batch.
467-
* In non-batched mode (single batch) the one batch is always the boundary. */
468-
bool apply_extend =
469-
extend_last_bucket &&
470-
(policy_data.refresh_newest_first ? processing_batch == 1 :
471-
processing_batch == context.number_of_batches);
472-
473-
continuous_agg_refresh_internal(policy_data.cagg,
474-
refresh_window,
475-
context,
476-
refresh_window->start_isnull,
477-
refresh_window->end_isnull,
478-
(context.callctx != CAGG_REFRESH_POLICY_BATCHED),
479-
false, /* force */
480-
apply_extend);
481-
DEBUG_ERROR_INJECTION(psprintf("cagg_policy_batch_%d_after_refresh", processing_batch));
482-
if (processing_batch >= policy_data.max_batches_per_execution &&
483-
processing_batch < context.number_of_batches &&
484-
policy_data.max_batches_per_execution > 0)
485-
{
486-
elog(LOG,
487-
"reached maximum number of batches per execution (%d), batches not processed (%d)",
488-
policy_data.max_batches_per_execution,
489-
context.number_of_batches - processing_batch);
490-
break;
491-
}
492-
}
425+
ContinuousAggRefreshContext context = {
426+
.callctx = CAGG_REFRESH_POLICY,
427+
.job_id = job_id,
428+
.buckets_per_batch = policy_data.buckets_per_batch,
429+
.max_batches_per_execution = policy_data.max_batches_per_execution,
430+
.refresh_newest_first = policy_data.refresh_newest_first,
431+
};
432+
433+
continuous_agg_refresh_batched(policy_data.cagg,
434+
&policy_data.refresh_window,
435+
context,
436+
extend_last_bucket);
493437

494438
if (!policy_data.include_tiered_data_isnull)
495439
{

tsl/src/continuous_aggs/common.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2102,3 +2102,21 @@ cagg_find_groupingcols(ContinuousAgg *agg, Hypertable *mat_ht)
21022102
}
21032103
return retlist;
21042104
}
2105+
2106+
void
2107+
emit_up_to_date_notice(const ContinuousAgg *cagg, const ContinuousAggRefreshContext context)
2108+
{
2109+
switch (context.callctx)
2110+
{
2111+
case CAGG_REFRESH_WINDOW:
2112+
case CAGG_REFRESH_CREATION:
2113+
case CAGG_REFRESH_WINDOW_BATCHED:
2114+
elog(NOTICE,
2115+
"continuous aggregate \"%s\" is already up-to-date",
2116+
NameStr(cagg->data.user_view_name));
2117+
break;
2118+
case CAGG_REFRESH_POLICY:
2119+
case CAGG_REFRESH_POLICY_BATCHED:
2120+
break;
2121+
}
2122+
}

tsl/src/continuous_aggs/common.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ typedef enum ContinuousAggRefreshCallContext
7979
{
8080
CAGG_REFRESH_CREATION,
8181
CAGG_REFRESH_WINDOW,
82+
CAGG_REFRESH_WINDOW_BATCHED,
8283
CAGG_REFRESH_POLICY,
8384
CAGG_REFRESH_POLICY_BATCHED
8485
} ContinuousAggRefreshCallContext;
@@ -89,6 +90,11 @@ typedef struct ContinuousAggRefreshContext
8990
int32 job_id;
9091
int32 processing_batch;
9192
int32 number_of_batches;
93+
/* Batch configuration */
94+
int32 buckets_per_batch; /* 0 = disabled */
95+
int32 max_batches_per_execution; /* 0 = no limit */
96+
bool refresh_newest_first;
97+
bool force; /* re-materialize the whole window, ignoring invalidations */
9298
} ContinuousAggRefreshContext;
9399

94100
#define IS_TIME_BUCKET_INFO_TIME_BASED(bucket_function) \
@@ -173,3 +179,5 @@ extern bool caggtimebucket_validate_common(ContinuousAggBucketFunction *bf, List
173179
List *targetList, List *rtable, int ht_partcolno,
174180
StringInfo msg, bool is_cagg_create,
175181
const bool for_rewrites);
182+
extern void emit_up_to_date_notice(const ContinuousAgg *cagg,
183+
const ContinuousAggRefreshContext context);

tsl/src/continuous_aggs/create.c

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -857,8 +857,11 @@ tsl_process_continuous_agg_viewstmt(Node *node, const char *query_string, void *
857857

858858
if (!stmt->into->skipData)
859859
{
860+
bool refreshed = false;
860861
InternalTimeRange refresh_window = {
861862
.type = InvalidOid,
863+
.start_isnull = true,
864+
.end_isnull = true,
862865
};
863866

864867
/*
@@ -887,14 +890,15 @@ tsl_process_continuous_agg_viewstmt(Node *node, const char *query_string, void *
887890
refresh_window.end = ts_time_get_noend_or_max(refresh_window.type);
888891

889892
ContinuousAggRefreshContext context = { .callctx = CAGG_REFRESH_CREATION };
890-
continuous_agg_refresh_internal(cagg,
891-
&refresh_window,
892-
context,
893-
true, /* start_isnull */
894-
true, /* end_isnull */
895-
true, /* bucketing_refresh_window */
896-
false, /* force */
897-
false /*extend_last_bucket*/);
893+
refreshed = continuous_agg_refresh_internal(cagg,
894+
&refresh_window,
895+
context,
896+
true, /* bucketing_refresh_window */
897+
false /*extend_last_bucket*/);
898+
if (!refreshed)
899+
{
900+
emit_up_to_date_notice(cagg, context);
901+
}
898902
}
899903

900904
return DDL_DONE;

0 commit comments

Comments
 (0)