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