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, WaitWalDurableSnafu, 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(Clone)]
55pub(crate) struct DurabilityBarrier(
56 Arc<dyn Fn(EntryId) -> BoxFuture<'static, Result<()>> + Send + Sync>,
57);
58
59impl DurabilityBarrier {
60 pub(crate) async fn wait(&self, entry_id: EntryId) -> Result<()> {
62 (self.0)(entry_id).await
63 }
64
65 #[cfg(test)]
67 pub(crate) fn noop() -> Self {
68 Self(Arc::new(|_| Box::pin(async { Ok(()) })))
69 }
70}
71
72impl std::fmt::Debug for DurabilityBarrier {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 f.write_str("DurabilityBarrier")
75 }
76}
77
78#[derive(Debug)]
82pub struct Wal<S> {
83 store: Arc<S>,
85}
86
87impl<S> Wal<S> {
88 pub fn new(store: Arc<S>) -> Self {
90 Self { store }
91 }
92
93 pub fn store(&self) -> &Arc<S> {
94 &self.store
95 }
96}
97
98impl<S> Clone for Wal<S> {
99 fn clone(&self) -> Self {
100 Self {
101 store: Arc::clone(&self.store),
102 }
103 }
104}
105
106impl<S: LogStore> Wal<S> {
107 pub fn writer(&self) -> WalWriter<S> {
109 WalWriter {
110 store: self.store.clone(),
111 entries: Vec::new(),
112 providers: HashMap::new(),
113 encoder: WalEntryEncoder::new(),
114 }
115 }
116
117 pub(crate) fn on_region_opened(
119 &self,
120 ) -> impl FnOnce(RegionId, EntryId, &Provider) -> BoxFuture<Result<()>> {
121 let store = self.store.clone();
122 move |region_id, last_entry_id, provider| -> BoxFuture<'_, Result<()>> {
123 if let Provider::Noop = provider {
124 debug!("Skip obsolete for region: {}", region_id);
125 return Box::pin(async move { Ok(()) });
126 }
127 Box::pin(async move {
128 store
129 .obsolete(provider, region_id, last_entry_id)
130 .await
131 .map_err(BoxedError::new)
132 .context(DeleteWalSnafu { region_id })
133 })
134 }
135 }
136
137 pub(crate) fn durability_barrier(
139 &self,
140 region_id: RegionId,
141 provider: &Provider,
142 ) -> DurabilityBarrier {
143 let store = self.store.clone();
144 let provider = provider.clone();
145 DurabilityBarrier(Arc::new(move |entry_id| {
146 let store = store.clone();
147 let provider = provider.clone();
148 Box::pin(async move {
149 if let Provider::Noop = provider {
150 return Ok(());
151 }
152 store
153 .wait_durable(&provider, entry_id)
154 .await
155 .map_err(BoxedError::new)
156 .context(WaitWalDurableSnafu { region_id })
157 })
158 }))
159 }
160
161 pub(crate) fn wal_entry_reader(
163 &self,
164 provider: &Provider,
165 region_id: RegionId,
166 location_id: Option<u64>,
167 ) -> Box<dyn WalEntryReader> {
168 match provider {
169 Provider::RaftEngine(_) => Box::new(LogStoreEntryReader::new(
170 LogStoreRawEntryReader::new(self.store.clone()),
171 )),
172 Provider::Kafka(_) | Provider::ObjectStore(_) => {
174 let reader = if let Some(location_id) = location_id {
175 LogStoreRawEntryReader::new(self.store.clone())
176 .with_wal_index(WalIndex::new(region_id, location_id))
177 } else {
178 LogStoreRawEntryReader::new(self.store.clone())
179 };
180
181 Box::new(LogStoreEntryReader::new(RegionRawEntryReader::new(
182 reader, region_id,
183 )))
184 }
185 Provider::Noop => Box::new(NoopEntryReader),
186 }
187 }
188
189 pub fn scan<'a>(
192 &'a self,
193 region_id: RegionId,
194 start_id: EntryId,
195 provider: &'a Provider,
196 ) -> Result<WalEntryStream<'a>> {
197 let mut reader = self.wal_entry_reader(provider, region_id, None);
198 reader.read(provider, start_id)
199 }
200
201 pub async fn obsolete(
203 &self,
204 region_id: RegionId,
205 last_id: EntryId,
206 provider: &Provider,
207 ) -> Result<()> {
208 if let Provider::Noop = provider {
209 return Ok(());
210 }
211 self.store
212 .obsolete(provider, region_id, last_id)
213 .await
214 .map_err(BoxedError::new)
215 .context(DeleteWalSnafu { region_id })
216 }
217
218 pub async fn delete_namespace(&self, region_id: RegionId, provider: &Provider) -> Result<()> {
220 if let Provider::Noop = provider {
221 return Ok(());
222 }
223 self.store
224 .delete_namespace(provider)
225 .await
226 .map_err(BoxedError::new)
227 .context(DeleteWalSnafu { region_id })
228 }
229
230 pub async fn obsolete_all(&self, region_id: RegionId, provider: &Provider) -> Result<()> {
233 self.store
234 .obsolete_all(provider, region_id)
235 .await
236 .map_err(BoxedError::new)
237 .context(DeleteWalSnafu { region_id })
238 }
239}
240
241pub struct WalWriter<S: LogStore> {
243 store: Arc<S>,
245 entries: Vec<Entry>,
247 providers: HashMap<RegionId, Provider>,
249 encoder: WalEntryEncoder,
251}
252
253impl<S: LogStore> WalWriter<S> {
254 pub fn add_entry(
256 &mut self,
257 region_id: RegionId,
258 entry_id: EntryId,
259 wal_entry: &WalEntry,
260 provider: &Provider,
261 ) -> Result<()> {
262 let provider = self
264 .providers
265 .entry(region_id)
266 .or_insert_with(|| provider.clone());
267
268 let data = self.encoder.encode_to_vec(wal_entry);
269 let entry = self
270 .store
271 .entry(data, entry_id, region_id, provider)
272 .map_err(BoxedError::new)
273 .context(BuildEntrySnafu { region_id })?;
274
275 self.entries.push(entry);
276
277 Ok(())
278 }
279
280 pub async fn write_to_wal(&mut self) -> Result<AppendBatchResponse> {
282 let entries = mem::take(&mut self.entries);
285 self.store
286 .append_batch(entries)
287 .await
288 .map_err(BoxedError::new)
289 .context(WriteWalSnafu)
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use api::v1::helper::{tag_column_schema, time_index_column_schema};
296 use api::v1::{
297 ArrowIpc, BulkWalEntry, ColumnDataType, Mutation, OpType, Row, Rows, Value, bulk_wal_entry,
298 value,
299 };
300 use common_recordbatch::DfRecordBatch;
301 use common_test_util::flight::encode_to_flight_data;
302 use common_test_util::temp_dir::{TempDir, create_temp_dir};
303 use datatypes::arrow;
304 use datatypes::arrow::array::{ArrayRef, TimestampMillisecondArray};
305 use datatypes::arrow::datatypes::Field;
306 use datatypes::arrow_array::StringArray;
307 use futures::TryStreamExt;
308 use log_store::raft_engine::log_store::RaftEngineLogStore;
309 use log_store::test_util::log_store_util;
310 use store_api::storage::SequenceNumber;
311
312 use super::*;
313
314 struct WalEnv {
315 _wal_dir: TempDir,
316 log_store: Option<Arc<RaftEngineLogStore>>,
317 }
318
319 impl WalEnv {
320 async fn new() -> WalEnv {
321 let wal_dir = create_temp_dir("");
322 let log_store =
323 log_store_util::create_tmp_local_file_log_store(wal_dir.path().to_str().unwrap())
324 .await;
325 WalEnv {
326 _wal_dir: wal_dir,
327 log_store: Some(Arc::new(log_store)),
328 }
329 }
330
331 fn new_wal(&self) -> Wal<RaftEngineLogStore> {
332 let log_store = self.log_store.clone().unwrap();
333 Wal::new(log_store)
334 }
335 }
336
337 fn new_mutation(op_type: OpType, sequence: SequenceNumber, rows: &[(&str, i64)]) -> Mutation {
341 let rows = rows
342 .iter()
343 .map(|(str_col, int_col)| {
344 let values = vec![
345 Value {
346 value_data: Some(value::ValueData::StringValue(str_col.to_string())),
347 },
348 Value {
349 value_data: Some(value::ValueData::TimestampMillisecondValue(*int_col)),
350 },
351 ];
352 Row { values }
353 })
354 .collect();
355 let schema = vec![
356 tag_column_schema("tag", ColumnDataType::String),
357 time_index_column_schema("ts", ColumnDataType::TimestampMillisecond),
358 ];
359
360 Mutation {
361 op_type: op_type as i32,
362 sequence,
363 rows: Some(Rows { schema, rows }),
364 write_hint: None,
365 }
366 }
367
368 #[tokio::test]
369 async fn test_write_wal() {
370 let env = WalEnv::new().await;
371 let wal = env.new_wal();
372
373 let entry = WalEntry {
374 mutations: vec![
375 new_mutation(OpType::Put, 1, &[("k1", 1), ("k2", 2)]),
376 new_mutation(OpType::Put, 2, &[("k3", 3), ("k4", 4)]),
377 ],
378 bulk_entries: vec![],
379 };
380 let mut writer = wal.writer();
381 let region_id = RegionId::new(1, 1);
383 writer
384 .add_entry(
385 region_id,
386 1,
387 &entry,
388 &Provider::raft_engine_provider(region_id.as_u64()),
389 )
390 .unwrap();
391 let region_id = RegionId::new(1, 2);
393 writer
394 .add_entry(
395 region_id,
396 1,
397 &entry,
398 &Provider::raft_engine_provider(region_id.as_u64()),
399 )
400 .unwrap();
401 let region_id = RegionId::new(1, 2);
403 writer
404 .add_entry(
405 region_id,
406 2,
407 &entry,
408 &Provider::raft_engine_provider(region_id.as_u64()),
409 )
410 .unwrap();
411
412 writer.write_to_wal().await.unwrap();
414 }
415
416 fn build_record_batch(rows: &[(&str, i64)]) -> DfRecordBatch {
417 let schema = Arc::new(arrow::datatypes::Schema::new(vec![
418 Field::new("tag", arrow::datatypes::DataType::Utf8, false),
419 Field::new(
420 "ts",
421 arrow::datatypes::DataType::Timestamp(
422 arrow::datatypes::TimeUnit::Millisecond,
423 None,
424 ),
425 false,
426 ),
427 ]));
428
429 let tag = Arc::new(StringArray::from_iter_values(
430 rows.iter().map(|r| r.0.to_string()),
431 )) as ArrayRef;
432 let ts = Arc::new(TimestampMillisecondArray::from_iter_values(
433 rows.iter().map(|r| r.1),
434 )) as ArrayRef;
435 DfRecordBatch::try_new(schema, vec![tag, ts]).unwrap()
436 }
437
438 fn build_bulk_wal_entry(sequence_number: SequenceNumber, rows: &[(&str, i64)]) -> BulkWalEntry {
439 let rb = build_record_batch(rows);
440 let (schema, rb) = encode_to_flight_data(rb);
441 let max_ts = rows.iter().map(|r| r.1).max().unwrap();
442 let min_ts = rows.iter().map(|r| r.1).min().unwrap();
443 BulkWalEntry {
444 sequence: sequence_number,
445 max_ts,
446 min_ts,
447 timestamp_index: 1,
448 body: Some(bulk_wal_entry::Body::ArrowIpc(ArrowIpc {
449 schema: schema.data_header,
450 data_header: rb.data_header,
451 payload: rb.data_body,
452 })),
453 }
454 }
455
456 fn sample_entries() -> Vec<WalEntry> {
457 vec![
458 WalEntry {
459 mutations: vec![
460 new_mutation(OpType::Put, 1, &[("k1", 1), ("k2", 2)]),
461 new_mutation(OpType::Put, 2, &[("k3", 3), ("k4", 4)]),
462 ],
463 bulk_entries: vec![],
464 },
465 WalEntry {
466 mutations: vec![new_mutation(OpType::Put, 3, &[("k1", 1), ("k2", 2)])],
467 bulk_entries: vec![],
468 },
469 WalEntry {
470 mutations: vec![
471 new_mutation(OpType::Put, 4, &[("k1", 1), ("k2", 2)]),
472 new_mutation(OpType::Put, 5, &[("k3", 3), ("k4", 4)]),
473 ],
474 bulk_entries: vec![],
475 },
476 WalEntry {
477 mutations: vec![new_mutation(OpType::Put, 6, &[("k1", 1), ("k2", 2)])],
478 bulk_entries: vec![build_bulk_wal_entry(7, &[("k1", 8), ("k2", 9)])],
479 },
480 ]
481 }
482
483 fn check_entries(
484 expect: &[WalEntry],
485 expect_start_id: EntryId,
486 actual: &[(EntryId, WalEntry)],
487 ) {
488 for (idx, (expect_entry, (actual_id, actual_entry))) in
489 expect.iter().zip(actual.iter()).enumerate()
490 {
491 let expect_id_entry = (expect_start_id + idx as u64, expect_entry);
492 assert_eq!(expect_id_entry, (*actual_id, actual_entry));
493 }
494 assert_eq!(expect.len(), actual.len());
495 }
496
497 #[tokio::test]
498 async fn test_scan_wal() {
499 let env = WalEnv::new().await;
500 let wal = env.new_wal();
501
502 let entries = sample_entries();
503 let (id1, id2) = (RegionId::new(1, 1), RegionId::new(1, 2));
504 let ns1 = Provider::raft_engine_provider(id1.as_u64());
505 let ns2 = Provider::raft_engine_provider(id2.as_u64());
506 let mut writer = wal.writer();
507 writer.add_entry(id1, 1, &entries[0], &ns1).unwrap();
508 writer.add_entry(id2, 1, &entries[0], &ns2).unwrap();
510 writer.add_entry(id1, 2, &entries[1], &ns1).unwrap();
511 writer.add_entry(id1, 3, &entries[2], &ns1).unwrap();
512 writer.add_entry(id1, 4, &entries[3], &ns1).unwrap();
513
514 writer.write_to_wal().await.unwrap();
515
516 let stream = wal.scan(id1, 1, &ns1).unwrap();
518 let actual: Vec<_> = stream.try_collect().await.unwrap();
519 check_entries(&entries, 1, &actual);
520
521 let stream = wal.scan(id1, 2, &ns1).unwrap();
523 let actual: Vec<_> = stream.try_collect().await.unwrap();
524 check_entries(&entries[1..], 2, &actual);
525
526 let stream = wal.scan(id1, 5, &ns1).unwrap();
528 let actual: Vec<_> = stream.try_collect().await.unwrap();
529 assert!(actual.is_empty());
530 }
531
532 #[tokio::test]
533 async fn test_obsolete_wal() {
534 let env = WalEnv::new().await;
535 let wal = env.new_wal();
536
537 let entries = sample_entries();
538 let mut writer = wal.writer();
539 let region_id = RegionId::new(1, 1);
540 let ns = Provider::raft_engine_provider(region_id.as_u64());
541 writer.add_entry(region_id, 1, &entries[0], &ns).unwrap();
542 writer.add_entry(region_id, 2, &entries[1], &ns).unwrap();
543 writer.add_entry(region_id, 3, &entries[2], &ns).unwrap();
544
545 writer.write_to_wal().await.unwrap();
546
547 wal.obsolete(region_id, 2, &ns).await.unwrap();
549
550 let mut writer = wal.writer();
552 writer.add_entry(region_id, 4, &entries[3], &ns).unwrap();
553 writer.write_to_wal().await.unwrap();
554
555 let stream = wal.scan(region_id, 1, &ns).unwrap();
557 let actual: Vec<_> = stream.try_collect().await.unwrap();
558 check_entries(&entries[2..], 3, &actual);
559 }
560}