1pub 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
43pub type EntryId = store_api::logstore::entry::Id;
45pub type WalEntryStream<'a> = BoxStream<'a, Result<(EntryId, WalEntry)>>;
47
48#[derive(Debug)]
52pub struct Wal<S> {
53 store: Arc<S>,
55}
56
57impl<S> Wal<S> {
58 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 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 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 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 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 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 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 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
186pub struct WalWriter<S: LogStore> {
188 store: Arc<S>,
190 entries: Vec<Entry>,
192 providers: HashMap<RegionId, Provider>,
194 encoder: WalEntryEncoder,
196}
197
198impl<S: LogStore> WalWriter<S> {
199 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 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 pub async fn write_to_wal(&mut self) -> Result<AppendBatchResponse> {
227 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 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 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 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 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 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 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 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 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 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 wal.obsolete(region_id, 2, &ns).await.unwrap();
494
495 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 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}