Skip to content

Commit 3f0907d

Browse files
zzylolclaude
andcommitted
fix(asap-tools): send ClickHouse ticks at once in the paced feeder
Slot-by-slot sending is only needed for remote write, where ASAPQuery can drop samples behind a group's watermark. ClickHouse sorts on insert, and waiting between slots doubled its tick time at 2 samples/s (about 450 ms to 950 ms at 1e6 rows/s). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
1 parent e89e8e8 commit 3f0907d

2 files changed

Lines changed: 22 additions & 13 deletions

File tree

‎asap-tools/data-sources/paced-feeder/README.md‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,12 @@ credentials.
5353

5454
## Pacing and stats
5555

56-
Each tick sends its `--samples-per-sec` sub-second slots in turn. A slot's
57-
rows are split into `--batch-rows` requests and sent with up to
58-
`--concurrency` in flight, and the next slot starts only after every request
59-
of the current one returns, so a series' samples never arrive out of order.
56+
Each tick's rows are split into `--batch-rows` requests and sent with up to
57+
`--concurrency` in flight. For remote write, a tick sends its
58+
`--samples-per-sec` sub-second slots in turn, and the next slot starts only
59+
after every request of the current one returns, so each aggregation group's
60+
samples reach ASAPQuery in timestamp order. ClickHouse sorts on insert, so it
61+
gets the whole tick at once.
6062
`--samples-per-sec` is at most 1000, so timestamps (in ms) never repeat. The
6163
default batch size is 50,000 rows for remote write, which keeps a compressed
6264
request under axum's 2 MB body limit in ASAPQuery's ingest, and 100,000 for

‎asap-tools/data-sources/paced-feeder/src/main.rs‎

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use serde::Serialize;
1616
use std::sync::Arc;
1717
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
1818
use tokio::sync::Semaphore;
19-
use workload::Workload;
19+
use workload::{Row, Workload};
2020

2121
#[derive(Debug, Clone, Copy, ValueEnum, Serialize)]
2222
#[serde(rename_all = "kebab-case")]
@@ -263,18 +263,25 @@ async fn main() -> Result<()> {
263263
let started = Instant::now();
264264

265265
let (mut rows, mut bytes) = (0, 0);
266-
// A slot's requests all return before the next slot's are sent, so a
267-
// series' samples never arrive out of timestamp order.
268-
for slot in 0..args.samples_per_sec {
269-
let slot_rows = Arc::new(workload.slot_rows(tick, slot, start_ms));
266+
// ASAPQuery can drop samples behind a group's watermark, so remote write
267+
// sends one slot at a time: a slot's requests all return before the
268+
// next slot's are sent. ClickHouse sorts on insert, so it sends the
269+
// whole tick at once.
270+
let slots = (0..args.samples_per_sec).map(|slot| workload.slot_rows(tick, slot, start_ms));
271+
let waves: Vec<Vec<Row>> = match args.sink {
272+
SinkKind::RemoteWrite => slots.collect(),
273+
SinkKind::Clickhouse => vec![slots.flatten().collect()],
274+
};
275+
for wave in waves {
276+
let wave_rows = Arc::new(wave);
270277
let mut tasks = tokio::task::JoinSet::new();
271-
for begin in (0..slot_rows.len()).step_by(batch_rows) {
272-
let end = (begin + batch_rows).min(slot_rows.len());
273-
let (slot_rows, sink) = (Arc::clone(&slot_rows), Arc::clone(&sink));
278+
for begin in (0..wave_rows.len()).step_by(batch_rows) {
279+
let end = (begin + batch_rows).min(wave_rows.len());
280+
let (wave_rows, sink) = (Arc::clone(&wave_rows), Arc::clone(&sink));
274281
let permit = Arc::clone(&permits).acquire_owned().await?;
275282
tasks.spawn(async move {
276283
let _permit = permit;
277-
let body = sink.encoder.encode(&slot_rows[begin..end]);
284+
let body = sink.encoder.encode(&wave_rows[begin..end]);
278285
let bytes = body.len();
279286
sink.send(body).await.map(|()| (end - begin, bytes))
280287
});

0 commit comments

Comments
 (0)