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;
45use crate::{error, metrics};
46
47impl Inserter {
48 pub async fn flush_bulk_batch(
53 &self,
54 table_info: TableInfoRef,
55 batch: RecordBatch,
56 ctx: QueryContextRef,
57 ) -> Result<AffectedRows> {
58 let requests = TablePartitioner::try_new(&self.partition_manager, &table_info)
59 .await
60 .context(error::InvalidPartitionSnafu)?
61 .partition_bulk_insert_requests(batch, None, ctx.skip_wal())?;
62 let writes = requests
63 .into_iter()
64 .map(|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 .collect();
73 let writes = self.write_dispatcher.prepare(writes).await?;
74 Ok(self
75 .write_dispatcher
76 .execute(writes, None, None)
77 .await
78 .context(error::RequestInsertsSnafu)?
79 .affected_rows)
80 }
81
82 pub async fn handle_bulk_insert(
84 &self,
85 table: TableRef,
86 raw_flight_data: FlightData,
87 record_batch: RecordBatch,
88 schema_bytes: Bytes,
89 skip_wal: bool,
90 channel: Channel,
91 ) -> Result<AffectedRows> {
92 let table_info = table.table_info();
93 let table_id = table_info.table_id();
94 let db_name = table_info.get_db_string();
95
96 if record_batch.num_rows() == 0 {
97 return Ok(0);
98 }
99
100 for field in record_batch.schema_ref().fields() {
102 ensure!(
103 table_info
104 .meta
105 .schema
106 .column_schema_by_name(field.name())
107 .is_some(),
108 error::InvalidInsertRequestSnafu {
109 reason: format!(
110 "Column '{}' not found in table '{}'",
111 field.name(),
112 table_info.full_table_name()
113 ),
114 }
115 );
116 }
117
118 write_meter!(MeterRecord::new(
121 table_info.catalog_name.clone(),
122 table_info.schema_name.clone(),
123 0,
124 record_batch.num_rows() as u64,
125 channel as u8,
126 ))
127 .await
128 .context(error::WriteRejectedSnafu)?;
129
130 let body_size = raw_flight_data.data_body.len();
131 self.maybe_update_flow_dirty_window(table_info.clone(), record_batch.clone());
134
135 metrics::BULK_REQUEST_MESSAGE_SIZE.observe(body_size as f64);
136 metrics::BULK_REQUEST_ROWS
137 .with_label_values(&["raw"])
138 .observe(record_batch.num_rows() as f64);
139
140 let partition_timer = metrics::HANDLE_BULK_INSERT_ELAPSED
141 .with_label_values(&["partition"])
142 .start_timer();
143 let (partition_rule, partition_versions) = self
144 .partition_manager
145 .find_table_partition_rule(&table_info)
146 .await
147 .context(error::InvalidPartitionSnafu)?;
148
149 let region_masks = partition_rule
151 .split_record_batch(&record_batch)
152 .context(error::SplitInsertSnafu)?;
153 partition_timer.observe_duration();
154
155 if region_masks.len() == 1 {
157 metrics::BULK_REQUEST_ROWS
158 .with_label_values(&["rows_per_region"])
159 .observe(record_batch.num_rows() as f64);
160
161 let (region_number, _) = region_masks.into_iter().next().unwrap();
163 let region_id = RegionId::new(table_id, region_number);
164 let partition_expr_version = partition_versions
165 .get(®ion_number)
166 .copied()
167 .unwrap_or_default();
168 let request = RegionRequest {
169 header: Some(RegionRequestHeader {
170 tracing_context: TracingContext::from_current_span().to_w3c(),
171 ..Default::default()
172 }),
173 body: Some(region_request::Body::BulkInsert(BulkInsertRequest {
174 skip_wal,
175 region_id: region_id.as_u64(),
176 partition_expr_version: partition_expr_version
177 .map(|value| PartitionExprVersion { value }),
178 aligned_schema_version: None,
179 body: Some(bulk_insert_request::Body::ArrowIpc(ArrowIpc {
180 schema: schema_bytes.clone(),
181 data_header: raw_flight_data.data_header,
182 payload: raw_flight_data.data_body,
183 })),
184 })),
185 };
186
187 let writes = self.write_dispatcher.prepare(vec![request]).await?;
188 let result = self
189 .write_dispatcher
190 .execute(
191 writes,
192 Some(
193 metrics::HANDLE_BULK_INSERT_ELAPSED.with_label_values(&["datanode_handle"]),
194 ),
195 None,
196 )
197 .await
198 .context(error::RequestRegionSnafu)
199 .map(|r| r.affected_rows);
200 if let Ok(rows) = result {
201 crate::metrics::DIST_INGEST_ROW_COUNT
202 .with_label_values(&[db_name.as_str()])
203 .inc_by(rows as u64);
204 }
205 return result;
206 }
207
208 let regions = region_masks
209 .keys()
210 .map(|region| RegionId::new(table_id, *region))
211 .collect::<Vec<_>>();
212 let mut peers = self
213 .write_dispatcher
214 .resolve_peers(®ions)
215 .await
216 .context(error::FindRegionLeaderSnafu)?;
217
218 let wait_all_datanode_timer = metrics::HANDLE_BULK_INSERT_ELAPSED
219 .with_label_values(&["wait_all_datanode"])
220 .start_timer();
221
222 let cancellation = CancellationToken::new();
224 let _guard = cancellation.clone().drop_guard();
225 let mut handles = Vec::with_capacity(region_masks.len());
226
227 for (region_number, mask) in region_masks {
228 let region_id = RegionId::new(table_id, region_number);
229 if mask.select_none() {
230 continue;
231 }
232 let partition_expr_version = partition_versions
233 .get(®ion_id.region_number())
234 .copied()
235 .unwrap_or_default();
236 let peer = peers
237 .remove(®ion_id)
238 .context(partition::error::FindLeaderSnafu {
239 region_id,
240 table_id,
241 })
242 .context(error::FindRegionLeaderSnafu)?;
243 let dispatcher = self.write_dispatcher.clone();
244 let rb = record_batch.clone();
245 let schema_bytes = schema_bytes.clone();
246 let raw_header_and_data = if mask.select_all() {
247 Some((
248 raw_flight_data.data_header.clone(),
249 raw_flight_data.data_body.clone(),
250 ))
251 } else {
252 None
253 };
254 let handle: common_runtime::JoinHandle<Result<WriteAttempt>> =
255 common_runtime::spawn_global(async move {
256 let (header, payload) = if mask.select_all() {
257 raw_header_and_data.unwrap()
259 } else {
260 let filter_timer = metrics::HANDLE_BULK_INSERT_ELAPSED
261 .with_label_values(&["filter"])
262 .start_timer();
263 let batch = compute::filter_record_batch(&rb, mask.array())
264 .context(error::ComputeArrowSnafu)?;
265 filter_timer.observe_duration();
266 metrics::BULK_REQUEST_ROWS
267 .with_label_values(&["rows_per_region"])
268 .observe(batch.num_rows() as f64);
269
270 let encode_timer = metrics::HANDLE_BULK_INSERT_ELAPSED
271 .with_label_values(&["encode"])
272 .start_timer();
273 let mut iter = FlightEncoder::default()
274 .encode(FlightMessage::RecordBatch(batch))
275 .into_iter();
276 let Some(flight_data) = iter.next() else {
277 unreachable!()
280 };
281 ensure!(
282 iter.next().is_none(),
283 error::NotSupportedSnafu {
284 feat: "bulk insert RecordBatch with dictionary arrays",
285 }
286 );
287 encode_timer.observe_duration();
288 (flight_data.data_header, flight_data.data_body)
289 };
290 let request = RegionRequest {
291 header: Some(RegionRequestHeader {
292 tracing_context: TracingContext::from_current_span().to_w3c(),
293 ..Default::default()
294 }),
295 body: Some(region_request::Body::BulkInsert(BulkInsertRequest {
296 skip_wal,
297 region_id: region_id.as_u64(),
298 partition_expr_version: partition_expr_version
299 .map(|value| PartitionExprVersion { value }),
300 aligned_schema_version: None,
301 body: Some(bulk_insert_request::Body::ArrowIpc(ArrowIpc {
302 schema: schema_bytes,
303 data_header: header,
304 payload,
305 })),
306 })),
307 };
308
309 Ok(dispatcher
310 .send(
311 peer,
312 request,
313 Some(
314 &metrics::HANDLE_BULK_INSERT_ELAPSED
315 .with_label_values(&["datanode_handle"]),
316 ),
317 )
318 .await)
319 });
320 handles.push(handle);
321 }
322
323 let dispatcher = self.write_dispatcher.clone();
324 let rows_inserted = common_runtime::spawn_global(async move {
325 let results = future::try_join_all(handles)
326 .await
327 .context(error::JoinTaskSnafu)?;
328 let attempts = results.into_iter().collect::<Result<Vec<_>>>()?;
329 dispatcher
330 .finish(
331 attempts,
332 Some(
333 &metrics::HANDLE_BULK_INSERT_ELAPSED
334 .with_label_values(&["datanode_handle"]),
335 ),
336 Some(&cancellation),
337 )
338 .await
339 .context(error::RequestRegionSnafu)
340 .map(|response| response.affected_rows)
341 })
342 .await
343 .context(error::JoinTaskSnafu)??;
344 wait_all_datanode_timer.observe_duration();
345 crate::metrics::DIST_INGEST_ROW_COUNT
346 .with_label_values(&[db_name.as_str()])
347 .inc_by(rows_inserted as u64);
348 Ok(rows_inserted)
349 }
350
351 fn maybe_update_flow_dirty_window(&self, table_info: TableInfoRef, record_batch: RecordBatch) {
352 let table_id = table_info.table_id();
353 let table_flownode_set_cache = self.table_flownode_set_cache.clone();
354 let node_manager = self.node_manager.clone();
355 common_runtime::spawn_global(async move {
356 let result = table_flownode_set_cache
357 .get(table_id)
358 .await
359 .context(error::RequestInsertsSnafu);
360 let flownodes = match result {
361 Ok(flownodes) => flownodes.unwrap_or_default(),
362 Err(e) => {
363 error!(e; "Failed to get flownodes for table id: {}", table_id);
364 return;
365 }
366 };
367
368 let peers: HashSet<_> = flownodes.values().cloned().collect();
369 if peers.is_empty() {
370 return;
371 }
372
373 let Ok(timestamps) = insert::extract_timestamps(
374 &record_batch,
375 &table_info
376 .meta
377 .schema
378 .timestamp_column()
379 .as_ref()
380 .unwrap()
381 .name,
382 )
383 .inspect_err(|e| {
384 error!(e; "Failed to extract timestamps from record batch");
385 }) else {
386 return;
387 };
388
389 for peer in peers {
390 let node_manager = node_manager.clone();
391 let timestamps = timestamps.clone();
392 common_runtime::spawn_global(async move {
393 if let Err(e) = node_manager
394 .flownode(&peer)
395 .await
396 .handle_mark_window_dirty(DirtyWindowRequests {
397 requests: vec![DirtyWindowRequest {
398 table_id,
399 timestamps,
400 time_ranges: vec![],
401 }],
402 })
403 .await
404 .context(error::RequestInsertsSnafu)
405 {
406 error!(e; "Failed to mark timestamps as dirty, table: {}", table_id);
407 }
408 });
409 }
410 });
411 }
412}