dag: make NameDag use MultiLog for data consistency

Summary: This ensures IdMap and IdDag are guaranteed consistent in the storage layer.

Reviewed By: DurhamG

Differential Revision: D19432658

fbshipit-source-id: 00f1a9b4c747baa1f14d78c31d925682317463b4
This commit is contained in:
Jun Wu 2020-01-17 21:47:32 -08:00 committed by Facebook Github Bot
parent 907aadcdd7
commit 124e275377
3 changed files with 54 additions and 27 deletions

View File

@ -54,7 +54,22 @@ impl IdMap {
/// access, call [`IdMap::make_writable`] to get a writable instance.
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let log = log::OpenOptions::new()
let log = Self::log_open_options().open(path)?;
Self::open_from_log(log)
}
pub(crate) fn open_from_log(log: log::Log) -> Result<Self> {
let path = log.path().as_opt_path().unwrap().to_path_buf();
Ok(Self {
log,
path,
cached_next_free_ids: Default::default(),
need_rebuild_non_master: false,
})
}
pub(crate) fn log_open_options() -> log::OpenOptions {
log::OpenOptions::new()
.create(true)
.index("id", |data| {
assert!(Self::MAGIC_CLEAR_NON_MASTER.len() < 8);
@ -81,14 +96,6 @@ impl IdMap {
.flush_filter(Some(|_, _| {
panic!("programming error: idmap changed by other process")
}))
.open(path)?;
let path = path.to_path_buf();
Ok(Self {
log,
path,
cached_next_free_ids: Default::default(),
need_rebuild_non_master: false,
})
}
/// Return a [`SyncableIdMap`] instance that provides race-free

View File

@ -17,6 +17,7 @@ use crate::idmap::SyncableIdMap;
use crate::segment::IdDag;
use crate::segment::SyncableIdDag;
use anyhow::{anyhow, bail, ensure, Result};
use indexedlog::multi;
use std::collections::{HashMap, HashSet};
use std::path::Path;
@ -28,6 +29,8 @@ pub struct NameDag {
pub(crate) dag: IdDag,
pub(crate) map: IdMap,
mlog: multi::MultiLog,
/// Heads added via `add_heads` that are not flushed yet.
pending_heads: Vec<VertexName>,
}
@ -35,16 +38,20 @@ pub struct NameDag {
impl NameDag {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let mut map = IdMap::open(path.join("idmap"))?;
// Take a lock so map and dag are loaded consistently. A better (lock-free) way to ensure
// this is to use a single "meta" file for both indexedlogs. However that requires some
// API changes on the indexedlog side.
let _locked = map.prepare_filesystem_sync()?;
map.reload()?;
let dag = IdDag::open(path.join("segments"))?;
let opts = multi::OpenOptions::from_name_opts(vec![
("idmap", IdMap::log_open_options()),
("iddag", IdDag::log_open_options()),
]);
let mut mlog = opts.open(path)?;
let mut logs = mlog.detach_logs();
let dag_log = logs.pop().unwrap();
let map_log = logs.pop().unwrap();
let map = IdMap::open_from_log(map_log)?;
let dag = IdDag::open_from_log(dag_log)?;
Ok(Self {
dag,
map,
mlog,
pending_heads: Default::default(),
})
}
@ -81,6 +88,10 @@ impl NameDag {
}
// Take lock.
//
// Reload meta. This drops in-memory changes, which is fine because we have
// checked there are no in-memory changes at the beginning.
let lock = self.mlog.lock()?;
let mut map = self.map.prepare_filesystem_sync()?;
let mut dag = self.dag.prepare_filesystem_sync()?;
@ -96,6 +107,7 @@ impl NameDag {
// Write to disk.
map.sync()?;
dag.sync(std::iter::once(&mut self.dag))?;
self.mlog.write_meta(&lock)?;
Ok(())
}

View File

@ -93,7 +93,25 @@ impl IdDag {
/// Open [`IdDag`] at the given directory. Create it on demand.
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let log = log::OpenOptions::new()
let log = Self::log_open_options().open(path)?;
Self::open_from_log(log)
}
pub(crate) fn open_from_log(log: log::Log) -> Result<Self> {
let path = log.path().as_opt_path().unwrap().to_path_buf();
let max_level = Self::max_level_from_log(&log)?;
let mut dag = Self {
log,
path,
max_level,
new_seg_size: 16, // see D16660078 for this default setting
};
dag.build_all_high_level_segments(false)?;
Ok(dag)
}
pub(crate) fn log_open_options() -> log::OpenOptions {
log::OpenOptions::new()
.create(true)
.index("level-head", |data| {
// (level, high)
@ -147,16 +165,6 @@ impl IdDag {
}
result
})
.open(path)?;
let max_level = Self::max_level_from_log(&log)?;
let mut dag = Self {
log,
path: path.to_path_buf(),
max_level,
new_seg_size: 16, // see D16660078 for this default setting
};
dag.build_all_high_level_segments(false)?;
Ok(dag)
}
fn max_level_from_log(log: &log::Log) -> Result<Level> {