Skip to content

Commit cebdfdc

Browse files
authored
test(orchestration): guard wide catalog listing with a same-run ratio (#1352)
* test(orchestration): guard wide catalog listing with a same-run ratio The 3,000-node listing guard used a fixed 450 ms median, which hosted runners exceed by runner speed alone (480-530 ms on code that lists in about 210 ms locally). It now times the listing at 1,000 and 3,000 nodes in the same run and requires the larger listing to stay within six times the smaller: linear listing measures about three times, quadratic about nine. * test(orchestration): say the wide listing guard covers linear growth, not batching
1 parent 575dee6 commit cebdfdc

1 file changed

Lines changed: 37 additions & 15 deletions

File tree

  • crates/sqlbuild-orchestration/tests/api/node_status

‎crates/sqlbuild-orchestration/tests/api/node_status/helpers.rs‎

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -358,21 +358,50 @@ async fn listing_ignores_build_history(
358358
);
359359
}
360360

361-
/// Three thousand built nodes, half in a named database, are read in batches and keep their own builds.
361+
/// Three thousand built nodes, half in a named database, keep their own builds, and listing them grows linearly.
362362
async fn listing_reads_wide_targets_in_batches(
363363
store: &ControlStore,
364364
credentials: (&AuditContext, Uuid),
365365
(location, mut input): (Uuid, SnapshotInput),
366366
) {
367367
let names: Vec<String> = (0..3_000).map(|index| format!("wide_{index:04}")).collect();
368+
let mut medians: Vec<std::time::Duration> = Vec::new();
369+
for (first, size) in [(0, 1_000), (1_000, names.len())] {
370+
medians.push(
371+
list_wide_target(
372+
store,
373+
credentials,
374+
(location, &mut input),
375+
(&names, first, size),
376+
)
377+
.await,
378+
);
379+
}
380+
let ratio: f64 = medians[1].as_secs_f64() / medians[0].as_secs_f64();
381+
assert!(
382+
ratio < 6.0,
383+
"listing {} nodes took {:?}, {ratio:.1} times the {:?} for 1,000; listing must stay linear",
384+
names.len(),
385+
medians[1],
386+
medians[0]
387+
);
388+
}
389+
390+
/// Add `names[first..size]` with one build each and return the median time to list the first `size`.
391+
async fn list_wide_target(
392+
store: &ControlStore,
393+
credentials: (&AuditContext, Uuid),
394+
(location, input): (Uuid, &mut SnapshotInput),
395+
(names, first, size): (&[String], usize, usize),
396+
) -> std::time::Duration {
368397
let database = |index: usize| (index % 2 == 1).then(|| "analytics_db".to_owned());
369398
let built_at = |index: usize| {
370399
chrono::DateTime::parse_from_rfc3339("2026-02-01T00:00:00Z")
371400
.expect("build start")
372401
.to_utc()
373402
+ chrono::Duration::seconds(i64::try_from(index).expect("seconds"))
374403
};
375-
for (index, name) in names.iter().enumerate() {
404+
for (index, name) in names.iter().enumerate().take(size).skip(first) {
376405
input.nodes.insert(format!("model:{name}"), SnapshotNode {
377406
kind: "model".into(),
378407
definition: json!({"name":name,"destination":{"database":database(index),"schema":"main","name":name}}),
@@ -382,9 +411,7 @@ async fn listing_reads_wide_targets_in_batches(
382411
});
383412
}
384413
input.generation += 1;
385-
publish_snapshot(store, &input)
386-
.await
387-
.expect("wide snapshot");
414+
publish_snapshot(store, input).await.expect("wide snapshot");
388415
let transaction = begin(store, TransactionMode::Write).await.expect("begin");
389416
let (mut transaction, rows) = query(
390417
transaction,
@@ -396,11 +423,11 @@ async fn listing_reads_wide_targets_in_batches(
396423
.await
397424
.expect("template build");
398425
let template: Uuid = serde_json::from_value(rows[0]["event_id"].clone()).expect("event id");
399-
for (chunk_index, chunk) in names.chunks(300).enumerate() {
426+
for (chunk_index, chunk) in names[first..size].chunks(300).enumerate() {
400427
let mut parameters = vec![Parameter::Id(template)];
401428
let mut values: Vec<String> = Vec::new();
402429
for (offset, name) in chunk.iter().enumerate() {
403-
let index = chunk_index * 300 + offset;
430+
let index = first + chunk_index * 300 + offset;
404431
values.push(format!(
405432
"(${},${},${},${},${})",
406433
parameters.len() + 1,
@@ -448,7 +475,7 @@ async fn listing_reads_wide_targets_in_batches(
448475
.iter()
449476
.map(|node| (node.node_key.as_str(), node.last_build.as_ref()))
450477
.collect();
451-
let built = names
478+
let built = names[..size]
452479
.iter()
453480
.enumerate()
454481
.filter(|(index, name)| {
@@ -462,13 +489,8 @@ async fn listing_reads_wide_targets_in_batches(
462489
})
463490
})
464491
.count();
465-
assert_eq!(built, names.len(), "every wide node keeps its own build");
492+
assert_eq!(built, size, "every wide node keeps its own build");
466493
}
467494
timings.sort();
468-
assert!(
469-
timings[1] < std::time::Duration::from_millis(450),
470-
"listing {} nodes took {:?}",
471-
names.len(),
472-
timings[1]
473-
);
495+
timings[1]
474496
}

0 commit comments

Comments
 (0)