risingwave_stream/executor/backfill/cdc/upstream_table/
snapshot.rs1use std::future::Future;
16
17use futures::{Stream, pin_mut};
18use futures_async_stream::try_stream;
19use risingwave_common::array::StreamChunk;
20use risingwave_common::catalog::{ColumnDesc, Field};
21use risingwave_common::row::OwnedRow;
22use risingwave_common::types::{Scalar, ScalarImpl, Timestamptz};
23use risingwave_common::util::chunk_coalesce::DataChunkBuilder;
24use risingwave_common_rate_limit::RateLimiter;
25use risingwave_connector::source::cdc::external::{
26 CdcOffset, ExternalTableReader, ExternalTableReaderImpl, SchemaTableName,
27};
28use risingwave_pb::plan_common::additional_column::ColumnType;
29
30use super::external::ExternalStorageTable;
31use crate::common::rate_limit::limited_chunk_size;
32use crate::executor::backfill::utils::{get_new_pos, iter_chunks};
33use crate::executor::{StreamExecutorError, StreamExecutorResult};
34
35pub trait UpstreamTableRead {
36 fn snapshot_read_full_table(
37 &self,
38 args: SnapshotReadArgs,
39 batch_size: u32,
40 ) -> impl Stream<Item = StreamExecutorResult<Option<StreamChunk>>> + Send + '_;
41
42 fn current_cdc_offset(
43 &self,
44 ) -> impl Future<Output = StreamExecutorResult<Option<CdcOffset>>> + Send + '_;
45
46 async fn disconnect(self) -> StreamExecutorResult<()>;
47
48 fn snapshot_read_table_split(
49 &self,
50 args: SplitSnapshotReadArgs,
51 ) -> impl Stream<Item = StreamExecutorResult<Option<StreamChunk>>> + Send + '_;
52}
53
54#[derive(Debug, Clone)]
55pub struct SnapshotReadArgs {
56 pub current_pos: Option<OwnedRow>,
57 pub rate_limit_rps: Option<u32>,
58 pub pk_indices: Vec<usize>,
59 pub additional_columns: Vec<ColumnDesc>,
60 pub schema_table_name: SchemaTableName,
61 pub database_name: String,
62}
63
64impl SnapshotReadArgs {
65 pub fn new(
66 current_pos: Option<OwnedRow>,
67 rate_limit_rps: Option<u32>,
68 pk_indices: Vec<usize>,
69 additional_columns: Vec<ColumnDesc>,
70 schema_table_name: SchemaTableName,
71 database_name: String,
72 ) -> Self {
73 Self {
74 current_pos,
75 rate_limit_rps,
76 pk_indices,
77 additional_columns,
78 schema_table_name,
79 database_name,
80 }
81 }
82}
83
84#[derive(Debug, Clone)]
85pub struct SplitSnapshotReadArgs {
86 pub left_bound_inclusive: OwnedRow,
87 pub right_bound_exclusive: OwnedRow,
88 pub split_columns: Vec<Field>,
89 pub rate_limit_rps: Option<u32>,
90 pub additional_columns: Vec<ColumnDesc>,
91 pub schema_table_name: SchemaTableName,
92 pub database_name: String,
93}
94
95impl SplitSnapshotReadArgs {
96 pub fn new(
97 left_bound_inclusive: OwnedRow,
98 right_bound_exclusive: OwnedRow,
99 split_columns: Vec<Field>,
100 rate_limit_rps: Option<u32>,
101 additional_columns: Vec<ColumnDesc>,
102 schema_table_name: SchemaTableName,
103 database_name: String,
104 ) -> Self {
105 Self {
106 left_bound_inclusive,
107 right_bound_exclusive,
108 split_columns,
109 rate_limit_rps,
110 additional_columns,
111 schema_table_name,
112 database_name,
113 }
114 }
115}
116
117pub struct UpstreamTableReader<T> {
121 table: T,
122 pub(crate) reader: ExternalTableReaderImpl,
123}
124
125impl<T> UpstreamTableReader<T> {
126 pub fn new(table: T, reader: ExternalTableReaderImpl) -> Self {
127 Self { table, reader }
128 }
129}
130
131fn with_additional_columns(
133 snapshot_chunk: StreamChunk,
134 additional_columns: &[ColumnDesc],
135 schema_table_name: SchemaTableName,
136 database_name: String,
137) -> StreamChunk {
138 let (ops, mut columns, visibility) = snapshot_chunk.into_inner();
139 for desc in additional_columns {
140 let mut builder = desc.data_type.create_array_builder(visibility.len());
141 match *desc.additional_column.column_type.as_ref().unwrap() {
142 ColumnType::Timestamp(_) => builder.append_n(
144 visibility.len(),
145 Some(Timestamptz::default().to_scalar_value()),
146 ),
147 ColumnType::DatabaseName(_) => {
148 builder.append_n(
149 visibility.len(),
150 Some(ScalarImpl::from(database_name.clone())),
151 );
152 }
153 ColumnType::SchemaName(_) => {
154 builder.append_n(
155 visibility.len(),
156 Some(ScalarImpl::from(schema_table_name.schema_name.clone())),
157 );
158 }
159 ColumnType::TableName(_) => {
160 builder.append_n(
161 visibility.len(),
162 Some(ScalarImpl::from(schema_table_name.table_name.clone())),
163 );
164 }
165 _ => {
167 builder.append_n_null(visibility.len());
168 }
169 }
170 columns.push(builder.finish().into());
171 }
172 StreamChunk::with_visibility(ops, columns, visibility)
173}
174
175impl UpstreamTableRead for UpstreamTableReader<ExternalStorageTable> {
176 #[try_stream(ok = Option<StreamChunk>, error = StreamExecutorError)]
177 async fn snapshot_read_full_table(&self, args: SnapshotReadArgs, batch_size: u32) {
178 let primary_keys = self.table.pk_names();
179
180 if args.rate_limit_rps == Some(0) {
182 let future = futures::future::pending::<()>();
185 future.await;
186 unreachable!();
187 }
188
189 let rate_limiter = RateLimiter::new(
190 args.rate_limit_rps
191 .inspect(|limit| tracing::info!(rate_limit = limit, "rate limit applied"))
192 .into(),
193 );
194
195 let mut read_args = args;
196 let schema_table_name = read_args.schema_table_name.clone();
197 let database_name = read_args.database_name.clone();
198 loop {
200 tracing::debug!(
201 "snapshot_read primary keys: {:?}, current_pos: {:?}",
202 primary_keys,
203 read_args.current_pos
204 );
205
206 let mut read_count: usize = 0;
207 let row_stream = self.reader.snapshot_read(
208 self.table.schema_table_name(),
209 read_args.current_pos.clone(),
210 primary_keys.clone(),
211 batch_size,
212 );
213
214 pin_mut!(row_stream);
215 let mut builder = DataChunkBuilder::new(
216 self.table.schema().data_types(),
217 limited_chunk_size(read_args.rate_limit_rps),
218 );
219 let chunk_stream = iter_chunks(row_stream, &mut builder);
220 let mut current_pk_pos = read_args.current_pos.clone().unwrap_or_default();
221
222 #[for_await]
223 for chunk in chunk_stream {
224 let chunk = chunk?;
225 let chunk_size = chunk.capacity();
226 read_count += chunk.cardinality();
227 current_pk_pos = get_new_pos(&chunk, &read_args.pk_indices);
228
229 if let Some(rate_limit_rps) = read_args.rate_limit_rps
230 && chunk_size != 0
231 {
232 let limit = rate_limit_rps as usize;
235
236 assert!(chunk_size <= limit);
239
240 rate_limiter.wait(chunk_size as _).await;
242 yield Some(with_additional_columns(
243 chunk,
244 &read_args.additional_columns,
245 schema_table_name.clone(),
246 database_name.clone(),
247 ));
248 } else {
249 yield Some(with_additional_columns(
251 chunk,
252 &read_args.additional_columns,
253 schema_table_name.clone(),
254 database_name.clone(),
255 ));
256 continue;
257 }
258 }
259
260 if read_count < batch_size as _ {
262 tracing::debug!("finished loading of full table snapshot");
263 yield None;
264 unreachable!()
265 } else {
266 read_args.current_pos = Some(current_pk_pos);
268 }
269 }
270 }
271
272 #[try_stream(ok = Option<StreamChunk>, error = StreamExecutorError)]
273 async fn snapshot_read_table_split(&self, args: SplitSnapshotReadArgs) {
274 if args.rate_limit_rps == Some(0) {
276 let future = futures::future::pending::<()>();
279 future.await;
280 unreachable!();
281 }
282
283 let rate_limiter = RateLimiter::new(
284 args.rate_limit_rps
285 .inspect(|limit| tracing::info!(rate_limit = limit, "rate limit applied"))
286 .into(),
287 );
288
289 let read_args = args;
290 let schema_table_name = read_args.schema_table_name.clone();
291 let database_name = read_args.database_name.clone();
292 let row_stream = self.reader.split_snapshot_read(
295 self.table.schema_table_name(),
296 read_args.left_bound_inclusive.clone(),
297 read_args.right_bound_exclusive.clone(),
298 read_args.split_columns.clone(),
299 );
300
301 pin_mut!(row_stream);
302 let mut builder = DataChunkBuilder::new(
303 self.table.schema().data_types(),
304 limited_chunk_size(read_args.rate_limit_rps),
305 );
306 let chunk_stream = iter_chunks(row_stream, &mut builder);
307
308 #[for_await]
309 for chunk in chunk_stream {
310 let chunk = chunk?;
311 let chunk_size = chunk.capacity();
312
313 if let Some(rate_limit_rps) = read_args.rate_limit_rps
314 && chunk_size != 0
315 {
316 let limit = rate_limit_rps as usize;
319
320 assert!(chunk_size <= limit);
323
324 rate_limiter.wait(chunk_size as _).await;
326 yield Some(with_additional_columns(
327 chunk,
328 &read_args.additional_columns,
329 schema_table_name.clone(),
330 database_name.clone(),
331 ));
332 } else {
333 yield Some(with_additional_columns(
335 chunk,
336 &read_args.additional_columns,
337 schema_table_name.clone(),
338 database_name.clone(),
339 ));
340 }
341 }
342 yield None;
343 }
344
345 async fn current_cdc_offset(&self) -> StreamExecutorResult<Option<CdcOffset>> {
346 let binlog = self.reader.current_cdc_offset();
347 let binlog = binlog.await?;
348 Ok(Some(binlog))
349 }
350
351 async fn disconnect(self) -> StreamExecutorResult<()> {
352 self.reader.disconnect().await?;
353 Ok(())
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use std::collections::HashMap;
360
361 use futures::pin_mut;
362 use futures_async_stream::for_await;
363 use maplit::{convert_args, hashmap};
364 use risingwave_common::catalog::{ColumnDesc, ColumnId, Field, Schema};
365 use risingwave_common::row::OwnedRow;
366 use risingwave_common::types::{DataType, ScalarImpl};
367 use risingwave_common::util::chunk_coalesce::DataChunkBuilder;
368 use risingwave_connector::source::cdc::external::mysql::MySqlExternalTableReader;
369 use risingwave_connector::source::cdc::external::{
370 ExternalTableConfig, ExternalTableReader, SchemaTableName,
371 };
372
373 use crate::executor::backfill::utils::{get_new_pos, iter_chunks};
374
375 #[ignore]
376 #[tokio::test]
377 async fn test_mysql_table_reader() {
378 let columns = [
379 ColumnDesc::named("o_orderkey", ColumnId::new(1), DataType::Int64),
380 ColumnDesc::named("o_custkey", ColumnId::new(2), DataType::Int64),
381 ColumnDesc::named("o_orderstatus", ColumnId::new(3), DataType::Varchar),
382 ];
383 let rw_schema = Schema {
384 fields: columns.iter().map(Field::from).collect(),
385 };
386 let props: HashMap<String, String> = convert_args!(hashmap!(
387 "hostname" => "localhost",
388 "port" => "8306",
389 "username" => "root",
390 "password" => "123456",
391 "database.name" => "mydb",
392 "table.name" => "orders_rw"));
393
394 let config =
395 serde_json::from_value::<ExternalTableConfig>(serde_json::to_value(props).unwrap())
396 .unwrap();
397 let reader = MySqlExternalTableReader::new(config, rw_schema.clone(), vec![0])
398 .await
399 .unwrap();
400
401 let mut cnt: usize = 0;
402 let mut start_pk = Some(OwnedRow::new(vec![Some(ScalarImpl::Int64(0))]));
403 loop {
404 let row_stream = reader.snapshot_read(
405 SchemaTableName {
406 schema_name: "mydb".to_owned(),
407 table_name: "orders_rw".to_owned(),
408 },
409 start_pk.clone(),
410 vec!["o_orderkey".to_owned()],
411 1000,
412 );
413 let mut builder = DataChunkBuilder::new(rw_schema.clone().data_types(), 256);
414 let chunk_stream = iter_chunks(row_stream, &mut builder);
415 let pk_indices = vec![0];
416 pin_mut!(chunk_stream);
417 #[for_await]
418 for chunk in chunk_stream {
419 let chunk = chunk.expect("data");
420 start_pk = Some(get_new_pos(&chunk, &pk_indices));
421 cnt += chunk.capacity();
422 println!("cnt: {}", cnt);
424 }
425 if cnt >= 1499900 {
426 println!("bye!");
427 break;
428 }
429 }
430 }
431}