risingwave_connector/sink/
writer.rs1use std::future::{Future, Ready};
16use std::pin::pin;
17use std::time::Instant;
18
19use async_trait::async_trait;
20use await_tree::InstrumentAwait;
21use futures::TryFuture;
22use futures::future::{Either, select};
23use risingwave_common::array::StreamChunk;
24use rw_futures_util::drop_either_future;
25
26use crate::sink::encoder::SerTo;
27use crate::sink::formatter::SinkFormatter;
28use crate::sink::log_store::{
29 DeliveryFutureManager, DeliveryFutureManagerAddFuture, LogStoreReadItem, TruncateOffset,
30};
31use crate::sink::{LogSinker, Result, SinkError, SinkLogReader, SinkWriterMetrics};
32
33#[async_trait]
34pub trait SinkWriter: Send + 'static {
35 type CommitMetadata: Send = ();
36 async fn begin_epoch(&mut self, epoch: u64) -> Result<()>;
38
39 async fn write_batch(&mut self, chunk: StreamChunk) -> Result<()>;
41
42 async fn barrier(&mut self, is_checkpoint: bool) -> Result<Self::CommitMetadata>;
45
46 async fn abort(&mut self) -> Result<()> {
48 Ok(())
49 }
50}
51
52pub type DummyDeliveryFuture = Ready<std::result::Result<(), SinkError>>;
53
54pub trait AsyncTruncateSinkWriter: Send + 'static {
55 type DeliveryFuture: TryFuture<Ok = (), Error = SinkError> + Unpin + Send + 'static =
56 DummyDeliveryFuture;
57
58 fn write_chunk<'a>(
59 &'a mut self,
60 chunk: StreamChunk,
61 add_future: DeliveryFutureManagerAddFuture<'a, Self::DeliveryFuture>,
62 ) -> impl Future<Output = Result<()>> + Send + 'a;
63
64 fn barrier(&mut self, _is_checkpoint: bool) -> impl Future<Output = Result<()>> + Send + '_ {
65 async { Ok(()) }
66 }
67}
68
69pub trait FormattedSink {
80 type K;
81 type V;
82 async fn write_one(&mut self, k: Option<Self::K>, v: Option<Self::V>) -> Result<()>;
83
84 async fn write_chunk<F: SinkFormatter>(
85 &mut self,
86 chunk: StreamChunk,
87 formatter: &F,
88 ) -> Result<()>
89 where
90 F::K: SerTo<Self::K>,
91 F::V: SerTo<Self::V>,
92 {
93 for r in formatter.format_chunk(&chunk) {
94 let (event_key_object, event_object) = r?;
95
96 self.write_one(
97 event_key_object.map(SerTo::ser_to).transpose()?,
98 event_object.map(SerTo::ser_to).transpose()?,
99 )
100 .await?;
101 }
102
103 Ok(())
104 }
105}
106
107pub struct LogSinkerOf<W> {
108 writer: W,
109 sink_writer_metrics: SinkWriterMetrics,
110}
111
112impl<W> LogSinkerOf<W> {
113 pub fn new(writer: W, sink_writer_metrics: SinkWriterMetrics) -> Self {
114 LogSinkerOf {
115 writer,
116 sink_writer_metrics,
117 }
118 }
119}
120
121#[async_trait]
122impl<W: SinkWriter<CommitMetadata = ()>> LogSinker for LogSinkerOf<W> {
123 async fn consume_log_and_sink(self, mut log_reader: impl SinkLogReader) -> Result<!> {
124 log_reader.start_from(None).await?;
125 let mut sink_writer = self.writer;
126 let metrics = self.sink_writer_metrics;
127 #[derive(Debug)]
128 enum LogConsumerState {
129 Uninitialized,
131
132 EpochBegun { curr_epoch: u64 },
134
135 BarrierReceived { prev_epoch: u64 },
137 }
138
139 let mut state = LogConsumerState::Uninitialized;
140
141 loop {
142 let (epoch, item): (u64, LogStoreReadItem) = log_reader.next_item().await?;
143 state = match state {
145 LogConsumerState::Uninitialized => {
146 sink_writer
147 .begin_epoch(epoch)
148 .instrument_await(await_tree::span!("sink_begin_epoch epoch={epoch}"))
149 .await?;
150 LogConsumerState::EpochBegun { curr_epoch: epoch }
151 }
152 LogConsumerState::EpochBegun { curr_epoch } => {
153 assert!(
154 epoch >= curr_epoch,
155 "new epoch {} should not be below the current epoch {}",
156 epoch,
157 curr_epoch
158 );
159 LogConsumerState::EpochBegun { curr_epoch: epoch }
160 }
161 LogConsumerState::BarrierReceived { prev_epoch } => {
162 assert!(
163 epoch > prev_epoch,
164 "new epoch {} should be greater than prev epoch {}",
165 epoch,
166 prev_epoch
167 );
168 sink_writer
169 .begin_epoch(epoch)
170 .instrument_await(await_tree::span!("sink_begin_epoch epoch={epoch}"))
171 .await?;
172 LogConsumerState::EpochBegun { curr_epoch: epoch }
173 }
174 };
175 match item {
176 LogStoreReadItem::StreamChunk { chunk, .. } => {
177 if let Err(e) = sink_writer
178 .write_batch(chunk)
179 .instrument_await(await_tree::span!("sink_write_batch").verbose())
180 .await
181 {
182 sink_writer.abort().instrument_await("sink_abort").await?;
183 return Err(e);
184 }
185 }
186 LogStoreReadItem::Barrier {
187 is_checkpoint,
188 new_vnode_bitmap,
189 ..
190 } => {
191 let prev_epoch = match state {
192 LogConsumerState::EpochBegun { curr_epoch } => curr_epoch,
193 _ => unreachable!("epoch must have begun before handling barrier"),
194 };
195 if is_checkpoint {
196 let start_time = Instant::now();
197 sink_writer
198 .barrier(true)
199 .instrument_await(await_tree::span!(
200 "sink_barrier checkpoint=true epoch={epoch}"
201 ))
202 .await?;
203 metrics
204 .sink_commit_duration
205 .observe(start_time.elapsed().as_secs_f64());
206 log_reader.truncate(TruncateOffset::Barrier { epoch })?;
207 } else {
208 assert!(new_vnode_bitmap.is_none());
209 sink_writer
210 .barrier(false)
211 .instrument_await(await_tree::span!(
212 "sink_barrier checkpoint=false epoch={epoch}"
213 ))
214 .await?;
215 }
216 state = LogConsumerState::BarrierReceived { prev_epoch }
217 }
218 }
219 }
220 }
221}
222
223#[easy_ext::ext(SinkWriterExt)]
224impl<T> T
225where
226 T: SinkWriter<CommitMetadata = ()> + Sized,
227{
228 pub fn into_log_sinker(self, sink_writer_metrics: SinkWriterMetrics) -> LogSinkerOf<Self> {
229 LogSinkerOf {
230 writer: self,
231 sink_writer_metrics,
232 }
233 }
234}
235
236pub struct AsyncTruncateLogSinkerOf<W: AsyncTruncateSinkWriter> {
237 writer: W,
238 future_manager: DeliveryFutureManager<W::DeliveryFuture>,
239}
240
241impl<W: AsyncTruncateSinkWriter> AsyncTruncateLogSinkerOf<W> {
242 pub fn new(writer: W, max_future_count: usize) -> Self {
243 AsyncTruncateLogSinkerOf {
244 writer,
245 future_manager: DeliveryFutureManager::new(max_future_count),
246 }
247 }
248}
249
250#[async_trait]
251impl<W: AsyncTruncateSinkWriter> LogSinker for AsyncTruncateLogSinkerOf<W> {
252 async fn consume_log_and_sink(mut self, mut log_reader: impl SinkLogReader) -> Result<!> {
253 log_reader.start_from(None).await?;
254 loop {
255 let next_truncate_offset = self
256 .future_manager
257 .next_truncate_offset()
258 .instrument_await("sink_wait_delivery");
259 let select_result = drop_either_future(
260 select(pin!(log_reader.next_item()), pin!(next_truncate_offset)).await,
261 );
262 match select_result {
263 Either::Left(item_result) => {
264 let (epoch, item) = item_result?;
265 match item {
266 LogStoreReadItem::StreamChunk { chunk_id, chunk } => {
267 let add_future = self.future_manager.start_write_chunk(epoch, chunk_id);
268 self.writer
269 .write_chunk(chunk, add_future)
270 .instrument_await(await_tree::span!("sink_write_batch").verbose())
271 .await?;
272 }
273 LogStoreReadItem::Barrier { is_checkpoint, .. } => {
274 self.writer
275 .barrier(is_checkpoint)
276 .instrument_await(await_tree::span!(
277 "sink_barrier checkpoint={is_checkpoint} epoch={epoch}"
278 ))
279 .await?;
280 self.future_manager.add_barrier(epoch);
281 }
282 }
283 }
284 Either::Right(offset_result) => {
285 let offset = offset_result?;
286 log_reader.truncate(offset)?;
287 }
288 }
289 }
290 }
291}
292
293#[easy_ext::ext(AsyncTruncateSinkWriterExt)]
294impl<T> T
295where
296 T: AsyncTruncateSinkWriter + Sized,
297{
298 pub fn into_log_sinker(self, max_future_count: usize) -> AsyncTruncateLogSinkerOf<Self> {
299 AsyncTruncateLogSinkerOf::new(self, max_future_count)
300 }
301}