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