1use std::{
2 fs::File,
3 io::{Read, Seek, SeekFrom, Write},
4 mem::MaybeUninit,
5 path::PathBuf, sync::Arc,
6};
7
8pub use crate::pb::{ManifestChange as PbManifestChange, ManifestChangeSet};
9use indexmap::IndexMap;
10use parking_lot::Mutex;
11use prost::Message;
12
13const MANIFEST_FILENAME: &str = "MANIFEST";
14const MANIFEST_REWRITE_FILENAME: &str = "MANIFEST-REWRITE";
15
16const MAGIC_VERSION: u16 = 0;
18const MAGIC_TEXT: [u8; 4] = *b"al8n";
20
21const DEFAULT_DELETIONS_REWRITE_THRESHOLD: u64 = 10_000;
22const DEFAULT_DELETIONS_RATIO: u64 = 10;
23
24#[derive(Debug, thiserror::Error)]
26pub enum ManifestError {
27 #[error("roreo: manifest io: {0}")]
29 IO(#[from] std::io::Error),
30 #[error("roreo: manifest removes non-existing {0} file {1}")]
32 RemoveNotExists(FileType, u64),
33 #[error("roreo: manifest adds already existing {0} file {1}")]
35 AddExists(FileType, u64),
36 #[error("roreo: manifest file not exists, required for read-only db")]
38 NotFound,
39 #[error("roreo: manifest has bad magic")]
41 BadMagic,
42 #[error("roreo: manifest has checksum mismatch")]
44 ChecksumMismatch,
45 #[error("roreo: manifest cannot open DB because the external magic number doesn't match (expected {0} != {1}).")]
47 ExtrenalMagicMismtach(u16, u16),
48 #[error("roreo: manifest buffer length {buf_len} greater than file size {file_size}, file may be corrupted")]
50 Corrupted {
51 buf_len: u64,
53 file_size: u64
55 },
56 #[error("roreo: manifest decode error: {0}")]
58 DecodeError(#[from] prost::DecodeError),
59 #[error("roreo: manifest unknown manifest operation, manifest file may be corrupted")]
61 InvalidOperation,
62 #[error("roreo: manifest unknown file type, manifest file may be corrupted")]
64 InvalidFileType,
65 #[error("roreo: manifest unsupported version {version} (we support {supported_version}).\n please see https://github.com/al8n/roreo/blob/main/sorted/version.md on how to fix this.")]
67 UnsupportedVersion {
68 version: u16,
70 supported_version: u16,
72 },
73}
74
75#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
77#[repr(u8)]
78pub enum FileType {
79 WAL,
81 Data,
83 Hint,
85}
86
87impl FileType {
88 #[inline]
89 const fn wal() -> Self {
90 Self::WAL
91 }
92
93 #[inline]
94 const fn data() -> Self {
95 Self::Data
96 }
97
98 #[inline]
99 const fn hint() -> Self {
100 Self::Hint
101 }
102}
103
104impl core::fmt::Display for FileType {
105 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
106 match self {
107 Self::WAL => write!(f, "wal"),
108 Self::Data => write!(f, "data"),
109 Self::Hint => write!(f, "hint"),
110 }
111 }
112}
113
114#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
116#[repr(u8)]
117pub enum FileOp {
118 Create,
120 Delete,
122}
123
124
125#[viewit::viewit(
126 vis_all = "pub(crate)",
127 setters(vis_all = "pub"),
128 getters(vis_all = "pub")
129)]
130#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
131pub struct ManifestChange {
133 file_op: FileOp,
135 file_type: FileType,
136 compression: u8,
137 encryption_algo: u8,
138 fid: u64,
139}
140
141impl From<ManifestChange> for PbManifestChange {
142 fn from(value: ManifestChange) -> Self {
143 Self {
144 meta: u32::from_le_bytes([
145 value.file_op as u8,
146 value.file_type as u8,
147 value.compression,
148 value.encryption_algo,
149 ]),
150 fid: value.fid,
151 }
152 }
153}
154
155impl TryFrom<PbManifestChange> for ManifestChange {
156 type Error = ManifestError;
157
158 fn try_from(value: PbManifestChange) -> Result<Self, Self::Error> {
159 let meta = value.meta.to_le_bytes();
160 Ok(Self {
161 file_op: match meta[0] {
162 0 => FileOp::Create,
163 1 => FileOp::Delete,
164 _ => return Err(ManifestError::InvalidOperation),
165 },
166 file_type: match meta[1] {
167 0 => FileType::wal(),
168 1 => FileType::data(),
169 2 => FileType::hint(),
170 _ => return Err(ManifestError::InvalidFileType),
171 },
172 compression: meta[2],
173 encryption_algo: meta[3],
174 fid: value.fid,
175 })
176 }
177}
178
179#[viewit::viewit(
181 vis_all = "pub(crate)",
182 setters(vis_all = "pub"),
183 getters(vis_all = "pub")
184)]
185#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
186pub struct ManifestOptions {
187 #[viewit(getter(style = "ref"))]
188 dir: PathBuf,
189 deletion_threshold: u64,
190 deletion_ratio: u64,
191 external_magic_version: u16,
192 read_only: bool,
193}
194
195impl ManifestOptions {
196 #[inline]
198 pub const fn new(dir: PathBuf) -> Self {
199 Self {
200 dir,
201 deletion_threshold: DEFAULT_DELETIONS_REWRITE_THRESHOLD,
202 deletion_ratio: DEFAULT_DELETIONS_RATIO,
203 external_magic_version: 0,
204 read_only: false,
205 }
206 }
207}
208
209#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
210struct ManifestInfo {
211 compression: u8,
212 encryption_algo: u8,
213}
214
215#[derive(Debug, Default)]
216#[repr(transparent)]
217struct WALManifest {
218 files: IndexMap<u64, ManifestInfo>,
219}
220
221#[derive(Debug, Default)]
222#[repr(transparent)]
223struct DataManifest {
224 files: IndexMap<u64, ManifestInfo>,
225}
226
227#[derive(Debug, Default)]
228#[repr(transparent)]
229struct HintManifest {
230 files: IndexMap<u64, ManifestInfo>,
231}
232
233#[derive(Debug, Default)]
234struct Manifest {
235 wal: WALManifest,
236 data: DataManifest,
237 hint: HintManifest,
238
239 creations: u64,
242 deletions: u64,
243}
244
245impl From<&Manifest> for Vec<PbManifestChange> {
246 fn from(value: &Manifest) -> Self {
247 let mut changes =
248 Vec::with_capacity(value.wal.files.len() + value.data.files.len() + value.hint.files.len());
249
250 for (&fid, info) in &value.wal.files {
251 changes.push(PbManifestChange {
252 meta: u32::from_le_bytes([
253 FileOp::Create as u8,
254 FileType::WAL as u8,
255 info.compression,
256 info.encryption_algo,
257 ]),
258 fid,
259 });
260 }
261
262 for (&fid, info) in &value.data.files {
263 changes.push(PbManifestChange {
264 meta: u32::from_le_bytes([
265 FileOp::Create as u8,
266 FileType::Data as u8,
267 info.compression,
268 info.encryption_algo,
269 ]),
270 fid,
271 });
272 }
273
274 for (&fid, info) in &value.hint.files {
275 changes.push(PbManifestChange {
276 meta: u32::from_le_bytes([
277 FileOp::Create as u8,
278 FileType::Hint as u8,
279 info.compression,
280 info.encryption_algo,
281 ]),
282 fid,
283 });
284 }
285
286 changes
287 }
288}
289
290macro_rules! insert_bail {
291 ($manifest:ident::$change:ident::$name:ident: $name_str: literal) => {
292 if $manifest
293 .$name
294 .files
295 .insert(
296 $change.fid,
297 ManifestInfo {
298 compression: $change.compression,
299 encryption_algo: $change.encryption_algo,
300 },
301 )
302 .is_some()
303 {
304 tracing::error!(
305 target = "manifest",
306 concat!(
307 "roreo: try to add ",
308 $name_str,
309 " file {}, but this fid already exists in manifest"
310 ),
311 $change.fid
312 );
313 return Err(ManifestError::AddExists(FileType::$name(), $change.fid));
314 } else {
315 tracing::info!(
316 target = "manifest",
317 concat!("roreo: add ", $name_str, " file {} to manifest"),
318 $change.fid
319 );
320 }
321 };
322}
323
324macro_rules! remove_bail {
325 ($manifest:ident::$change:ident::$name:ident: $name_str: literal) => {
326 match $manifest.$name.files.remove(&$change.fid) {
327 Some(_) => {
328 tracing::info!(
329 target = "manifest",
330 concat!("roreo: remove ", $name_str, " file {} from manifest"),
331 $change.fid
332 );
333 }
334 None => {
335 tracing::error!(
336 target = "manifest",
337 concat!(
338 "roreo: try to remove ",
339 $name_str,
340 "file {}, but not found in manifest"
341 ),
342 $change.fid
343 );
344 return Err(ManifestError::RemoveNotExists(
345 FileType::$name(),
346 $change.fid,
347 ));
348 }
349 }
350 };
351}
352
353impl Manifest {
354 #[inline]
355 fn apply_changes(
356 &mut self,
357 changes: Vec<ManifestChange>,
358 ) -> Result<ManifestChangeSet, ManifestError> {
359 let mut creations = 0;
360 let mut deletions = 0;
361 let set = ManifestChangeSet {
362 changes: changes
363 .into_iter()
364 .map(|change| {
365 match (change.file_type, change.file_op) {
366 (FileType::WAL, FileOp::Create) => {
367 insert_bail!(self::change::wal: "wal");
368 creations += 1;
369 }
370 (FileType::WAL, FileOp::Delete) => {
371 remove_bail!(self::change::wal: "wal");
372 deletions += 1;
373 }
374 (FileType::Data, FileOp::Create) => {
375 insert_bail!(self::change::data: "data");
376 creations += 1;
377 }
378 (FileType::Data, FileOp::Delete) => {
379 remove_bail!(self::change::data: "data");
380 deletions += 1;
381 }
382 (FileType::Hint, FileOp::Create) => {
383 insert_bail!(self::change::hint: "hint");
384 creations += 1;
385 }
386 (FileType::Hint, FileOp::Delete) => {
387 remove_bail!(self::change::hint: "hint");
388 deletions += 1;
389 }
390 }
391 Ok(change.into())
392 })
393 .collect::<Result<Vec<_>, ManifestError>>()?,
394 };
395
396 self.deletions += deletions;
397 self.creations += creations;
398 Ok(set)
399 }
400
401 #[inline]
402 fn apply_change_set(&mut self, changes: ManifestChangeSet) -> Result<(), ManifestError> {
403 for change in changes.changes {
404 let change: ManifestChange = change.try_into()?;
405 match (change.file_type, change.file_op) {
406 (FileType::WAL, FileOp::Create) => {
407 insert_bail!(self::change::wal: "wal");
408 self.creations += 1;
409 }
410 (FileType::WAL, FileOp::Delete) => {
411 remove_bail!(self::change::wal: "wal");
412 self.deletions += 1;
413 }
414 (FileType::Data, FileOp::Create) => {
415 insert_bail!(self::change::data: "data");
416 self.creations += 1;
417 }
418 (FileType::Data, FileOp::Delete) => {
419 remove_bail!(self::change::data: "data");
420 self.deletions += 1;
421 }
422 (FileType::Hint, FileOp::Create) => {
423 insert_bail!(self::change::hint: "hint");
424 self.creations += 1;
425 }
426 (FileType::Hint, FileOp::Delete) => {
427 remove_bail!(self::change::hint: "hint");
428 self.deletions += 1;
429 }
430 }
431 }
432 Ok(())
433 }
434}
435
436#[derive(Debug)]
437struct ManifestFileInner {
438 manifest: Manifest,
439 wal: MaybeUninit<File>,
440}
441
442impl ManifestFileInner {
443 fn rewrite(&mut self, manifest_opts: &ManifestOptions) -> Result<u64, ManifestError> {
444 unsafe { self.wal.assume_init_drop() };
447
448 let changes = Vec::<PbManifestChange>::from(&self.manifest);
449 let creations = changes.len() as u64;
450 let dir = &manifest_opts.dir;
451 let rewrite_file_name = dir.join(MANIFEST_REWRITE_FILENAME);
452 let mut opts = std::fs::OpenOptions::new();
453 let mut file = opts
454 .create(true)
455 .write(true)
456 .truncate(true)
457 .read(true)
458 .open(&rewrite_file_name)?;
459
460 let set = ManifestChangeSet { changes }.encode_to_vec();
465 let mut buf_writter = std::io::BufWriter::with_capacity(
466 4 + 2 + 2 + 4 + 4 + set.len(),
472 &mut file,
473 );
474
475 buf_writter.write_all(&MAGIC_TEXT).unwrap();
476 buf_writter
477 .write_all(&manifest_opts.external_magic_version.to_le_bytes())
478 .unwrap();
479 buf_writter.write_all(&MAGIC_VERSION.to_le_bytes()).unwrap();
480 buf_writter
481 .write_all(&(set.len() as u32).to_le_bytes())
482 .unwrap();
483 buf_writter
484 .write_all(crc32fast::hash(&set).to_le_bytes().as_ref())
485 .unwrap();
486 buf_writter.write_all(&set).unwrap();
487 buf_writter.flush()?;
488
489 drop(buf_writter);
491 drop(file);
492
493 let manifest_file_path = dir.join(MANIFEST_FILENAME);
494 std::fs::rename(&rewrite_file_name, &manifest_file_path)?;
495
496 opts
497 .create(false)
498 .write(true)
499 .append(true)
500 .read(true)
501 .open(&manifest_file_path)
502 .and_then(|mut f| f.seek(SeekFrom::End(0)).map(|_| self.wal.write(f)))
503 .and_then(|_| File::open(&manifest_opts.dir).and_then(|dir| dir.sync_all()))
504 .map(|_| creations)
505 .map_err(From::from)
506 }
507}
508
509#[derive(Debug, Clone)]
511pub struct ManifestFile {
512 opts: ManifestOptions,
513 inner: Arc<Mutex<ManifestFileInner>>,
514}
515
516impl ManifestFile {
517 pub fn open(opt: ManifestOptions) -> Result<Self, ManifestError> {
519 let file_path = opt.dir.join(MANIFEST_FILENAME);
520 let mut opts = std::fs::OpenOptions::new();
521 if opt.read_only {
522 opts.read(true);
523 } else {
524 opts.read(true).write(true).create(true);
525 }
526
527 if !file_path.exists() {
528 if opt.read_only {
529 return Err(ManifestError::NotFound);
530 }
531 return opts
532 .open(&file_path)
533 .and_then(|mut f| {
534 let mut magic_buf = [0u8; 8];
535 magic_buf[..4].copy_from_slice(&MAGIC_TEXT);
536 magic_buf[4..6].copy_from_slice(&opt.external_magic_version.to_le_bytes());
537 magic_buf[6..].copy_from_slice(&MAGIC_VERSION.to_le_bytes());
538 f.write_all(&magic_buf).map(|_| Self {
539 opts: opt,
540 inner: Arc::new(Mutex::new(ManifestFileInner {
541 manifest: Default::default(),
542 wal: MaybeUninit::new(f),
543 })),
544 })
545 })
546 .map_err(From::from);
547 }
548
549 opts.open(&file_path).map_err(From::from).and_then(|mut f| {
550 replay(&mut f, opt.external_magic_version, opt.read_only).map(|m| Self {
551 opts: opt,
552 inner: Arc::new(Mutex::new(ManifestFileInner {
553 manifest: m,
554 wal: MaybeUninit::new(f),
555 })),
556 })
557 })
558 }
559
560 pub fn write_change(&self, change: ManifestChange) -> Result<(), ManifestError> {
562 let mut inner = self.inner.lock();
563 if inner.manifest.deletions > self.opts.deletion_threshold
564 && inner.manifest.deletions
565 > self.opts.deletion_ratio * (inner.manifest.creations + inner.manifest.deletions)
566 {
567 inner.rewrite(&self.opts).map(|creations| {
568 inner.manifest.creations = creations;
569 inner.manifest.deletions = 0;
570 })
571 } else {
572 let encoded = ManifestChangeSet {
573 changes: vec![PbManifestChange::from(change)],
574 }
575 .encode_to_vec();
576 let encoded_len = encoded.len();
577 let mut buf = std::io::BufWriter::with_capacity(
578 4 + 4 + encoded_len,
579 unsafe { inner.wal.assume_init_mut() },
581 );
582
583 buf.write_all(&(encoded_len as u32).to_le_bytes()).unwrap();
585 buf
586 .write_all(crc32fast::hash(&encoded).to_le_bytes().as_ref())
587 .unwrap();
588 buf.write_all(&encoded).unwrap();
589 buf.flush().map_err(From::from)
590 }
591 }
592
593 pub fn write_changes(&self, changes: Vec<ManifestChange>) -> Result<(), ManifestError> {
595 let mut inner = self.inner.lock();
596 let change_set = inner.manifest.apply_changes(changes)?;
597
598 if inner.manifest.deletions > self.opts.deletion_threshold
600 && inner.manifest.deletions
601 > self.opts.deletion_ratio * (inner.manifest.creations + inner.manifest.deletions)
602 {
603 inner.rewrite(&self.opts).map(|creations| {
604 inner.manifest.creations = creations;
605 inner.manifest.deletions = 0;
606 })
607 } else {
608 let encoded = change_set.encode_to_vec();
609 let encoded_len = encoded.len();
610 let mut buf = std::io::BufWriter::with_capacity(
611 4 + 4 + encoded_len,
612 unsafe { inner.wal.assume_init_mut() },
614 );
615
616 buf.write_all(&(encoded_len as u32).to_le_bytes()).unwrap();
618 buf
619 .write_all(crc32fast::hash(&encoded).to_le_bytes().as_ref())
620 .unwrap();
621 buf.write_all(&encoded).unwrap();
622 buf.flush().map_err(From::from)
623 }
624 }
625}
626
627#[inline]
628fn replay(
629 file: &mut File,
630 external_magic: u16,
631 read_only: bool,
632) -> Result<Manifest, ManifestError> {
633 let stat = file.metadata()?;
634 let mut reader = std::io::BufReader::new(file);
635
636 let mut magic_buf = [0; 8];
637 reader.read_exact(&mut magic_buf).map_err(|e| {
638 tracing::error!(target = "manifest", "roreo: read magic failed: {}", e);
639 ManifestError::BadMagic
640 })?;
641
642 if magic_buf[..4] != MAGIC_TEXT {
643 tracing::error!(target = "manifest", "roreo: magic mismatch");
644 return Err(ManifestError::BadMagic);
645 }
646
647 let ext_version = u16::from_le_bytes(magic_buf[4..6].try_into().unwrap());
648 let version = u16::from_le_bytes(magic_buf[6..8].try_into().unwrap());
649
650 if version != MAGIC_VERSION {
651 return Err(ManifestError::UnsupportedVersion {
652 version,
653 supported_version: MAGIC_VERSION,
654 });
655 }
656
657 if ext_version != external_magic {
658 tracing::error!(
659 target = "manifest",
660 "roreo: external magic mismatch expected {}, found {}",
661 external_magic,
662 ext_version
663 );
664 return Err(ManifestError::ExtrenalMagicMismtach(
665 external_magic,
666 ext_version,
667 ));
668 }
669
670 let mut manifest = Manifest::default();
671
672 let mut offset = 8u64;
673 loop {
674 let mut len_crc_buf = [0; 8];
675 if let Err(e) = reader.read_exact(&mut len_crc_buf) {
676 if e.kind() == std::io::ErrorKind::UnexpectedEof {
677 break;
678 }
679 return Err(e.into());
680 }
681 offset += 8;
682
683 let len = u32::from_le_bytes(len_crc_buf[..4].try_into().unwrap()) as u64;
684 if len > stat.len() {
686 return Err(ManifestError::Corrupted {
687 buf_len: len,
688 file_size: stat.len(),
689 });
690 }
691
692 let mut buf = vec![0; len as usize];
693 if let Err(e) = reader.read_exact(&mut buf) {
694 if e.kind() == std::io::ErrorKind::UnexpectedEof {
695 break;
696 }
697 return Err(e.into());
698 }
699 offset += len;
700
701 let crc = u32::from_le_bytes(len_crc_buf[4..8].try_into().unwrap());
702 if crc != crc32fast::hash(&buf) {
703 return Err(ManifestError::ChecksumMismatch);
704 }
705
706 ManifestChangeSet::decode(buf.as_slice())
707 .map_err(From::from)
708 .and_then(|cs| manifest.apply_change_set(cs))?;
709 }
710 let file = reader.into_inner();
711 if !read_only {
712 file.set_len(offset)?;
714 }
715 file.seek(SeekFrom::End(0))?;
716
717 Ok(manifest)
718}
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723
724 #[test]
725 fn test_manifest_basic() {
726 let dir = tempfile::tempdir().unwrap();
727 let opts = ManifestOptions::new(dir.path().to_path_buf()).set_deletion_threshold(100);
728 let manifest = ManifestFile::open(opts.clone()).unwrap();
729
730 let mut changes = vec![];
731 for i in 0..1000usize {
732 changes.push(vec![ManifestChange {
733 file_op: FileOp::Create,
734 file_type: FileType::WAL,
735 compression: 0,
736 encryption_algo: 0,
737 fid: i as u64,
738 }]);
739 changes.push(vec![
740 ManifestChange {
741 file_op: FileOp::Create,
742 file_type: FileType::Data,
743 compression: 0,
744 encryption_algo: 0,
745 fid: i as u64,
746 },
747 ManifestChange {
748 file_op: FileOp::Create,
749 file_type: FileType::Hint,
750 compression: 0,
751 encryption_algo: 0,
752 fid: i as u64,
753 },
754 ]);
755 changes.push(vec![ManifestChange {
756 file_op: FileOp::Delete,
757 file_type: FileType::WAL,
758 compression: 0,
759 encryption_algo: 0,
760 fid: i as u64,
761 }]);
762 }
763
764 for change in changes {
765 manifest.write_changes(change).unwrap();
766 }
767 drop(manifest);
768
769 let manifest = ManifestFile::open(opts).unwrap();
770
771 let inner = manifest.inner.lock();
772 assert!(inner.manifest.wal.files.is_empty());
773 assert_eq!(inner.manifest.data.files.len(), 1000);
774 assert_eq!(inner.manifest.hint.files.len(), 1000);
775 assert_eq!(
776 inner.manifest.data.files,
777 (0..1000)
778 .map(|idx| (
779 idx,
780 ManifestInfo {
781 compression: 0,
782 encryption_algo: 0
783 }
784 ))
785 .collect::<IndexMap<_, _>>()
786 );
787 assert_eq!(
788 inner.manifest.hint.files,
789 (0..1000)
790 .map(|idx| (
791 idx,
792 ManifestInfo {
793 compression: 0,
794 encryption_algo: 0
795 }
796 ))
797 .collect::<IndexMap<_, _>>()
798 );
799 }
800}