risingwave_connector/sink/
coordinate.rs1use std::cmp::Ordering;
16use std::future::pending;
17use std::num::NonZeroU64;
18use std::time::Instant;
19
20use anyhow::anyhow;
21use async_trait::async_trait;
22use risingwave_common::bail;
23use risingwave_common::bitmap::Bitmap;
24use risingwave_pb::connector_service::SinkMetadata;
25use tracing::{info, warn};
26
27use super::{
28 LogSinker, SinkCoordinationRpcClientEnum, SinkLogReader, SinkWriterMetrics, SinkWriterParam,
29};
30use crate::sink::decouple_checkpoint_log_sink::should_force_commit_on_checkpoint_barrier;
31use crate::sink::writer::SinkWriter;
32use crate::sink::{LogStoreReadItem, Result, SinkError, SinkParam, TruncateOffset};
33
34pub struct CoordinatedLogSinker<W: SinkWriter<CommitMetadata = Option<SinkMetadata>>> {
35 writer: W,
36 sink_coordinate_client: SinkCoordinationRpcClientEnum,
37 param: SinkParam,
38 vnode_bitmap: Bitmap,
39 commit_checkpoint_interval: NonZeroU64,
40 sink_writer_metrics: SinkWriterMetrics,
41}
42
43impl<W: SinkWriter<CommitMetadata = Option<SinkMetadata>>> CoordinatedLogSinker<W> {
44 pub async fn new(
45 writer_param: &SinkWriterParam,
46 param: SinkParam,
47 writer: W,
48 commit_checkpoint_interval: NonZeroU64,
49 ) -> Result<Self> {
50 Ok(Self {
51 writer,
52 sink_coordinate_client: writer_param
53 .meta_client
54 .as_ref()
55 .ok_or_else(|| anyhow!("should have meta client"))?
56 .clone()
57 .sink_coordinate_client()
58 .await,
59 param,
60 vnode_bitmap: writer_param
61 .vnode_bitmap
62 .as_ref()
63 .ok_or_else(|| {
64 anyhow!("sink needs coordination and should not have singleton input")
65 })?
66 .clone(),
67 commit_checkpoint_interval,
68 sink_writer_metrics: SinkWriterMetrics::new(writer_param),
69 })
70 }
71}
72
73#[async_trait]
74impl<W: SinkWriter<CommitMetadata = Option<SinkMetadata>>> LogSinker for CoordinatedLogSinker<W> {
75 async fn consume_log_and_sink(self, mut log_reader: impl SinkLogReader) -> Result<!> {
76 let (mut coordinator_stream_handle, log_store_rewind_start_epoch) = self
77 .sink_coordinate_client
78 .new_stream_handle(&self.param, self.vnode_bitmap)
79 .await?;
80 let mut sink_writer = self.writer;
81 log_reader.start_from(log_store_rewind_start_epoch).await?;
82 let mut first_item = log_reader.next_item().await?;
83 if let (Some(log_store_rewind_start_epoch), (first_epoch, _)) =
84 (log_store_rewind_start_epoch, &first_item)
85 {
86 if log_store_rewind_start_epoch >= *first_epoch {
87 bail!(
88 "log_store_rewind_start_epoch {} not later than first_epoch {}",
89 log_store_rewind_start_epoch,
90 first_epoch
91 );
92 }
93 } else {
94 let &(initial_epoch, _) = &first_item;
95 let aligned_initial_epoch = coordinator_stream_handle
96 .align_initial_epoch(initial_epoch)
97 .await?;
98 if initial_epoch != aligned_initial_epoch {
99 warn!(
100 initial_epoch,
101 aligned_initial_epoch,
102 sink_id = %self.param.sink_id,
103 "initial epoch not matched aligned initial epoch"
104 );
105 let mut peeked_first = Some(first_item);
106 first_item = loop {
107 let (epoch, item) = if let Some(peeked_first) = peeked_first.take() {
108 peeked_first
109 } else {
110 log_reader.next_item().await?
111 };
112 match epoch.cmp(&aligned_initial_epoch) {
113 Ordering::Less => {
114 continue;
115 }
116 Ordering::Equal => {
117 break (epoch, item);
118 }
119 Ordering::Greater => {
120 return Err(anyhow!(
121 "initial epoch {} greater than aligned initial epoch {}",
122 initial_epoch,
123 aligned_initial_epoch
124 )
125 .into());
126 }
127 }
128 };
129 }
130 }
131
132 let mut first_item = Some(first_item);
133
134 #[derive(Debug)]
135 enum LogConsumerState {
136 Uninitialized,
138
139 EpochBegun { curr_epoch: u64 },
141
142 BarrierReceived { prev_epoch: u64 },
144 }
145
146 let mut state = LogConsumerState::Uninitialized;
147
148 let mut current_checkpoint: u64 = 0;
149 let commit_checkpoint_interval = self.commit_checkpoint_interval;
150 let sink_writer_metrics = self.sink_writer_metrics;
151
152 loop {
153 let (epoch, item) = if let Some(item) = first_item.take() {
154 item
155 } else {
156 log_reader.next_item().await?
157 };
158
159 state = match state {
161 LogConsumerState::Uninitialized => {
162 sink_writer.begin_epoch(epoch).await?;
163 LogConsumerState::EpochBegun { curr_epoch: epoch }
164 }
165 LogConsumerState::EpochBegun { curr_epoch } => {
166 assert!(
167 epoch >= curr_epoch,
168 "new epoch {} should not be below the current epoch {}",
169 epoch,
170 curr_epoch
171 );
172 LogConsumerState::EpochBegun { curr_epoch: epoch }
173 }
174 LogConsumerState::BarrierReceived { prev_epoch, .. } => {
175 assert!(
176 epoch > prev_epoch,
177 "new epoch {} should be greater than prev epoch {}",
178 epoch,
179 prev_epoch
180 );
181
182 sink_writer.begin_epoch(epoch).await?;
183 LogConsumerState::EpochBegun { curr_epoch: epoch }
184 }
185 };
186 match item {
187 LogStoreReadItem::StreamChunk { chunk, .. } => {
188 if let Err(e) = sink_writer.write_batch(chunk).await {
189 sink_writer.abort().await?;
190 return Err(e);
191 }
192 }
193 LogStoreReadItem::Barrier {
194 is_checkpoint,
195 new_vnode_bitmap,
196 is_stop,
197 schema_change,
198 } => {
199 let prev_epoch = match state {
200 LogConsumerState::EpochBegun { curr_epoch } => curr_epoch,
201 _ => unreachable!("epoch must have begun before handling barrier"),
202 };
203 if is_checkpoint {
204 current_checkpoint += 1;
205 if current_checkpoint >= commit_checkpoint_interval.get()
206 || should_force_commit_on_checkpoint_barrier(
207 new_vnode_bitmap.is_some(),
208 is_stop,
209 schema_change.is_some(),
210 )
211 {
212 let start_time = Instant::now();
213 let metadata = sink_writer.barrier(true).await?;
214 let metadata = metadata.ok_or_else(|| {
215 SinkError::Coordinator(anyhow!(
216 "should get metadata on checkpoint barrier"
217 ))
218 })?;
219 if schema_change.is_some() {
220 tracing::info!(
221 sink_id = %self.param.sink_id,
222 ?schema_change,
223 "schema change received for coordinated log sinker"
224 );
225 assert!(
226 is_stop,
227 "schema change should stop current sink for sink {}",
228 self.param.sink_id
229 );
230 }
231 coordinator_stream_handle
232 .commit(epoch, metadata, schema_change)
233 .await?;
234 sink_writer_metrics
235 .sink_commit_duration
236 .observe(start_time.elapsed().as_secs_f64());
237
238 current_checkpoint = 0;
239 if let Some(new_vnode_bitmap) = new_vnode_bitmap {
240 let epoch = coordinator_stream_handle
241 .update_vnode_bitmap(&new_vnode_bitmap)
242 .await?;
243 if epoch != prev_epoch {
244 bail!(
245 "newly start epoch {} after update vnode bitmap not matched with prev_epoch {}",
246 epoch,
247 prev_epoch
248 );
249 }
250 }
251 if is_stop {
252 coordinator_stream_handle.stop().await?;
253 info!(
254 sink_id = %self.param.sink_id,
255 "coordinated log sinker stops"
256 );
257 log_reader.truncate(TruncateOffset::Barrier { epoch })?;
258 return pending().await;
259 }
260 log_reader.truncate(TruncateOffset::Barrier { epoch })?;
261 } else {
262 let metadata = sink_writer.barrier(false).await?;
263 if let Some(metadata) = metadata {
264 warn!(?metadata, "get metadata on non-checkpoint barrier");
265 }
266 }
267 } else {
268 let metadata = sink_writer.barrier(false).await?;
269 if let Some(metadata) = metadata {
270 warn!(?metadata, "get metadata on non-checkpoint barrier");
271 }
272 }
273 state = LogConsumerState::BarrierReceived { prev_epoch }
274 }
275 }
276 }
277 }
278}