Skip to main content

mito2/
wal.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
15//! Write ahead log of the engine.
16
17pub mod encoder;
18pub(crate) mod entry_distributor;
19pub(crate) mod entry_reader;
20pub(crate) mod raw_entry_reader;
21
22use std::collections::HashMap;
23use std::mem;
24use std::sync::Arc;
25
26use api::v1::WalEntry;
27use common_error::ext::BoxedError;
28use common_telemetry::debug;
29use encoder::WalEntryEncoder;
30use entry_reader::NoopEntryReader;
31use futures::future::BoxFuture;
32use futures::stream::BoxStream;
33use snafu::ResultExt;
34use store_api::logstore::entry::Entry;
35use store_api::logstore::provider::Provider;
36use store_api::logstore::{AppendBatchResponse, LogStore, WalIndex};
37use store_api::storage::RegionId;
38
39use crate::error::{BuildEntrySnafu, DeleteWalSnafu, Result, WriteWalSnafu};
40use crate::wal::entry_reader::{LogStoreEntryReader, WalEntryReader};
41use crate::wal::raw_entry_reader::{LogStoreRawEntryReader, RegionRawEntryReader};
42
43/// WAL entry id.
44pub type EntryId = store_api::logstore::entry::Id;
45/// A stream that yields tuple of WAL entry id and corresponding entry.
46pub type WalEntryStream<'a> = BoxStream<'a, Result<(EntryId, WalEntry)>>;
47
48/// Write ahead log.
49///
50/// All regions in the engine shares the same WAL instance.
51#[derive(Debug)]
52pub struct Wal<S> {
53    /// The underlying log store.
54    store: Arc<S>,
55}
56
57impl<S> Wal<S> {
58    /// Creates a new [Wal] from the log store.
59    pub fn new(store: Arc<S>) -> Self {
60        Self { store }
61    }
62
63    pub fn store(&self) -> &Arc<S> {
64        &self.store
65    }
66}
67
68impl<S> Clone for Wal<S> {
69    fn clone(&self) -> Self {
70        Self {
71            store: Arc::clone(&self.store),
72        }
73    }
74}
75
76impl<S: LogStore> Wal<S> {
77    /// Returns a writer to write to the WAL.
78    pub fn writer(&self) -> WalWriter<S> {
79        WalWriter {
80            store: self.store.clone(),
81            entries: Vec::new(),
82            providers: HashMap::new(),
83            encoder: WalEntryEncoder::new(),
84        }
85    }
86
87    /// Returns a [OnRegionOpened] function.
88    pub(crate) fn on_region_opened(
89        &self,
90    ) -> impl FnOnce(RegionId, EntryId, &Provider) -> BoxFuture<Result<()>> {
91        let store = self.store.clone();
92        move |region_id, last_entry_id, provider| -> BoxFuture<'_, Result<()>> {
93            if let Provider::Noop = provider {
94                debug!("Skip obsolete for region: {}", region_id);
95                return Box::pin(async move { Ok(()) });
96            }
97            Box::pin(async move {
98                store
99                    .obsolete(provider, region_id, last_entry_id)
100                    .await
101                    .map_err(BoxedError::new)
102                    .context(DeleteWalSnafu { region_id })
103            })
104        }
105    }
106
107    /// Returns a [WalEntryReader]
108    pub(crate) fn wal_entry_reader(
109        &self,
110        provider: &Provider,
111        region_id: RegionId,
112        location_id: Option<u64>,
113    ) -> Box<dyn WalEntryReader> {
114        match provider {
115            Provider::RaftEngine(_) => Box::new(LogStoreEntryReader::new(
116                LogStoreRawEntryReader::new(self.store.clone()),
117            )),
118            // Entries of many regions share one namespace, so the reader filters by region.
119            Provider::Kafka(_) | Provider::ObjectStore(_) => {
120                let reader = if let Some(location_id) = location_id {
121                    LogStoreRawEntryReader::new(self.store.clone())
122                        .with_wal_index(WalIndex::new(region_id, location_id))
123                } else {
124                    LogStoreRawEntryReader::new(self.store.clone())
125                };
126
127                Box::new(LogStoreEntryReader::new(RegionRawEntryReader::new(
128                    reader, region_id,
129                )))
130            }
131            Provider::Noop => Box::new(NoopEntryReader),
132        }
133    }
134
135    /// Scan entries of specific region starting from `start_id` (inclusive).
136    /// Currently only used in tests.
137    pub fn scan<'a>(
138        &'a self,
139        region_id: RegionId,
140        start_id: EntryId,
141        provider: &'a Provider,
142    ) -> Result<WalEntryStream<'a>> {
143        let mut reader = self.wal_entry_reader(provider, region_id, None);
144        reader.read(provider, start_id)
145    }
146
147    /// Mark entries whose ids `<= last_id` as deleted.
148    pub async fn obsolete(
149        &self,
150        region_id: RegionId,
151        last_id: EntryId,
152        provider: &Provider,
153    ) -> Result<()> {
154        if let Provider::Noop = provider {
155            return Ok(());
156        }
157        self.store
158            .obsolete(provider, region_id, last_id)
159            .await
160            .map_err(BoxedError::new)
161            .context(DeleteWalSnafu { region_id })
162    }
163
164    /// Deletes all WAL entries in the namespace represented by `provider`.
165    pub async fn delete_namespace(&self, region_id: RegionId, provider: &Provider) -> Result<()> {
166        if let Provider::Noop = provider {
167            return Ok(());
168        }
169        self.store
170            .delete_namespace(provider)
171            .await
172            .map_err(BoxedError::new)
173            .context(DeleteWalSnafu { region_id })
174    }
175
176    /// Marks all WAL entries of a region as obsolete and removes its dedicated namespace when
177    /// supported by the backend.
178    pub async fn obsolete_all(&self, region_id: RegionId, provider: &Provider) -> Result<()> {
179        self.store
180            .obsolete_all(provider, region_id)
181            .await
182            .map_err(BoxedError::new)
183            .context(DeleteWalSnafu { region_id })
184    }
185}
186
187/// WAL batch writer.
188pub struct WalWriter<S: LogStore> {
189    /// Log store of the WAL.
190    store: Arc<S>,
191    /// Entries to write.
192    entries: Vec<Entry>,
193    /// Providers of regions being written into.
194    providers: HashMap<RegionId, Provider>,
195    /// Cached-size single-pass encoder, reused across entries in this batch.
196    encoder: WalEntryEncoder,
197}
198
199impl<S: LogStore> WalWriter<S> {
200    /// Add a wal entry for specific region to the writer's buffer.
201    pub fn add_entry(
202        &mut self,
203        region_id: RegionId,
204        entry_id: EntryId,
205        wal_entry: &WalEntry,
206        provider: &Provider,
207    ) -> Result<()> {
208        // Gets or inserts with a newly built provider.
209        let provider = self
210            .providers
211            .entry(region_id)
212            .or_insert_with(|| provider.clone());
213
214        let data = self.encoder.encode_to_vec(wal_entry);
215        let entry = self
216            .store
217            .entry(data, entry_id, region_id, provider)
218            .map_err(BoxedError::new)
219            .context(BuildEntrySnafu { region_id })?;
220
221        self.entries.push(entry);
222
223        Ok(())
224    }
225
226    /// Write all buffered entries to the WAL.
227    pub async fn write_to_wal(&mut self) -> Result<AppendBatchResponse> {
228        // TODO(yingwen): metrics.
229
230        let entries = mem::take(&mut self.entries);
231        self.store
232            .append_batch(entries)
233            .await
234            .map_err(BoxedError::new)
235            .context(WriteWalSnafu)
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use api::v1::helper::{tag_column_schema, time_index_column_schema};
242    use api::v1::{
243        ArrowIpc, BulkWalEntry, ColumnDataType, Mutation, OpType, Row, Rows, Value, bulk_wal_entry,
244        value,
245    };
246    use common_recordbatch::DfRecordBatch;
247    use common_test_util::flight::encode_to_flight_data;
248    use common_test_util::temp_dir::{TempDir, create_temp_dir};
249    use datatypes::arrow;
250    use datatypes::arrow::array::{ArrayRef, TimestampMillisecondArray};
251    use datatypes::arrow::datatypes::Field;
252    use datatypes::arrow_array::StringArray;
253    use futures::TryStreamExt;
254    use log_store::raft_engine::log_store::RaftEngineLogStore;
255    use log_store::test_util::log_store_util;
256    use store_api::storage::SequenceNumber;
257
258    use super::*;
259
260    struct WalEnv {
261        _wal_dir: TempDir,
262        log_store: Option<Arc<RaftEngineLogStore>>,
263    }
264
265    impl WalEnv {
266        async fn new() -> WalEnv {
267            let wal_dir = create_temp_dir("");
268            let log_store =
269                log_store_util::create_tmp_local_file_log_store(wal_dir.path().to_str().unwrap())
270                    .await;
271            WalEnv {
272                _wal_dir: wal_dir,
273                log_store: Some(Arc::new(log_store)),
274            }
275        }
276
277        fn new_wal(&self) -> Wal<RaftEngineLogStore> {
278            let log_store = self.log_store.clone().unwrap();
279            Wal::new(log_store)
280        }
281    }
282
283    /// Create a new mutation from rows.
284    ///
285    /// The row format is (string, i64).
286    fn new_mutation(op_type: OpType, sequence: SequenceNumber, rows: &[(&str, i64)]) -> Mutation {
287        let rows = rows
288            .iter()
289            .map(|(str_col, int_col)| {
290                let values = vec![
291                    Value {
292                        value_data: Some(value::ValueData::StringValue(str_col.to_string())),
293                    },
294                    Value {
295                        value_data: Some(value::ValueData::TimestampMillisecondValue(*int_col)),
296                    },
297                ];
298                Row { values }
299            })
300            .collect();
301        let schema = vec![
302            tag_column_schema("tag", ColumnDataType::String),
303            time_index_column_schema("ts", ColumnDataType::TimestampMillisecond),
304        ];
305
306        Mutation {
307            op_type: op_type as i32,
308            sequence,
309            rows: Some(Rows { schema, rows }),
310            write_hint: None,
311        }
312    }
313
314    #[tokio::test]
315    async fn test_write_wal() {
316        let env = WalEnv::new().await;
317        let wal = env.new_wal();
318
319        let entry = WalEntry {
320            mutations: vec![
321                new_mutation(OpType::Put, 1, &[("k1", 1), ("k2", 2)]),
322                new_mutation(OpType::Put, 2, &[("k3", 3), ("k4", 4)]),
323            ],
324            bulk_entries: vec![],
325        };
326        let mut writer = wal.writer();
327        // Region 1 entry 1.
328        let region_id = RegionId::new(1, 1);
329        writer
330            .add_entry(
331                region_id,
332                1,
333                &entry,
334                &Provider::raft_engine_provider(region_id.as_u64()),
335            )
336            .unwrap();
337        // Region 2 entry 1.
338        let region_id = RegionId::new(1, 2);
339        writer
340            .add_entry(
341                region_id,
342                1,
343                &entry,
344                &Provider::raft_engine_provider(region_id.as_u64()),
345            )
346            .unwrap();
347        // Region 1 entry 2.
348        let region_id = RegionId::new(1, 2);
349        writer
350            .add_entry(
351                region_id,
352                2,
353                &entry,
354                &Provider::raft_engine_provider(region_id.as_u64()),
355            )
356            .unwrap();
357
358        // Test writing multiple region to wal.
359        writer.write_to_wal().await.unwrap();
360    }
361
362    fn build_record_batch(rows: &[(&str, i64)]) -> DfRecordBatch {
363        let schema = Arc::new(arrow::datatypes::Schema::new(vec![
364            Field::new("tag", arrow::datatypes::DataType::Utf8, false),
365            Field::new(
366                "ts",
367                arrow::datatypes::DataType::Timestamp(
368                    arrow::datatypes::TimeUnit::Millisecond,
369                    None,
370                ),
371                false,
372            ),
373        ]));
374
375        let tag = Arc::new(StringArray::from_iter_values(
376            rows.iter().map(|r| r.0.to_string()),
377        )) as ArrayRef;
378        let ts = Arc::new(TimestampMillisecondArray::from_iter_values(
379            rows.iter().map(|r| r.1),
380        )) as ArrayRef;
381        DfRecordBatch::try_new(schema, vec![tag, ts]).unwrap()
382    }
383
384    fn build_bulk_wal_entry(sequence_number: SequenceNumber, rows: &[(&str, i64)]) -> BulkWalEntry {
385        let rb = build_record_batch(rows);
386        let (schema, rb) = encode_to_flight_data(rb);
387        let max_ts = rows.iter().map(|r| r.1).max().unwrap();
388        let min_ts = rows.iter().map(|r| r.1).min().unwrap();
389        BulkWalEntry {
390            sequence: sequence_number,
391            max_ts,
392            min_ts,
393            timestamp_index: 1,
394            body: Some(bulk_wal_entry::Body::ArrowIpc(ArrowIpc {
395                schema: schema.data_header,
396                data_header: rb.data_header,
397                payload: rb.data_body,
398            })),
399        }
400    }
401
402    fn sample_entries() -> Vec<WalEntry> {
403        vec![
404            WalEntry {
405                mutations: vec![
406                    new_mutation(OpType::Put, 1, &[("k1", 1), ("k2", 2)]),
407                    new_mutation(OpType::Put, 2, &[("k3", 3), ("k4", 4)]),
408                ],
409                bulk_entries: vec![],
410            },
411            WalEntry {
412                mutations: vec![new_mutation(OpType::Put, 3, &[("k1", 1), ("k2", 2)])],
413                bulk_entries: vec![],
414            },
415            WalEntry {
416                mutations: vec![
417                    new_mutation(OpType::Put, 4, &[("k1", 1), ("k2", 2)]),
418                    new_mutation(OpType::Put, 5, &[("k3", 3), ("k4", 4)]),
419                ],
420                bulk_entries: vec![],
421            },
422            WalEntry {
423                mutations: vec![new_mutation(OpType::Put, 6, &[("k1", 1), ("k2", 2)])],
424                bulk_entries: vec![build_bulk_wal_entry(7, &[("k1", 8), ("k2", 9)])],
425            },
426        ]
427    }
428
429    fn check_entries(
430        expect: &[WalEntry],
431        expect_start_id: EntryId,
432        actual: &[(EntryId, WalEntry)],
433    ) {
434        for (idx, (expect_entry, (actual_id, actual_entry))) in
435            expect.iter().zip(actual.iter()).enumerate()
436        {
437            let expect_id_entry = (expect_start_id + idx as u64, expect_entry);
438            assert_eq!(expect_id_entry, (*actual_id, actual_entry));
439        }
440        assert_eq!(expect.len(), actual.len());
441    }
442
443    #[tokio::test]
444    async fn test_scan_wal() {
445        let env = WalEnv::new().await;
446        let wal = env.new_wal();
447
448        let entries = sample_entries();
449        let (id1, id2) = (RegionId::new(1, 1), RegionId::new(1, 2));
450        let ns1 = Provider::raft_engine_provider(id1.as_u64());
451        let ns2 = Provider::raft_engine_provider(id2.as_u64());
452        let mut writer = wal.writer();
453        writer.add_entry(id1, 1, &entries[0], &ns1).unwrap();
454        // Insert one entry into region2. Scan should not return this entry.
455        writer.add_entry(id2, 1, &entries[0], &ns2).unwrap();
456        writer.add_entry(id1, 2, &entries[1], &ns1).unwrap();
457        writer.add_entry(id1, 3, &entries[2], &ns1).unwrap();
458        writer.add_entry(id1, 4, &entries[3], &ns1).unwrap();
459
460        writer.write_to_wal().await.unwrap();
461
462        // Scan all contents region1
463        let stream = wal.scan(id1, 1, &ns1).unwrap();
464        let actual: Vec<_> = stream.try_collect().await.unwrap();
465        check_entries(&entries, 1, &actual);
466
467        // Scan parts of contents
468        let stream = wal.scan(id1, 2, &ns1).unwrap();
469        let actual: Vec<_> = stream.try_collect().await.unwrap();
470        check_entries(&entries[1..], 2, &actual);
471
472        // Scan out of range
473        let stream = wal.scan(id1, 5, &ns1).unwrap();
474        let actual: Vec<_> = stream.try_collect().await.unwrap();
475        assert!(actual.is_empty());
476    }
477
478    #[tokio::test]
479    async fn test_obsolete_wal() {
480        let env = WalEnv::new().await;
481        let wal = env.new_wal();
482
483        let entries = sample_entries();
484        let mut writer = wal.writer();
485        let region_id = RegionId::new(1, 1);
486        let ns = Provider::raft_engine_provider(region_id.as_u64());
487        writer.add_entry(region_id, 1, &entries[0], &ns).unwrap();
488        writer.add_entry(region_id, 2, &entries[1], &ns).unwrap();
489        writer.add_entry(region_id, 3, &entries[2], &ns).unwrap();
490
491        writer.write_to_wal().await.unwrap();
492
493        // Delete 1, 2.
494        wal.obsolete(region_id, 2, &ns).await.unwrap();
495
496        // Put 4.
497        let mut writer = wal.writer();
498        writer.add_entry(region_id, 4, &entries[3], &ns).unwrap();
499        writer.write_to_wal().await.unwrap();
500
501        // Scan all
502        let stream = wal.scan(region_id, 1, &ns).unwrap();
503        let actual: Vec<_> = stream.try_collect().await.unwrap();
504        check_entries(&entries[2..], 3, &actual);
505    }
506}