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