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::sync::Arc;
17use std::time::Duration;
18
19use await_tree::InstrumentAwait;
20use itertools::Itertools;
21use risingwave_common::array::StreamChunk;
22use risingwave_common::bail;
23use risingwave_common::row::Row;
24use risingwave_common_rate_limit::RateLimiter;
25use risingwave_connector::error::ConnectorError;
26use risingwave_connector::source::{
27    BoxSourceChunkStream, BoxSourceReaderEventStream, BoxStreamingFileSourceChunkStream,
28    SourceColumnDesc, SourceReaderEvent, SplitId,
29};
30use risingwave_pb::plan_common::AdditionalColumn;
31use risingwave_pb::plan_common::additional_column::ColumnType;
32pub use state_table_handler::*;
33
34mod executor_core;
35pub use executor_core::StreamSourceCore;
36
37mod reader_stream;
38
39mod source_executor;
40pub use source_executor::*;
41mod dummy_source_executor;
42pub use dummy_source_executor::*;
43mod source_backfill_executor;
44pub use source_backfill_executor::*;
45mod fs_list_executor;
46pub use fs_list_executor::*;
47mod fs_fetch_executor;
48pub use fs_fetch_executor::*;
49mod iceberg_list_executor;
50pub use iceberg_list_executor::*;
51mod iceberg_fetch_executor;
52pub use iceberg_fetch_executor::*;
53mod iceberg_update;
54pub(crate) use iceberg_update::validate_update_state_table;
55pub use iceberg_update::{IcebergUpdateFetchExecutor, IcebergUpdateListExecutor};
56mod batch_source; // For refreshable batch source executors
57pub use batch_source::*;
58mod source_backfill_state_table;
59pub(crate) use source_backfill_state_table::BackfillStateTableHandler;
60
61pub mod state_table_handler;
62use futures_async_stream::try_stream;
63use risingwave_common::util::retry::exponential_backoff;
64use tokio::sync::mpsc::UnboundedReceiver;
65use tokio_retry::strategy::jitter;
66
67use crate::common::rate_limit::rate_limited_pieces;
68use crate::executor::error::StreamExecutorError;
69use crate::executor::{Barrier, Message};
70
71/// Receive barriers from barrier manager with the channel, error on channel close.
72#[try_stream(ok = Message, error = StreamExecutorError)]
73pub async fn barrier_to_message_stream(mut rx: UnboundedReceiver<Barrier>) {
74    while let Some(barrier) = rx.recv().instrument_await("receive_barrier").await {
75        yield Message::Barrier(barrier);
76    }
77    bail!("barrier reader closed unexpectedly");
78}
79
80pub fn get_split_offset_mapping_from_chunk(
81    chunk: &StreamChunk,
82    split_idx: usize,
83    offset_idx: usize,
84) -> Option<HashMap<SplitId, String>> {
85    let mut split_offset_mapping = HashMap::new();
86    // All rows (including those visible or invisible) will be used to update the source offset.
87    for i in 0..chunk.capacity() {
88        let (_, row, _) = chunk.row_at(i);
89        let split_id = row.datum_at(split_idx).unwrap().into_utf8().into();
90        let offset = row.datum_at(offset_idx).unwrap().into_utf8();
91        split_offset_mapping.insert(split_id, offset.to_owned());
92    }
93    Some(split_offset_mapping)
94}
95
96/// Get the indices of the split, offset, and pulsar message id columns.
97pub fn get_split_offset_col_idx(
98    column_descs: &[SourceColumnDesc],
99) -> (Option<usize>, Option<usize>, Option<usize>) {
100    let mut split_idx = None;
101    let mut offset_idx = None;
102    let mut pulsar_message_id_idx = None;
103    for (idx, column) in column_descs.iter().enumerate() {
104        match column.additional_column {
105            AdditionalColumn {
106                column_type: Some(ColumnType::Partition(_) | ColumnType::Filename(_)),
107            } => {
108                split_idx = Some(idx);
109            }
110            AdditionalColumn {
111                column_type: Some(ColumnType::Offset(_)),
112            } => {
113                offset_idx = Some(idx);
114            }
115            AdditionalColumn {
116                column_type: Some(ColumnType::PulsarMessageIdData(_)),
117            } => {
118                pulsar_message_id_idx = Some(idx);
119            }
120            _ => (),
121        }
122    }
123    (split_idx, offset_idx, pulsar_message_id_idx)
124}
125
126pub fn prune_additional_cols(
127    chunk: &StreamChunk,
128    to_prune_indices: &[usize],
129    column_descs: &[SourceColumnDesc],
130) -> StreamChunk {
131    chunk.project(
132        &(0..chunk.dimension())
133            .filter(|&idx| !to_prune_indices.contains(&idx) || column_descs[idx].is_visible())
134            .collect_vec(),
135    )
136}
137
138#[try_stream(ok = StreamChunk, error = ConnectorError)]
139pub async fn apply_rate_limit(stream: BoxSourceChunkStream, rate_limit_rps: Option<u32>) {
140    if rate_limit_rps == Some(0) {
141        // block the stream until the rate limit is reset
142        let future = futures::future::pending::<()>();
143        future.await;
144        unreachable!();
145    }
146
147    let limiter = RateLimiter::new(
148        rate_limit_rps
149            .inspect(|limit| tracing::info!(rate_limit = limit, "rate limit applied"))
150            .into(),
151    );
152
153    #[for_await]
154    for chunk in stream {
155        let chunk = chunk?;
156        yield process_chunk(chunk, rate_limit_rps, &limiter).await;
157    }
158}
159
160#[try_stream(ok = SourceReaderEvent, error = ConnectorError)]
161pub async fn apply_rate_limit_to_source_reader_event(
162    stream: BoxSourceReaderEventStream,
163    rate_limit_rps: Option<u32>,
164) {
165    if rate_limit_rps == Some(0) {
166        // block the stream until the rate limit is reset
167        let future = futures::future::pending::<()>();
168        future.await;
169        unreachable!();
170    }
171
172    let limiter = RateLimiter::new(
173        rate_limit_rps
174            .inspect(|limit| tracing::info!(rate_limit = limit, "rate limit applied"))
175            .into(),
176    );
177
178    #[for_await]
179    for event in stream {
180        match event? {
181            SourceReaderEvent::DataChunk(chunk) => {
182                yield SourceReaderEvent::DataChunk(
183                    process_chunk(chunk, rate_limit_rps, &limiter).await,
184                )
185            }
186            SourceReaderEvent::SplitProgress(progress) => {
187                yield SourceReaderEvent::SplitProgress(progress)
188            }
189        }
190    }
191}
192
193#[try_stream(ok = StreamChunk, error = ConnectorError)]
194pub async fn source_reader_event_to_chunk_stream(stream: BoxSourceReaderEventStream) {
195    #[for_await]
196    for event in stream {
197        match event? {
198            SourceReaderEvent::DataChunk(chunk) => yield chunk,
199            SourceReaderEvent::SplitProgress(_) => {}
200        }
201    }
202}
203
204/// Pace a file source reader with a limiter shared with its executor, so that a `Throttle`
205/// mutation also applies to the file that is already being read.
206#[try_stream(ok = Option<StreamChunk>, error = ConnectorError)]
207pub async fn apply_shared_rate_limit_to_file_source_reader(
208    stream: BoxStreamingFileSourceChunkStream,
209    limiter: Arc<RateLimiter>,
210) {
211    #[for_await]
212    for chunk in stream {
213        match chunk? {
214            Some(chunk) =>
215            {
216                #[for_await]
217                for piece in rate_limited_pieces(&limiter, chunk) {
218                    yield Some(piece);
219                }
220            }
221            None => yield None,
222        }
223    }
224}
225
226#[try_stream(ok = Option<StreamChunk>, error = ConnectorError)]
227pub async fn apply_rate_limit_with_for_streaming_file_source_reader(
228    stream: BoxStreamingFileSourceChunkStream,
229    rate_limit_rps: Option<u32>,
230) {
231    if rate_limit_rps == Some(0) {
232        // block the stream until the rate limit is reset
233        let future = futures::future::pending::<()>();
234        future.await;
235        unreachable!();
236    }
237
238    let limiter = RateLimiter::new(
239        rate_limit_rps
240            .inspect(|limit| tracing::info!(rate_limit = limit, "rate limit applied"))
241            .into(),
242    );
243
244    #[for_await]
245    for chunk in stream {
246        let chunk_option = chunk?;
247        match chunk_option {
248            Some(chunk) => {
249                let processed_chunk = process_chunk(chunk, rate_limit_rps, &limiter).await;
250                yield Some(processed_chunk);
251            }
252            None => yield None,
253        }
254    }
255}
256
257async fn process_chunk(
258    chunk: StreamChunk,
259    rate_limit_rps: Option<u32>,
260    limiter: &RateLimiter,
261) -> StreamChunk {
262    let chunk_size = chunk.capacity();
263
264    if rate_limit_rps.is_none() || chunk_size == 0 {
265        // no limit, or empty chunk
266        return chunk;
267    }
268
269    let limit = rate_limit_rps.unwrap() as u64;
270    let required_permits = chunk.rate_limit_permits();
271    if required_permits > limit {
272        // This should not happen after the mentioned PR.
273        tracing::error!(
274            chunk_size,
275            required_permits,
276            limit,
277            "unexpected large chunk size"
278        );
279    }
280
281    limiter.wait(required_permits).await;
282    chunk
283}
284
285pub fn get_infinite_backoff_strategy() -> impl Iterator<Item = Duration> {
286    const BASE_DELAY: Duration = Duration::from_secs(1);
287    const BACKOFF_FACTOR: u64 = 2;
288    const MAX_DELAY: Duration = Duration::from_secs(10);
289    exponential_backoff(BASE_DELAY, BACKOFF_FACTOR, MAX_DELAY).map(jitter)
290}