Skip to main content

risingwave_stream/executor/source/
mod.rs

1// Copyright 2022 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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; // For refreshable batch source executors
53pub 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/// Receive barriers from barrier manager with the channel, error on channel close.
67#[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    // All rows (including those visible or invisible) will be used to update the source offset.
82    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
91/// Get the indices of the split, offset, and pulsar message id columns.
92pub 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        // block the stream until the rate limit is reset
137        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        // block the stream until the rate limit is reset
162        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        // block the stream until the rate limit is reset
206        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        // no limit, or empty chunk
239        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        // This should not happen after the mentioned PR.
246        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}