简单看看
env里封装了随机读,顺序写,以及后台任务
现在的实现有Photon和Std。可能对应的就是异步和同步接口这样
最外面是Table,里面会包一个raw::Table<Env>
,这里的Env就是上面的std或者photon
raw Table是异步的接口。std的Table外面套了个polling封装成同步接口
主要就是Table的open,close
对于kv的get,put,delete。但是要传进来一个lsn,目前不太清楚期望的lsn是什么样的
还有一个特殊的pin,目前不太清楚是干什么的。
这里结构和sled有一定差异,tree和store是解耦开的。比较有意思
pub(super) struct AtomicTxnStats {
pub(super) read: Counter,
pub(super) write: Counter,
pub(super) split_page: Counter,
pub(super) reconcile_page: Counter,
pub(super) consolidate_page: Counter,
}
pub(super) struct AtomicStats {
pub(super) success: AtomicTxnStats,
pub(super) conflict: AtomicTxnStats,
}
pub(crate) struct Tree {
options: Options,
stats: AtomicStats,
safe_lsn: AtomicU64,
}
这个Tree的结构有点迷惑
pub(crate) struct PageStore<E: Env> {
options: Options,
env: E,
table: PageTable,
version_owner: Arc<VersionOwner>,
page_files: Arc<PageFiles<E>>,
manifest: Arc<Mutex<Manifest<E>>>,
jobs: Vec<E::JoinHandle<()>>,
shutdown: ShutdownNotifier,
}
还不清楚page files是怎么和PageTable联动的,看一下写链路吧。
读写都是会走txn
从这里看就感觉和LevelDB很类似了。除了lsn是用户传的以外
key就是raw key + lsn。value则是Delete或者Put
这里store的guard实际上就是当前的Version。Version里面含有了文件列表等信息,相当于是对当前的version加一个引用,防止被gc掉。和leveldb是类似的。
从这个角度看,txn实际上是拿到file的快照
从这里看,刚才tree的stats貌似是一些统计信息。
try_write中,会先find leaf。定位到对应的btree node
pub(super) struct PageView<'a> {
pub(super) id: u64,
pub(super) addr: u64,
pub(super) page: PageRef<'a>,
pub(super) range: Option<Range<'a>>,
}
这里的Range竟然是自己传进去的,我还以为是记录在Page上的
通过PageView根据id读出page。判断Epoch是否有变化,如果有,则说明出现了SMO,我们会做执行reconcile_page,然后重试
看代码貌似没有merge的样子。。。
到了leaf就返回了,否则就从当前的view中找child的page id,以及他的range。
这里的Range和Split有关,下来再仔细看看
构建Delta。从guard中开启一个PageTxn。
这里猜测可能就是从LSS中分配一个buffer,然后把Delta写进去。
大概可以猜到,当PageTable CAS失败的时候,这个PageTxn就会GG掉,然后释放掉这个PageTxn,以及对应的buffer
把新的Page Prepend上去
然后去page table中做CAS。如果失败了,就返回一个Addr。
成功的话,就会执行落盘,notify flush job
看起来确实没有Abort LSS buffer的地方。并发的读写貌似是根据lsn来的。通过在key中加入lsn来保证不会乱掉,因为每个key都是unique的。
会在consolidate的时候,根据lsn合并版本。他有一个safe lsn。超过safe lsn的version可能在宕机后就读不到了。所以合并的时候会保留小于safe lsn的最新一个版本用来做持久化,以及所有后续的版本。
做完consolidate之后会删除掉那些delta中的page。删除的信息也是写在了和Page一样的Log中,然后做CAS。CAS如果失败就会把这段删除信息丢掉。
GC的时候遍历Version。如果是空文件,直接Rewrite。然后计算空间利用率,如果不高,就开始GC
GC会遍历文件,并根据空间利用率计算分数,然后排序,取出最高的一个做rewrite
cleaned file就表示已经被rewrite的文件,用来避免重复rewrite。目前还不太清楚为什么这样做,因为GC线程只有一个,不太清楚为什么需要用一个变量标记。也可能是为了简单,因为每次扫描都是重新扫描所有文件