1use std::collections::{HashMap, hash_map};
16use std::fmt::{Debug, Formatter};
17use std::sync::Arc;
18use std::time::Duration;
19
20use async_stream::stream;
21use common_runtime::{RepeatedTask, TaskFunction};
22use common_telemetry::{debug, error, info};
23use common_wal::config::raft_engine::RaftEngineConfig;
24use raft_engine::{Config, Engine, LogBatch, MessageExt, ReadableSize, RecoveryMode};
25use snafu::{OptionExt, ResultExt, ensure};
26use store_api::logstore::entry::{Entry, Id as EntryId, NaiveEntry};
27use store_api::logstore::provider::{Provider, RaftEngineProvider};
28use store_api::logstore::{AppendBatchResponse, LogStore, SendableEntryStream};
29use store_api::storage::RegionId;
30
31use crate::error::{
32 AddEntryLogBatchSnafu, DiscontinuousLogIndexSnafu, Error, FetchEntrySnafu,
33 IllegalNamespaceSnafu, IllegalStateSnafu, InvalidProviderSnafu, OverrideCompactedEntrySnafu,
34 RaftEngineSnafu, Result, StartWalTaskSnafu, StopWalTaskSnafu,
35};
36use crate::metrics;
37use crate::raft_engine::backend::SYSTEM_NAMESPACE;
38use crate::raft_engine::protos::logstore::{EntryImpl, NamespaceImpl};
39
40const NAMESPACE_PREFIX: &str = "$sys/";
41
42pub struct RaftEngineLogStore {
43 sync_write: bool,
44 sync_period: Option<Duration>,
45 read_batch_size: usize,
46 engine: Arc<Engine>,
47 gc_task: RepeatedTask<Error>,
48 sync_task: RepeatedTask<Error>,
49}
50
51pub struct PurgeExpiredFilesFunction {
52 pub engine: Arc<Engine>,
53}
54
55#[async_trait::async_trait]
56impl TaskFunction<Error> for PurgeExpiredFilesFunction {
57 fn name(&self) -> &str {
58 "RaftEngineLogStore-gc-task"
59 }
60
61 async fn call(&mut self) -> Result<()> {
62 match self.engine.purge_expired_files().context(RaftEngineSnafu) {
63 Ok(res) => {
64 let log_string = format!(
67 "Successfully purged logstore files, namespaces need compaction: {:?}",
68 res
69 );
70 if res.is_empty() {
71 debug!(log_string);
72 } else {
73 info!(log_string);
74 }
75 }
76 Err(e) => {
77 error!(e; "Failed to purge files in logstore");
78 }
79 }
80
81 Ok(())
82 }
83}
84
85const PERIODIC_SYNC_MARKER_KEY: &[u8] = b"$wal_periodic_sync";
88
89fn durable_sync(engine: &Engine, batch: &mut LogBatch) -> raft_engine::Result<usize> {
101 debug_assert!(batch.is_empty());
102 batch.put(SYSTEM_NAMESPACE, PERIODIC_SYNC_MARKER_KEY.to_vec(), vec![0])?;
106 engine.write(batch, true)
107}
108
109pub struct SyncWalTaskFunction {
110 engine: Arc<Engine>,
111 batch: Option<LogBatch>,
114}
115
116#[async_trait::async_trait]
117impl TaskFunction<Error> for SyncWalTaskFunction {
118 async fn call(&mut self) -> std::result::Result<(), Error> {
119 let engine = self.engine.clone();
120 let mut batch = self
121 .batch
122 .take()
123 .unwrap_or_else(|| LogBatch::with_capacity(1));
124 match tokio::task::spawn_blocking(move || {
125 let res = durable_sync(&engine, &mut batch);
126 (res, batch)
127 })
128 .await
129 {
130 Ok((Ok(_), batch)) => self.batch = Some(batch),
131 Ok((Err(e), _)) => {
133 error!(e; "Failed to sync raft engine log files");
134 }
135 Err(e) => {
136 error!(e; "Failed to sync raft engine log files");
137 }
138 }
139 Ok(())
140 }
141
142 fn name(&self) -> &str {
143 "SyncWalTaskFunction"
144 }
145}
146
147impl SyncWalTaskFunction {
148 pub fn new(engine: Arc<Engine>) -> Self {
149 Self {
150 engine,
151 batch: None,
152 }
153 }
154}
155
156impl RaftEngineLogStore {
157 pub async fn try_new(dir: String, config: &RaftEngineConfig) -> Result<Self> {
158 let raft_engine_config = Config {
159 dir,
160 purge_threshold: ReadableSize(config.purge_threshold.0),
161 recovery_mode: RecoveryMode::TolerateTailCorruption,
162 batch_compression_threshold: ReadableSize::kb(8),
163 target_file_size: ReadableSize(config.file_size.0),
164 enable_log_recycle: config.enable_log_recycle,
165 prefill_for_recycle: config.prefill_log_files,
166 recovery_threads: config.recovery_parallelism,
167 ..Default::default()
168 };
169 let engine = Arc::new(Engine::open(raft_engine_config).context(RaftEngineSnafu)?);
170 let gc_task = RepeatedTask::new(
171 config.purge_interval,
172 Box::new(PurgeExpiredFilesFunction {
173 engine: engine.clone(),
174 }),
175 );
176
177 let sync_task = RepeatedTask::new(
178 config.sync_period.unwrap_or(Duration::from_secs(5)),
179 Box::new(SyncWalTaskFunction::new(engine.clone())),
180 );
181
182 let log_store = Self {
183 sync_write: config.sync_write,
184 sync_period: config.sync_period,
185 read_batch_size: config.read_batch_size,
186 engine,
187 gc_task,
188 sync_task,
189 };
190 log_store.start()?;
191 Ok(log_store)
192 }
193
194 pub fn started(&self) -> bool {
195 self.gc_task.started()
196 }
197
198 fn start(&self) -> Result<()> {
199 self.gc_task
200 .start(common_runtime::global_runtime())
201 .context(StartWalTaskSnafu { name: "gc_task" })?;
202 self.sync_task
203 .start(common_runtime::global_runtime())
204 .context(StartWalTaskSnafu { name: "sync_task" })
205 }
206
207 pub fn span(&self, provider: &RaftEngineProvider) -> (Option<u64>, Option<u64>) {
208 (
209 self.engine.first_index(provider.id),
210 self.engine.last_index(provider.id),
211 )
212 }
213
214 fn entries_to_batch(
218 &self,
219 entries: Vec<Entry>,
220 ) -> Result<(LogBatch, HashMap<RegionId, EntryId>)> {
221 let mut entry_ids: HashMap<RegionId, EntryId> = HashMap::with_capacity(entries.len());
223 let mut batch = LogBatch::with_capacity(entries.len());
224
225 for e in entries {
226 let region_id = e.region_id();
227 let entry_id = e.entry_id();
228 match entry_ids.entry(region_id) {
229 hash_map::Entry::Occupied(mut o) => {
230 let prev = *o.get();
231 ensure!(
232 entry_id == prev + 1,
233 DiscontinuousLogIndexSnafu {
234 region_id,
235 last_index: prev,
236 attempt_index: entry_id
237 }
238 );
239 o.insert(entry_id);
240 }
241 hash_map::Entry::Vacant(v) => {
242 if let Some(first_index) = self.engine.first_index(region_id.as_u64()) {
244 ensure!(
246 entry_id > first_index,
247 OverrideCompactedEntrySnafu {
248 namespace: region_id,
249 first_index,
250 attempt_index: entry_id,
251 }
252 );
253 }
254 if let Some(last_index) = self.engine.last_index(region_id.as_u64()) {
256 ensure!(
257 entry_id == last_index + 1,
258 DiscontinuousLogIndexSnafu {
259 region_id,
260 last_index,
261 attempt_index: entry_id
262 }
263 );
264 }
265 v.insert(entry_id);
266 }
267 }
268 batch
269 .add_entries::<MessageType>(
270 region_id.as_u64(),
271 &[EntryImpl {
272 id: entry_id,
273 namespace_id: region_id.as_u64(),
274 data: e.into_bytes(),
275 ..Default::default()
276 }],
277 )
278 .context(AddEntryLogBatchSnafu)?;
279 }
280
281 Ok((batch, entry_ids))
282 }
283}
284
285impl Debug for RaftEngineLogStore {
286 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
287 f.debug_struct("RaftEngineLogsStore")
288 .field("sync_write", &self.sync_write)
289 .field("sync_period", &self.sync_period)
290 .field("read_batch_size", &self.read_batch_size)
291 .field("started", &self.gc_task.started())
292 .finish()
293 }
294}
295
296#[async_trait::async_trait]
297impl LogStore for RaftEngineLogStore {
298 type Error = Error;
299
300 async fn stop(&self) -> Result<()> {
301 self.gc_task
302 .stop()
303 .await
304 .context(StopWalTaskSnafu { name: "gc_task" })?;
305 self.sync_task
306 .stop()
307 .await
308 .context(StopWalTaskSnafu { name: "sync_task" })
309 }
310
311 async fn append_batch(&self, entries: Vec<Entry>) -> Result<AppendBatchResponse> {
314 metrics::METRIC_RAFT_ENGINE_APPEND_BATCH_BYTES_TOTAL.inc_by(
315 entries
316 .iter()
317 .map(|entry| entry.estimated_size())
318 .sum::<usize>() as u64,
319 );
320 let _timer = metrics::METRIC_RAFT_ENGINE_APPEND_BATCH_ELAPSED.start_timer();
321
322 ensure!(self.started(), IllegalStateSnafu);
323 if entries.is_empty() {
324 return Ok(AppendBatchResponse::default());
325 }
326
327 let (mut batch, last_entry_ids) = self.entries_to_batch(entries)?;
328 let _ = self
329 .engine
330 .write(&mut batch, self.sync_write)
331 .context(RaftEngineSnafu)?;
332
333 Ok(AppendBatchResponse { last_entry_ids })
334 }
335
336 async fn read(
339 &self,
340 provider: &Provider,
341 entry_id: EntryId,
342 ) -> Result<SendableEntryStream<'static, Entry, Self::Error>> {
343 let ns = provider
344 .as_raft_engine_provider()
345 .with_context(|| InvalidProviderSnafu {
346 expected: RaftEngineProvider::type_name(),
347 actual: provider.type_name(),
348 })?;
349 let namespace_id = ns.id;
350 let _timer = metrics::METRIC_RAFT_ENGINE_READ_ELAPSED.start_timer();
351
352 ensure!(self.started(), IllegalStateSnafu);
353 let engine = self.engine.clone();
354
355 let last_index = engine.last_index(namespace_id).unwrap_or(0);
356 let mut start_index =
357 entry_id.max(engine.first_index(namespace_id).unwrap_or(last_index + 1));
358
359 info!(
360 "Read logstore, namespace: {}, start: {}, span: {:?}",
361 namespace_id,
362 entry_id,
363 self.span(ns)
364 );
365 let max_batch_size = self.read_batch_size;
366 let (tx, mut rx) = tokio::sync::mpsc::channel(max_batch_size);
367 let _handle = common_runtime::spawn_global(async move {
368 while start_index <= last_index {
369 let mut vec = Vec::with_capacity(max_batch_size);
370 match engine
371 .fetch_entries_to::<MessageType>(
372 namespace_id,
373 start_index,
374 last_index + 1,
375 Some(max_batch_size),
376 &mut vec,
377 )
378 .context(FetchEntrySnafu {
379 ns: namespace_id,
380 start: start_index,
381 end: last_index,
382 max_size: max_batch_size,
383 }) {
384 Ok(_) => {
385 if let Some(last_entry) = vec.last() {
386 start_index = last_entry.id + 1;
387 }
388 if tx.send(Ok(vec)).await.is_err() {
390 break;
391 }
392 }
393 Err(e) => {
394 let _ = tx.send(Err(e)).await;
395 break;
396 }
397 }
398 }
399 });
400
401 let s = stream!({
402 while let Some(res) = rx.recv().await {
403 let res = res?;
404
405 yield Ok(res.into_iter().map(Entry::from).collect::<Vec<_>>());
406 }
407 });
408 Ok(Box::pin(s))
409 }
410
411 async fn create_namespace(&self, ns: &Provider) -> Result<()> {
412 let ns = ns
413 .as_raft_engine_provider()
414 .with_context(|| InvalidProviderSnafu {
415 expected: RaftEngineProvider::type_name(),
416 actual: ns.type_name(),
417 })?;
418 let namespace_id = ns.id;
419 ensure!(
420 namespace_id != SYSTEM_NAMESPACE,
421 IllegalNamespaceSnafu { ns: namespace_id }
422 );
423 ensure!(self.started(), IllegalStateSnafu);
424 let key = format!("{}{}", NAMESPACE_PREFIX, namespace_id)
425 .as_bytes()
426 .to_vec();
427 let mut batch = LogBatch::with_capacity(1);
428 batch
429 .put_message::<NamespaceImpl>(
430 SYSTEM_NAMESPACE,
431 key,
432 &NamespaceImpl {
433 id: namespace_id,
434 ..Default::default()
435 },
436 )
437 .context(RaftEngineSnafu)?;
438 let _ = self
439 .engine
440 .write(&mut batch, true)
441 .context(RaftEngineSnafu)?;
442 Ok(())
443 }
444
445 async fn delete_namespace(&self, ns: &Provider) -> Result<()> {
446 let ns = ns
447 .as_raft_engine_provider()
448 .with_context(|| InvalidProviderSnafu {
449 expected: RaftEngineProvider::type_name(),
450 actual: ns.type_name(),
451 })?;
452 let namespace_id = ns.id;
453 ensure!(
454 namespace_id != SYSTEM_NAMESPACE,
455 IllegalNamespaceSnafu { ns: namespace_id }
456 );
457 ensure!(self.started(), IllegalStateSnafu);
458 let key = format!("{}{}", NAMESPACE_PREFIX, namespace_id)
459 .as_bytes()
460 .to_vec();
461 let mut batch = LogBatch::with_capacity(1);
462 batch.delete(SYSTEM_NAMESPACE, key);
463 let _ = self
464 .engine
465 .write(&mut batch, true)
466 .context(RaftEngineSnafu)?;
467 Ok(())
468 }
469
470 async fn list_namespaces(&self) -> Result<Vec<Provider>> {
471 ensure!(self.started(), IllegalStateSnafu);
472 let mut namespaces: Vec<Provider> = vec![];
473 self.engine
474 .scan_messages::<NamespaceImpl, _>(
475 SYSTEM_NAMESPACE,
476 Some(NAMESPACE_PREFIX.as_bytes()),
477 None,
478 false,
479 |_, v| {
480 namespaces.push(Provider::RaftEngine(RaftEngineProvider { id: v.id }));
481 true
482 },
483 )
484 .context(RaftEngineSnafu)?;
485 Ok(namespaces)
486 }
487
488 fn entry(
489 &self,
490 data: Vec<u8>,
491 entry_id: EntryId,
492 region_id: RegionId,
493 provider: &Provider,
494 ) -> Result<Entry> {
495 debug_assert_eq!(
496 provider.as_raft_engine_provider().unwrap().id,
497 region_id.as_u64()
498 );
499 Ok(Entry::Naive(NaiveEntry {
500 provider: provider.clone(),
501 region_id,
502 entry_id,
503 data,
504 }))
505 }
506
507 async fn obsolete(
508 &self,
509 provider: &Provider,
510 _region_id: RegionId,
511 entry_id: EntryId,
512 ) -> Result<()> {
513 let ns = provider
514 .as_raft_engine_provider()
515 .with_context(|| InvalidProviderSnafu {
516 expected: RaftEngineProvider::type_name(),
517 actual: provider.type_name(),
518 })?;
519 let namespace_id = ns.id;
520 ensure!(self.started(), IllegalStateSnafu);
521 let obsoleted = self.engine.compact_to(namespace_id, entry_id + 1);
522 info!(
523 "Namespace {} obsoleted {} entries, compacted index: {}, span: {:?}",
524 namespace_id,
525 obsoleted,
526 entry_id,
527 self.span(ns)
528 );
529 Ok(())
530 }
531
532 async fn obsolete_all(&self, provider: &Provider, region_id: RegionId) -> Result<()> {
533 let latest_entry_id = self.latest_entry_id(provider)?;
534 self.obsolete(provider, region_id, latest_entry_id).await?;
535 self.delete_namespace(provider).await
536 }
537
538 fn latest_entry_id(&self, provider: &Provider) -> Result<EntryId> {
539 let ns = provider
540 .as_raft_engine_provider()
541 .with_context(|| InvalidProviderSnafu {
542 expected: RaftEngineProvider::type_name(),
543 actual: provider.type_name(),
544 })?;
545 let namespace_id = ns.id;
546 let last_index = self.engine.last_index(namespace_id).unwrap_or(0);
547 Ok(last_index)
548 }
549}
550
551#[derive(Debug, Clone)]
552struct MessageType;
553
554impl MessageExt for MessageType {
555 type Entry = EntryImpl;
556
557 fn index(e: &Self::Entry) -> u64 {
558 e.id
559 }
560}
561
562#[cfg(test)]
563impl RaftEngineLogStore {
564 async fn append(&self, entry: Entry) -> Result<store_api::logstore::AppendResponse> {
567 let response = self.append_batch(vec![entry]).await?;
568 if let Some((_, last_entry_id)) = response.last_entry_ids.into_iter().next() {
569 return Ok(store_api::logstore::AppendResponse { last_entry_id });
570 }
571 unreachable!()
572 }
573}
574
575#[cfg(test)]
576mod tests {
577 use std::collections::HashSet;
578 use std::time::Duration;
579
580 use common_base::readable_size::ReadableSize;
581 use common_telemetry::debug;
582 use common_test_util::temp_dir::{TempDir, create_temp_dir};
583 use futures_util::StreamExt;
584 use store_api::logstore::{LogStore, SendableEntryStream};
585
586 use super::*;
587 use crate::error::Error;
588 use crate::raft_engine::log_store::RaftEngineLogStore;
589 use crate::raft_engine::protos::logstore::EntryImpl;
590
591 #[tokio::test]
592 async fn test_open_logstore() {
593 let dir = create_temp_dir("raft-engine-logstore-test");
594 let logstore = RaftEngineLogStore::try_new(
595 dir.path().to_str().unwrap().to_string(),
596 &RaftEngineConfig::default(),
597 )
598 .await
599 .unwrap();
600 let namespaces = logstore.list_namespaces().await.unwrap();
601 assert_eq!(0, namespaces.len());
602 }
603
604 #[tokio::test]
607 async fn test_durable_sync_writes_non_empty_batch() {
608 let dir = create_temp_dir("raft-engine-durable-sync-test");
609 let cfg = Config {
610 dir: dir.path().to_str().unwrap().to_string(),
611 recovery_mode: RecoveryMode::TolerateTailCorruption,
612 ..Default::default()
613 };
614 let engine = Engine::open(cfg).unwrap();
615
616 engine.sync().unwrap();
620 assert!(
621 engine
622 .get(SYSTEM_NAMESPACE, PERIODIC_SYNC_MARKER_KEY)
623 .is_none(),
624 "Engine::sync must not create the periodic sync marker"
625 );
626
627 let mut batch = LogBatch::with_capacity(1);
628 let written = durable_sync(&engine, &mut batch).unwrap();
629 assert!(written > 0, "durable_sync must write a non-empty batch");
630 assert!(batch.is_empty(), "a successful write drains the batch");
631 assert_eq!(
632 engine
633 .get(SYSTEM_NAMESPACE, PERIODIC_SYNC_MARKER_KEY)
634 .as_deref(),
635 Some(&[0][..]),
636 "durable_sync must persist the periodic sync marker"
637 );
638
639 let written_again = durable_sync(&engine, &mut batch).unwrap();
641 assert!(written_again > 0);
642 assert!(batch.is_empty());
643 }
644
645 #[tokio::test]
646 async fn test_sync_wal_task_reuses_batch_and_keeps_namespaces() {
647 let dir = create_temp_dir("raft-engine-sync-wal-task-test");
648 let logstore = RaftEngineLogStore::try_new(
649 dir.path().to_str().unwrap().to_string(),
650 &RaftEngineConfig::default(),
651 )
652 .await
653 .unwrap();
654 logstore
655 .create_namespace(&Provider::raft_engine_provider(42))
656 .await
657 .unwrap();
658
659 let mut task = SyncWalTaskFunction::new(logstore.engine.clone());
660 for _ in 0..3 {
661 task.call().await.unwrap();
662 let batch = task.batch.as_ref().expect("batch is kept after a sync");
663 assert!(batch.is_empty());
664 }
665
666 let namespaces = logstore.list_namespaces().await.unwrap();
669 assert_eq!(vec![Provider::raft_engine_provider(42)], namespaces);
670 }
671
672 #[tokio::test]
673 async fn test_manage_namespace() {
674 let dir = create_temp_dir("raft-engine-logstore-test");
675 let logstore = RaftEngineLogStore::try_new(
676 dir.path().to_str().unwrap().to_string(),
677 &RaftEngineConfig::default(),
678 )
679 .await
680 .unwrap();
681 assert!(logstore.list_namespaces().await.unwrap().is_empty());
682
683 logstore
684 .create_namespace(&Provider::raft_engine_provider(42))
685 .await
686 .unwrap();
687 let namespaces = logstore.list_namespaces().await.unwrap();
688 assert_eq!(1, namespaces.len());
689 assert_eq!(Provider::raft_engine_provider(42), namespaces[0]);
690
691 logstore
692 .delete_namespace(&Provider::raft_engine_provider(42))
693 .await
694 .unwrap();
695 assert!(logstore.list_namespaces().await.unwrap().is_empty());
696 }
697
698 #[tokio::test]
699 async fn test_obsolete_all_removes_entries_and_namespace() {
700 let dir = create_temp_dir("raft-engine-logstore-test");
701 let logstore = RaftEngineLogStore::try_new(
702 dir.path().to_str().unwrap().to_string(),
703 &RaftEngineConfig::default(),
704 )
705 .await
706 .unwrap();
707 let region_id = RegionId::new(1, 1);
708 let provider = Provider::raft_engine_provider(region_id.as_u64());
709 logstore.create_namespace(&provider).await.unwrap();
710 for entry_id in 1..=3 {
711 logstore
712 .append(
713 EntryImpl::create(
714 entry_id,
715 region_id.as_u64(),
716 entry_id.to_string().into_bytes(),
717 )
718 .into(),
719 )
720 .await
721 .unwrap();
722 }
723
724 logstore.obsolete_all(&provider, region_id).await.unwrap();
725
726 assert_eq!(0, logstore.latest_entry_id(&provider).unwrap());
727 assert!(logstore.list_namespaces().await.unwrap().is_empty());
728 }
729
730 #[tokio::test]
731 async fn test_append_and_read() {
732 let dir = create_temp_dir("raft-engine-logstore-test");
733 let logstore = RaftEngineLogStore::try_new(
734 dir.path().to_str().unwrap().to_string(),
735 &RaftEngineConfig::default(),
736 )
737 .await
738 .unwrap();
739
740 let namespace_id = 1;
741 let cnt = 1024;
742 for i in 0..cnt {
743 let response = logstore
744 .append(
745 EntryImpl::create(i, namespace_id, i.to_string().as_bytes().to_vec()).into(),
746 )
747 .await
748 .unwrap();
749 assert_eq!(i, response.last_entry_id);
750 }
751 let mut entries = HashSet::with_capacity(1024);
752 let mut s = logstore
753 .read(&Provider::raft_engine_provider(1), 0)
754 .await
755 .unwrap();
756 while let Some(r) = s.next().await {
757 let vec = r.unwrap();
758 entries.extend(vec.into_iter().map(|e| e.entry_id()));
759 }
760 assert_eq!((0..cnt).collect::<HashSet<_>>(), entries);
761 }
762
763 async fn collect_entries(mut s: SendableEntryStream<'_, Entry, Error>) -> Vec<Entry> {
764 let mut res = vec![];
765 while let Some(r) = s.next().await {
766 res.extend(r.unwrap());
767 }
768 res
769 }
770
771 #[tokio::test]
772 async fn test_reopen() {
773 let dir = create_temp_dir("raft-engine-logstore-reopen-test");
774 {
775 let logstore = RaftEngineLogStore::try_new(
776 dir.path().to_str().unwrap().to_string(),
777 &RaftEngineConfig::default(),
778 )
779 .await
780 .unwrap();
781 assert!(
782 logstore
783 .append(EntryImpl::create(1, 1, "1".as_bytes().to_vec()).into())
784 .await
785 .is_ok()
786 );
787 let entries = logstore
788 .read(&Provider::raft_engine_provider(1), 1)
789 .await
790 .unwrap()
791 .collect::<Vec<_>>()
792 .await;
793 assert_eq!(1, entries.len());
794 logstore.stop().await.unwrap();
795 }
796
797 let logstore = RaftEngineLogStore::try_new(
798 dir.path().to_str().unwrap().to_string(),
799 &RaftEngineConfig::default(),
800 )
801 .await
802 .unwrap();
803
804 let entries = collect_entries(
805 logstore
806 .read(&Provider::raft_engine_provider(1), 1)
807 .await
808 .unwrap(),
809 )
810 .await;
811 assert_eq!(1, entries.len());
812 assert_eq!(1, entries[0].entry_id());
813 assert_eq!(1, entries[0].region_id().as_u64());
814 }
815
816 async fn wal_dir_usage(path: impl AsRef<str>) -> usize {
817 let mut size: usize = 0;
818 let mut read_dir = tokio::fs::read_dir(path.as_ref()).await.unwrap();
819 while let Ok(dir_entry) = read_dir.next_entry().await {
820 let Some(entry) = dir_entry else {
821 break;
822 };
823 if entry.file_type().await.unwrap().is_file() {
824 let file_name = entry.file_name();
825 let file_size = entry.metadata().await.unwrap().len() as usize;
826 debug!("File: {file_name:?}, size: {file_size}");
827 size += file_size;
828 }
829 }
830 size
831 }
832
833 async fn new_test_log_store(dir: &TempDir) -> RaftEngineLogStore {
834 let path = dir.path().to_str().unwrap().to_string();
835
836 let config = RaftEngineConfig {
837 file_size: ReadableSize::mb(2),
838 purge_threshold: ReadableSize::mb(4),
839 purge_interval: Duration::from_secs(5),
840 ..Default::default()
841 };
842
843 RaftEngineLogStore::try_new(path, &config).await.unwrap()
844 }
845
846 #[tokio::test]
847 async fn test_compaction() {
848 common_telemetry::init_default_ut_logging();
849 let dir = create_temp_dir("raft-engine-logstore-test");
850 let logstore = new_test_log_store(&dir).await;
851
852 let region_id = RegionId::new(1, 1);
853 let namespace_id = region_id.as_u64();
854 let namespace = Provider::raft_engine_provider(namespace_id);
855 for id in 0..4096 {
856 let entry = EntryImpl::create(id, namespace_id, [b'x'; 4096].to_vec()).into();
857 let _ = logstore.append(entry).await.unwrap();
858 }
859
860 let before_purge = wal_dir_usage(dir.path().to_str().unwrap()).await;
861 logstore
862 .obsolete(&namespace, region_id, 4000)
863 .await
864 .unwrap();
865
866 tokio::time::sleep(Duration::from_secs(6)).await;
867 let after_purge = wal_dir_usage(dir.path().to_str().unwrap()).await;
868 debug!(
869 "Before purge: {}, after purge: {}",
870 before_purge, after_purge
871 );
872 assert!(before_purge > after_purge);
873 }
874
875 #[tokio::test]
876 async fn test_obsolete() {
877 common_telemetry::init_default_ut_logging();
878 let dir = create_temp_dir("raft-engine-logstore-test");
879 let logstore = new_test_log_store(&dir).await;
880
881 let region_id = RegionId::new(1, 1);
882 let namespace_id = region_id.as_u64();
883 let namespace = Provider::raft_engine_provider(namespace_id);
884 for id in 0..1024 {
885 let entry = EntryImpl::create(id, namespace_id, [b'x'; 4096].to_vec()).into();
886 let _ = logstore.append(entry).await.unwrap();
887 }
888
889 logstore.obsolete(&namespace, region_id, 100).await.unwrap();
890 assert_eq!(101, logstore.engine.first_index(namespace_id).unwrap());
891
892 let res = logstore.read(&namespace, 100).await.unwrap();
893 let mut vec = collect_entries(res).await;
894 vec.sort_by(|a, b| a.entry_id().partial_cmp(&b.entry_id()).unwrap());
895 assert_eq!(101, vec.first().unwrap().entry_id());
896 }
897
898 #[tokio::test]
899 async fn test_append_batch() {
900 common_telemetry::init_default_ut_logging();
901 let dir = create_temp_dir("logstore-append-batch-test");
902 let logstore = new_test_log_store(&dir).await;
903
904 let entries = (0..8)
905 .flat_map(|ns_id| {
906 let data = [ns_id as u8].repeat(4096);
907 (0..16).map(move |idx| EntryImpl::create(idx, ns_id, data.clone()).into())
908 })
909 .collect();
910
911 logstore.append_batch(entries).await.unwrap();
912 for ns_id in 0..8 {
913 let namespace = &RaftEngineProvider::new(ns_id);
914 let (first, last) = logstore.span(namespace);
915 assert_eq!(0, first.unwrap());
916 assert_eq!(15, last.unwrap());
917 }
918 }
919
920 #[tokio::test]
921 async fn test_append_batch_interleaved() {
922 common_telemetry::init_default_ut_logging();
923 let dir = create_temp_dir("logstore-append-batch-test");
924 let logstore = new_test_log_store(&dir).await;
925 let entries = vec![
926 EntryImpl::create(0, 0, [b'0'; 4096].to_vec()).into(),
927 EntryImpl::create(1, 0, [b'0'; 4096].to_vec()).into(),
928 EntryImpl::create(0, 1, [b'1'; 4096].to_vec()).into(),
929 EntryImpl::create(2, 0, [b'0'; 4096].to_vec()).into(),
930 EntryImpl::create(1, 1, [b'1'; 4096].to_vec()).into(),
931 ];
932
933 logstore.append_batch(entries).await.unwrap();
934
935 assert_eq!(
936 (Some(0), Some(2)),
937 logstore.span(&RaftEngineProvider::new(0))
938 );
939 assert_eq!(
940 (Some(0), Some(1)),
941 logstore.span(&RaftEngineProvider::new(1))
942 );
943 }
944
945 #[tokio::test]
946 async fn test_append_batch_response() {
947 common_telemetry::init_default_ut_logging();
948 let dir = create_temp_dir("logstore-append-batch-test");
949 let logstore = new_test_log_store(&dir).await;
950
951 let entries = vec![
952 EntryImpl::create(0, 0, [b'0'; 4096].to_vec()).into(),
954 EntryImpl::create(0, 1, [b'1'; 4096].to_vec()).into(),
956 EntryImpl::create(1, 0, [b'1'; 4096].to_vec()).into(),
958 EntryImpl::create(1, 1, [b'0'; 4096].to_vec()).into(),
960 EntryImpl::create(2, 2, [b'2'; 4096].to_vec()).into(),
962 ];
963
964 let last_entry_ids = logstore.append_batch(entries).await.unwrap().last_entry_ids;
966 assert_eq!(last_entry_ids[&(0.into())], 1);
967 assert_eq!(last_entry_ids[&(1.into())], 1);
968 assert_eq!(last_entry_ids[&(2.into())], 2);
969 }
970}