Skip to main content

roreo_common/
manifest.rs

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
16// The magic version number. It is allocated 2 bytes, so it's value must be <= u16::MAX
17const MAGIC_VERSION: u16 = 0;
18// Has to be 4 bytes. The value can nerver change, ever, anyway.
19const MAGIC_TEXT: [u8; 4] = *b"al8n";
20
21const DEFAULT_DELETIONS_REWRITE_THRESHOLD: u64 = 10_000;
22const DEFAULT_DELETIONS_RATIO: u64 = 10;
23
24/// Manifest file errors.
25#[derive(Debug, thiserror::Error)]
26pub enum ManifestError {
27  /// IO error
28  #[error("roreo: manifest io: {0}")]
29  IO(#[from] std::io::Error),
30  /// manifest removes non-existing 
31  #[error("roreo: manifest removes non-existing {0} file {1}")]
32  RemoveNotExists(FileType, u64),
33  /// manifest adds already existing
34  #[error("roreo: manifest adds already existing {0} file {1}")]
35  AddExists(FileType, u64),
36  /// manifest file not exists
37  #[error("roreo: manifest file not exists, required for read-only db")]
38  NotFound,
39  /// manifest has bad magic
40  #[error("roreo: manifest has bad magic")]
41  BadMagic,
42  /// manifest checksum mismatch
43  #[error("roreo: manifest has checksum mismatch")]
44  ChecksumMismatch,
45  /// manifest cannot open DB because the external magic number doesn't match
46  #[error("roreo: manifest cannot open DB because the external magic number doesn't match (expected {0} != {1}).")]
47  ExtrenalMagicMismtach(u16, u16),
48  /// manifest file maybe corrupted
49  #[error("roreo: manifest buffer length {buf_len} greater than file size {file_size}, file may be corrupted")]
50  Corrupted {
51    /// the length of the buffer
52    buf_len: u64,
53    /// the size of the file
54    file_size: u64
55  },
56  /// manifest decode error
57  #[error("roreo: manifest decode error: {0}")]
58  DecodeError(#[from] prost::DecodeError),
59  /// manifest unkonwn operation type
60  #[error("roreo: manifest unknown manifest operation, manifest file may be corrupted")]
61  InvalidOperation,
62  /// manifest unkonwn file type
63  #[error("roreo: manifest unknown file type, manifest file may be corrupted")]
64  InvalidFileType,
65  /// manifest unsupported version
66  #[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    /// the version used in the application
69    version: u16,
70    /// the version supported by the roreo
71    supported_version: u16,
72  },
73}
74
75/// The type of a file in the manifest.
76#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
77#[repr(u8)]
78pub enum FileType {
79  /// WAL file
80  WAL,
81  /// Data file
82  Data,
83  /// Hint file
84  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/// The operation of a file in the manifest.
115#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
116#[repr(u8)]
117pub enum FileOp {
118  /// Create a file
119  Create,
120  /// Delete a file
121  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)]
131/// The in memory version atomic manifest change.
132pub struct ManifestChange {
133  // the fields order is the order of encoding in the manifest file
134  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/// The options used for the manifest file.
180#[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  /// Create a default ManifestOptions with the given directory.
197  #[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  // Contains total number of creation and deletion changes in the manifest -- used to compute
240  // whether it'd be useful to rewrite the manifest.
241  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    // close the old manifest file
445    // Safety: we have a mutex to ensure that only one thread can write to the manifest at a time, and we also
446    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    // magic bytes are structured as
461    // +---------------------+-------------------------+-----------------------+
462    // | magicText (4 bytes) | externalMagic (2 bytes) | roreoMagic (2 bytes) |
463    // +---------------------+-------------------------+-----------------------+
464    let set = ManifestChangeSet { changes }.encode_to_vec();
465    let mut buf_writter = std::io::BufWriter::with_capacity(
466      4 // magic text
467      + 2 // external magic
468      + 2 // roreo magic
469      + 4 // data length
470      + 4 // cks
471      + 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    // close the rewrite file for renaming
490    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/// The write-ahead log manifest file
510#[derive(Debug, Clone)]
511pub struct ManifestFile {
512  opts: ManifestOptions,
513  inner: Arc<Mutex<ManifestFileInner>>,
514}
515
516impl ManifestFile {
517  /// Open or create a manifest file
518  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  /// Write a change to the manifest file, this fn is atomic
561  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        // Safety: We know the wal is initialized.
580        unsafe { inner.wal.assume_init_mut() },
581      );
582
583      // Panic: impossible to fail, because the buffer is pre-allocated
584      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  /// Write a list of changes to the manifest file, this fn is atomic
594  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    // Rewrite manifest if it'd shrink by 1/10 and it's big enough to care
599    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        // Safety: We know the wal is initialized.
613        unsafe { inner.wal.assume_init_mut() },
614      );
615
616      // Panic: impossible to fail, because the buffer is pre-allocated
617      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    // Sanity check to ensure we don't over-allocate memory.
685    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    // Truncate file so we don't have a half-written entry at the end.
713    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}