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