Skip to content

Commit 2bc39f5

Browse files
fix(precompute): handle first-batch window ordering
1 parent 1e1abe1 commit 2bc39f5

1 file changed

Lines changed: 72 additions & 2 deletions

File tree

  • asap-query-engine/src/precompute_engine

asap-query-engine/src/precompute_engine/worker.rs

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,12 +435,25 @@ impl Worker {
435435
.map(|(_, ts, _)| *ts)
436436
.max()
437437
.unwrap_or(i64::MIN);
438+
let batch_min_ts = samples
439+
.iter()
440+
.map(|(_, ts, _)| *ts)
441+
.min()
442+
.unwrap_or(batch_max_ts);
438443
let previous_wm = state.previous_watermark_ms;
439444
let current_wm = if batch_max_ts > previous_wm {
440445
batch_max_ts
441446
} else {
442447
previous_wm
443448
};
449+
// On the first batch there is no prior watermark. Use the earliest
450+
// sample as the closure baseline so a batch spanning multiple windows
451+
// can close windows older than that sample after all samples are routed.
452+
let closure_previous_wm = if previous_wm == i64::MIN {
453+
batch_min_ts
454+
} else {
455+
previous_wm
456+
};
444457

445458
let mut emit_batch: Vec<(PrecomputedOutput, Box<dyn AggregateCore>)> = Vec::new();
446459

@@ -460,7 +473,7 @@ impl Worker {
460473

461474
// Check if pane was already evicted (late data for a closed window)
462475
if !state.active_panes.contains_key(&pane_start)
463-
&& current_wm >= pane_start + state.window_manager.window_size_ms()
476+
&& previous_wm >= pane_start + state.window_manager.window_size_ms()
464477
{
465478
let window_start = pane_start;
466479
let window_end = pane_start + state.window_manager.window_size_ms();
@@ -523,7 +536,9 @@ impl Worker {
523536
}
524537

525538
// Check for closed windows
526-
let closed = state.window_manager.closed_windows(previous_wm, current_wm);
539+
let closed = state
540+
.window_manager
541+
.closed_windows(closure_previous_wm, current_wm);
527542

528543
for window_start in &closed {
529544
let (_, window_end) = state.window_manager.window_bounds(*window_start);
@@ -2529,6 +2544,61 @@ mod tests {
25292544
);
25302545
}
25312546

2547+
#[test]
2548+
fn test_delta_set_aggregator_keeps_on_time_samples_in_first_multi_window_batch() {
2549+
let config = make_agg_config_full(
2550+
7,
2551+
"cpu",
2552+
AggregationType::DeltaSetAggregator,
2553+
"",
2554+
10_000,
2555+
0,
2556+
vec![],
2557+
vec!["host"],
2558+
);
2559+
let mut agg_configs = HashMap::new();
2560+
agg_configs.insert(7, config);
2561+
2562+
let sink = Arc::new(CapturingOutputSink::new());
2563+
let mut worker = make_worker(
2564+
arc_configs(agg_configs),
2565+
sink.clone(),
2566+
false,
2567+
0,
2568+
LateDataPolicy::ForwardToStore,
2569+
);
2570+
2571+
worker
2572+
.process_group_samples(
2573+
7,
2574+
"",
2575+
vec![
2576+
("cpu{host=\"a\"}".to_string(), 500, 1.0),
2577+
("cpu{host=\"b\"}".to_string(), 20_000, 1.0),
2578+
],
2579+
)
2580+
.unwrap();
2581+
worker
2582+
.process_group_samples(7, "", vec![("cpu{host=\"b\"}".to_string(), 30_000, 1.0)])
2583+
.unwrap();
2584+
2585+
let first_window = sink
2586+
.drain()
2587+
.into_iter()
2588+
.find(|(output, _)| output.start_timestamp == 0)
2589+
.expect("first DeltaSetAggregator window should be emitted");
2590+
let first_delta = first_window
2591+
.1
2592+
.as_any()
2593+
.downcast_ref::<DeltaSetAggregatorAccumulator>()
2594+
.expect("first output should be DeltaSetAggregatorAccumulator");
2595+
let key_a = KeyByLabelValues::new_with_labels(vec!["a".to_string()]);
2596+
assert!(
2597+
first_delta.added.contains(&key_a),
2598+
"on-time key in the first batch must not be treated as late"
2599+
);
2600+
}
2601+
25322602
// -----------------------------------------------------------------------
25332603
// Test: worker from streaming_config YAML
25342604
// -----------------------------------------------------------------------

0 commit comments

Comments
 (0)