1mod range;
16
17pub(crate) use range::RegionWalRange;
18
19use std::collections::VecDeque;
20use std::ops::Range;
21use std::pin::Pin;
22use std::sync::Arc;
23use std::task::{Context, Poll};
24
25use common_telemetry::debug;
26use derive_builder::Builder;
27use futures::future::{BoxFuture, Fuse, FusedFuture};
28use futures::{FutureExt, Stream};
29use pin_project::pin_project;
30use rskafka::client::partition::PartitionClient;
31use rskafka::record::RecordAndOffset;
32
33use crate::kafka::consumer::range::NextBatchHint;
34
35pub struct FetchResult {
36 pub records: Vec<RecordAndOffset>,
38
39 pub high_watermark: i64,
41
42 pub encoded_response_size: usize,
44}
45
46#[async_trait::async_trait]
47pub trait FetchClient: std::fmt::Debug + Send + Sync {
48 async fn fetch_records(
52 &self,
53 offset: i64,
54 bytes: Range<i32>,
55 max_wait_ms: i32,
56 ) -> rskafka::client::error::Result<FetchResult>;
57
58 fn topic(&self) -> &str;
59}
60
61#[async_trait::async_trait]
62impl FetchClient for PartitionClient {
63 async fn fetch_records(
64 &self,
65 offset: i64,
66 bytes: Range<i32>,
67 max_wait_ms: i32,
68 ) -> rskafka::client::error::Result<FetchResult> {
69 self.fetch_records(offset, bytes, max_wait_ms)
70 .await
71 .map(|r| FetchResult {
72 records: r.records,
73 high_watermark: r.high_watermark,
74 encoded_response_size: r.encoded_response_size,
75 })
76 }
77
78 fn topic(&self) -> &str {
79 self.topic()
80 }
81}
82
83struct FetchResultInner {
84 records_and_offsets: Vec<RecordAndOffset>,
85 batch_size: usize,
86 fetch_bytes: usize,
87 watermark: i64,
88 used_offset: i64,
89}
90
91const MAX_BATCH_SIZE: usize = 52428800;
92const AVG_RECORD_SIZE: usize = 256 * 1024;
93
94#[pin_project]
97#[derive(Builder)]
98#[builder(pattern = "owned")]
99pub struct Consumer {
100 #[builder(default = "-1")]
101 last_high_watermark: i64,
102
103 client: Arc<dyn FetchClient>,
105
106 #[builder(default = "MAX_BATCH_SIZE")]
108 max_batch_size: usize,
109
110 #[builder(default = "500")]
112 max_wait_ms: u32,
113
114 #[builder(default = "AVG_RECORD_SIZE")]
116 avg_record_size: usize,
117
118 #[builder(default = "false")]
120 terminated: bool,
121
122 buffer: RecordsBuffer,
124
125 #[builder(default = "Fuse::terminated()")]
127 fetch_fut: Fuse<BoxFuture<'static, rskafka::client::error::Result<FetchResultInner>>>,
128
129 #[builder(default = "0")]
131 total_fetched_bytes: u64,
132}
133
134impl Consumer {
135 pub fn total_fetched_bytes(&self) -> u64 {
137 self.total_fetched_bytes
138 }
139
140 pub fn topic(&self) -> &str {
142 self.client.topic()
143 }
144}
145
146pub(crate) struct RecordsBuffer {
147 buffer: VecDeque<RecordAndOffset>,
148
149 index: RegionWalRange,
150}
151
152impl RecordsBuffer {
153 pub fn new(index: RegionWalRange) -> Self {
155 RecordsBuffer {
156 buffer: VecDeque::new(),
157 index,
158 }
159 }
160}
161
162impl RecordsBuffer {
163 fn pop_front(&mut self) -> Option<RecordAndOffset> {
164 while let Some(index) = self.index.peek() {
165 let record_and_offset = self.buffer.pop_front()?;
166 if index == record_and_offset.offset as u64 {
167 self.index.next();
168 return Some(record_and_offset);
169 }
170 }
171
172 self.buffer.clear();
173 None
174 }
175
176 fn extend(&mut self, records: Vec<RecordAndOffset>) {
177 if let (Some(first), Some(index)) = (records.first(), self.index.peek()) {
178 assert!(
180 index <= first.offset as u64,
181 "index: {index}, first offset: {}",
182 first.offset
183 );
184 }
185 self.buffer.extend(records);
186 }
187}
188
189impl Stream for Consumer {
190 type Item = rskafka::client::error::Result<(RecordAndOffset, i64)>;
191
192 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
193 let this = self.project();
194
195 loop {
196 if *this.terminated {
197 return Poll::Ready(None);
198 }
199
200 if this.buffer.index.peek().is_none() {
201 return Poll::Ready(None);
202 }
203
204 if let Some(x) = this.buffer.pop_front() {
205 debug!("Yielding record with offset: {}", x.offset);
206 return Poll::Ready(Some(Ok((x, *this.last_high_watermark))));
207 }
208
209 if this.fetch_fut.is_terminated() {
210 match this.buffer.index.peek() {
211 Some(next_offset) => {
212 let client = Arc::clone(this.client);
213 let max_wait_ms = *this.max_wait_ms as i32;
214 let offset = next_offset as i64;
215 let NextBatchHint { bytes, len } = this
216 .buffer
217 .index
218 .next_batch_hint(*this.avg_record_size)
219 .unwrap_or(NextBatchHint {
220 bytes: *this.avg_record_size,
221 len: 1,
222 });
223
224 let fetch_range =
225 1i32..(bytes.saturating_add(1).min(*this.max_batch_size) as i32);
226 *this.fetch_fut = FutureExt::fuse(Box::pin(async move {
227 let FetchResult {
228 records: records_and_offsets,
229 high_watermark: watermark,
230 encoded_response_size,
231 ..
232 } = client
233 .fetch_records(offset, fetch_range, max_wait_ms)
234 .await?;
235
236 Ok(FetchResultInner {
237 records_and_offsets,
238 watermark,
239 used_offset: offset,
240 fetch_bytes: encoded_response_size,
241 batch_size: len,
242 })
243 }));
244 }
245 None => {
246 return Poll::Ready(None);
247 }
248 }
249 }
250
251 let data = futures::ready!(this.fetch_fut.poll_unpin(cx));
252
253 match data {
254 Ok(FetchResultInner {
255 mut records_and_offsets,
256 watermark,
257 used_offset,
258 fetch_bytes,
259 batch_size,
260 }) => {
261 records_and_offsets.sort_unstable_by_key(|x| x.offset);
263 *this.last_high_watermark = watermark;
264 if !records_and_offsets.is_empty() {
265 *this.avg_record_size = fetch_bytes / records_and_offsets.len();
266 debug!("set avg_record_size: {}", *this.avg_record_size);
267 }
268 *this.total_fetched_bytes += fetch_bytes as u64;
269
270 debug!(
271 "Fetch result: {:?}, used_offset: {used_offset}, max_batch_size: {fetch_bytes}, expected batch_num: {batch_size}, actual batch_num: {}",
272 records_and_offsets
273 .iter()
274 .map(|record| record.offset)
275 .collect::<Vec<_>>(),
276 records_and_offsets.len()
277 );
278 this.buffer.extend(records_and_offsets);
279 continue;
280 }
281 Err(e) => {
282 *this.terminated = true;
283
284 return Poll::Ready(Some(Err(e)));
285 }
286 }
287 }
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use std::collections::VecDeque;
294 use std::ops::Range;
295 use std::sync::Arc;
296
297 use chrono::{TimeZone, Utc};
298 use futures::TryStreamExt;
299 use futures::future::Fuse;
300 use rskafka::record::{Record, RecordAndOffset};
301
302 use super::*;
303 use crate::kafka::consumer::{Consumer, RecordsBuffer};
304
305 #[derive(Debug)]
306 struct MockFetchClient {
307 record: Record,
308 }
309
310 #[async_trait::async_trait]
311 impl FetchClient for MockFetchClient {
312 async fn fetch_records(
313 &self,
314 offset: i64,
315 bytes: Range<i32>,
316 _max_wait_ms: i32,
317 ) -> rskafka::client::error::Result<FetchResult> {
318 let record_size = self.record.approximate_size();
319 let num = (bytes.end.unsigned_abs() as usize / record_size).max(1);
320
321 let records = (0..num)
322 .map(|idx| RecordAndOffset {
323 record: self.record.clone(),
324 offset: offset + idx as i64,
325 })
326 .collect::<Vec<_>>();
327
328 let max_offset = offset + records.len() as i64;
329 let encoded_response_size = records.iter().map(|r| r.record.approximate_size()).sum();
330 Ok(FetchResult {
331 records,
332 high_watermark: max_offset,
333 encoded_response_size,
334 })
335 }
336
337 fn topic(&self) -> &str {
338 "test"
339 }
340 }
341
342 fn test_record() -> Record {
343 Record {
344 key: Some(vec![0; 4]),
345 value: Some(vec![0; 6]),
346 headers: Default::default(),
347 timestamp: Utc.timestamp_millis_opt(1337).unwrap(),
348 }
349 }
350
351 #[tokio::test]
352 async fn test_consumer_without_index() {
353 common_telemetry::init_default_ut_logging();
354 let record = test_record();
355 let mock_client = MockFetchClient {
356 record: record.clone(),
357 };
358 let index = RegionWalRange::new(0..30, 1024);
359 let consumer = Consumer {
360 last_high_watermark: -1,
361 client: Arc::new(mock_client),
362 max_batch_size: usize::MAX,
363 max_wait_ms: 500,
364 avg_record_size: record.approximate_size(),
365 terminated: false,
366 buffer: RecordsBuffer {
367 buffer: VecDeque::new(),
368 index,
369 },
370 fetch_fut: Fuse::terminated(),
371 total_fetched_bytes: 0,
372 };
373
374 let records = consumer.try_collect::<Vec<_>>().await.unwrap();
375 assert_eq!(
376 records
377 .into_iter()
378 .map(|(x, _)| x.offset)
379 .collect::<Vec<_>>(),
380 (0..30).collect::<Vec<_>>()
381 )
382 }
383
384 #[tokio::test]
385 async fn test_consumer_range_boundaries() {
386 for (start, end, max_batch_size) in [
387 (0, 0, 1024),
388 (5, 5, 1024),
389 (6, 5, 1024),
390 (5, 6, 1024),
391 (5, 35, 128),
392 ] {
393 let record = test_record();
394 let consumer = ConsumerBuilder::default()
395 .client(Arc::new(MockFetchClient {
396 record: record.clone(),
397 }))
398 .buffer(RecordsBuffer::new(RegionWalRange::new(
399 start..end,
400 max_batch_size,
401 )))
402 .avg_record_size(record.approximate_size())
403 .build()
404 .unwrap();
405 let offsets = consumer
406 .try_collect::<Vec<_>>()
407 .await
408 .unwrap()
409 .into_iter()
410 .map(|(record, _)| record.offset as u64)
411 .collect::<Vec<_>>();
412 assert_eq!(offsets, (start..end).collect::<Vec<_>>());
413 }
414 }
415}