1use std::collections::HashSet;
16
17use api::v1::flow::{DirtyWindowRequest, DirtyWindowRequests};
18use api::v1::region::{
19 BulkInsertRequest, RegionRequest, RegionRequestHeader, bulk_insert_request, region_request,
20};
21use api::v1::{ArrowIpc, PartitionExprVersion};
22use arrow::compute;
23use arrow::record_batch::RecordBatch;
24use bytes::Bytes;
25use common_base::AffectedRows;
26use common_grpc::FlightData;
27use common_grpc::flight::{FlightEncoder, FlightMessage};
28use common_telemetry::error;
29use common_telemetry::tracing_context::TracingContext;
30use futures::future;
31use meter_core::data::MeterRecord;
32use meter_macros::write_meter;
33use session::context::{Channel, QueryContextRef};
34use snafu::{OptionExt, ResultExt, ensure};
35use store_api::storage::RegionId;
36use table::TableRef;
37use table::metadata::TableInfoRef;
38use tokio_util::sync::CancellationToken;
39
40use crate::error::Result;
41use crate::insert::Inserter;
42use crate::req_convert::TablePartitioner;
43use crate::req_convert::insert;
44use crate::write_dispatcher::{WriteAttempt, WriteAttribution};
45use crate::{error, metrics};
46
47impl Inserter {
48 pub async fn flush_bulk_batch_tracked(
50 &self,
51 table_info: TableInfoRef,
52 batch: RecordBatch,
53 ctx: QueryContextRef,
54 owners: Vec<usize>,
55 inputs: usize,
56 ) -> Result<Vec<common_query::WriteProgress>> {
57 let requests = TablePartitioner::try_new(&self.partition_manager, &table_info)
58 .await
59 .context(error::InvalidPartitionSnafu)?
60 .partition_tracked_bulk_insert_requests(batch, owners, ctx.skip_wal())?;
61 let requests = requests
62 .into_iter()
63 .map(|(request, owners)| crate::write_dispatcher::TrackedWrite {
64 request: RegionRequest {
65 header: Some(RegionRequestHeader {
66 dbname: ctx.get_db_string(),
67 tracing_context: TracingContext::from_current_span().to_w3c(),
68 ..Default::default()
69 }),
70 body: Some(region_request::Body::BulkInsert(request)),
71 },
72 owners,
73 })
74 .collect();
75 Ok(self
76 .write_dispatcher
77 .dispatch_tracked(requests, inputs, None)
78 .await)
79 }
80
81 pub async fn flush_bulk_batch(
86 &self,
87 table_info: TableInfoRef,
88 batch: RecordBatch,
89 ctx: QueryContextRef,
90 attribution: WriteAttribution,
91 ) -> Result<AffectedRows> {
92 let requests = TablePartitioner::try_new(&self.partition_manager, &table_info)
93 .await
94 .context(error::InvalidPartitionSnafu)?
95 .partition_attributed_bulk_requests(batch, ctx.skip_wal(), attribution)?;
96 let writes = requests
97 .into_iter()
98 .map(|(request, attribution)| {
99 (
100 RegionRequest {
101 header: Some(RegionRequestHeader {
102 dbname: ctx.get_db_string(),
103 tracing_context: TracingContext::from_current_span().to_w3c(),
104 ..Default::default()
105 }),
106 body: Some(region_request::Body::BulkInsert(request)),
107 },
108 attribution,
109 )
110 })
111 .collect();
112 Ok(self
113 .write_dispatcher
114 .dispatch_attributed(writes, None)
115 .await
116 .context(error::RequestInsertsSnafu)?
117 .affected_rows)
118 }
119
120 pub async fn handle_bulk_insert(
122 &self,
123 table: TableRef,
124 raw_flight_data: FlightData,
125 record_batch: RecordBatch,
126 schema_bytes: Bytes,
127 skip_wal: bool,
128 channel: Channel,
129 ) -> Result<AffectedRows> {
130 let table_info = table.table_info();
131 let table_id = table_info.table_id();
132 let db_name = table_info.get_db_string();
133
134 if record_batch.num_rows() == 0 {
135 return Ok(0);
136 }
137
138 for field in record_batch.schema_ref().fields() {
140 ensure!(
141 table_info
142 .meta
143 .schema
144 .column_schema_by_name(field.name())
145 .is_some(),
146 error::InvalidInsertRequestSnafu {
147 reason: format!(
148 "Column '{}' not found in table '{}'",
149 field.name(),
150 table_info.full_table_name()
151 ),
152 }
153 );
154 }
155
156 write_meter!(MeterRecord::new(
159 table_info.catalog_name.clone(),
160 table_info.schema_name.clone(),
161 0,
162 record_batch.num_rows() as u64,
163 channel as u8,
164 ))
165 .await
166 .context(error::WriteRejectedSnafu)?;
167
168 let body_size = raw_flight_data.data_body.len();
169 self.maybe_update_flow_dirty_window(table_info.clone(), record_batch.clone());
172
173 metrics::BULK_REQUEST_MESSAGE_SIZE.observe(body_size as f64);
174 metrics::BULK_REQUEST_ROWS
175 .with_label_values(&["raw"])
176 .observe(record_batch.num_rows() as f64);
177
178 let partition_timer = metrics::HANDLE_BULK_INSERT_ELAPSED
179 .with_label_values(&["partition"])
180 .start_timer();
181 let (partition_rule, partition_versions) = self
182 .partition_manager
183 .find_table_partition_rule(&table_info)
184 .await
185 .context(error::InvalidPartitionSnafu)?;
186
187 let region_masks = partition_rule
189 .split_record_batch(&record_batch)
190 .context(error::SplitInsertSnafu)?;
191 partition_timer.observe_duration();
192
193 if region_masks.len() == 1 {
195 metrics::BULK_REQUEST_ROWS
196 .with_label_values(&["rows_per_region"])
197 .observe(record_batch.num_rows() as f64);
198
199 let (region_number, _) = region_masks.into_iter().next().unwrap();
201 let region_id = RegionId::new(table_id, region_number);
202 let partition_expr_version = partition_versions
203 .get(®ion_number)
204 .copied()
205 .unwrap_or_default();
206 let request = RegionRequest {
207 header: Some(RegionRequestHeader {
208 tracing_context: TracingContext::from_current_span().to_w3c(),
209 ..Default::default()
210 }),
211 body: Some(region_request::Body::BulkInsert(BulkInsertRequest {
212 skip_wal,
213 region_id: region_id.as_u64(),
214 partition_expr_version: partition_expr_version
215 .map(|value| PartitionExprVersion { value }),
216 aligned_schema_version: None,
217 body: Some(bulk_insert_request::Body::ArrowIpc(ArrowIpc {
218 schema: schema_bytes.clone(),
219 data_header: raw_flight_data.data_header,
220 payload: raw_flight_data.data_body,
221 })),
222 })),
223 };
224
225 let writes = self.write_dispatcher.prepare(vec![request]).await?;
226 let result = self
227 .write_dispatcher
228 .execute(
229 writes,
230 Some(
231 metrics::HANDLE_BULK_INSERT_ELAPSED.with_label_values(&["datanode_handle"]),
232 ),
233 None,
234 )
235 .await
236 .context(error::RequestRegionSnafu)
237 .map(|r| r.affected_rows);
238 if let Ok(rows) = result {
239 crate::metrics::DIST_INGEST_ROW_COUNT
240 .with_label_values(&[db_name.as_str()])
241 .inc_by(rows as u64);
242 }
243 return result;
244 }
245
246 let regions = region_masks
247 .keys()
248 .map(|region| RegionId::new(table_id, *region))
249 .collect::<Vec<_>>();
250 let mut peers = self
251 .write_dispatcher
252 .resolve_peers(®ions)
253 .await
254 .context(error::FindRegionLeaderSnafu)?;
255
256 let wait_all_datanode_timer = metrics::HANDLE_BULK_INSERT_ELAPSED
257 .with_label_values(&["wait_all_datanode"])
258 .start_timer();
259
260 let cancellation = CancellationToken::new();
262 let _guard = cancellation.clone().drop_guard();
263 let mut handles = Vec::with_capacity(region_masks.len());
264
265 for (region_number, mask) in region_masks {
266 let region_id = RegionId::new(table_id, region_number);
267 if mask.select_none() {
268 continue;
269 }
270 let partition_expr_version = partition_versions
271 .get(®ion_id.region_number())
272 .copied()
273 .unwrap_or_default();
274 let peer = peers
275 .remove(®ion_id)
276 .context(partition::error::FindLeaderSnafu {
277 region_id,
278 table_id,
279 })
280 .context(error::FindRegionLeaderSnafu)?;
281 let dispatcher = self.write_dispatcher.clone();
282 let rb = record_batch.clone();
283 let schema_bytes = schema_bytes.clone();
284 let raw_header_and_data = if mask.select_all() {
285 Some((
286 raw_flight_data.data_header.clone(),
287 raw_flight_data.data_body.clone(),
288 ))
289 } else {
290 None
291 };
292 let handle: common_runtime::JoinHandle<Result<WriteAttempt>> =
293 common_runtime::spawn_global(async move {
294 let (header, payload) = if mask.select_all() {
295 raw_header_and_data.unwrap()
297 } else {
298 let filter_timer = metrics::HANDLE_BULK_INSERT_ELAPSED
299 .with_label_values(&["filter"])
300 .start_timer();
301 let batch = compute::filter_record_batch(&rb, mask.array())
302 .context(error::ComputeArrowSnafu)?;
303 filter_timer.observe_duration();
304 metrics::BULK_REQUEST_ROWS
305 .with_label_values(&["rows_per_region"])
306 .observe(batch.num_rows() as f64);
307
308 let encode_timer = metrics::HANDLE_BULK_INSERT_ELAPSED
309 .with_label_values(&["encode"])
310 .start_timer();
311 let mut iter = FlightEncoder::default()
312 .encode(FlightMessage::RecordBatch(batch))
313 .into_iter();
314 let Some(flight_data) = iter.next() else {
315 unreachable!()
318 };
319 ensure!(
320 iter.next().is_none(),
321 error::NotSupportedSnafu {
322 feat: "bulk insert RecordBatch with dictionary arrays",
323 }
324 );
325 encode_timer.observe_duration();
326 (flight_data.data_header, flight_data.data_body)
327 };
328 let request = RegionRequest {
329 header: Some(RegionRequestHeader {
330 tracing_context: TracingContext::from_current_span().to_w3c(),
331 ..Default::default()
332 }),
333 body: Some(region_request::Body::BulkInsert(BulkInsertRequest {
334 skip_wal,
335 region_id: region_id.as_u64(),
336 partition_expr_version: partition_expr_version
337 .map(|value| PartitionExprVersion { value }),
338 aligned_schema_version: None,
339 body: Some(bulk_insert_request::Body::ArrowIpc(ArrowIpc {
340 schema: schema_bytes,
341 data_header: header,
342 payload,
343 })),
344 })),
345 };
346
347 Ok(dispatcher
348 .send(
349 peer,
350 request,
351 Some(
352 &metrics::HANDLE_BULK_INSERT_ELAPSED
353 .with_label_values(&["datanode_handle"]),
354 ),
355 )
356 .await)
357 });
358 handles.push(handle);
359 }
360
361 let dispatcher = self.write_dispatcher.clone();
362 let rows_inserted = common_runtime::spawn_global(async move {
363 let results = future::try_join_all(handles)
364 .await
365 .context(error::JoinTaskSnafu)?;
366 let attempts = results.into_iter().collect::<Result<Vec<_>>>()?;
367 dispatcher
368 .finish(
369 attempts,
370 Some(
371 &metrics::HANDLE_BULK_INSERT_ELAPSED
372 .with_label_values(&["datanode_handle"]),
373 ),
374 Some(&cancellation),
375 )
376 .await
377 .context(error::RequestRegionSnafu)
378 .map(|response| response.affected_rows)
379 })
380 .await
381 .context(error::JoinTaskSnafu)??;
382 wait_all_datanode_timer.observe_duration();
383 crate::metrics::DIST_INGEST_ROW_COUNT
384 .with_label_values(&[db_name.as_str()])
385 .inc_by(rows_inserted as u64);
386 Ok(rows_inserted)
387 }
388
389 fn maybe_update_flow_dirty_window(&self, table_info: TableInfoRef, record_batch: RecordBatch) {
390 let table_id = table_info.table_id();
391 let table_flownode_set_cache = self.table_flownode_set_cache.clone();
392 let node_manager = self.node_manager.clone();
393 common_runtime::spawn_global(async move {
394 let result = table_flownode_set_cache
395 .get(table_id)
396 .await
397 .context(error::RequestInsertsSnafu);
398 let flownodes = match result {
399 Ok(flownodes) => flownodes.unwrap_or_default(),
400 Err(e) => {
401 error!(e; "Failed to get flownodes for table id: {}", table_id);
402 return;
403 }
404 };
405
406 let peers: HashSet<_> = flownodes.values().cloned().collect();
407 if peers.is_empty() {
408 return;
409 }
410
411 let Ok(timestamps) = insert::extract_timestamps(
412 &record_batch,
413 &table_info
414 .meta
415 .schema
416 .timestamp_column()
417 .as_ref()
418 .unwrap()
419 .name,
420 )
421 .inspect_err(|e| {
422 error!(e; "Failed to extract timestamps from record batch");
423 }) else {
424 return;
425 };
426
427 for peer in peers {
428 let node_manager = node_manager.clone();
429 let timestamps = timestamps.clone();
430 common_runtime::spawn_global(async move {
431 if let Err(e) = node_manager
432 .flownode(&peer)
433 .await
434 .handle_mark_window_dirty(DirtyWindowRequests {
435 requests: vec![DirtyWindowRequest {
436 table_id,
437 timestamps,
438 time_ranges: vec![],
439 }],
440 })
441 .await
442 .context(error::RequestInsertsSnafu)
443 {
444 error!(e; "Failed to mark timestamps as dirty, table: {}", table_id);
445 }
446 });
447 }
448 });
449 }
450}