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};
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 ) -> Box<dyn WalEntryReader> {
167 match provider {
168 Provider::RaftEngine(_) => Box::new(LogStoreEntryReader::new(
169 LogStoreRawEntryReader::new(self.store.clone()),
170 )),
171 Provider::Kafka(_) | Provider::ObjectStore(_) => {
173 Box::new(LogStoreEntryReader::new(RegionRawEntryReader::new(
174 LogStoreRawEntryReader::new(self.store.clone()),
175 region_id,
176 )))
177 }
178 Provider::Noop => Box::new(NoopEntryReader),
179 }
180 }
181
182 pub fn scan<'a>(
185 &'a self,
186 region_id: RegionId,
187 start_id: EntryId,
188 provider: &'a Provider,
189 ) -> Result<WalEntryStream<'a>> {
190 let mut reader = self.wal_entry_reader(provider, region_id);
191 reader.read(provider, start_id)
192 }
193
194 pub async fn obsolete(
196 &self,
197 region_id: RegionId,
198 last_id: EntryId,
199 provider: &Provider,
200 ) -> Result<()> {
201 if let Provider::Noop = provider {
202 return Ok(());
203 }
204 self.store
205 .obsolete(provider, region_id, last_id)
206 .await
207 .map_err(BoxedError::new)
208 .context(DeleteWalSnafu { region_id })
209 }
210
211 pub async fn delete_namespace(&self, region_id: RegionId, provider: &Provider) -> Result<()> {
213 if let Provider::Noop = provider {
214 return Ok(());
215 }
216 self.store
217 .delete_namespace(provider)
218 .await
219 .map_err(BoxedError::new)
220 .context(DeleteWalSnafu { region_id })
221 }
222
223 pub async fn release_region(&self, region_id: RegionId, provider: &Provider) -> Result<()> {
225 if let Provider::Noop = provider {
226 return Ok(());
227 }
228 self.store
229 .release_region(provider, region_id)
230 .await
231 .map_err(BoxedError::new)
232 .context(DeleteWalSnafu { region_id })
233 }
234
235 pub async fn obsolete_all(&self, region_id: RegionId, provider: &Provider) -> Result<()> {
238 self.store
239 .obsolete_all(provider, region_id)
240 .await
241 .map_err(BoxedError::new)
242 .context(DeleteWalSnafu { region_id })
243 }
244}
245
246pub struct WalWriter<S: LogStore> {
248 store: Arc<S>,
250 entries: Vec<Entry>,
252 providers: HashMap<RegionId, Provider>,
254 encoder: WalEntryEncoder,
256}
257
258impl<S: LogStore> WalWriter<S> {
259 pub fn add_entry(
261 &mut self,
262 region_id: RegionId,
263 entry_id: EntryId,
264 wal_entry: &WalEntry,
265 provider: &Provider,
266 ) -> Result<()> {
267 let provider = self
269 .providers
270 .entry(region_id)
271 .or_insert_with(|| provider.clone());
272
273 let data = self.encoder.encode_to_vec(wal_entry);
274 let entry = self
275 .store
276 .entry(data, entry_id, region_id, provider)
277 .map_err(BoxedError::new)
278 .context(BuildEntrySnafu { region_id })?;
279
280 self.entries.push(entry);
281
282 Ok(())
283 }
284
285 pub async fn write_to_wal(&mut self) -> Result<AppendBatchResponse> {
287 let entries = mem::take(&mut self.entries);
290 self.store
291 .append_batch(entries)
292 .await
293 .map_err(BoxedError::new)
294 .context(WriteWalSnafu)
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use api::v1::helper::{tag_column_schema, time_index_column_schema};
301 use api::v1::{
302 ArrowIpc, BulkWalEntry, ColumnDataType, Mutation, OpType, Row, Rows, Value, bulk_wal_entry,
303 value,
304 };
305 use common_recordbatch::DfRecordBatch;
306 use common_test_util::flight::encode_to_flight_data;
307 use common_test_util::temp_dir::{TempDir, create_temp_dir};
308 use datatypes::arrow;
309 use datatypes::arrow::array::{ArrayRef, TimestampMillisecondArray};
310 use datatypes::arrow::datatypes::Field;
311 use datatypes::arrow_array::StringArray;
312 use futures::TryStreamExt;
313 use log_store::raft_engine::log_store::RaftEngineLogStore;
314 use log_store::test_util::log_store_util;
315 use store_api::storage::SequenceNumber;
316
317 use super::*;
318
319 struct WalEnv {
320 _wal_dir: TempDir,
321 log_store: Option<Arc<RaftEngineLogStore>>,
322 }
323
324 impl WalEnv {
325 async fn new() -> WalEnv {
326 let wal_dir = create_temp_dir("");
327 let log_store =
328 log_store_util::create_tmp_local_file_log_store(wal_dir.path().to_str().unwrap())
329 .await;
330 WalEnv {
331 _wal_dir: wal_dir,
332 log_store: Some(Arc::new(log_store)),
333 }
334 }
335
336 fn new_wal(&self) -> Wal<RaftEngineLogStore> {
337 let log_store = self.log_store.clone().unwrap();
338 Wal::new(log_store)
339 }
340 }
341
342 fn new_mutation(op_type: OpType, sequence: SequenceNumber, rows: &[(&str, i64)]) -> Mutation {
346 let rows = rows
347 .iter()
348 .map(|(str_col, int_col)| {
349 let values = vec![
350 Value {
351 value_data: Some(value::ValueData::StringValue(str_col.to_string())),
352 },
353 Value {
354 value_data: Some(value::ValueData::TimestampMillisecondValue(*int_col)),
355 },
356 ];
357 Row { values }
358 })
359 .collect();
360 let schema = vec![
361 tag_column_schema("tag", ColumnDataType::String),
362 time_index_column_schema("ts", ColumnDataType::TimestampMillisecond),
363 ];
364
365 Mutation {
366 op_type: op_type as i32,
367 sequence,
368 rows: Some(Rows { schema, rows }),
369 write_hint: None,
370 }
371 }
372
373 #[tokio::test]
374 async fn test_write_wal() {
375 let env = WalEnv::new().await;
376 let wal = env.new_wal();
377
378 let entry = WalEntry {
379 mutations: vec![
380 new_mutation(OpType::Put, 1, &[("k1", 1), ("k2", 2)]),
381 new_mutation(OpType::Put, 2, &[("k3", 3), ("k4", 4)]),
382 ],
383 bulk_entries: vec![],
384 };
385 let mut writer = wal.writer();
386 let region_id = RegionId::new(1, 1);
388 writer
389 .add_entry(
390 region_id,
391 1,
392 &entry,
393 &Provider::raft_engine_provider(region_id.as_u64()),
394 )
395 .unwrap();
396 let region_id = RegionId::new(1, 2);
398 writer
399 .add_entry(
400 region_id,
401 1,
402 &entry,
403 &Provider::raft_engine_provider(region_id.as_u64()),
404 )
405 .unwrap();
406 let region_id = RegionId::new(1, 2);
408 writer
409 .add_entry(
410 region_id,
411 2,
412 &entry,
413 &Provider::raft_engine_provider(region_id.as_u64()),
414 )
415 .unwrap();
416
417 writer.write_to_wal().await.unwrap();
419 }
420
421 fn build_record_batch(rows: &[(&str, i64)]) -> DfRecordBatch {
422 let schema = Arc::new(arrow::datatypes::Schema::new(vec![
423 Field::new("tag", arrow::datatypes::DataType::Utf8, false),
424 Field::new(
425 "ts",
426 arrow::datatypes::DataType::Timestamp(
427 arrow::datatypes::TimeUnit::Millisecond,
428 None,
429 ),
430 false,
431 ),
432 ]));
433
434 let tag = Arc::new(StringArray::from_iter_values(
435 rows.iter().map(|r| r.0.to_string()),
436 )) as ArrayRef;
437 let ts = Arc::new(TimestampMillisecondArray::from_iter_values(
438 rows.iter().map(|r| r.1),
439 )) as ArrayRef;
440 DfRecordBatch::try_new(schema, vec![tag, ts]).unwrap()
441 }
442
443 fn build_bulk_wal_entry(sequence_number: SequenceNumber, rows: &[(&str, i64)]) -> BulkWalEntry {
444 let rb = build_record_batch(rows);
445 let (schema, rb) = encode_to_flight_data(rb);
446 let max_ts = rows.iter().map(|r| r.1).max().unwrap();
447 let min_ts = rows.iter().map(|r| r.1).min().unwrap();
448 BulkWalEntry {
449 sequence: sequence_number,
450 max_ts,
451 min_ts,
452 timestamp_index: 1,
453 body: Some(bulk_wal_entry::Body::ArrowIpc(ArrowIpc {
454 schema: schema.data_header,
455 data_header: rb.data_header,
456 payload: rb.data_body,
457 })),
458 }
459 }
460
461 fn sample_entries() -> Vec<WalEntry> {
462 vec![
463 WalEntry {
464 mutations: vec![
465 new_mutation(OpType::Put, 1, &[("k1", 1), ("k2", 2)]),
466 new_mutation(OpType::Put, 2, &[("k3", 3), ("k4", 4)]),
467 ],
468 bulk_entries: vec![],
469 },
470 WalEntry {
471 mutations: vec![new_mutation(OpType::Put, 3, &[("k1", 1), ("k2", 2)])],
472 bulk_entries: vec![],
473 },
474 WalEntry {
475 mutations: vec![
476 new_mutation(OpType::Put, 4, &[("k1", 1), ("k2", 2)]),
477 new_mutation(OpType::Put, 5, &[("k3", 3), ("k4", 4)]),
478 ],
479 bulk_entries: vec![],
480 },
481 WalEntry {
482 mutations: vec![new_mutation(OpType::Put, 6, &[("k1", 1), ("k2", 2)])],
483 bulk_entries: vec![build_bulk_wal_entry(7, &[("k1", 8), ("k2", 9)])],
484 },
485 ]
486 }
487
488 fn check_entries(
489 expect: &[WalEntry],
490 expect_start_id: EntryId,
491 actual: &[(EntryId, WalEntry)],
492 ) {
493 for (idx, (expect_entry, (actual_id, actual_entry))) in
494 expect.iter().zip(actual.iter()).enumerate()
495 {
496 let expect_id_entry = (expect_start_id + idx as u64, expect_entry);
497 assert_eq!(expect_id_entry, (*actual_id, actual_entry));
498 }
499 assert_eq!(expect.len(), actual.len());
500 }
501
502 #[tokio::test]
503 async fn test_scan_wal() {
504 let env = WalEnv::new().await;
505 let wal = env.new_wal();
506
507 let entries = sample_entries();
508 let (id1, id2) = (RegionId::new(1, 1), RegionId::new(1, 2));
509 let ns1 = Provider::raft_engine_provider(id1.as_u64());
510 let ns2 = Provider::raft_engine_provider(id2.as_u64());
511 let mut writer = wal.writer();
512 writer.add_entry(id1, 1, &entries[0], &ns1).unwrap();
513 writer.add_entry(id2, 1, &entries[0], &ns2).unwrap();
515 writer.add_entry(id1, 2, &entries[1], &ns1).unwrap();
516 writer.add_entry(id1, 3, &entries[2], &ns1).unwrap();
517 writer.add_entry(id1, 4, &entries[3], &ns1).unwrap();
518
519 writer.write_to_wal().await.unwrap();
520
521 let stream = wal.scan(id1, 1, &ns1).unwrap();
523 let actual: Vec<_> = stream.try_collect().await.unwrap();
524 check_entries(&entries, 1, &actual);
525
526 let stream = wal.scan(id1, 2, &ns1).unwrap();
528 let actual: Vec<_> = stream.try_collect().await.unwrap();
529 check_entries(&entries[1..], 2, &actual);
530
531 let stream = wal.scan(id1, 5, &ns1).unwrap();
533 let actual: Vec<_> = stream.try_collect().await.unwrap();
534 assert!(actual.is_empty());
535 }
536
537 #[tokio::test]
538 async fn test_obsolete_wal() {
539 let env = WalEnv::new().await;
540 let wal = env.new_wal();
541
542 let entries = sample_entries();
543 let mut writer = wal.writer();
544 let region_id = RegionId::new(1, 1);
545 let ns = Provider::raft_engine_provider(region_id.as_u64());
546 writer.add_entry(region_id, 1, &entries[0], &ns).unwrap();
547 writer.add_entry(region_id, 2, &entries[1], &ns).unwrap();
548 writer.add_entry(region_id, 3, &entries[2], &ns).unwrap();
549
550 writer.write_to_wal().await.unwrap();
551
552 wal.obsolete(region_id, 2, &ns).await.unwrap();
554
555 let mut writer = wal.writer();
557 writer.add_entry(region_id, 4, &entries[3], &ns).unwrap();
558 writer.write_to_wal().await.unwrap();
559
560 let stream = wal.scan(region_id, 1, &ns).unwrap();
562 let actual: Vec<_> = stream.try_collect().await.unwrap();
563 check_entries(&entries[2..], 3, &actual);
564 }
565}