1use std::path::Path;
20use std::sync::atomic::{AtomicU64, Ordering};
21
22#[cfg(target_os = "linux")]
23use std::os::fd::AsRawFd;
24
25use memmap2::Mmap;
26
27use crate::error::{Result, WalError};
28use crate::record::{HEADER_SIZE, RecordHeader, RecordType, WAL_MAGIC, WalRecord};
29
30pub mod observability {
34 use super::{AtomicU64, Ordering};
35 pub(super) static SEGMENTS_OPENED: AtomicU64 = AtomicU64::new(0);
36 pub(super) static FADV_DONTNEED_COUNT: AtomicU64 = AtomicU64::new(0);
37 pub(super) static MADV_SEQUENTIAL_COUNT: AtomicU64 = AtomicU64::new(0);
38
39 pub fn segments_opened() -> u64 {
40 SEGMENTS_OPENED.load(Ordering::Relaxed)
41 }
42 pub fn fadv_dontneed_count() -> u64 {
43 FADV_DONTNEED_COUNT.load(Ordering::Relaxed)
44 }
45 pub fn madv_sequential_count() -> u64 {
46 MADV_SEQUENTIAL_COUNT.load(Ordering::Relaxed)
47 }
48}
49
50fn fadv_dontneed(fd: &std::fs::File, len: usize, path: &Path) {
56 if len == 0 {
57 return;
58 }
59 #[cfg(target_os = "linux")]
60 {
61 let rc = unsafe {
62 libc::posix_fadvise(
63 fd.as_raw_fd(),
64 0,
65 len as libc::off_t,
66 libc::POSIX_FADV_DONTNEED,
67 )
68 };
69 if rc == 0 {
70 observability::FADV_DONTNEED_COUNT.fetch_add(1, Ordering::Relaxed);
71 } else {
72 tracing::warn!(
73 path = %path.display(),
74 errno = rc,
75 "posix_fadvise(DONTNEED) failed on exhausted WAL segment",
76 );
77 }
78 }
79 #[cfg(not(target_os = "linux"))]
80 {
81 let _ = (fd, path);
82 }
83}
84
85pub struct MmapWalReader {
91 mmap: Mmap,
92 offset: usize,
93 file: std::fs::File,
94 path: std::path::PathBuf,
95 madvise_state: Option<libc::c_int>,
96}
97
98impl MmapWalReader {
99 pub fn open(path: &Path) -> Result<Self> {
101 observability::SEGMENTS_OPENED.fetch_add(1, Ordering::Relaxed);
102 let file = std::fs::File::open(path)?;
103 let mmap = unsafe { Mmap::map(&file)? };
107
108 let mut madvise_state = None;
112 if !mmap.is_empty() {
113 let rc = unsafe {
114 libc::madvise(
115 mmap.as_ptr() as *mut libc::c_void,
116 mmap.len(),
117 libc::MADV_SEQUENTIAL,
118 )
119 };
120 if rc == 0 {
121 madvise_state = Some(libc::MADV_SEQUENTIAL);
122 observability::MADV_SEQUENTIAL_COUNT.fetch_add(1, Ordering::Relaxed);
123 } else {
124 tracing::warn!(
125 path = %path.display(),
126 errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0),
127 "madvise(MADV_SEQUENTIAL) failed on WAL segment; continuing",
128 );
129 }
130 }
131
132 Ok(Self {
133 mmap,
134 offset: 0,
135 file,
136 path: path.to_path_buf(),
137 madvise_state,
138 })
139 }
140
141 pub fn madvise_state(&self) -> Option<libc::c_int> {
143 self.madvise_state
144 }
145
146 pub fn release_pages(&self) {
149 fadv_dontneed(&self.file, self.mmap.len(), &self.path);
150 }
151
152 pub fn next_record(&mut self) -> Result<Option<WalRecord>> {
157 let data = &self.mmap[..];
158
159 loop {
160 if self.offset + HEADER_SIZE > data.len() {
162 return Ok(None);
163 }
164
165 let header_bytes: &[u8; HEADER_SIZE] = data[self.offset..self.offset + HEADER_SIZE]
167 .try_into()
168 .map_err(|_| {
169 WalError::Io(std::io::Error::new(
170 std::io::ErrorKind::InvalidData,
171 "header slice conversion failed",
172 ))
173 })?;
174 let header = RecordHeader::from_bytes(header_bytes);
175
176 if header.magic != WAL_MAGIC {
178 return Ok(None);
179 }
180
181 if header.validate(self.offset as u64).is_err() {
183 return Ok(None);
184 }
185
186 let payload_len = header.payload_len as usize;
187 let record_end = self.offset + HEADER_SIZE + payload_len;
188
189 if record_end > data.len() {
191 return Ok(None); }
193
194 let payload = data[self.offset + HEADER_SIZE..record_end].to_vec();
196 self.offset = record_end;
197
198 let record = WalRecord { header, payload };
199
200 if record.verify_checksum().is_err() {
202 return Ok(None); }
204
205 let logical_type = record.logical_record_type();
207 if RecordType::from_raw(logical_type).is_none() {
208 if RecordType::is_required(logical_type) {
209 return Err(WalError::UnknownRequiredRecordType {
210 record_type: header.record_type,
211 lsn: header.lsn,
212 });
213 }
214 continue;
216 }
217
218 return Ok(Some(record));
219 }
220 }
221
222 pub fn records(self) -> MmapRecordIter {
224 MmapRecordIter { reader: self }
225 }
226
227 pub fn offset(&self) -> usize {
229 self.offset
230 }
231
232 pub fn len(&self) -> usize {
234 self.mmap.len()
235 }
236
237 pub fn is_empty(&self) -> bool {
239 self.mmap.is_empty()
240 }
241}
242
243pub struct MmapRecordIter {
245 reader: MmapWalReader,
246}
247
248impl Iterator for MmapRecordIter {
249 type Item = Result<WalRecord>;
250
251 fn next(&mut self) -> Option<Self::Item> {
252 match self.reader.next_record() {
253 Ok(Some(record)) => Some(Ok(record)),
254 Ok(None) => None,
255 Err(e) => Some(Err(e)),
256 }
257 }
258}
259
260const PARALLEL_SEGMENT_THRESHOLD: usize = 4;
262
263pub fn replay_segments_mmap(wal_dir: &Path, from_lsn: u64) -> Result<Vec<WalRecord>> {
273 let segments = crate::segment::discover_segments(wal_dir)?;
274 let live = filter_segments_by_lsn(&segments, from_lsn);
275
276 if live.len() < PARALLEL_SEGMENT_THRESHOLD {
277 return replay_segments_sequential(live, from_lsn);
278 }
279
280 replay_segments_parallel(live, from_lsn)
281}
282
283fn filter_segments_by_lsn(
289 segments: &[crate::segment::SegmentMeta],
290 from_lsn: u64,
291) -> &[crate::segment::SegmentMeta] {
292 let mut start = 0;
297 for i in 0..segments.len() {
298 let upper = segments.get(i + 1).map(|s| s.first_lsn).unwrap_or(u64::MAX);
300 if upper > from_lsn {
301 start = i;
302 break;
303 }
304 start = i + 1;
305 }
306 if start >= segments.len() {
307 return &[];
309 }
310 &segments[start..]
311}
312
313fn replay_segments_sequential(
315 segments: &[crate::segment::SegmentMeta],
316 from_lsn: u64,
317) -> Result<Vec<WalRecord>> {
318 let mut records = Vec::new();
319 for seg in segments {
320 let mut reader = MmapWalReader::open(&seg.path)?;
321 while let Some(record) = reader.next_record()? {
322 if record.header.lsn >= from_lsn {
323 records.push(record);
324 }
325 }
326 reader.release_pages();
327 }
328 Ok(records)
329}
330
331fn replay_segments_parallel(
337 segments: &[crate::segment::SegmentMeta],
338 from_lsn: u64,
339) -> Result<Vec<WalRecord>> {
340 let mut per_segment: Vec<Result<Vec<WalRecord>>> = Vec::with_capacity(segments.len());
342
343 std::thread::scope(|scope| {
344 let handles: Vec<_> = segments
345 .iter()
346 .map(|seg| {
347 scope.spawn(move || -> Result<Vec<WalRecord>> {
348 let mut reader = MmapWalReader::open(&seg.path)?;
349 let mut seg_records = Vec::new();
350 while let Some(record) = reader.next_record()? {
351 if record.header.lsn >= from_lsn {
352 seg_records.push(record);
353 }
354 }
355 reader.release_pages();
356 Ok(seg_records)
357 })
358 })
359 .collect();
360
361 for handle in handles {
362 per_segment.push(handle.join().unwrap_or_else(|_| {
363 Err(WalError::Io(std::io::Error::other(
364 "segment replay thread panicked",
365 )))
366 }));
367 }
368 });
369
370 let total_estimate: usize = per_segment
372 .iter()
373 .map(|r| r.as_ref().map(|v| v.len()).unwrap_or(0))
374 .sum();
375 let mut records = Vec::with_capacity(total_estimate);
376 for seg_result in per_segment {
377 records.extend(seg_result?);
378 }
379
380 Ok(records)
381}
382
383pub fn replay_segments_mmap_limit(
392 wal_dir: &Path,
393 from_lsn: u64,
394 max_records: usize,
395) -> Result<(Vec<WalRecord>, bool)> {
396 let segments = crate::segment::discover_segments(wal_dir)?;
397 let live = filter_segments_by_lsn(&segments, from_lsn);
398 let mut records = Vec::with_capacity(max_records.min(4096));
399
400 for seg in live {
401 let mut reader = MmapWalReader::open(&seg.path)?;
402 while let Some(record) = reader.next_record()? {
403 if record.header.lsn >= from_lsn {
404 records.push(record);
405 if records.len() >= max_records {
406 return Ok((records, true));
409 }
410 }
411 }
412 reader.release_pages();
413 }
414
415 Ok((records, false))
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421 use crate::record::RecordType;
422 use crate::writer::{WalWriter, WalWriterConfig};
423
424 fn test_writer(path: &Path) -> WalWriter {
425 let config = WalWriterConfig {
426 use_direct_io: false, ..Default::default()
428 };
429 WalWriter::open(path, config).unwrap()
430 }
431
432 #[test]
433 fn mmap_reader_basic() {
434 let dir = tempfile::tempdir().unwrap();
435 let path = dir.path().join("test.wal");
436
437 {
439 let mut writer = test_writer(&path);
440 writer
441 .append(RecordType::Put as u32, 1, 0, 0, b"hello")
442 .unwrap();
443 writer
444 .append(RecordType::Put as u32, 1, 0, 0, b"world")
445 .unwrap();
446 writer.sync().unwrap();
447 }
448
449 let reader = MmapWalReader::open(&path).unwrap();
451 let records: Vec<WalRecord> = reader.records().collect::<Result<Vec<_>>>().unwrap();
452
453 assert_eq!(records.len(), 2);
454 assert_eq!(records[0].payload, b"hello");
455 assert_eq!(records[1].payload, b"world");
456 }
457
458 #[test]
459 fn mmap_reader_empty_file() {
460 let dir = tempfile::tempdir().unwrap();
461 let path = dir.path().join("empty.wal");
462 std::fs::write(&path, []).unwrap();
463
464 let reader = MmapWalReader::open(&path).unwrap();
465 let records: Vec<WalRecord> = reader.records().collect::<Result<Vec<_>>>().unwrap();
466 assert!(records.is_empty());
467 }
468
469 #[test]
470 fn mmap_reader_truncated_header() {
471 let dir = tempfile::tempdir().unwrap();
472 let path = dir.path().join("truncated.wal");
473 std::fs::write(&path, [0u8; 10]).unwrap();
475
476 let reader = MmapWalReader::open(&path).unwrap();
477 let records: Vec<WalRecord> = reader.records().collect::<Result<Vec<_>>>().unwrap();
478 assert!(records.is_empty());
479 }
480
481 #[test]
482 fn replay_mmap_from_lsn() {
483 let dir = tempfile::tempdir().unwrap();
484 let wal_dir = dir.path().join("wal");
485 std::fs::create_dir_all(&wal_dir).unwrap();
486
487 let config = crate::segmented::SegmentedWalConfig::for_testing(wal_dir.clone());
488 let mut wal = crate::segmented::SegmentedWal::open(config).unwrap();
489
490 let lsn1 = wal.append(RecordType::Put as u32, 1, 0, 0, b"a").unwrap();
491 let lsn2 = wal.append(RecordType::Put as u32, 1, 0, 0, b"b").unwrap();
492 let lsn3 = wal.append(RecordType::Put as u32, 1, 0, 0, b"c").unwrap();
493 wal.sync().unwrap();
494
495 let records = replay_segments_mmap(&wal_dir, lsn2).unwrap();
497 assert_eq!(records.len(), 2);
498 assert_eq!(records[0].header.lsn, lsn2);
499 assert_eq!(records[1].header.lsn, lsn3);
500
501 let all = replay_segments_mmap(&wal_dir, lsn1).unwrap();
503 assert_eq!(all.len(), 3);
504 }
505}