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(_) | 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 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 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 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 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
187pub struct WalWriter<S: LogStore> {
189 store: Arc<S>,
191 entries: Vec<Entry>,
193 providers: HashMap<RegionId, Provider>,
195 encoder: WalEntryEncoder,
197}
198
199impl<S: LogStore> WalWriter<S> {
200 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 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 pub async fn write_to_wal(&mut self) -> Result<AppendBatchResponse> {
228 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 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 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 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 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 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 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 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 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 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 wal.obsolete(region_id, 2, &ns).await.unwrap();
495
496 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 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}