sui_indexer_alt_framework/pipeline/
mod.rs1use std::time::Duration;
5
6pub use crate::config::ConcurrencyConfig;
7use crate::store::CommitterWatermark;
8pub use processor::Processor;
9use rand::Rng;
10use serde::Deserialize;
11use serde::Serialize;
12
13mod batch_floor;
14pub mod concurrent;
15mod logging;
16mod processor;
17pub mod sequential;
18
19const WARN_PENDING_WATERMARKS: usize = 10000;
24
25#[derive(Serialize, Deserialize, Debug, Clone)]
26pub struct CommitterConfig {
27 pub write_concurrency: usize,
29
30 pub watermark_interval_ms: u64,
34
35 pub watermark_interval_jitter_ms: u64,
37
38 #[serde(default)]
45 pub min_batch_rows: usize,
46
47 #[serde(default = "default_max_batch_delay_ms")]
50 pub max_batch_delay_ms: u64,
51}
52
53#[derive(Serialize, Deserialize, Debug, Clone, Default)]
55pub struct IngestionConfig {
56 pub subscriber_channel_size: Option<usize>,
59}
60
61impl IngestionConfig {
62 pub fn subscriber_channel_size(&self) -> usize {
70 self.subscriber_channel_size
71 .unwrap_or_else(|| (num_cpus::get() / 2).max(4))
72 }
73}
74
75struct IndexedCheckpoint<P: Processor> {
78 values: Vec<P::Value>,
80 watermark: CommitterWatermark,
82}
83
84#[derive(Debug, Clone)]
86struct WatermarkPart {
87 watermark: CommitterWatermark,
89 batch_rows: usize,
91 total_rows: usize,
93}
94
95impl CommitterConfig {
96 pub fn max_batch_delay(&self) -> Duration {
97 Duration::from_millis(self.max_batch_delay_ms)
98 }
99
100 pub fn watermark_interval(&self) -> Duration {
101 Duration::from_millis(self.watermark_interval_ms)
102 }
103
104 pub fn watermark_interval_with_jitter(&self) -> tokio::time::Instant {
107 let jitter = if self.watermark_interval_jitter_ms == 0 {
108 0
109 } else {
110 rand::thread_rng().gen_range(0..=self.watermark_interval_jitter_ms)
111 };
112 tokio::time::Instant::now() + Duration::from_millis(self.watermark_interval_ms + jitter)
113 }
114}
115
116impl<P: Processor> IndexedCheckpoint<P> {
117 fn new(
118 epoch: u64,
119 cp_sequence_number: u64,
120 tx_hi: u64,
121 timestamp_ms: u64,
122 values: Vec<P::Value>,
123 ) -> Self {
124 Self {
125 watermark: CommitterWatermark {
126 epoch_hi_inclusive: epoch,
127 checkpoint_hi_inclusive: cp_sequence_number,
128 tx_hi,
129 timestamp_ms_hi_inclusive: timestamp_ms,
130 },
131 values,
132 }
133 }
134
135 fn len(&self) -> usize {
137 self.values.len()
138 }
139
140 fn checkpoint(&self) -> u64 {
142 self.watermark.checkpoint_hi_inclusive
143 }
144}
145
146impl WatermarkPart {
147 fn checkpoint(&self) -> u64 {
148 self.watermark.checkpoint_hi_inclusive
149 }
150
151 fn timestamp_ms(&self) -> u64 {
152 self.watermark.timestamp_ms_hi_inclusive
153 }
154
155 fn is_complete(&self) -> bool {
157 self.batch_rows == self.total_rows
158 }
159
160 fn add(&mut self, other: WatermarkPart) {
162 assert_eq!(self.checkpoint(), other.checkpoint());
163 self.batch_rows += other.batch_rows;
164 assert!(
165 self.batch_rows <= self.total_rows,
166 "batch_rows ({}) exceeded total_rows ({})",
167 self.batch_rows,
168 self.total_rows,
169 );
170 }
171
172 fn take(&mut self, rows: usize) -> WatermarkPart {
174 assert!(
175 self.batch_rows >= rows,
176 "Can't take more rows than are available"
177 );
178
179 self.batch_rows -= rows;
180 WatermarkPart {
181 watermark: self.watermark,
182 batch_rows: rows,
183 total_rows: self.total_rows,
184 }
185 }
186}
187
188impl Default for CommitterConfig {
189 fn default() -> Self {
190 Self {
191 write_concurrency: 5,
192 watermark_interval_ms: 500,
193 watermark_interval_jitter_ms: 0,
194 min_batch_rows: 0,
195 max_batch_delay_ms: default_max_batch_delay_ms(),
196 }
197 }
198}
199
200fn default_max_batch_delay_ms() -> u64 {
201 500
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use async_trait::async_trait;
208 use std::sync::Arc;
209 use sui_types::full_checkpoint_content::Checkpoint;
210
211 struct TestProcessor;
213 #[async_trait]
214 impl Processor for TestProcessor {
215 const NAME: &'static str = "test";
216 type Value = i32;
217
218 async fn process(&self, _checkpoint: &Arc<Checkpoint>) -> anyhow::Result<Vec<Self::Value>> {
219 Ok(vec![1, 2, 3])
220 }
221 }
222
223 #[test]
224 fn test_watermark_part_getters() {
225 let watermark = CommitterWatermark {
226 epoch_hi_inclusive: 1,
227 checkpoint_hi_inclusive: 100,
228 tx_hi: 1000,
229 timestamp_ms_hi_inclusive: 1234567890,
230 };
231
232 let part = WatermarkPart {
233 watermark,
234 batch_rows: 50,
235 total_rows: 200,
236 };
237
238 assert_eq!(part.checkpoint(), 100);
239 assert_eq!(part.timestamp_ms(), 1234567890);
240 }
241
242 #[test]
243 fn test_watermark_part_is_complete() {
244 let part = WatermarkPart {
245 watermark: CommitterWatermark::default(),
246 batch_rows: 200,
247 total_rows: 200,
248 };
249
250 assert!(part.is_complete());
251 }
252
253 #[test]
254 fn test_watermark_part_is_not_complete() {
255 let part = WatermarkPart {
256 watermark: CommitterWatermark::default(),
257 batch_rows: 199,
258 total_rows: 200,
259 };
260
261 assert!(!part.is_complete());
262 }
263
264 #[test]
265 fn test_watermark_part_becomes_complete_after_adding_new_batch() {
266 let mut part = WatermarkPart {
267 watermark: CommitterWatermark::default(),
268 batch_rows: 199,
269 total_rows: 200,
270 };
271
272 part.add(WatermarkPart {
274 watermark: CommitterWatermark::default(),
275 batch_rows: 1,
276 total_rows: 200,
277 });
278
279 assert!(part.is_complete());
280 assert_eq!(part.batch_rows, 200);
281 }
282
283 #[test]
284 fn test_watermark_part_becomes_incomplete_after_taking_away_batch() {
285 let mut part = WatermarkPart {
286 watermark: CommitterWatermark::default(),
287 batch_rows: 200,
288 total_rows: 200,
289 };
290 assert!(part.is_complete(), "Initial part should be complete");
291
292 let extracted_part = part.take(10);
294
295 assert!(!extracted_part.is_complete());
297 assert_eq!(extracted_part.batch_rows, 10);
298 assert_eq!(extracted_part.total_rows, 200);
299 }
300
301 #[test]
302 fn test_indexed_checkpoint() {
303 let epoch = 1;
304 let cp_sequence_number = 100;
305 let tx_hi = 1000;
306 let timestamp_ms = 1234567890;
307 let values = vec![1, 2, 3];
308
309 let checkpoint = IndexedCheckpoint::<TestProcessor>::new(
310 epoch,
311 cp_sequence_number,
312 tx_hi,
313 timestamp_ms,
314 values,
315 );
316
317 assert_eq!(checkpoint.len(), 3);
318 assert_eq!(checkpoint.checkpoint(), 100);
319 }
320
321 #[test]
322 fn test_indexed_checkpoint_with_empty_values() {
323 let epoch = 1;
324 let cp_sequence_number = 100;
325 let tx_hi = 1000;
326 let timestamp_ms = 1234567890;
327 let values: Vec<<TestProcessor as Processor>::Value> = vec![];
328
329 let checkpoint = IndexedCheckpoint::<TestProcessor>::new(
330 epoch,
331 cp_sequence_number,
332 tx_hi,
333 timestamp_ms,
334 values,
335 );
336
337 assert_eq!(checkpoint.len(), 0);
338 assert_eq!(checkpoint.checkpoint(), 100);
339 }
340}