1use std::num::NonZeroUsize;
16
17use arrow::record_batch::RecordBatch;
18use common_meta::cache::TableFlownodeSetCacheRef;
19use common_meta::node_manager::NodeManagerRef;
20use common_telemetry::error;
21use lazy_static::lazy_static;
22use operator::req_convert::insert::extract_timestamps;
23use prometheus::{IntCounterVec, register_int_counter_vec};
24use table::metadata::TableInfoRef;
25
26use crate::batcher::flow_notifier::{
27 FlowNotifier as NotificationQueue, start_flow_notification_worker,
28};
29use crate::batcher::flow_sender::FlowNotification;
30
31lazy_static! {
32 static ref FLOW_NOTIFICATION_DROPPED: IntCounterVec = register_int_counter_vec!(
33 "greptime_table_batcher_flow_notification_dropped_total",
34 "Ordinary batch flow notifications dropped before delivery",
35 &["reason"]
36 )
37 .unwrap();
38}
39
40#[derive(Clone)]
42pub(in crate::batcher::table) struct FlowNotifier {
43 notifier: NotificationQueue,
44}
45
46impl FlowNotifier {
47 pub fn new(
49 cache: TableFlownodeSetCacheRef,
50 node_manager: NodeManagerRef,
51 capacity: NonZeroUsize,
52 ) -> Option<Self> {
53 let (notifier, receiver) =
54 NotificationQueue::try_new(capacity.get(), FLOW_NOTIFICATION_DROPPED.clone())?;
55 start_flow_notification_worker(receiver, cache, node_manager);
56 Some(Self { notifier })
57 }
58
59 pub fn notify<'a>(
62 &self,
63 table_info: TableInfoRef,
64 batches: impl IntoIterator<Item = &'a RecordBatch>,
65 ) {
66 let Some(timestamp_column) = table_info.meta.schema.timestamp_column() else {
67 return;
68 };
69 let mut timestamps = Vec::new();
70 for batch in batches {
71 match extract_timestamps(batch, ×tamp_column.name) {
72 Ok(batch_timestamps) => timestamps.extend(batch_timestamps),
73 Err(error) => {
74 error!(error; "Failed to extract flow notification timestamps, table_id: {}", table_info.table_id());
75 }
76 }
77 }
78 if timestamps.is_empty() {
79 return;
80 }
81 self.enqueue(FlowNotification {
82 table_id: table_info.table_id(),
83 timestamps,
84 });
85 }
86
87 fn enqueue(&self, notification: FlowNotification) {
88 self.notifier.try_notify(notification);
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use std::sync::Arc;
95
96 use api::helper::ColumnDataTypeWrapper;
97 use api::region::RegionResponse;
98 use api::v1::helper::{tag_column_schema, time_index_column_schema};
99 use api::v1::region::{RegionRequest, bulk_insert_request, region_request};
100 use api::v1::value::ValueData;
101 use api::v1::{ColumnDataType, Row, Rows, Value};
102 use arrow::array::{
103 ArrayRef, Int32Array, TimestampMicrosecondArray, TimestampMillisecondArray,
104 TimestampNanosecondArray, TimestampSecondArray,
105 };
106 use arrow::record_batch::RecordBatch;
107 use catalog::memory::MemoryCatalogManager;
108 use common_grpc::flight::FlightDecoder;
109 use common_meta::error::Result as MetaResult;
110 use common_meta::peer::Peer;
111 use common_meta::test_util::{MockDatanodeHandler, MockDatanodeManager};
112 use common_query::request::QueryRequest;
113 use common_recordbatch::SendableRecordBatchStream;
114 use datatypes::data_type::ConcreteDataType;
115 use datatypes::schema::{ColumnSchema, Schema};
116 use operator::insert::Inserter;
117 use operator::req_convert::insert::rows_to_record_batch;
118 use operator::test_util::{
119 create_partition_rule_manager, new_test_table_info, prepare_mocked_backend,
120 };
121 use session::context::QueryContext;
122 use tokio::sync::{Semaphore, oneshot};
123
124 use crate::batcher::flow_notifier::FlowNotifier as NotificationQueue;
125 use crate::batcher::table::batch::{Batch, flush_batch};
126 use crate::batcher::table::flow_notifier::{
127 FLOW_NOTIFICATION_DROPPED, FlowNotification, FlowNotifier,
128 };
129 use crate::batcher::table::metrics;
130 use crate::batcher::table::pending_batch::PendingBatch;
131 use crate::batcher::test_util::mock_table_flownode_cache;
132
133 #[tokio::test]
134 async fn test_flush_coalesces_notifications_for_successful_submissions() {
135 #[derive(Clone)]
136 struct PartialHandler;
137
138 #[async_trait::async_trait]
139 impl MockDatanodeHandler for PartialHandler {
140 async fn handle(&self, _: &Peer, request: RegionRequest) -> MetaResult<RegionResponse> {
141 let Some(region_request::Body::BulkInsert(request)) = request.body else {
142 panic!("expected bulk insert")
143 };
144 let Some(bulk_insert_request::Body::ArrowIpc(ipc)) = request.body else {
145 panic!("expected Arrow IPC")
146 };
147 let batch = FlightDecoder::try_from_schema_bytes(&ipc.schema)
148 .unwrap()
149 .try_decode_record_batch(&ipc.data_header, &ipc.payload)
150 .unwrap();
151 let values = batch
152 .column(0)
153 .as_any()
154 .downcast_ref::<Int32Array>()
155 .unwrap();
156 if values.values().contains(&20) {
157 return common_meta::error::UnexpectedSnafu {
158 err_msg: "lost acknowledgement",
159 }
160 .fail();
161 }
162 Ok(RegionResponse::new(batch.num_rows()))
163 }
164
165 async fn handle_query(
166 &self,
167 _: &Peer,
168 _: QueryRequest,
169 ) -> MetaResult<SendableRecordBatchStream> {
170 unreachable!()
171 }
172 }
173
174 let partitions = create_partition_rule_manager(prepare_mocked_backend().await).await;
175 let inserter = Arc::new(Inserter::new(
176 MemoryCatalogManager::new(),
177 partitions,
178 Arc::new(MockDatanodeManager::new(PartialHandler)),
179 mock_table_flownode_cache(1, vec![]).await,
180 true,
181 ));
182 let table = Arc::new(new_test_table_info(1, "table_1", [1, 2, 3].into_iter()));
183 let cases = [
184 (
185 vec![vec![1], vec![2]],
186 vec![(1, false), (1, false)],
187 vec![60_000, 120_000],
188 true,
189 ),
190 (
191 vec![vec![1, 20], vec![2], vec![3]],
192 vec![(1, true), (1, false), (1, false)],
193 vec![120_000, 180_000],
194 false,
195 ),
196 (
197 vec![vec![20], vec![20]],
198 vec![(0, true), (0, true)],
199 vec![],
200 false,
201 ),
202 (
203 vec![vec![1]; 100],
204 vec![(1, false); 100],
205 vec![60_000; 100],
206 true,
207 ),
208 ];
209 let ctx = Arc::new(QueryContext::arc().with_confirmed_writes());
210 let ingested = operator::metrics::DIST_INGEST_ROW_COUNT
211 .with_label_values(&[ctx.get_db_string().as_str()]);
212 for (inputs, expected_progress, expected_timestamps, successful_flush) in cases {
213 let permits = Arc::new(Semaphore::new(inputs.len()));
214 let mut submissions = Vec::new();
215 let mut completions = Vec::new();
216 let mut total_rows = 0;
217 for values in inputs {
218 let rows = Rows {
219 schema: vec![
220 tag_column_schema("a", ColumnDataType::Int32),
221 time_index_column_schema("ts", ColumnDataType::TimestampMillisecond),
222 ],
223 rows: values
224 .into_iter()
225 .map(|value| Row {
226 values: vec![
227 Value {
228 value_data: Some(ValueData::I32Value(value)),
229 },
230 Value {
231 value_data: Some(ValueData::TimestampMillisecondValue(
232 i64::from(value) * 60_000,
233 )),
234 },
235 ],
236 })
237 .collect(),
238 };
239 total_rows += rows.rows.len();
240 let (response_tx, response_rx) = oneshot::channel();
241 submissions.push(PendingBatch {
242 _completion: None,
243 table_info: table.clone(),
244 batch: rows_to_record_batch(&rows, &table).unwrap(),
245 ctx: ctx.clone(),
246 response_tx,
247 _permit: Arc::new(permits.clone().acquire_owned().await.unwrap()),
248 });
249 completions.push(response_rx);
250 }
251 let (notifier, mut notifications) =
253 NotificationQueue::try_new(1, FLOW_NOTIFICATION_DROPPED.clone()).unwrap();
254 let successes = metrics::FLUSH_TOTAL.get();
255 let failures = metrics::FLUSH_FAILURES.get();
256 let samples = metrics::FLUSH_ROWS.get_sample_count();
257 let sum = metrics::FLUSH_ROWS.get_sample_sum();
258 let dropped = metrics::FLUSH_DROPPED_ROWS.get();
259 let written = ingested.get();
260 flush_batch(
261 Batch {
262 submissions,
263 total_rows,
264 },
265 inserter.clone(),
266 FlowNotifier { notifier },
267 )
268 .await;
269
270 let expected_written: usize = expected_progress.iter().map(|(rows, _)| rows).sum();
271 assert_eq!(
272 metrics::FLUSH_TOTAL.get(),
273 successes + u64::from(successful_flush)
274 );
275 assert_eq!(
276 metrics::FLUSH_FAILURES.get(),
277 failures + u64::from(!successful_flush)
278 );
279 assert_eq!(
280 metrics::FLUSH_ROWS.get_sample_count(),
281 samples + u64::from(successful_flush)
282 );
283 assert_eq!(
284 metrics::FLUSH_ROWS.get_sample_sum(),
285 sum + if successful_flush {
286 total_rows as f64
287 } else {
288 0.0
289 }
290 );
291 assert_eq!(
292 metrics::FLUSH_DROPPED_ROWS.get(),
293 dropped + (total_rows - expected_written) as u64
294 );
295 assert_eq!(ingested.get(), written + expected_written as u64);
296 for (completion, (affected_rows, failed)) in
297 completions.into_iter().zip(expected_progress)
298 {
299 let progress = completion.await.unwrap();
300 assert_eq!(progress.affected_rows, affected_rows);
301 assert_eq!(progress.error.is_some(), failed);
302 }
303 if !expected_timestamps.is_empty() {
304 let notification = notifications.try_recv().unwrap();
305 assert_eq!(notification.table_id, 1);
306 assert_eq!(notification.timestamps, expected_timestamps);
307 }
308 assert!(notifications.try_recv().is_err());
309 }
310 }
311
312 #[test]
313 fn test_full_and_closed_queues_do_not_block() {
314 let (notifier, receiver) =
315 NotificationQueue::try_new(1, FLOW_NOTIFICATION_DROPPED.clone()).unwrap();
316 let notifier = FlowNotifier { notifier };
317 let full = FLOW_NOTIFICATION_DROPPED.with_label_values(&["full"]);
318 let closed = FLOW_NOTIFICATION_DROPPED.with_label_values(&["closed"]);
319 let before_full = full.get();
320 let before_closed = closed.get();
321 for _ in 0..2 {
322 notifier.enqueue(FlowNotification {
323 table_id: 1,
324 timestamps: vec![42],
325 });
326 }
327 assert!(full.get() > before_full);
328 drop(receiver);
329 notifier.enqueue(FlowNotification {
330 table_id: 1,
331 timestamps: vec![42],
332 });
333 assert!(closed.get() > before_closed);
334 }
335 #[test]
336 fn test_notification_preserves_native_timestamp_units() {
337 let cases: Vec<(ColumnDataType, ArrayRef)> = vec![
338 (
339 ColumnDataType::TimestampSecond,
340 Arc::new(TimestampSecondArray::from(vec![Some(42), None])),
341 ),
342 (
343 ColumnDataType::TimestampMillisecond,
344 Arc::new(TimestampMillisecondArray::from(vec![Some(42), None])),
345 ),
346 (
347 ColumnDataType::TimestampMicrosecond,
348 Arc::new(TimestampMicrosecondArray::from(vec![Some(42), None])),
349 ),
350 (
351 ColumnDataType::TimestampNanosecond,
352 Arc::new(TimestampNanosecondArray::from(vec![Some(42), None])),
353 ),
354 ];
355 for (datatype, array) in cases {
356 let mut table = new_test_table_info(1, "test", [0].into_iter());
357 let column = ColumnSchema::new(
358 "ts",
359 ConcreteDataType::from(ColumnDataTypeWrapper::new(datatype, None)),
360 true,
361 )
362 .with_time_index(true);
363 table.meta.schema = Arc::new(Schema::new(vec![column]));
364 let batch = RecordBatch::try_new(table.meta.schema.arrow_schema().clone(), vec![array])
365 .unwrap();
366 let (notifier, mut receiver) =
367 NotificationQueue::try_new(1, FLOW_NOTIFICATION_DROPPED.clone()).unwrap();
368 FlowNotifier { notifier }.notify(Arc::new(table), [&batch, &batch]);
369 let notification = receiver.try_recv().unwrap();
370 assert_eq!(notification.table_id, 1);
371 assert_eq!(notification.timestamps, vec![42, 42]);
372 assert!(receiver.try_recv().is_err());
373 }
374 }
375}