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.vnode_bitmap.clone(),
61 commit_checkpoint_interval,
62 sink_writer_metrics: SinkWriterMetrics::new(writer_param),
63 })
64 }
65}
66
67#[async_trait]
68impl<W: SinkWriter<CommitMetadata = Option<SinkMetadata>>> LogSinker for CoordinatedLogSinker<W> {
69 async fn consume_log_and_sink(self, mut log_reader: impl SinkLogReader) -> Result<!> {
70 let (mut coordinator_stream_handle, log_store_rewind_start_epoch) = self
71 .sink_coordinate_client
72 .new_stream_handle(&self.param, self.vnode_bitmap)
73 .await?;
74 let mut sink_writer = self.writer;
75 log_reader.start_from(log_store_rewind_start_epoch).await?;
76 let mut first_item = log_reader.next_item().await?;
77 if let (Some(log_store_rewind_start_epoch), (first_epoch, _)) =
78 (log_store_rewind_start_epoch, &first_item)
79 {
80 if log_store_rewind_start_epoch >= *first_epoch {
81 bail!(
82 "log_store_rewind_start_epoch {} not later than first_epoch {}",
83 log_store_rewind_start_epoch,
84 first_epoch
85 );
86 }
87 } else {
88 let &(initial_epoch, _) = &first_item;
89 let aligned_initial_epoch = coordinator_stream_handle
90 .align_initial_epoch(initial_epoch)
91 .await?;
92 if initial_epoch != aligned_initial_epoch {
93 warn!(
94 initial_epoch,
95 aligned_initial_epoch,
96 sink_id = %self.param.sink_id,
97 "initial epoch not matched aligned initial epoch"
98 );
99 let mut peeked_first = Some(first_item);
100 first_item = loop {
101 let (epoch, item) = if let Some(peeked_first) = peeked_first.take() {
102 peeked_first
103 } else {
104 log_reader.next_item().await?
105 };
106 match epoch.cmp(&aligned_initial_epoch) {
107 Ordering::Less => {
108 continue;
109 }
110 Ordering::Equal => {
111 break (epoch, item);
112 }
113 Ordering::Greater => {
114 return Err(anyhow!(
115 "initial epoch {} greater than aligned initial epoch {}",
116 initial_epoch,
117 aligned_initial_epoch
118 )
119 .into());
120 }
121 }
122 };
123 }
124 }
125
126 let mut first_item = Some(first_item);
127
128 #[derive(Debug)]
129 enum LogConsumerState {
130 Uninitialized,
132
133 EpochBegun { curr_epoch: u64 },
135
136 BarrierReceived { prev_epoch: u64 },
138 }
139
140 let mut state = LogConsumerState::Uninitialized;
141
142 let mut current_checkpoint: u64 = 0;
143 let commit_checkpoint_interval = self.commit_checkpoint_interval;
144 let sink_writer_metrics = self.sink_writer_metrics;
145
146 loop {
147 let (epoch, item) = if let Some(item) = first_item.take() {
148 item
149 } else {
150 log_reader.next_item().await?
151 };
152
153 state = match state {
155 LogConsumerState::Uninitialized => {
156 sink_writer.begin_epoch(epoch).await?;
157 LogConsumerState::EpochBegun { curr_epoch: epoch }
158 }
159 LogConsumerState::EpochBegun { curr_epoch } => {
160 assert!(
161 epoch >= curr_epoch,
162 "new epoch {} should not be below the current epoch {}",
163 epoch,
164 curr_epoch
165 );
166 LogConsumerState::EpochBegun { curr_epoch: epoch }
167 }
168 LogConsumerState::BarrierReceived { prev_epoch, .. } => {
169 assert!(
170 epoch > prev_epoch,
171 "new epoch {} should be greater than prev epoch {}",
172 epoch,
173 prev_epoch
174 );
175
176 sink_writer.begin_epoch(epoch).await?;
177 LogConsumerState::EpochBegun { curr_epoch: epoch }
178 }
179 };
180 match item {
181 LogStoreReadItem::StreamChunk { chunk, .. } => {
182 if let Err(e) = sink_writer.write_batch(chunk).await {
183 sink_writer.abort().await?;
184 return Err(e);
185 }
186 }
187 LogStoreReadItem::Barrier {
188 is_checkpoint,
189 new_vnode_bitmap,
190 is_stop,
191 schema_change,
192 } => {
193 let prev_epoch = match state {
194 LogConsumerState::EpochBegun { curr_epoch } => curr_epoch,
195 _ => unreachable!("epoch must have begun before handling barrier"),
196 };
197 if is_checkpoint {
198 current_checkpoint += 1;
199 if current_checkpoint >= commit_checkpoint_interval.get()
200 || should_force_commit_on_checkpoint_barrier(
201 new_vnode_bitmap.is_some(),
202 is_stop,
203 schema_change.is_some(),
204 )
205 {
206 let start_time = Instant::now();
207 let metadata = sink_writer.barrier(true).await?;
208 let metadata = metadata.ok_or_else(|| {
209 SinkError::Coordinator(anyhow!(
210 "should get metadata on checkpoint barrier"
211 ))
212 })?;
213 if schema_change.is_some() {
214 tracing::info!(
215 sink_id = %self.param.sink_id,
216 ?schema_change,
217 "schema change received for coordinated log sinker"
218 );
219 assert!(
220 is_stop,
221 "schema change should stop current sink for sink {}",
222 self.param.sink_id
223 );
224 }
225 coordinator_stream_handle
226 .commit(epoch, metadata, schema_change)
227 .await?;
228 sink_writer_metrics
229 .sink_commit_duration
230 .observe(start_time.elapsed().as_secs_f64());
231
232 current_checkpoint = 0;
233 if let Some(new_vnode_bitmap) = new_vnode_bitmap {
234 let epoch = coordinator_stream_handle
235 .update_vnode_bitmap(&new_vnode_bitmap)
236 .await?;
237 if epoch != prev_epoch {
238 bail!(
239 "newly start epoch {} after update vnode bitmap not matched with prev_epoch {}",
240 epoch,
241 prev_epoch
242 );
243 }
244 }
245 if is_stop {
246 coordinator_stream_handle.stop().await?;
247 info!(
248 sink_id = %self.param.sink_id,
249 "coordinated log sinker stops"
250 );
251 log_reader.truncate(TruncateOffset::Barrier { epoch })?;
252 return pending().await;
253 }
254 log_reader.truncate(TruncateOffset::Barrier { epoch })?;
255 } else {
256 let metadata = sink_writer.barrier(false).await?;
257 if let Some(metadata) = metadata {
258 warn!(?metadata, "get metadata on non-checkpoint barrier");
259 }
260 }
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 state = LogConsumerState::BarrierReceived { prev_epoch }
268 }
269 }
270 }
271 }
272}