Skip to main content

log_store/raft_engine/
log_store.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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                // TODO(hl): the retval of purge_expired_files indicates the namespaces need to be compact,
65                // which is useful when monitoring regions failed to flush it's memtable to SSTs.
66                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
85/// Key used by the periodic WAL sync task. Overwritten each period so the
86/// write stays non-empty on raft-engine 0.4.2 (see [`durable_sync`]).
87const PERIODIC_SYNC_MARKER_KEY: &[u8] = b"$wal_periodic_sync";
88
89/// Force an fsync of previously appended WAL data on raft-engine 0.4.2.
90///
91/// `Engine::sync()` writes an empty [`LogBatch`] with `sync = true`, but
92/// 0.4.2's `Engine::write` returns early for empty batches *before* the
93/// fsync (tikv/raft-engine#395). Upstream fixed this in tikv/raft-engine#400,
94/// which is not on crates.io yet. Issuing a minimal non-empty put with
95/// `sync = true` reaches the fsync path on 0.4.2.
96///
97/// `batch` must be empty and can be reused across calls: a successful
98/// `Engine::write` drains its items but keeps its encoding buffer. On error the
99/// batch may be left partially encoded, so callers must discard it.
100fn durable_sync(engine: &Engine, batch: &mut LogBatch) -> raft_engine::Result<usize> {
101    debug_assert!(batch.is_empty());
102    // The value is unused, but it must not decode as a `NamespaceImpl`: the
103    // marker key sorts after `NAMESPACE_PREFIX`, inside the range scanned by
104    // `list_namespaces`. Overwriting the same key keeps the marker bounded.
105    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 reused across ticks to avoid allocating a new one each period.
112    /// `None` after a failed sync; a fresh batch is created on the next tick.
113    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            // The batch may be partially encoded after a failed write; drop it.
132            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    /// Converts entries to `LogBatch` and checks if entry ids are valid.
215    /// Returns the `LogBatch` converted along with the last entry id
216    /// to append in each namespace(region).
217    fn entries_to_batch(
218        &self,
219        entries: Vec<Entry>,
220    ) -> Result<(LogBatch, HashMap<RegionId, EntryId>)> {
221        // Records the last entry id for each region's entries.
222        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                    // this entry is the first in batch of given region.
243                    if let Some(first_index) = self.engine.first_index(region_id.as_u64()) {
244                        // ensure the first in batch does not override compacted entry.
245                        ensure!(
246                            entry_id > first_index,
247                            OverrideCompactedEntrySnafu {
248                                namespace: region_id,
249                                first_index,
250                                attempt_index: entry_id,
251                            }
252                        );
253                    }
254                    // ensure the first in batch does not form a hole in raft-engine.
255                    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    /// Appends a batch of entries to logstore. `RaftEngineLogStore` assures the atomicity of
312    /// batch append.
313    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    /// Create a stream of entries from logstore in the given namespace. The end of stream is
337    /// determined by the current "last index" of the namespace.
338    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                        // reader side closed, cancel following reads
389                        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    /// Appends a batch of entries and returns a response containing a map where the key is a region id
565    /// while the value is the id of the last successfully written entry of the region.
566    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    /// Regression for #9712: periodic sync must write a non-empty batch so
605    /// raft-engine 0.4.2 actually fsyncs (empty `Engine::sync()` is a no-op).
606    #[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        // Control: empty sync leaves no marker and returns Ok without fsync
617        // on 0.4.2 (we cannot count fsyncs here, but we can show the empty
618        // path writes nothing).
619        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        // Reusing the drained batch overwrites the same key and still writes.
640        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        // The marker lies in the range scanned by `list_namespaces` and must
667        // not be reported as a namespace.
668        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            // Entry[0] from region 0.
953            EntryImpl::create(0, 0, [b'0'; 4096].to_vec()).into(),
954            // Entry[0] from region 1.
955            EntryImpl::create(0, 1, [b'1'; 4096].to_vec()).into(),
956            // Entry[1] from region 1.
957            EntryImpl::create(1, 0, [b'1'; 4096].to_vec()).into(),
958            // Entry[1] from region 0.
959            EntryImpl::create(1, 1, [b'0'; 4096].to_vec()).into(),
960            // Entry[2] from region 2.
961            EntryImpl::create(2, 2, [b'2'; 4096].to_vec()).into(),
962        ];
963
964        // Ensure the last entry id returned for each region is the expected one.
965        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}