TCBDB存储结构
TCBDB是tokyo cabinet的一部分,实现了基于B+树的key/value存储,本文简单介绍TCBDB中分支及叶子节点的存储结构。
分支节点(非叶子节点)的结构
typedef struct { // type of structure for a page index
uint64_t pid; // ID number of the referring page
int ksiz; // size of the key region
} BDBIDX;
typedef struct { // type of structure for a node page
uint64_t id; // ID number of the node
uint64_t heir; // ID of the child before the first index
TCPTRLIST *idxs; // list of indices
bool dirty; // whether to be written back
bool dead; // whether to be removed
} BDBNODE;
每个节点作为一个TCHDB的value进行存储,对应的key为节点的ID(通过ID值则可获取节点存储的信息),叶子节点和分支节点的ID值会在不同的范围内,每个节点的value不能超过一个page,page的值默认为65536。
分支节点应该包括m个key以及m+1一个节点指针(索引), idxs即用于存储节点的索引信息,比第一个key关键字小的子节点的指针存放在BDBNODE的heir字段中,这样保证剩余的信息是结构化的。
BDBIDX中包含一个key的大小和一个节点指针(ID)信息,实际的key则紧接在该结构之后,m个BDIDX加一个heir字段组成分支节点的索引。
节点信息序列化到磁盘上按照多个{[heir][pid][ksiz][kvalue]}存储的,这样当需要获取节点信息时,通过反序列化即可获得节点的内存结构。
叶子节点的结构
typedef struct { // type of structure for a record
int ksiz; // size of the key region
int vsiz; // size of the value region
TCLIST *rest; // list of value objects
} BDBREC;
typedef struct { // type of structure for a leaf page
uint64_t id; // ID number of the leaf
TCPTRLIST *recs; // list of records
int size; // predicted size of serialized buffer
uint64_t prev; // ID number of the previous leaf
uint64_t next; // ID number of the next leaf
bool dirty; // whether to be written back
bool dead; // whether to be removed
} BDBLEAF;
叶子节点用于实际存储数据,每个叶子包含指向前后叶子节点的指针(对应叶子节点的ID)。每个叶子节点中包含多个records<key, value-list>,因TCBDB允许key重复出现,即一个key可对应多个value,BDBREC中存储一个key以及其对应的所有value的信息。
TCBDB元信息
TCBDB的头部会包含一些整个数据库的元信息,包括分支节点成员个数,叶节点成员个数,root节点的id,第一个叶节点的id,最后一个叶节点的id,叶节点数,分支节点数,记录数等信息。
当需要访问TCBDB的某个key对应的value时,会从root节点进行查找,并通过分支节点的索引定位到叶子节点,然后在叶子节点中二分查找指定的key对应的记录。为了加速查找过程, TCBDB将最近访问的节点进行了缓存。
2011年12月3日补充:
TCBDB为区分叶子节点和分支节点,叶子和分支id在使用TCHDB进行存储时使用不同范围的ID,临界点为BDBNODEIDBASE。另外,TCBDB将最近访问的node和leaf进行缓存,因为在一次存储或删除对象的过程中,可能多次访问路径上的节点。
存储(put)一个对象,包含如下步骤:
(1) 从根节点起,按查找路径下降至叶子节点(id < BDBNODEIDBASE)停止,此id即为对象应该插入的叶子节点,并在下降的过程中记录路径上的所有节点id.
(2) 将put的对象插入到叶子节点中,如果叶子节点中对象个数超出限定值(lmemb)或者叶子节点的总大小超出限定值(lsmax),则需要对叶子节点进行分裂。
(3) 分裂了叶子节点后需要在父分支节点中增加一个索引项,这可能导致分支节点索引个数超出阈值(nmemb),因此要沿着下降的路径(在步骤1中记录)上升至根节点,对需要分裂的分支进行分裂,该过程可能导致树增高。
删除(out)一个对象,包含如下步骤:
(1) 与put过程相同,找到对应的叶子节点,记录从根到叶子节点的路径。
(2) 从叶子节点中删除对应的对象,如果叶子节点中无任何对象了,则需要kill掉该叶子节点,并从叶子节点的父节点删除索引项,如果父节点中再无任何索引,则需要kill掉父节点。
在删除节点时,并没有严格按照B+树的定义进行,只有在叶子(分支)节点中再无任何对象(索引)时才将对应的节点移除,可能因为在实际的系统应用中,删除操作本身并不多,没有必要完全按照定义来进行删除。
lnum、nnum并非表示B树当前的叶子、分支数,而时代表创建过的叶子、节点数,因每创建一个新的叶子,都是直接使用++lnum,而且在删除叶子的时候也没有减少lnum的值。