servers/batcher/table/
batch.rs1use std::sync::Arc;
16use std::time::Instant;
17
18use arrow::compute::concat_batches;
19use arrow::record_batch::RecordBatch;
20use common_query::WriteProgress;
21use common_telemetry::error;
22use operator::error::{ComputeArrowSnafu, Result, UnexpectedSnafu};
23use operator::insert::Inserter;
24use operator::metrics::DIST_INGEST_ROW_COUNT;
25use snafu::ResultExt;
26
27use crate::batcher::table::flow_notifier::FlowNotifier;
28use crate::batcher::table::metrics::{
29 FLUSH_DROPPED_ROWS, FLUSH_ELAPSED, FLUSH_FAILURES, FLUSH_ROWS, FLUSH_TOTAL,
30};
31use crate::batcher::table::pending_batch::{PendingBatch, notify_batches};
32
33pub(in crate::batcher::table) struct Batch {
35 pub submissions: Vec<PendingBatch>,
36 pub total_rows: usize,
37}
38
39pub(in crate::batcher::table) async fn flush_batch(
40 batch: Batch,
41 inserter: Arc<Inserter>,
42 notifier: FlowNotifier,
43) {
44 let started = Instant::now();
45 let result = send_batch(&batch, &inserter).await;
46 FLUSH_ELAPSED.observe(started.elapsed().as_secs_f64());
47 match result {
48 Ok(progress) => {
49 if progress.iter().any(|result| result.error.is_some()) {
50 FLUSH_FAILURES.inc();
51 } else {
52 FLUSH_TOTAL.inc();
53 FLUSH_ROWS.observe(batch.total_rows as f64);
54 }
55 if let Some(first) = batch.submissions.first() {
56 notifier.notify(
57 first.table_info.clone(),
58 batch
59 .submissions
60 .iter()
61 .zip(&progress)
62 .filter(|(_, result)| result.error.is_none())
63 .map(|(pending, _)| &pending.batch),
64 );
65 }
66 for (pending, result) in batch.submissions.into_iter().zip(progress) {
67 if let Some(counter) = pending.ctx.write_progress() {
68 counter.add_written(result.affected_rows);
69 }
70 DIST_INGEST_ROW_COUNT
71 .with_label_values(&[pending.ctx.get_db_string().as_str()])
72 .inc_by(result.affected_rows as u64);
73 if result.error.is_some() {
74 FLUSH_DROPPED_ROWS
75 .inc_by((pending.batch.num_rows() - result.affected_rows) as u64);
76 }
77 let _ = pending.response_tx.send(result);
78 }
79 }
80 Err(error) => {
81 error!(error; "Failed to flush table batch, rows: {}", batch.total_rows);
82 FLUSH_FAILURES.inc();
83 FLUSH_DROPPED_ROWS.inc_by(batch.total_rows as u64);
84 notify_batches(batch.submissions, Err(Arc::new(error)));
85 }
86 }
87}
88
89async fn send_batch(batch: &Batch, inserter: &Inserter) -> Result<Vec<WriteProgress>> {
90 let first = batch.submissions.first().ok_or_else(|| {
91 UnexpectedSnafu {
92 violated: "cannot execute an empty flush".to_string(),
93 }
94 .build()
95 })?;
96 let combined = combine_batches(batch.submissions.iter().map(|pending| &pending.batch))?;
97 let mut owners = Vec::with_capacity(batch.total_rows);
98 for (owner, pending) in batch.submissions.iter().enumerate() {
99 owners.extend(std::iter::repeat_n(owner, pending.batch.num_rows()));
100 }
101 inserter
102 .flush_bulk_batch_tracked(
103 first.table_info.clone(),
104 combined,
105 first.ctx.clone(),
106 owners,
107 batch.submissions.len(),
108 )
109 .await
110}
111
112fn combine_batches<'a>(batches: impl IntoIterator<Item = &'a RecordBatch>) -> Result<RecordBatch> {
113 let batches = batches.into_iter().collect::<Vec<_>>();
114 let Some(first) = batches.first() else {
115 return UnexpectedSnafu {
116 violated: "cannot combine an empty flush".to_string(),
117 }
118 .fail();
119 };
120 let schema = first.schema();
121 if batches.iter().any(|batch| batch.schema() != schema) {
122 return UnexpectedSnafu {
123 violated: "cannot combine different Arrow schemas".to_string(),
124 }
125 .fail();
126 }
127 concat_batches(&schema, batches).context(ComputeArrowSnafu)
128}
129
130#[cfg(test)]
131mod tests {
132 use arrow::array::Int32Array;
133 use arrow::datatypes::{DataType, Field, Schema};
134
135 use crate::batcher::table::batch::*;
136
137 fn batch(name: &str, values: Vec<i32>) -> RecordBatch {
138 RecordBatch::try_new(
139 Arc::new(Schema::new(vec![Field::new(name, DataType::Int32, false)])),
140 vec![Arc::new(Int32Array::from(values))],
141 )
142 .unwrap()
143 }
144
145 #[test]
146 fn test_combine_preserves_rows_and_rejects_schema_changes() {
147 let first = batch("a", vec![1, 2]);
148 let second = batch("a", vec![3]);
149 assert_eq!(
150 batch("a", vec![1, 2, 3]),
151 combine_batches([&first, &second]).unwrap()
152 );
153 let different = batch("b", vec![4]);
154 assert!(combine_batches([&first, &different]).is_err());
155 let schema = first.schema();
156 let metadata = Schema::new_with_metadata(
157 schema.fields().clone(),
158 [("version".to_string(), "2".to_string())]
159 .into_iter()
160 .collect(),
161 );
162 let changed = RecordBatch::try_new(Arc::new(metadata), first.columns().to_vec()).unwrap();
163 assert!(combine_batches([&first, &changed]).is_err());
164 }
165}