1use std::collections::{HashMap, HashSet};
16use std::sync::Arc;
17use std::time::{Duration, Instant};
18
19use common_base::readable_size::ReadableSize;
20use common_meta::datanode::TopicStatsReporter;
21use common_meta::distributed_time_constants::TOPIC_STATS_REPORT_INTERVAL_SECS;
22use common_telemetry::{debug, info, warn};
23use common_time::util::current_time_millis;
24use common_wal::config::kafka::DatanodeKafkaConfig;
25use dashmap::DashMap;
26use futures::future::try_join_all;
27use futures_util::StreamExt;
28use rskafka::client::partition::OffsetAt;
29use snafu::{OptionExt, ResultExt};
30use store_api::logstore::entry::{
31 Entry, Id as EntryId, MultiplePartEntry, MultiplePartHeader, NaiveEntry,
32};
33use store_api::logstore::provider::{KafkaProvider, Provider};
34use store_api::logstore::{AppendBatchResponse, LogStore, SendableEntryStream};
35use store_api::storage::RegionId;
36
37use crate::error::{self, ConsumeRecordSnafu, Error, GetOffsetSnafu, InvalidProviderSnafu, Result};
38use crate::kafka::client_manager::{ClientManager, ClientManagerRef};
39use crate::kafka::consumer::{ConsumerBuilder, RecordsBuffer, RegionWalRange};
40use crate::kafka::periodic_offset_fetcher::PeriodicOffsetFetcher;
41use crate::kafka::producer::OrderedBatchProducerRef;
42use crate::kafka::util::record::{
43 ESTIMATED_META_SIZE, Record, convert_to_kafka_records, is_record_meta_key, maybe_emit_entry,
44 remaining_entries,
45};
46use crate::metrics;
47
48#[derive(Debug, Clone, Copy, Default)]
50pub struct TopicStat {
51 pub latest_offset: u64,
58 pub record_size: u64,
60 pub record_num: u64,
62}
63
64#[derive(Debug)]
66pub struct KafkaLogStore {
67 client_manager: ClientManagerRef,
69 max_batch_bytes: usize,
71 consumer_wait_timeout: Duration,
73 overwrite_entry_start_id: bool,
75 topic_stats: Arc<DashMap<Arc<KafkaProvider>, TopicStat>>,
80}
81
82struct PeriodicTopicStatsReporter {
83 topic_stats: Arc<DashMap<Arc<KafkaProvider>, TopicStat>>,
84 last_reported_timestamp_millis: i64,
85 report_interval_millis: i64,
86}
87
88impl PeriodicTopicStatsReporter {
89 fn align_ts(ts: i64, report_interval_millis: i64) -> i64 {
90 (ts / report_interval_millis) * report_interval_millis
91 }
92
93 fn new(
99 topic_stats: Arc<DashMap<Arc<KafkaProvider>, TopicStat>>,
100 report_interval: Duration,
101 ) -> Self {
102 assert!(!report_interval.is_zero());
103 let report_interval_millis = report_interval.as_millis() as i64;
104 let last_reported_timestamp_millis =
105 Self::align_ts(current_time_millis(), report_interval_millis);
106
107 Self {
108 topic_stats,
109 last_reported_timestamp_millis,
110 report_interval_millis,
111 }
112 }
113}
114
115impl TopicStatsReporter for PeriodicTopicStatsReporter {
116 fn reportable_topics(&mut self) -> Vec<common_meta::datanode::TopicStat> {
117 let now = Self::align_ts(current_time_millis(), self.report_interval_millis);
118 if now < self.last_reported_timestamp_millis + self.report_interval_millis {
119 debug!("Skip reporting topic stats because the interval is not reached");
120 return vec![];
121 }
122
123 self.last_reported_timestamp_millis = now;
124 let mut reportable_topics = Vec::with_capacity(self.topic_stats.len());
125 for e in self.topic_stats.iter() {
126 let topic_stat = e.value();
127 let topic_stat = common_meta::datanode::TopicStat {
128 topic: e.key().topic.clone(),
129 latest_entry_id: topic_stat.latest_offset,
130 record_size: topic_stat.record_size,
131 record_num: topic_stat.record_num,
132 };
133 debug!("Reportable topic: {:?}", topic_stat);
134 reportable_topics.push(topic_stat);
135 }
136 debug!("Reportable {} topics at {}", reportable_topics.len(), now);
137 reportable_topics
138 }
139}
140
141impl KafkaLogStore {
142 pub async fn try_new(config: &DatanodeKafkaConfig) -> Result<Self> {
144 let topic_stats = Arc::new(DashMap::new());
145 let client_manager = Arc::new(ClientManager::try_new(config, topic_stats.clone()).await?);
146 let fetcher = PeriodicOffsetFetcher::new(
147 config.topic_latest_offset_fetch_interval,
148 client_manager.clone(),
149 );
150 fetcher.run().await;
151
152 Ok(Self {
153 client_manager,
154 max_batch_bytes: config.max_batch_bytes.as_bytes() as usize,
155 consumer_wait_timeout: config.consumer_wait_timeout,
156 overwrite_entry_start_id: config.overwrite_entry_start_id,
157 topic_stats,
158 })
159 }
160
161 pub fn topic_stats_reporter(&self) -> Box<dyn TopicStatsReporter> {
163 Box::new(PeriodicTopicStatsReporter::new(
164 self.topic_stats.clone(),
165 Duration::from_secs(TOPIC_STATS_REPORT_INTERVAL_SECS),
166 ))
167 }
168}
169
170fn build_entry(
171 data: Vec<u8>,
172 entry_id: EntryId,
173 region_id: RegionId,
174 provider: &Provider,
175 max_data_size: usize,
176) -> Entry {
177 if data.len() <= max_data_size {
178 Entry::Naive(NaiveEntry {
179 provider: provider.clone(),
180 region_id,
181 entry_id,
182 data,
183 })
184 } else {
185 let parts = data
186 .chunks(max_data_size)
187 .map(|s| s.into())
188 .collect::<Vec<_>>();
189 let num_parts = parts.len();
190
191 let mut headers = Vec::with_capacity(num_parts);
192 headers.push(MultiplePartHeader::First);
193 headers.extend((1..num_parts - 1).map(MultiplePartHeader::Middle));
194 headers.push(MultiplePartHeader::Last);
195
196 Entry::MultiplePart(MultiplePartEntry {
197 provider: provider.clone(),
198 region_id,
199 entry_id,
200 headers,
201 parts,
202 })
203 }
204}
205
206#[async_trait::async_trait]
207impl LogStore for KafkaLogStore {
208 type Error = Error;
209
210 fn entry(
212 &self,
213 data: Vec<u8>,
214 entry_id: EntryId,
215 region_id: RegionId,
216 provider: &Provider,
217 ) -> Result<Entry> {
218 provider
219 .as_kafka_provider()
220 .with_context(|| InvalidProviderSnafu {
221 expected: KafkaProvider::type_name(),
222 actual: provider.type_name(),
223 })?;
224
225 let max_data_size = self.max_batch_bytes - ESTIMATED_META_SIZE;
226 Ok(build_entry(
227 data,
228 entry_id,
229 region_id,
230 provider,
231 max_data_size,
232 ))
233 }
234
235 async fn append_batch(&self, entries: Vec<Entry>) -> Result<AppendBatchResponse> {
238 metrics::METRIC_KAFKA_APPEND_BATCH_BYTES_TOTAL.inc_by(
239 entries
240 .iter()
241 .map(|entry| entry.estimated_size())
242 .sum::<usize>() as u64,
243 );
244 let _timer = metrics::METRIC_KAFKA_APPEND_BATCH_ELAPSED.start_timer();
245
246 if entries.is_empty() {
247 return Ok(AppendBatchResponse::default());
248 }
249
250 let region_ids = entries
251 .iter()
252 .map(|entry| entry.region_id())
253 .collect::<HashSet<_>>();
254 let mut region_grouped_records: HashMap<RegionId, (OrderedBatchProducerRef, Vec<_>)> =
255 HashMap::with_capacity(region_ids.len());
256 let mut region_to_provider = HashMap::with_capacity(region_ids.len());
257 for entry in entries {
258 let provider = entry.provider().as_kafka_provider().with_context(|| {
259 error::InvalidProviderSnafu {
260 expected: KafkaProvider::type_name(),
261 actual: entry.provider().type_name(),
262 }
263 })?;
264 region_to_provider.insert(entry.region_id(), provider.clone());
265 let region_id = entry.region_id();
266 match region_grouped_records.entry(region_id) {
267 std::collections::hash_map::Entry::Occupied(mut slot) => {
268 slot.get_mut().1.extend(convert_to_kafka_records(entry)?);
269 }
270 std::collections::hash_map::Entry::Vacant(slot) => {
271 let producer = self
272 .client_manager
273 .get_or_insert(provider)
274 .await?
275 .producer()
276 .clone();
277
278 slot.insert((producer, convert_to_kafka_records(entry)?));
279 }
280 }
281 }
282
283 let mut region_grouped_result_receivers = Vec::with_capacity(region_ids.len());
284 for (region_id, (producer, records)) in region_grouped_records {
285 region_grouped_result_receivers.push((region_id, producer.produce(records).await?))
288 }
289
290 let region_grouped_max_offset =
291 try_join_all(region_grouped_result_receivers.into_iter().map(
292 |(region_id, receiver)| async move {
293 receiver.wait().await.map(|offset| (region_id, offset))
294 },
295 ))
296 .await?;
297 debug!(
298 "Appended batch to Kafka, region_grouped_max_offset: {:?}",
299 region_grouped_max_offset
300 );
301
302 Ok(AppendBatchResponse {
303 last_entry_ids: region_grouped_max_offset.into_iter().collect(),
304 })
305 }
306
307 async fn read(
310 &self,
311 provider: &Provider,
312 mut entry_id: EntryId,
313 ) -> Result<SendableEntryStream<'static, Entry, Self::Error>> {
314 let provider = provider
315 .as_kafka_provider()
316 .with_context(|| InvalidProviderSnafu {
317 expected: KafkaProvider::type_name(),
318 actual: provider.type_name(),
319 })?;
320
321 let _timer = metrics::METRIC_KAFKA_READ_ELAPSED.start_timer();
322
323 let client = self
325 .client_manager
326 .get_or_insert(provider)
327 .await?
328 .client()
329 .clone();
330
331 if self.overwrite_entry_start_id {
332 let start_offset =
333 client
334 .get_offset(OffsetAt::Earliest)
335 .await
336 .context(GetOffsetSnafu {
337 topic: &provider.topic,
338 })?;
339
340 if entry_id as i64 <= start_offset {
341 warn!(
342 "The entry_id: {} is less than start_offset: {}, topic: {}. Overwriting entry_id with start_offset",
343 entry_id, start_offset, &provider.topic
344 );
345
346 entry_id = start_offset as u64;
347 }
348 }
349
350 let end_offset = client
355 .get_offset(OffsetAt::Latest)
356 .await
357 .context(GetOffsetSnafu {
358 topic: &provider.topic,
359 })?;
360 let latest_offset = (end_offset as u64).saturating_sub(1);
361 self.topic_stats
362 .entry(provider.clone())
363 .and_modify(|stat| {
364 stat.latest_offset = stat.latest_offset.max(latest_offset);
365 })
366 .or_insert_with(|| TopicStat {
367 latest_offset,
368 record_size: 0,
369 record_num: 0,
370 });
371
372 let range = entry_id..end_offset as u64;
373 if range.is_empty() {
374 warn!("No new entries in range {:?} of ns {}", range, provider);
375 return Ok(futures_util::stream::empty().boxed());
376 }
377
378 let iterator = RegionWalRange::new(range, self.max_batch_bytes);
379 debug!("Reading entries with {:?} of ns {}", iterator, provider);
380
381 let mut stream_consumer = ConsumerBuilder::default()
383 .client(client)
384 .buffer(RecordsBuffer::new(iterator))
386 .max_batch_size(self.max_batch_bytes)
387 .max_wait_ms(self.consumer_wait_timeout.as_millis() as u32)
388 .build()
389 .unwrap();
390
391 let mut entry_records: HashMap<RegionId, Vec<Record>> = HashMap::new();
393 let provider = provider.clone();
394 let stream = async_stream::stream!({
395 let now = Instant::now();
396 while let Some(consume_result) = stream_consumer.next().await {
397 let (record_and_offset, high_watermark) =
401 consume_result.context(ConsumeRecordSnafu {
402 topic: &provider.topic,
403 })?;
404 let (kafka_record, offset) = (record_and_offset.record, record_and_offset.offset);
405
406 debug!(
407 "Read a record at offset {} for topic {}, high watermark: {}",
408 offset, provider.topic, high_watermark
409 );
410
411 if kafka_record.value.is_some() {
413 let key = kafka_record.key;
414 match Record::try_from_parts(key.as_deref(), kafka_record.value) {
415 Ok(record) => {
416 if let Some(mut entry) =
418 maybe_emit_entry(&provider, record, &mut entry_records)?
419 {
420 entry.set_entry_id(offset as u64);
424 yield Ok(vec![entry]);
425 }
426 }
427 Err(err) => {
428 if key.as_deref().is_some_and(is_record_meta_key) {
435 yield Err(err);
436 return;
437 }
438 warn!(
439 "Skipping unparsable record at offset {} of topic {}, reason: {}",
440 offset, provider.topic, err
441 );
442 }
443 }
444 }
445
446 if check_termination(offset, end_offset) {
448 if let Some(entries) = remaining_entries(&provider, &mut entry_records) {
449 yield Ok(entries);
450 }
451 break;
452 }
453 }
454
455 metrics::METRIC_KAFKA_READ_BYTES_TOTAL.inc_by(stream_consumer.total_fetched_bytes());
456
457 info!(
458 "Fetched {} bytes from topic: {}, start_entry_id: {}, end_offset: {}, elapsed: {:?}",
459 ReadableSize(stream_consumer.total_fetched_bytes()),
460 stream_consumer.topic(),
461 entry_id,
462 end_offset,
463 now.elapsed()
464 );
465 });
466 Ok(Box::pin(stream))
467 }
468
469 async fn create_namespace(&self, _provider: &Provider) -> Result<()> {
471 Ok(())
472 }
473
474 async fn delete_namespace(&self, _provider: &Provider) -> Result<()> {
476 Ok(())
477 }
478
479 async fn list_namespaces(&self) -> Result<Vec<Provider>> {
481 Ok(vec![])
482 }
483
484 async fn obsolete(
488 &self,
489 _provider: &Provider,
490 _region_id: RegionId,
491 _entry_id: EntryId,
492 ) -> Result<()> {
493 Ok(())
495 }
496
497 async fn obsolete_all(&self, _provider: &Provider, _region_id: RegionId) -> Result<()> {
498 Ok(())
499 }
500
501 fn latest_entry_id(&self, provider: &Provider) -> Result<EntryId> {
503 let provider = provider
504 .as_kafka_provider()
505 .with_context(|| InvalidProviderSnafu {
506 expected: KafkaProvider::type_name(),
507 actual: provider.type_name(),
508 })?;
509
510 let stat = self
511 .topic_stats
512 .get(provider)
513 .as_deref()
514 .copied()
515 .unwrap_or_default();
516
517 Ok(stat.latest_offset)
518 }
519
520 async fn stop(&self) -> Result<()> {
522 Ok(())
523 }
524}
525
526fn check_termination(offset: i64, end_offset: i64) -> bool {
527 if offset >= end_offset {
529 debug!("Stream consumer terminates at offset {}", offset);
530 true
532 } else {
533 false
534 }
535}
536
537#[cfg(test)]
538mod tests {
539
540 use std::assert_matches;
541 use std::collections::HashMap;
542 use std::sync::Arc;
543 use std::time::Duration;
544
545 use common_base::readable_size::ReadableSize;
546 use common_meta::datanode::TopicStatsReporter;
547 use common_telemetry::info;
548 use common_wal::config::kafka::DatanodeKafkaConfig;
549 use common_wal::config::kafka::common::KafkaConnectionConfig;
550 use common_wal::maybe_skip_kafka_integration_test;
551 use common_wal::test_util::get_kafka_endpoints;
552 use dashmap::DashMap;
553 use futures::TryStreamExt;
554 use rand::Rng;
555 use rand::prelude::SliceRandom;
556 use rskafka::client::partition::{Compression, OffsetAt};
557 use rskafka::record::Record as KafkaRecord;
558 use store_api::logstore::LogStore;
559 use store_api::logstore::entry::{Entry, MultiplePartEntry, MultiplePartHeader, NaiveEntry};
560 use store_api::logstore::provider::Provider;
561 use store_api::storage::RegionId;
562
563 use super::build_entry;
564 use crate::error;
565 use crate::kafka::log_store::{KafkaLogStore, PeriodicTopicStatsReporter, TopicStat};
566 use crate::kafka::util::record::{ESTIMATED_META_SIZE, convert_to_kafka_records};
567
568 #[test]
569 fn test_build_naive_entry() {
570 let provider = Provider::kafka_provider("my_topic".to_string());
571 let region_id = RegionId::new(1, 1);
572 let entry = build_entry(vec![1; 100], 1, region_id, &provider, 120);
573
574 assert_eq!(
575 entry.into_naive_entry().unwrap(),
576 NaiveEntry {
577 provider,
578 region_id,
579 entry_id: 1,
580 data: vec![1; 100]
581 }
582 )
583 }
584
585 #[test]
586 fn test_build_into_multiple_part_entry() {
587 let provider = Provider::kafka_provider("my_topic".to_string());
588 let region_id = RegionId::new(1, 1);
589 let entry = build_entry(vec![1; 100], 1, region_id, &provider, 50);
590
591 assert_eq!(
592 entry.into_multiple_part_entry().unwrap(),
593 MultiplePartEntry {
594 provider: provider.clone(),
595 region_id,
596 entry_id: 1,
597 headers: vec![MultiplePartHeader::First, MultiplePartHeader::Last],
598 parts: vec![vec![1; 50], vec![1; 50]],
599 }
600 );
601
602 let region_id = RegionId::new(1, 1);
603 let entry = build_entry(vec![1; 100], 1, region_id, &provider, 21);
604
605 assert_eq!(
606 entry.into_multiple_part_entry().unwrap(),
607 MultiplePartEntry {
608 provider,
609 region_id,
610 entry_id: 1,
611 headers: vec![
612 MultiplePartHeader::First,
613 MultiplePartHeader::Middle(1),
614 MultiplePartHeader::Middle(2),
615 MultiplePartHeader::Middle(3),
616 MultiplePartHeader::Last
617 ],
618 parts: vec![
619 vec![1; 21],
620 vec![1; 21],
621 vec![1; 21],
622 vec![1; 21],
623 vec![1; 16]
624 ],
625 }
626 )
627 }
628
629 fn generate_entries(
630 logstore: &KafkaLogStore,
631 provider: &Provider,
632 num_entries: usize,
633 region_id: RegionId,
634 data_len: usize,
635 ) -> Vec<Entry> {
636 (0..num_entries)
637 .map(|_| {
638 let data: Vec<u8> = (0..data_len).map(|_| rand::random::<u8>()).collect();
639 logstore.entry(data, 0, region_id, provider).unwrap()
641 })
642 .collect()
643 }
644
645 async fn prepare_topic(logstore: &KafkaLogStore, topic_name: &str) {
646 let controller_client = logstore.client_manager.controller_client();
647 controller_client
648 .create_topic(topic_name.to_string(), 1, 1, 5000)
649 .await
650 .unwrap();
651 }
652
653 async fn new_test_logstore(max_batch_bytes: ReadableSize) -> KafkaLogStore {
654 let config = DatanodeKafkaConfig {
655 connection: KafkaConnectionConfig {
656 broker_endpoints: get_kafka_endpoints(),
657 ..Default::default()
658 },
659 max_batch_bytes,
660 ..Default::default()
661 };
662 KafkaLogStore::try_new(&config).await.unwrap()
663 }
664
665 async fn produce_records(
666 logstore: &KafkaLogStore,
667 provider: &Provider,
668 records: Vec<KafkaRecord>,
669 ) {
670 let client = logstore
673 .client_manager
674 .get_or_insert(provider.as_kafka_provider().unwrap())
675 .await
676 .unwrap()
677 .client()
678 .clone();
679 client
680 .produce(records, Compression::NoCompression)
681 .await
682 .unwrap();
683 }
684
685 fn foreign_record(key: Option<&[u8]>) -> KafkaRecord {
687 KafkaRecord {
688 key: key.map(|key| key.to_vec()),
689 value: Some(b"foreign payload".to_vec()),
690 timestamp: chrono::Utc::now(),
691 headers: Default::default(),
692 }
693 }
694
695 async fn log_end_offset(logstore: &KafkaLogStore, provider: &Provider) -> i64 {
698 let client = logstore
699 .client_manager
700 .get_or_insert(provider.as_kafka_provider().unwrap())
701 .await
702 .unwrap();
703 client.client().get_offset(OffsetAt::Latest).await.unwrap()
704 }
705
706 fn foreign_records() -> Vec<KafkaRecord> {
710 vec![
711 foreign_record(None),
712 foreign_record(Some(b"not-a-json-key")),
713 foreign_record(Some(b"[1, 2, 3]")),
714 foreign_record(Some(br#"{"hello": "world"}"#)),
715 ]
716 }
717
718 #[tokio::test]
719 async fn test_append_batch_basic() {
720 common_telemetry::init_default_ut_logging();
721 maybe_skip_kafka_integration_test!();
722 let logstore = new_test_logstore(ReadableSize::kb(32)).await;
723 let topic_name = uuid::Uuid::new_v4().to_string();
724 prepare_topic(&logstore, &topic_name).await;
725 let provider = Provider::kafka_provider(topic_name);
726
727 let region_entries = (0..5)
728 .map(|i| {
729 let region_id = RegionId::new(1, i);
730 (
731 region_id,
732 generate_entries(&logstore, &provider, 20, region_id, 1024),
733 )
734 })
735 .collect::<HashMap<RegionId, Vec<_>>>();
736
737 let mut all_entries = region_entries
738 .values()
739 .flatten()
740 .cloned()
741 .collect::<Vec<_>>();
742 all_entries.shuffle(&mut rand::rng());
743
744 let response = logstore.append_batch(all_entries.clone()).await.unwrap();
745 assert_eq!(response.last_entry_ids.len(), 5);
747 let got_entries = logstore
748 .read(&provider, 0)
749 .await
750 .unwrap()
751 .try_collect::<Vec<_>>()
752 .await
753 .unwrap()
754 .into_iter()
755 .flatten()
756 .collect::<Vec<_>>();
757 for (region_id, _) in region_entries {
758 let expected_entries = all_entries
759 .iter()
760 .filter(|entry| entry.region_id() == region_id)
761 .cloned()
762 .collect::<Vec<_>>();
763 let mut actual_entries = got_entries
764 .iter()
765 .filter(|entry| entry.region_id() == region_id)
766 .cloned()
767 .collect::<Vec<_>>();
768 actual_entries
769 .iter_mut()
770 .for_each(|entry| entry.set_entry_id(0));
771 assert_eq!(expected_entries, actual_entries);
772 }
773 let latest_entry_id = logstore.latest_entry_id(&provider).unwrap();
774 let client = logstore
775 .client_manager
776 .get_or_insert(provider.as_kafka_provider().unwrap())
777 .await
778 .unwrap();
779 assert_eq!(latest_entry_id, 99);
780 let latest = client.client().get_offset(OffsetAt::Latest).await.unwrap();
782 assert_eq!(latest, 100);
783 }
784
785 #[tokio::test]
786 async fn test_append_batch_basic_large() {
787 common_telemetry::init_default_ut_logging();
788 maybe_skip_kafka_integration_test!();
789 let data_size_kb = rand::rng().random_range(9..31usize);
790 info!("Entry size: {}Ki", data_size_kb);
791 let logstore = new_test_logstore(ReadableSize::kb(8)).await;
792 let topic_name = uuid::Uuid::new_v4().to_string();
793 prepare_topic(&logstore, &topic_name).await;
794 let provider = Provider::kafka_provider(topic_name);
795 let region_entries = (0..5)
796 .map(|i| {
797 let region_id = RegionId::new(1, i);
798 (
799 region_id,
800 generate_entries(&logstore, &provider, 20, region_id, data_size_kb * 1024),
801 )
802 })
803 .collect::<HashMap<RegionId, Vec<_>>>();
804
805 let mut all_entries = region_entries
806 .values()
807 .flatten()
808 .cloned()
809 .collect::<Vec<_>>();
810 assert_matches!(all_entries[0], Entry::MultiplePart(_));
811 all_entries.shuffle(&mut rand::rng());
812
813 let response = logstore.append_batch(all_entries.clone()).await.unwrap();
814 assert_eq!(response.last_entry_ids.len(), 5);
816 let got_entries = logstore
817 .read(&provider, 0)
818 .await
819 .unwrap()
820 .try_collect::<Vec<_>>()
821 .await
822 .unwrap()
823 .into_iter()
824 .flatten()
825 .collect::<Vec<_>>();
826 for (region_id, _) in region_entries {
827 let expected_entries = all_entries
828 .iter()
829 .filter(|entry| entry.region_id() == region_id)
830 .cloned()
831 .collect::<Vec<_>>();
832 let mut actual_entries = got_entries
833 .iter()
834 .filter(|entry| entry.region_id() == region_id)
835 .cloned()
836 .collect::<Vec<_>>();
837 actual_entries
838 .iter_mut()
839 .for_each(|entry| entry.set_entry_id(0));
840 assert_eq!(expected_entries, actual_entries);
841 }
842 let high_wathermark = logstore.latest_entry_id(&provider).unwrap();
843 assert_eq!(high_wathermark, (data_size_kb as u64 / 8 + 1) * 20 * 5 - 1);
844 }
845
846 #[tokio::test]
847 async fn test_read_skips_unparsable_record_inside_multipart_entry() {
848 maybe_skip_kafka_integration_test!();
849 let logstore = new_test_logstore(ReadableSize::kb(8)).await;
850 let topic_name = uuid::Uuid::new_v4().to_string();
851 prepare_topic(&logstore, &topic_name).await;
852 let provider = Provider::kafka_provider(topic_name);
853
854 let region_id = RegionId::new(1, 1);
855 let max_data_size = 8 * 1024 - ESTIMATED_META_SIZE;
857 let data = vec![7u8; max_data_size * 2 + 10];
858 let entry = logstore
859 .entry(data.clone(), 0, region_id, &provider)
860 .unwrap();
861 assert_matches!(entry, Entry::MultiplePart(_));
862 let mut records = convert_to_kafka_records(entry).unwrap();
863 assert_eq!(records.len(), 3);
864
865 let last = records.pop().unwrap();
866 produce_records(
868 &logstore,
869 &provider,
870 vec![foreign_record(Some(b"not-a-json-key"))],
871 )
872 .await;
873 produce_records(&logstore, &provider, vec![records[0].clone()]).await;
874 produce_records(
875 &logstore,
876 &provider,
877 vec![foreign_record(Some(br#"{"hello": "world"}"#))],
878 )
879 .await;
880 produce_records(&logstore, &provider, vec![records[1].clone()]).await;
881 produce_records(
882 &logstore,
883 &provider,
884 vec![foreign_record(Some(b"not-a-json-key"))],
885 )
886 .await;
887 produce_records(&logstore, &provider, vec![last]).await;
888
889 let entries = logstore
890 .read(&provider, 0)
891 .await
892 .unwrap()
893 .try_collect::<Vec<_>>()
894 .await
895 .unwrap()
896 .into_iter()
897 .flatten()
898 .collect::<Vec<_>>();
899 assert_eq!(entries.len(), 1);
900 let entry = entries
901 .into_iter()
902 .next()
903 .unwrap()
904 .into_multiple_part_entry()
905 .unwrap();
906 assert_eq!(
907 entry.headers,
908 vec![
909 MultiplePartHeader::First,
910 MultiplePartHeader::Middle(1),
911 MultiplePartHeader::Last
912 ]
913 );
914 assert_eq!(entry.parts.concat(), data);
915 }
916
917 #[tokio::test]
918 async fn test_read_skips_unparsable_record_across_regions() {
919 maybe_skip_kafka_integration_test!();
920 let logstore = new_test_logstore(ReadableSize::kb(8)).await;
921 let topic_name = uuid::Uuid::new_v4().to_string();
922 prepare_topic(&logstore, &topic_name).await;
923 let provider = Provider::kafka_provider(topic_name);
924
925 let region_a = RegionId::new(1, 1);
926 let region_b = RegionId::new(1, 2);
927 let max_data_size = 8 * 1024 - ESTIMATED_META_SIZE;
928 let data_a = vec![1u8; max_data_size * 2 + 10];
929 let data_b = vec![2u8; max_data_size * 2 + 10];
930 let records_a = convert_to_kafka_records(
931 logstore
932 .entry(data_a.clone(), 0, region_a, &provider)
933 .unwrap(),
934 )
935 .unwrap();
936 let records_b = convert_to_kafka_records(
937 logstore
938 .entry(data_b.clone(), 0, region_b, &provider)
939 .unwrap(),
940 )
941 .unwrap();
942 assert_eq!(records_a.len(), 3);
943 assert_eq!(records_b.len(), 3);
944
945 for i in 0..3 {
947 produce_records(&logstore, &provider, vec![records_a[i].clone()]).await;
948 produce_records(&logstore, &provider, vec![records_b[i].clone()]).await;
949 produce_records(
950 &logstore,
951 &provider,
952 vec![foreign_record(Some(b"not-a-json-key"))],
953 )
954 .await;
955 }
956
957 let entries = logstore
958 .read(&provider, 0)
959 .await
960 .unwrap()
961 .try_collect::<Vec<_>>()
962 .await
963 .unwrap()
964 .into_iter()
965 .flatten()
966 .collect::<Vec<_>>();
967 assert_eq!(entries.len(), 2);
968
969 let mut entries = entries
970 .into_iter()
971 .map(|entry| (entry.region_id(), entry.into_multiple_part_entry().unwrap()))
972 .collect::<HashMap<_, _>>();
973 assert_eq!(entries.remove(®ion_a).unwrap().parts.concat(), data_a);
974 assert_eq!(entries.remove(®ion_b).unwrap().parts.concat(), data_b);
975 assert!(entries.is_empty());
976 }
977
978 #[tokio::test]
979 async fn test_read_finishes_when_last_record_is_unparsable() {
980 maybe_skip_kafka_integration_test!();
981 let logstore = new_test_logstore(ReadableSize::kb(32)).await;
982 let topic_name = uuid::Uuid::new_v4().to_string();
983 prepare_topic(&logstore, &topic_name).await;
984 let provider = Provider::kafka_provider(topic_name);
985
986 let region_id = RegionId::new(1, 1);
987 let mut expected = generate_entries(&logstore, &provider, 3, region_id, 128);
988 logstore.append_batch(expected.clone()).await.unwrap();
989 produce_records(
991 &logstore,
992 &provider,
993 vec![foreign_record(Some(b"not-a-json-key"))],
994 )
995 .await;
996
997 let entries = tokio::time::timeout(
998 Duration::from_secs(30),
999 logstore
1000 .read(&provider, 0)
1001 .await
1002 .unwrap()
1003 .try_collect::<Vec<_>>(),
1004 )
1005 .await
1006 .expect("read should finish after skipping the unparsable record")
1007 .unwrap()
1008 .into_iter()
1009 .flatten()
1010 .collect::<Vec<_>>();
1011
1012 expected.iter_mut().for_each(|entry| entry.set_entry_id(0));
1013 let mut entries = entries;
1014 entries.iter_mut().for_each(|entry| entry.set_entry_id(0));
1015 assert_eq!(entries, expected);
1016 }
1017
1018 #[tokio::test]
1019 async fn test_read_skips_foreign_records_of_every_kind() {
1020 maybe_skip_kafka_integration_test!();
1021 let logstore = new_test_logstore(ReadableSize::kb(32)).await;
1022 let topic_name = uuid::Uuid::new_v4().to_string();
1023 prepare_topic(&logstore, &topic_name).await;
1024 let provider = Provider::kafka_provider(topic_name);
1025
1026 for record in foreign_records() {
1028 produce_records(&logstore, &provider, vec![record]).await;
1029 }
1030
1031 let region_id = RegionId::new(1, 1);
1032 let mut expected = generate_entries(&logstore, &provider, 1, region_id, 128);
1033 logstore.append_batch(expected.clone()).await.unwrap();
1034
1035 let entries = logstore
1036 .read(&provider, 0)
1037 .await
1038 .unwrap()
1039 .try_collect::<Vec<_>>()
1040 .await
1041 .unwrap()
1042 .into_iter()
1043 .flatten()
1044 .collect::<Vec<_>>();
1045
1046 expected.iter_mut().for_each(|entry| entry.set_entry_id(0));
1047 let mut entries = entries;
1048 entries.iter_mut().for_each(|entry| entry.set_entry_id(0));
1049 assert_eq!(entries, expected);
1050 }
1051
1052 #[tokio::test]
1053 async fn test_read_finishes_when_topic_only_has_foreign_records() {
1054 maybe_skip_kafka_integration_test!();
1055 let logstore = new_test_logstore(ReadableSize::kb(32)).await;
1056 let topic_name = uuid::Uuid::new_v4().to_string();
1057 prepare_topic(&logstore, &topic_name).await;
1058 let provider = Provider::kafka_provider(topic_name);
1059
1060 for record in foreign_records() {
1061 produce_records(&logstore, &provider, vec![record]).await;
1062 }
1063
1064 assert_eq!(log_end_offset(&logstore, &provider).await, 4);
1067
1068 let entries = tokio::time::timeout(
1069 Duration::from_secs(30),
1070 logstore
1071 .read(&provider, 0)
1072 .await
1073 .unwrap()
1074 .try_collect::<Vec<_>>(),
1075 )
1076 .await
1077 .expect("read should finish after skipping all unparsable records")
1078 .unwrap();
1079
1080 assert!(entries.into_iter().flatten().next().is_none());
1081 }
1082
1083 #[tokio::test]
1084 async fn test_read_fails_on_incompatible_record_meta() {
1085 maybe_skip_kafka_integration_test!();
1086 let logstore = new_test_logstore(ReadableSize::kb(32)).await;
1087
1088 let keys: [&[u8]; 2] = [
1091 br#"{"version": 0, "tp": "Compressed", "entry_id": 0, "ns": {"region_id": 1, "topic": ""}}"#,
1093 br#"{"version": "0", "tp": "Full", "entry_id": 0, "ns": {"region_id": 1, "topic": ""}}"#,
1095 ];
1096 for key in keys {
1097 let topic_name = uuid::Uuid::new_v4().to_string();
1098 prepare_topic(&logstore, &topic_name).await;
1099 let provider = Provider::kafka_provider(topic_name);
1100 produce_records(&logstore, &provider, vec![foreign_record(Some(key))]).await;
1101
1102 let err = logstore
1103 .read(&provider, 0)
1104 .await
1105 .unwrap()
1106 .try_collect::<Vec<Vec<Entry>>>()
1107 .await
1108 .unwrap_err();
1109 assert_matches!(err, error::Error::DecodeJson { .. });
1110 }
1111 }
1112
1113 #[tokio::test]
1114 async fn test_read_fails_on_missing_middle_part() {
1115 maybe_skip_kafka_integration_test!();
1116 let logstore = new_test_logstore(ReadableSize::kb(8)).await;
1117 let topic_name = uuid::Uuid::new_v4().to_string();
1118 prepare_topic(&logstore, &topic_name).await;
1119 let provider = Provider::kafka_provider(topic_name);
1120
1121 let region_id = RegionId::new(1, 1);
1122 let max_data_size = 8 * 1024 - ESTIMATED_META_SIZE;
1124 let data = vec![7u8; max_data_size * 3 + 10];
1125 let entry = logstore.entry(data, 0, region_id, &provider).unwrap();
1126 let records = convert_to_kafka_records(entry).unwrap();
1127 assert_eq!(records.len(), 4);
1128
1129 produce_records(
1132 &logstore,
1133 &provider,
1134 vec![foreign_record(Some(b"not-a-json-key"))],
1135 )
1136 .await;
1137 produce_records(&logstore, &provider, vec![records[0].clone()]).await;
1138 produce_records(&logstore, &provider, vec![records[2].clone()]).await;
1139
1140 let err = logstore
1141 .read(&provider, 0)
1142 .await
1143 .unwrap()
1144 .try_collect::<Vec<Vec<Entry>>>()
1145 .await
1146 .unwrap_err();
1147 assert_matches!(err, error::Error::IllegalSequence { .. });
1148 }
1149
1150 #[tokio::test]
1151 async fn test_read_succeeds_on_two_part_entry() {
1152 maybe_skip_kafka_integration_test!();
1153 let logstore = new_test_logstore(ReadableSize::kb(8)).await;
1154 let topic_name = uuid::Uuid::new_v4().to_string();
1155 prepare_topic(&logstore, &topic_name).await;
1156 let provider = Provider::kafka_provider(topic_name);
1157
1158 let region_id = RegionId::new(1, 1);
1159 let max_data_size = 8 * 1024 - ESTIMATED_META_SIZE;
1161 let data = vec![7u8; max_data_size + 10];
1162 let entry = logstore
1163 .entry(data.clone(), 0, region_id, &provider)
1164 .unwrap();
1165 let records = convert_to_kafka_records(entry).unwrap();
1166 assert_eq!(records.len(), 2);
1167
1168 for record in records {
1169 produce_records(&logstore, &provider, vec![record]).await;
1170 }
1171
1172 let entries = logstore
1173 .read(&provider, 0)
1174 .await
1175 .unwrap()
1176 .try_collect::<Vec<_>>()
1177 .await
1178 .unwrap()
1179 .into_iter()
1180 .flatten()
1181 .collect::<Vec<_>>();
1182 assert_eq!(entries.len(), 1);
1183 let entry = entries
1184 .into_iter()
1185 .next()
1186 .unwrap()
1187 .into_multiple_part_entry()
1188 .unwrap();
1189 assert_eq!(
1190 entry.headers,
1191 vec![MultiplePartHeader::First, MultiplePartHeader::Last]
1192 );
1193 assert_eq!(entry.parts.concat(), data);
1194 }
1195
1196 #[tokio::test]
1197 async fn test_topic_stats_reporter() {
1198 common_telemetry::init_default_ut_logging();
1199 let topic_stats = Arc::new(DashMap::new());
1200 let provider = Provider::kafka_provider("my_topic".to_string());
1201 topic_stats.insert(
1202 provider.as_kafka_provider().unwrap().clone(),
1203 TopicStat {
1204 latest_offset: 0,
1205 record_size: 0,
1206 record_num: 0,
1207 },
1208 );
1209 let mut reporter = PeriodicTopicStatsReporter::new(topic_stats, Duration::from_secs(1));
1210 let reportable_topics = reporter.reportable_topics();
1212 assert_eq!(reportable_topics.len(), 0);
1213
1214 tokio::time::sleep(Duration::from_secs(1)).await;
1216 let reportable_topics = reporter.reportable_topics();
1217 assert_eq!(reportable_topics.len(), 1);
1218
1219 let reportable_topics = reporter.reportable_topics();
1221 assert_eq!(reportable_topics.len(), 0);
1222 }
1223}