1mod batch;
18mod flow_notifier;
19mod metrics;
20mod pending_batch;
21mod pending_worker;
22
23use std::num::NonZeroUsize;
24use std::sync::Arc;
25use std::time::Duration;
26
27use arrow::record_batch::RecordBatch;
28use async_trait::async_trait;
29use common_batcher::flush_limiter::FlushLimiter;
30use common_batcher::flush_policy::timing::TimingFlushPolicy;
31use common_batcher::request_limiter::RequestLimiter;
32use common_batcher::worker_registry::WorkerRegistry;
33use common_error::ext::BoxedError;
34use common_query::{Output, WriteCompletion, WriteProgress};
35use operator::batcher::PendingRowsBatcher;
36use operator::error::{ExternalSnafu, Result, UnexpectedSnafu};
37use operator::insert::Inserter;
38use session::context::QueryContextRef;
39use snafu::ResultExt;
40use table::metadata::TableInfoRef;
41use tokio::sync::{OwnedSemaphorePermit, Semaphore, broadcast, oneshot};
42
43use crate::batcher::pending_rows_batch_sync_enabled;
44use crate::batcher::table::flow_notifier::FlowNotifier;
45use crate::batcher::table::metrics::PENDING_WORKERS;
46use crate::batcher::table::pending_batch::PendingBatch;
47use crate::batcher::table::pending_worker::{PendingWorker, WorkerCommand, start_worker};
48
49#[derive(Clone, Debug, Hash, PartialEq, Eq)]
50struct BatchKey {
51 catalog: String,
52 schema: String,
53 table_name: String,
54 skip_wal: bool,
55}
56
57fn batch_key_from_ctx(table_name: &str, ctx: &QueryContextRef) -> BatchKey {
59 BatchKey {
60 catalog: ctx.current_catalog().to_string(),
61 schema: ctx.current_schema().clone(),
62 table_name: table_name.to_string(),
63 skip_wal: ctx.skip_wal(),
64 }
65}
66
67type PendingWorkers = WorkerRegistry<BatchKey, WorkerCommand>;
68
69pub struct TablePendingRowsBatcher {
71 workers: Arc<PendingWorkers>,
72 flush_policy: TimingFlushPolicy,
73 flush_limiter: FlushLimiter,
74 request_limiter: RequestLimiter,
75 worker_channel_capacity: usize,
76 pending_rows_batch_sync: bool,
77 worker_idle_timeout: Duration,
78 inserter: Arc<Inserter>,
79 flow_notifier: FlowNotifier,
80 shutdown: broadcast::Sender<()>,
81}
82
83impl TablePendingRowsBatcher {
84 pub fn try_new(
86 flush_interval: Duration,
87 max_batch_rows: usize,
88 max_concurrent_flushes: usize,
89 worker_channel_capacity: usize,
90 max_inflight_requests: usize,
91 flow_notification_queue_capacity: NonZeroUsize,
92 inserter: Arc<Inserter>,
93 ) -> Option<Arc<Self>> {
94 if worker_channel_capacity == 0
95 || worker_channel_capacity > Semaphore::MAX_PERMITS
96 || max_inflight_requests == 0
97 || max_inflight_requests > Semaphore::MAX_PERMITS
98 {
99 return None;
100 }
101 let flush_policy = TimingFlushPolicy::try_new(flush_interval, max_batch_rows)?;
102 let flush_limiter = FlushLimiter::try_new(max_concurrent_flushes)?;
103 let request_limiter = RequestLimiter::try_new(max_inflight_requests)?;
104 let flow_notifier = FlowNotifier::new(
105 inserter.table_flownode_set_cache().clone(),
106 inserter.node_manager().clone(),
107 flow_notification_queue_capacity,
108 )?;
109 let (shutdown, _) = broadcast::channel(1);
110 PENDING_WORKERS.set(0);
111 Some(Arc::new(Self {
112 workers: Arc::new(WorkerRegistry::new()),
113 flush_policy,
114 flush_limiter,
115 request_limiter,
116 worker_channel_capacity,
117 pending_rows_batch_sync: pending_rows_batch_sync_enabled(),
118 worker_idle_timeout: flush_interval.checked_mul(3).unwrap_or(flush_interval),
119 inserter,
120 flow_notifier,
121 shutdown,
122 }))
123 }
124
125 async fn worker(&self, key: &BatchKey) -> PendingWorker {
126 let (tx, receiver) = self
127 .workers
128 .get_or_create(key.clone(), self.worker_channel_capacity)
129 .await;
130 if let Some(rx) = receiver {
131 start_worker(
132 key.clone(),
133 tx.clone(),
134 self.workers.clone(),
135 rx,
136 self.shutdown.clone(),
137 self.flush_policy,
138 self.flush_limiter.clone(),
139 self.inserter.clone(),
140 self.flow_notifier.clone(),
141 self.worker_idle_timeout,
142 );
143 PENDING_WORKERS.set(self.workers.len().await as i64);
144 }
145 PendingWorker { tx }
146 }
147}
148
149impl Drop for TablePendingRowsBatcher {
150 fn drop(&mut self) {
151 let _ = self.shutdown.send(());
152 }
153}
154
155#[async_trait]
156impl PendingRowsBatcher for TablePendingRowsBatcher {
157 async fn acquire(&self) -> Result<Arc<OwnedSemaphorePermit>> {
160 self.request_limiter.acquire().await.map_err(|_| {
161 UnexpectedSnafu {
162 violated: "batcher admission closed".to_string(),
163 }
164 .build()
165 })
166 }
167
168 async fn submit(
171 &self,
172 table_info: TableInfoRef,
173 batch: RecordBatch,
174 ctx: QueryContextRef,
175 permit: Arc<OwnedSemaphorePermit>,
176 ) -> Result<Output> {
177 let total_rows = batch.num_rows();
178 let confirmed = ctx.confirmed_writes();
179 if total_rows == 0 {
180 return Ok(Output::new_with_affected_rows(0));
181 }
182 if table_info.catalog_name != ctx.current_catalog()
183 || table_info.schema_name != ctx.current_schema()
184 {
185 return UnexpectedSnafu {
186 violated: "batch table and request database differ".to_string(),
187 }
188 .fail();
189 }
190 let key = batch_key_from_ctx(&table_info.name, &ctx);
191 let (response_tx, response_rx) = oneshot::channel();
192 let progress = ctx.write_progress().cloned();
193 let pending = PendingBatch {
194 _completion: progress.as_ref().map(|progress| progress.start()),
195 table_info,
196 batch,
197 ctx,
198 response_tx,
199 _permit: permit,
200 };
201 let mut command = Some(WorkerCommand::Submit(pending));
202 for _ in 0..2 {
203 let worker = self.worker(&key).await;
204 let Some(pending) = command.take() else { break };
205 match worker.tx.send(pending).await {
206 Ok(()) => break,
207 Err(error) => {
208 command = Some(error.0);
209 if self.workers.remove_if_same(&key, &worker.tx).await {
210 PENDING_WORKERS.set(self.workers.len().await as i64);
211 }
212 }
213 }
214 }
215 if command.is_some() {
216 return UnexpectedSnafu {
217 violated: "batch worker channel closed".to_string(),
218 }
219 .fail();
220 }
221 let completion = async move {
222 response_rx.await.unwrap_or_else(|_| WriteProgress::failed(BoxedError::new(
223 UnexpectedSnafu { violated: "batch worker stopped before acknowledging its write; storage outcome unknown".to_string() }.build()
224 )))
225 };
226 let mut output = Output::new_with_affected_rows(total_rows);
227 if self.pending_rows_batch_sync && !confirmed {
228 completion.await.into_result().context(ExternalSnafu)?;
229 } else {
230 if let Some(progress) = progress {
231 progress.mark_accepted();
232 }
233 output
234 .meta
235 .write_completions
236 .push(WriteCompletion::with_progress(completion));
237 }
238 Ok(output)
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use std::collections::HashMap;
245 use std::num::NonZeroUsize;
246 use std::sync::atomic::{AtomicUsize, Ordering};
247 use std::sync::{Arc, Mutex};
248 use std::time::Duration;
249
250 use api::region::RegionResponse;
251 use api::v1::helper::{tag_column_schema, time_index_column_schema};
252 use api::v1::region::{RegionRequest, bulk_insert_request, region_request};
253 use api::v1::value::ValueData;
254 use api::v1::{ColumnDataType, Row, Rows, Value};
255 use arrow::array::{ArrayRef, Int32Array, TimestampMillisecondArray};
256 use arrow::datatypes::Schema as ArrowSchema;
257 use arrow::record_batch::RecordBatch;
258 use catalog::memory::MemoryCatalogManager;
259 use common_batcher::flush_limiter::FlushLimiter;
260 use common_batcher::flush_policy::timing::TimingFlushPolicy;
261 use common_batcher::request_limiter::RequestLimiter;
262 use common_batcher::worker_registry::WorkerRegistry;
263 use common_catalog::consts::default_engine;
264 use common_grpc::flight::FlightDecoder;
265 use common_meta::error::Result as MetaResult;
266 use common_meta::peer::Peer;
267 use common_meta::test_util::{MockDatanodeHandler, MockDatanodeManager};
268 use common_query::OutputData;
269 use common_query::request::QueryRequest;
270 use common_recordbatch::SendableRecordBatchStream;
271 use common_telemetry::info;
272 use datatypes::schema::{ColumnDefaultConstraint, SchemaBuilder};
273 use datatypes::value::Value as DtValue;
274 use datatypes::vectors::{Int32Vector, TimestampMillisecondVector, VectorRef};
275 use operator::batcher::PendingRowsBatcher;
276 use operator::insert::Inserter;
277 use operator::metrics::DIST_INGEST_ROW_COUNT;
278 use operator::req_convert::insert::rows_to_record_batch;
279 use operator::test_util::{
280 create_partition_rule_manager, new_test_table_info, prepare_mocked_backend,
281 };
282 use session::context::{Channel, QueryContext};
283 use store_api::storage::RegionId;
284 use table::dist_table::DistTable;
285 use table::metadata::TableInfoRef;
286 use table::requests::InsertRequest;
287 use tokio::sync::{broadcast, mpsc, oneshot};
288 use tokio::time::timeout;
289
290 use crate::batcher::table::flow_notifier::FlowNotifier;
291 use crate::batcher::table::pending_batch::PendingBatch;
292 use crate::batcher::table::pending_worker::{WorkerCommand, start_worker};
293 use crate::batcher::table::{PendingWorkers, TablePendingRowsBatcher, batch_key_from_ctx};
294 use crate::batcher::test_util::mock_table_flownode_cache;
295
296 #[derive(Clone)]
297 struct BulkHandler {
298 requests: Arc<Mutex<Vec<RecordBatch>>>,
299 report_missing_row: bool,
300 expected_skip_wal: bool,
301 expected_schema: String,
302 }
303
304 #[async_trait::async_trait]
305 impl MockDatanodeHandler for BulkHandler {
306 async fn handle(&self, peer: &Peer, request: RegionRequest) -> MetaResult<RegionResponse> {
307 assert_eq!(3, peer.id);
308 assert_eq!(self.expected_schema, request.header.unwrap().dbname);
309 let Some(region_request::Body::BulkInsert(request)) = request.body else {
310 panic!("expected bulk insert")
311 };
312 assert_eq!(RegionId::new(1, 3).as_u64(), request.region_id);
313 assert_eq!(self.expected_skip_wal, request.skip_wal);
314 assert!(request.partition_expr_version.is_some());
315 let Some(bulk_insert_request::Body::ArrowIpc(ipc)) = request.body else {
316 panic!("expected Arrow IPC")
317 };
318 let batch = FlightDecoder::try_from_schema_bytes(&ipc.schema)
319 .unwrap()
320 .try_decode_record_batch(&ipc.data_header, &ipc.payload)
321 .unwrap();
322 let rows = batch.num_rows();
323 self.requests.lock().unwrap().push(batch);
324 Ok(RegionResponse::new(
325 rows - usize::from(self.report_missing_row),
326 ))
327 }
328
329 async fn handle_query(
330 &self,
331 _: &Peer,
332 _: QueryRequest,
333 ) -> MetaResult<SendableRecordBatchStream> {
334 panic!("batching must not query the datanode")
335 }
336 }
337
338 #[tokio::test]
339 async fn test_acknowledgement_preserves_admission() {
340 use operator::error::UnexpectedSnafu;
341
342 use crate::batcher::pending_rows_batch_sync_enabled;
343 use crate::batcher::table::batch_key_from_ctx;
344 use crate::batcher::table::pending_batch::notify_batches;
345
346 for sync in [false, true] {
347 for fail in [false, true] {
348 let backend = prepare_mocked_backend().await;
349 let nodes = Arc::new(MockDatanodeManager::new(BulkHandler {
350 requests: Arc::new(Mutex::new(Vec::new())),
351 report_missing_row: false,
352 expected_skip_wal: false,
353 expected_schema: "public".to_string(),
354 }));
355 let inserter = Arc::new(Inserter::new(
356 MemoryCatalogManager::new(),
357 create_partition_rule_manager(backend).await,
358 nodes,
359 mock_table_flownode_cache(1, vec![]).await,
360 true,
361 ));
362 let mut batcher = TablePendingRowsBatcher::try_new(
363 Duration::from_secs(3600),
364 1,
365 1,
366 1,
367 1,
368 NonZeroUsize::new(1).unwrap(),
369 inserter,
370 )
371 .unwrap();
372 assert_eq!(
373 batcher.pending_rows_batch_sync,
374 pending_rows_batch_sync_enabled()
375 );
376 Arc::get_mut(&mut batcher).unwrap().pending_rows_batch_sync = sync;
377 let table = Arc::new(new_test_table_info(1, "ack", [0].into_iter()));
378 let progress = Arc::new(session::write_progress::WriteProgress::default());
379 let mut ctx = QueryContext::with("greptime", "public");
380 ctx.set_write_progress(progress.clone());
381 let ctx = Arc::new(ctx);
382 let key = batch_key_from_ctx(&table.name, &ctx);
383 let (_, receiver) = batcher.workers.get_or_create(key, 1).await;
385 let mut receiver = receiver.unwrap();
386 let batch = RecordBatch::try_from_iter(vec![(
387 "a",
388 Arc::new(Int32Array::from(vec![1])) as ArrayRef,
389 )])
390 .unwrap();
391 let permit = batcher.acquire().await.unwrap();
392 let submitter = batcher.clone();
393 let submitted =
394 tokio::spawn(async move { submitter.submit(table, batch, ctx, permit).await });
395 let WorkerCommand::Submit(pending) =
396 timeout(Duration::from_secs(5), receiver.recv())
397 .await
398 .unwrap()
399 .unwrap();
400 let mut submitted = Some(submitted);
401 let mut completion = None;
402 if sync {
403 assert!(!submitted.as_ref().unwrap().is_finished());
404 } else {
405 let mut output = timeout(Duration::from_secs(5), submitted.take().unwrap())
406 .await
407 .unwrap()
408 .unwrap()
409 .unwrap();
410 assert_eq!(output.extract_rows_and_cost().0, 1);
411 assert_eq!(output.meta.write_completions.len(), 1);
412 let mut pending = Box::pin(output.meta.write_completions.pop().unwrap().wait());
413 assert!(futures::poll!(pending.as_mut()).is_pending());
414 completion = Some(pending);
415 }
416 assert_eq!(progress.accepted(), !sync);
417 assert_eq!(progress.written(), 0);
418 let acquire = batcher.acquire();
420 tokio::pin!(acquire);
421 assert!(futures::poll!(acquire.as_mut()).is_pending());
422 let result = if fail {
423 Err(Arc::new(
424 UnexpectedSnafu {
425 violated: "flush failed".to_string(),
426 }
427 .build(),
428 ))
429 } else {
430 Ok(())
431 };
432 notify_batches(vec![pending], result);
433 if let Some(submitted) = submitted {
434 let result = timeout(Duration::from_secs(5), submitted)
435 .await
436 .unwrap()
437 .unwrap();
438 assert_eq!(result.is_err(), fail);
439 if !fail {
440 let output = result.unwrap();
441 assert_eq!(output.extract_rows_and_cost().0, 1);
442 assert!(output.meta.write_completions.is_empty());
443 }
444 }
445 if let Some(completion) = completion {
446 let result = timeout(Duration::from_secs(5), completion).await.unwrap();
447 assert_eq!(result.is_err(), fail);
448 }
449 timeout(Duration::from_secs(5), acquire)
450 .await
451 .unwrap()
452 .unwrap();
453 }
454 }
455 }
456
457 fn rows(value: i32) -> Rows {
458 Rows {
459 schema: vec![
460 tag_column_schema("a", ColumnDataType::Int32),
461 time_index_column_schema("ts", ColumnDataType::TimestampMillisecond),
462 ],
463 rows: vec![Row {
464 values: vec![
465 Value {
466 value_data: Some(ValueData::I32Value(value)),
467 },
468 Value {
469 value_data: Some(ValueData::TimestampMillisecondValue(
470 1000 + i64::from(value),
471 )),
472 },
473 ],
474 }],
475 }
476 }
477
478 async fn run_bulk_case(
479 max_batch_rows: usize,
480 report_missing_row: bool,
481 shared_request: bool,
482 ) -> usize {
483 run_bulk_case_with_wal(max_batch_rows, report_missing_row, shared_request, false).await
484 }
485
486 async fn run_bulk_case_with_wal(
487 max_batch_rows: usize,
488 report_missing_row: bool,
489 shared_request: bool,
490 skip_wal: bool,
491 ) -> usize {
492 let backend = prepare_mocked_backend().await;
493 let partitions = create_partition_rule_manager(backend.clone()).await;
494 let captured = Arc::new(Mutex::new(Vec::new()));
495 static NEXT_DATABASE: AtomicUsize = AtomicUsize::new(0);
497 let schema_name = format!(
498 "ingest_count_{}",
499 NEXT_DATABASE.fetch_add(1, Ordering::Relaxed)
500 );
501 let nodes = Arc::new(MockDatanodeManager::new(BulkHandler {
502 requests: captured.clone(),
503 report_missing_row,
504 expected_skip_wal: skip_wal,
505 expected_schema: schema_name.clone(),
506 }));
507 let inserter = Arc::new(Inserter::new(
508 MemoryCatalogManager::new(),
509 partitions.clone(),
510 nodes.clone(),
511 mock_table_flownode_cache(1, vec![]).await,
512 true,
513 ));
514 let mut table = new_test_table_info(1, "table_1", [1, 2, 3].into_iter());
515 table.schema_name = schema_name;
516 table.meta.engine = default_engine().to_string();
517 let mut columns = table.meta.schema.column_schemas().to_vec();
518 columns[2] = columns[2]
519 .clone()
520 .with_default_constraint(Some(ColumnDefaultConstraint::Value(DtValue::Int32(7))))
521 .unwrap();
522 table.meta.schema = Arc::new(
523 SchemaBuilder::try_from(columns)
524 .unwrap()
525 .version(123)
526 .build()
527 .unwrap(),
528 );
529 let table = Arc::new(table);
530 let first = rows_to_record_batch(&rows(1), &table).unwrap();
531 let second = rows_to_record_batch(&rows(2), &table).unwrap();
532 let batcher: Arc<dyn PendingRowsBatcher> = TablePendingRowsBatcher::try_new(
535 Duration::from_secs(3600),
536 max_batch_rows,
537 1,
538 4,
539 if shared_request { 1 } else { 4 },
540 NonZeroUsize::new(16).unwrap(),
541 inserter,
542 )
543 .unwrap();
544 let context = |channel| {
545 let mut ctx = QueryContext::with_channel("greptime", &table.schema_name, channel);
546 ctx.set_batching_enabled(true);
547 Arc::new(ctx)
548 };
549 let influx_ctx = context(Channel::Influx);
550 let opentsdb_ctx = context(Channel::Opentsdb);
551 let ingest_count =
552 DIST_INGEST_ROW_COUNT.with_label_values(&[influx_ctx.get_db_string().as_str()]);
553 assert_eq!(0, ingest_count.get());
554 influx_ctx.set_skip_wal(skip_wal);
555 opentsdb_ctx.set_skip_wal(skip_wal);
556 let (first_result, second_result) = timeout(Duration::from_secs(5), async {
557 if shared_request {
558 let permit = batcher.acquire().await.unwrap();
559 let (first, second) = tokio::join!(
560 batcher.submit(table.clone(), first, influx_ctx, permit.clone()),
561 batcher.submit(table.clone(), second, opentsdb_ctx, permit),
562 );
563 (
564 first.map(|output| output.extract_rows_and_cost().0),
565 second.map(|output| output.extract_rows_and_cost().0),
566 )
567 } else {
568 let inserter = Inserter::new(
569 MemoryCatalogManager::new_with_table(DistTable::table(table.clone())),
570 partitions,
571 nodes,
572 mock_table_flownode_cache(1, vec![]).await,
573 true,
574 )
575 .with_pending_rows_batcher(Some(batcher));
576 let request = |value: i32| InsertRequest {
577 catalog_name: table.catalog_name.clone(),
578 schema_name: table.schema_name.clone(),
579 table_name: table.name.clone(),
580 columns_values: HashMap::from([
581 (
582 "a".to_string(),
583 Arc::new(Int32Vector::from_slice([value])) as VectorRef,
584 ),
585 (
586 "ts".to_string(),
587 Arc::new(TimestampMillisecondVector::from_slice([
588 1000 + i64::from(value)
589 ])) as VectorRef,
590 ),
591 ]),
592 skip_wal,
593 };
594 let (first, second) = tokio::join!(
595 inserter.handle_table_insert(request(1), influx_ctx),
596 inserter.handle_table_insert(request(2), opentsdb_ctx),
597 );
598 (
599 first.map(|output| match output.data {
600 OutputData::AffectedRows(rows) => rows,
601 _ => panic!("expected affected rows"),
602 }),
603 second.map(|output| match output.data {
604 OutputData::AffectedRows(rows) => rows,
605 _ => panic!("expected affected rows"),
606 }),
607 )
608 }
609 })
610 .await
611 .expect("the row threshold did not dispatch the combined bulk insert");
612 if report_missing_row {
613 assert!(first_result.is_err());
614 assert!(second_result.is_err());
615 } else {
616 assert_eq!(1, first_result.unwrap());
617 assert_eq!(1, second_result.unwrap());
618 }
619 assert_eq!(if report_missing_row { 0 } else { 2 }, ingest_count.get());
620 let requests = captured.lock().unwrap();
621 let mut actual_rows = Vec::new();
622 for batch in requests.iter() {
623 assert_eq!(
624 vec!["a", "ts", "b"],
625 batch
626 .schema()
627 .fields()
628 .iter()
629 .map(|field| field.name().as_str())
630 .collect::<Vec<_>>()
631 );
632 let a = batch
633 .column(0)
634 .as_any()
635 .downcast_ref::<Int32Array>()
636 .unwrap();
637 let ts = batch
638 .column(1)
639 .as_any()
640 .downcast_ref::<TimestampMillisecondArray>()
641 .unwrap();
642 let b = batch
643 .column(2)
644 .as_any()
645 .downcast_ref::<Int32Array>()
646 .unwrap();
647 actual_rows.extend(
648 (0..batch.num_rows())
649 .map(|index| (a.value(index), ts.value(index), b.value(index))),
650 );
651 }
652 actual_rows.sort_unstable();
653 assert_eq!(vec![(1, 1001, 7), (2, 1002, 7)], actual_rows);
654 requests.len()
655 }
656
657 #[tokio::test]
658 async fn test_inserter_counts_batched_rows_once() {
659 for report_missing_row in [false, true] {
660 assert_eq!(1, run_bulk_case(2, report_missing_row, false).await);
661 }
662 }
663
664 #[tokio::test]
665 async fn test_row_threshold_ablation_reduces_bulk_requests() {
666 for repetition in 0..2 {
667 let direct_count = run_bulk_case(1, false, false).await;
668 let combined_count = run_bulk_case(2, false, false).await;
669 assert_eq!(2, direct_count);
670 assert_eq!(1, combined_count);
671 info!(
672 "repetition={repetition} rows=2 threshold=1 bulk_requests={direct_count}; threshold=2 bulk_requests={combined_count}"
673 );
674 }
675 }
676
677 #[tokio::test]
678 async fn test_original_request_shares_one_admission_slot() {
679 assert_eq!(1, run_bulk_case(2, false, true).await);
680 }
681
682 #[tokio::test]
683 async fn test_bulk_preserves_skip_wal() {
684 for skip_wal in [false, true] {
685 assert_eq!(1, run_bulk_case_with_wal(2, false, false, skip_wal).await);
686 }
687 }
688
689 #[tokio::test]
690 async fn test_exemplar_coalesced_table_flush_retains_each_requests_progress() {
691 #[derive(Clone)]
692 struct PartialHandler;
693 #[async_trait::async_trait]
694 impl MockDatanodeHandler for PartialHandler {
695 async fn handle(&self, _: &Peer, request: RegionRequest) -> MetaResult<RegionResponse> {
696 let Some(region_request::Body::BulkInsert(request)) = request.body else {
697 panic!("expected bulk")
698 };
699 let Some(bulk_insert_request::Body::ArrowIpc(ipc)) = request.body else {
700 panic!("expected Arrow")
701 };
702 let batch = FlightDecoder::try_from_schema_bytes(&ipc.schema)
703 .unwrap()
704 .try_decode_record_batch(&ipc.data_header, &ipc.payload)
705 .unwrap();
706 let values = batch
707 .column(0)
708 .as_any()
709 .downcast_ref::<Int32Array>()
710 .unwrap();
711 if values.values().contains(&20) {
712 return common_meta::error::UnexpectedSnafu {
713 err_msg: "lost acknowledgement",
714 }
715 .fail();
716 }
717 Ok(RegionResponse::new(batch.num_rows()))
718 }
719 async fn handle_query(
720 &self,
721 _: &Peer,
722 _: QueryRequest,
723 ) -> MetaResult<SendableRecordBatchStream> {
724 unreachable!()
725 }
726 }
727 for sync in [false, true] {
728 let partitions = create_partition_rule_manager(prepare_mocked_backend().await).await;
729 let inserter = Arc::new(Inserter::new(
730 MemoryCatalogManager::new(),
731 partitions,
732 Arc::new(MockDatanodeManager::new(PartialHandler)),
733 mock_table_flownode_cache(1, vec![]).await,
734 true,
735 ));
736 let mut batcher = TablePendingRowsBatcher::try_new(
737 Duration::from_secs(3600),
738 3,
739 1,
740 4,
741 4,
742 NonZeroUsize::new(16).unwrap(),
743 inserter,
744 )
745 .unwrap();
746 Arc::get_mut(&mut batcher).unwrap().pending_rows_batch_sync = sync;
747 let table = Arc::new(new_test_table_info(1, "table_1", [1, 2, 3].into_iter()));
748 let mut first = rows(1);
749 first.rows.extend(rows(20).rows);
750 let first = rows_to_record_batch(&first, &table).unwrap();
751 let second = rows_to_record_batch(&rows(2), &table).unwrap();
752 let counters =
753 [0, 1].map(|_| Arc::new(session::write_progress::WriteProgress::default()));
754 let contexts = counters.each_ref().map(|counter| {
755 let mut ctx = QueryContext::arc().with_confirmed_writes();
756 ctx.set_write_progress(counter.clone());
757 Arc::new(ctx)
758 });
759 let first_permit = batcher.acquire().await.unwrap();
760 let second_permit = batcher.acquire().await.unwrap();
761 let (first, second) = tokio::join!(
762 batcher.submit(table.clone(), first, contexts[0].clone(), first_permit),
763 batcher.submit(table, second, contexts[1].clone(), second_permit),
764 );
765 let first = timeout(Duration::from_secs(5), first.unwrap().write_progress())
766 .await
767 .unwrap();
768 let second = timeout(Duration::from_secs(5), second.unwrap().write_progress())
769 .await
770 .unwrap();
771 assert_eq!(first.affected_rows, 1);
772 assert!(first.error.is_some());
773 assert_eq!(second.affected_rows, 1);
774 assert!(second.error.is_none());
775 for counter in counters {
776 counter.wait().await;
777 assert_eq!(counter.written(), 1);
778 }
779 }
780 }
781
782 #[test]
783 fn test_batch_key_uses_name_and_wal_policy() {
784 let ctx = QueryContext::arc();
785 let table = new_test_table_info(1, "table_1", [1, 2, 3].into_iter());
786 let mut replacement = table.clone();
787 replacement.ident.table_id = 2;
788 replacement.ident.version += 1;
789 let key = batch_key_from_ctx(&table.name, &ctx);
790 assert_eq!(key, batch_key_from_ctx(&replacement.name, &ctx));
791 assert_ne!(key, batch_key_from_ctx("table_2", &ctx));
792 for (catalog, schema) in [("other", "public"), ("greptime", "other")] {
793 let other = Arc::new(QueryContext::with_channel(catalog, schema, Channel::Influx));
794 assert_ne!(key, batch_key_from_ctx(&table.name, &other));
795 }
796 ctx.set_skip_wal(true);
797 assert_ne!(key, batch_key_from_ctx(&table.name, &ctx));
798 }
799
800 const WORKER_TIMEOUT: Duration = Duration::from_secs(5);
801
802 #[derive(Clone)]
803 struct GatedHandler {
804 inner: BulkHandler,
805 entered: mpsc::UnboundedSender<oneshot::Sender<()>>,
806 }
807
808 #[async_trait::async_trait]
809 impl MockDatanodeHandler for GatedHandler {
810 async fn handle(&self, peer: &Peer, request: RegionRequest) -> MetaResult<RegionResponse> {
811 let response = self.inner.handle(peer, request).await?;
812 let (release_tx, release_rx) = oneshot::channel();
813 self.entered.send(release_tx).unwrap();
814 release_rx.await.unwrap();
815 Ok(response)
816 }
817
818 async fn handle_query(
819 &self,
820 _: &Peer,
821 _: QueryRequest,
822 ) -> MetaResult<SendableRecordBatchStream> {
823 panic!("worker must not query the datanode")
824 }
825 }
826
827 struct WorkerTest {
828 tx: mpsc::Sender<WorkerCommand>,
829 shutdown: broadcast::Sender<()>,
830 workers: Arc<PendingWorkers>,
831 table: TableInfoRef,
832 entered: mpsc::UnboundedReceiver<oneshot::Sender<()>>,
833 }
834
835 impl Drop for WorkerTest {
836 fn drop(&mut self) {
837 let _ = self.shutdown.send(());
838 }
839 }
840
841 impl WorkerTest {
842 async fn new(max_rows: usize, max_flushes: usize) -> Self {
843 let backend = prepare_mocked_backend().await;
844 let partitions = create_partition_rule_manager(backend.clone()).await;
845 let (entered_tx, entered) = mpsc::unbounded_channel();
846 let nodes = Arc::new(MockDatanodeManager::new(GatedHandler {
847 inner: BulkHandler {
848 requests: Arc::new(Mutex::new(Vec::new())),
849 report_missing_row: false,
850 expected_skip_wal: false,
851 expected_schema: "public".to_string(),
852 },
853 entered: entered_tx,
854 }));
855 let cache = mock_table_flownode_cache(1, vec![]).await;
856 let notifier =
857 FlowNotifier::new(cache.clone(), nodes.clone(), NonZeroUsize::new(16).unwrap())
858 .unwrap();
859 let inserter = Arc::new(Inserter::new(
860 MemoryCatalogManager::new(),
861 partitions,
862 nodes,
863 cache,
864 true,
865 ));
866 let table = Arc::new(new_test_table_info(1, "table_1", [1, 2, 3].into_iter()));
867 let key = batch_key_from_ctx(&table.name, &QueryContext::arc());
868 let workers = Arc::new(WorkerRegistry::new());
869 let (tx, rx) = mpsc::channel(1);
870 workers.get_or_insert_with(key.clone(), || tx.clone()).await;
871 let (shutdown, _) = broadcast::channel(1);
872 start_worker(
873 key,
874 tx.clone(),
875 workers.clone(),
876 rx,
877 shutdown.clone(),
878 TimingFlushPolicy::try_new(Duration::from_secs(3600), max_rows).unwrap(),
879 FlushLimiter::try_new(max_flushes).unwrap(),
880 inserter,
881 notifier,
882 Duration::from_secs(10800),
883 );
884 Self {
885 tx,
886 shutdown,
887 workers,
888 table,
889 entered,
890 }
891 }
892
893 async fn submit(
894 &self,
895 count: usize,
896 changed_schema: bool,
897 ) -> oneshot::Receiver<common_query::WriteProgress> {
898 self.submit_with_table(count, changed_schema, self.table.clone())
899 .await
900 }
901
902 async fn submit_with_table(
903 &self,
904 count: usize,
905 changed_schema: bool,
906 table_info: TableInfoRef,
907 ) -> oneshot::Receiver<common_query::WriteProgress> {
908 let mut input = rows(1);
909 input.rows = vec![input.rows[0].clone(); count];
910 let mut batch = rows_to_record_batch(&input, &self.table).unwrap();
911 if changed_schema {
912 let schema = ArrowSchema::new_with_metadata(
913 batch.schema().fields().clone(),
914 [("test_version".to_string(), "2".to_string())]
915 .into_iter()
916 .collect(),
917 );
918 batch = RecordBatch::try_new(Arc::new(schema), batch.columns().to_vec()).unwrap();
919 }
920 let limiter = RequestLimiter::try_new(1).unwrap();
921 let (response_tx, response_rx) = oneshot::channel();
922 self.tx
923 .send(WorkerCommand::Submit(PendingBatch {
924 _completion: None,
925 table_info,
926 batch,
927 ctx: QueryContext::arc(),
928 response_tx,
929 _permit: limiter.acquire().await.unwrap(),
930 }))
931 .await
932 .unwrap();
933 drop(
935 timeout(WORKER_TIMEOUT, self.tx.reserve())
936 .await
937 .unwrap()
938 .unwrap(),
939 );
940 response_rx
941 }
942
943 async fn entered(&mut self) -> oneshot::Sender<()> {
944 timeout(WORKER_TIMEOUT, self.entered.recv())
945 .await
946 .expect("flush did not reach datanode")
947 .unwrap()
948 }
949
950 async fn stopped(&self) {
951 timeout(WORKER_TIMEOUT, async {
952 while !self.workers.is_empty().await {
953 tokio::task::yield_now().await;
954 }
955 })
956 .await
957 .expect("worker did not remove its registry entry");
958 }
959 }
960
961 #[tokio::test]
962 async fn test_same_schema_flushes_overlap() {
963 let mut worker = WorkerTest::new(1, 2).await;
964 let first = worker.submit(1, false).await;
965 let first_release = worker.entered().await;
966 let second = worker.submit(1, false).await;
967 let second_release = worker.entered().await;
968 first_release.send(()).unwrap();
970 second_release.send(()).unwrap();
971 timeout(WORKER_TIMEOUT, first)
972 .await
973 .unwrap()
974 .unwrap()
975 .into_result()
976 .unwrap();
977 timeout(WORKER_TIMEOUT, second)
978 .await
979 .unwrap()
980 .unwrap()
981 .into_result()
982 .unwrap();
983 worker.shutdown.send(()).unwrap();
984 worker.stopped().await;
985 }
986
987 #[tokio::test]
988 async fn test_schema_change_waits_for_prior_flush() {
989 let mut worker = WorkerTest::new(1, 2).await;
990 let first = worker.submit(1, false).await;
991 let first_release = worker.entered().await;
992 let second = worker.submit(1, true).await;
993 assert!(
995 timeout(Duration::from_millis(100), worker.entered.recv())
996 .await
997 .is_err()
998 );
999 first_release.send(()).unwrap();
1000 let second_release = worker.entered().await;
1001 second_release.send(()).unwrap();
1002 timeout(WORKER_TIMEOUT, first)
1003 .await
1004 .unwrap()
1005 .unwrap()
1006 .into_result()
1007 .unwrap();
1008 timeout(WORKER_TIMEOUT, second)
1009 .await
1010 .unwrap()
1011 .unwrap()
1012 .into_result()
1013 .unwrap();
1014 worker.shutdown.send(()).unwrap();
1015 worker.stopped().await;
1016 }
1017
1018 #[tokio::test]
1019 async fn test_table_identity_change_flushes_pending_rows_before_switching() {
1020 for recreated in [false, true] {
1021 let mut worker = WorkerTest::new(2, 2).await;
1022 let first = worker.submit(1, false).await;
1023 let mut changed = (*worker.table).clone();
1024 if recreated {
1025 changed.ident.table_id = 2;
1026 } else {
1027 changed.ident.version += 1;
1028 }
1029 let mut second = worker.submit_with_table(2, false, Arc::new(changed)).await;
1030 let first_release = worker.entered().await;
1032 assert!(
1033 timeout(Duration::from_millis(100), worker.entered.recv())
1034 .await
1035 .is_err()
1036 );
1037 assert!(
1038 timeout(Duration::from_millis(100), &mut second)
1039 .await
1040 .is_err()
1041 );
1042 first_release.send(()).unwrap();
1043 timeout(WORKER_TIMEOUT, first)
1044 .await
1045 .unwrap()
1046 .unwrap()
1047 .into_result()
1048 .unwrap();
1049 if recreated {
1050 assert!(
1052 timeout(WORKER_TIMEOUT, second)
1053 .await
1054 .unwrap()
1055 .unwrap()
1056 .into_result()
1057 .is_err()
1058 );
1059 } else {
1060 let second_release = worker.entered().await;
1061 second_release.send(()).unwrap();
1062 timeout(WORKER_TIMEOUT, second)
1063 .await
1064 .unwrap()
1065 .unwrap()
1066 .into_result()
1067 .unwrap();
1068 }
1069 worker.shutdown.send(()).unwrap();
1070 worker.stopped().await;
1071 }
1072 }
1073
1074 #[tokio::test]
1075 async fn test_shutdown_flushes_inline_without_joining_inflight() {
1076 let mut worker = WorkerTest::new(2, 1).await;
1077 let mut first = worker.submit(2, false).await;
1078 let first_release = worker.entered().await;
1079 let pending = worker.submit(1, false).await;
1080 worker.shutdown.send(()).unwrap();
1081 let pending_release = worker.entered().await;
1083 pending_release.send(()).unwrap();
1084 timeout(WORKER_TIMEOUT, pending)
1085 .await
1086 .unwrap()
1087 .unwrap()
1088 .into_result()
1089 .unwrap();
1090 worker.stopped().await;
1091 assert!(matches!(
1092 first.try_recv(),
1093 Err(oneshot::error::TryRecvError::Empty)
1094 ));
1095 first_release.send(()).unwrap();
1096 timeout(WORKER_TIMEOUT, first)
1097 .await
1098 .unwrap()
1099 .unwrap()
1100 .into_result()
1101 .unwrap();
1102 }
1103}