risingwave_stream/executor/source/
mod.rs1use std::collections::HashMap;
16use std::time::Duration;
17
18use await_tree::InstrumentAwait;
19use itertools::Itertools;
20use risingwave_common::array::StreamChunk;
21use risingwave_common::bail;
22use risingwave_common::row::Row;
23use risingwave_common_rate_limit::RateLimiter;
24use risingwave_connector::error::ConnectorError;
25use risingwave_connector::source::{
26 BoxSourceChunkStream, BoxSourceReaderEventStream, BoxStreamingFileSourceChunkStream,
27 SourceColumnDesc, SourceReaderEvent, SplitId,
28};
29use risingwave_pb::plan_common::AdditionalColumn;
30use risingwave_pb::plan_common::additional_column::ColumnType;
31pub use state_table_handler::*;
32
33mod executor_core;
34pub use executor_core::StreamSourceCore;
35
36mod reader_stream;
37
38mod source_executor;
39pub use source_executor::*;
40mod dummy_source_executor;
41pub use dummy_source_executor::*;
42mod source_backfill_executor;
43pub use source_backfill_executor::*;
44mod fs_list_executor;
45pub use fs_list_executor::*;
46mod fs_fetch_executor;
47pub use fs_fetch_executor::*;
48mod iceberg_list_executor;
49pub use iceberg_list_executor::*;
50mod iceberg_fetch_executor;
51pub use iceberg_fetch_executor::*;
52mod batch_source; pub use batch_source::*;
54mod source_backfill_state_table;
55pub(crate) use source_backfill_state_table::BackfillStateTableHandler;
56
57pub mod state_table_handler;
58use futures_async_stream::try_stream;
59use risingwave_common::util::retry::exponential_backoff;
60use tokio::sync::mpsc::UnboundedReceiver;
61use tokio_retry::strategy::jitter;
62
63use crate::executor::error::StreamExecutorError;
64use crate::executor::{Barrier, Message};
65
66#[try_stream(ok = Message, error = StreamExecutorError)]
68pub async fn barrier_to_message_stream(mut rx: UnboundedReceiver<Barrier>) {
69 while let Some(barrier) = rx.recv().instrument_await("receive_barrier").await {
70 yield Message::Barrier(barrier);
71 }
72 bail!("barrier reader closed unexpectedly");
73}
74
75pub fn get_split_offset_mapping_from_chunk(
76 chunk: &StreamChunk,
77 split_idx: usize,
78 offset_idx: usize,
79) -> Option<HashMap<SplitId, String>> {
80 let mut split_offset_mapping = HashMap::new();
81 for i in 0..chunk.capacity() {
83 let (_, row, _) = chunk.row_at(i);
84 let split_id = row.datum_at(split_idx).unwrap().into_utf8().into();
85 let offset = row.datum_at(offset_idx).unwrap().into_utf8();
86 split_offset_mapping.insert(split_id, offset.to_owned());
87 }
88 Some(split_offset_mapping)
89}
90
91pub fn get_split_offset_col_idx(
93 column_descs: &[SourceColumnDesc],
94) -> (Option<usize>, Option<usize>, Option<usize>) {
95 let mut split_idx = None;
96 let mut offset_idx = None;
97 let mut pulsar_message_id_idx = None;
98 for (idx, column) in column_descs.iter().enumerate() {
99 match column.additional_column {
100 AdditionalColumn {
101 column_type: Some(ColumnType::Partition(_) | ColumnType::Filename(_)),
102 } => {
103 split_idx = Some(idx);
104 }
105 AdditionalColumn {
106 column_type: Some(ColumnType::Offset(_)),
107 } => {
108 offset_idx = Some(idx);
109 }
110 AdditionalColumn {
111 column_type: Some(ColumnType::PulsarMessageIdData(_)),
112 } => {
113 pulsar_message_id_idx = Some(idx);
114 }
115 _ => (),
116 }
117 }
118 (split_idx, offset_idx, pulsar_message_id_idx)
119}
120
121pub fn prune_additional_cols(
122 chunk: &StreamChunk,
123 to_prune_indices: &[usize],
124 column_descs: &[SourceColumnDesc],
125) -> StreamChunk {
126 chunk.project(
127 &(0..chunk.dimension())
128 .filter(|&idx| !to_prune_indices.contains(&idx) || column_descs[idx].is_visible())
129 .collect_vec(),
130 )
131}
132
133#[try_stream(ok = StreamChunk, error = ConnectorError)]
134pub async fn apply_rate_limit(stream: BoxSourceChunkStream, rate_limit_rps: Option<u32>) {
135 if rate_limit_rps == Some(0) {
136 let future = futures::future::pending::<()>();
138 future.await;
139 unreachable!();
140 }
141
142 let limiter = RateLimiter::new(
143 rate_limit_rps
144 .inspect(|limit| tracing::info!(rate_limit = limit, "rate limit applied"))
145 .into(),
146 );
147
148 #[for_await]
149 for chunk in stream {
150 let chunk = chunk?;
151 yield process_chunk(chunk, rate_limit_rps, &limiter).await;
152 }
153}
154
155#[try_stream(ok = SourceReaderEvent, error = ConnectorError)]
156pub async fn apply_rate_limit_to_source_reader_event(
157 stream: BoxSourceReaderEventStream,
158 rate_limit_rps: Option<u32>,
159) {
160 if rate_limit_rps == Some(0) {
161 let future = futures::future::pending::<()>();
163 future.await;
164 unreachable!();
165 }
166
167 let limiter = RateLimiter::new(
168 rate_limit_rps
169 .inspect(|limit| tracing::info!(rate_limit = limit, "rate limit applied"))
170 .into(),
171 );
172
173 #[for_await]
174 for event in stream {
175 match event? {
176 SourceReaderEvent::DataChunk(chunk) => {
177 yield SourceReaderEvent::DataChunk(
178 process_chunk(chunk, rate_limit_rps, &limiter).await,
179 )
180 }
181 SourceReaderEvent::SplitProgress(progress) => {
182 yield SourceReaderEvent::SplitProgress(progress)
183 }
184 }
185 }
186}
187
188#[try_stream(ok = StreamChunk, error = ConnectorError)]
189pub async fn source_reader_event_to_chunk_stream(stream: BoxSourceReaderEventStream) {
190 #[for_await]
191 for event in stream {
192 match event? {
193 SourceReaderEvent::DataChunk(chunk) => yield chunk,
194 SourceReaderEvent::SplitProgress(_) => {}
195 }
196 }
197}
198
199#[try_stream(ok = Option<StreamChunk>, error = ConnectorError)]
200pub async fn apply_rate_limit_with_for_streaming_file_source_reader(
201 stream: BoxStreamingFileSourceChunkStream,
202 rate_limit_rps: Option<u32>,
203) {
204 if rate_limit_rps == Some(0) {
205 let future = futures::future::pending::<()>();
207 future.await;
208 unreachable!();
209 }
210
211 let limiter = RateLimiter::new(
212 rate_limit_rps
213 .inspect(|limit| tracing::info!(rate_limit = limit, "rate limit applied"))
214 .into(),
215 );
216
217 #[for_await]
218 for chunk in stream {
219 let chunk_option = chunk?;
220 match chunk_option {
221 Some(chunk) => {
222 let processed_chunk = process_chunk(chunk, rate_limit_rps, &limiter).await;
223 yield Some(processed_chunk);
224 }
225 None => yield None,
226 }
227 }
228}
229
230async fn process_chunk(
231 chunk: StreamChunk,
232 rate_limit_rps: Option<u32>,
233 limiter: &RateLimiter,
234) -> StreamChunk {
235 let chunk_size = chunk.capacity();
236
237 if rate_limit_rps.is_none() || chunk_size == 0 {
238 return chunk;
240 }
241
242 let limit = rate_limit_rps.unwrap() as u64;
243 let required_permits = chunk.rate_limit_permits();
244 if required_permits > limit {
245 tracing::error!(
247 chunk_size,
248 required_permits,
249 limit,
250 "unexpected large chunk size"
251 );
252 }
253
254 limiter.wait(required_permits).await;
255 chunk
256}
257
258pub fn get_infinite_backoff_strategy() -> impl Iterator<Item = Duration> {
259 const BASE_DELAY: Duration = Duration::from_secs(1);
260 const BACKOFF_FACTOR: u64 = 2;
261 const MAX_DELAY: Duration = Duration::from_secs(10);
262 exponential_backoff(BASE_DELAY, BACKOFF_FACTOR, MAX_DELAY).map(jitter)
263}