Author: 如泽
TokuDB的buffer pool(在TokuDB中被称作cachetable)维护几个后台工作线程定期处理一些任务。 其中有一个工作线程叫做checkpointer线程,每60秒启动一次把cachetable中所有脏页写回到磁盘上。 TokuDB只支持这一种checkpoint方式,用MySQL术语来说就是sharp checkpoint。 每次checkpoint过程写的脏页数目可能会比较多,而且在写回的过程中需要一直持有节点的读写锁,因此,checkpoint时索引的访问性能会受到一定程度的影响。 为了降低checkpoint对性能影响,TokuDB对每个脏页clone一份用于写回,在clone的过程中是持有节点的读写锁的,clone结束会放掉读写锁。
TokuDB checkpoint过程分为如下五个步骤:
下面我们一起看一下begin checkpoint和end checkpoint的详细过程。
在checkpoint开始时刻要做一些准备工作,诸如:
pin FT 给CACHEFILE对应的FT加pinned_by_checkpoint标记,保证CACHEFILE不会从内存里移除。CACHEFILE记录了索引包含的数据节点列表和描述索引对应文件的相关信息。
注: 1, 2阶段在m_cf_list->read_lock保护下进行 4, 5阶段在此过程在pair list的锁和m_cf_list->read_lock保护下进行。。 注意是拿了pair list上所有的锁,m_list_lock读锁,m_pending_lock_expensive写锁,m_pending_lock_cheap写锁,保证不能向pair list添加/删除数据页;不能把pair list的数据页evict出内存;同时也阻止在get_and_pin的过程中client线程池帮助写回属于checkpoint的脏页。这三个锁都是保护pair list的,按照不同的功能拆分成三个锁。
void checkpointer::begin_checkpoint() {
// 1. Initialize the accountability counters.
m_checkpoint_num_txns = 0;
// 2. Make list of cachefiles to be included in the checkpoint.
m_cf_list->read_lock();
m_cf_list->m_active_fileid.iterate<void *, iterate_note_pin::fn>(nullptr);
m_checkpoint_num_files = m_cf_list->m_active_fileid.size();
m_cf_list->read_unlock();
// 3. Create log entries for this checkpoint.
if (m_logger) {
this->log_begin_checkpoint();
}
bjm_reset(m_checkpoint_clones_bjm);
m_list->write_pending_exp_lock();
m_list->read_list_lock();
m_cf_list->read_lock(); // needed for update_cachefiles
m_list->write_pending_cheap_lock();
// 4. Turn on all the relevant checkpoint pending bits.
this->turn_on_pending_bits();
// 5. Clone BTT and FT header
this->update_cachefiles();
m_list->write_pending_cheap_unlock();
m_cf_list->read_unlock();
m_list->read_list_unlock();
m_list->write_pending_exp_unlock();
}
在end checkpoint的阶段
void checkpointer::end_checkpoint(void (*testcallback_f)(void*), void* testextra) {
toku::scoped_malloc checkpoint_cfs_buf(m_checkpoint_num_files * sizeof(CACHEFILE));
CACHEFILE *checkpoint_cfs = reinterpret_cast<CACHEFILE *>(checkpoint_cfs_buf.get());
this->fill_checkpoint_cfs(checkpoint_cfs);
this->checkpoint_pending_pairs();
this->checkpoint_userdata(checkpoint_cfs);
// For testing purposes only. Dictionary has been fsync-ed to disk but log has not yet been written.
if (testcallback_f) {
testcallback_f(testextra);
}
this->log_end_checkpoint();
this->end_checkpoint_userdata(checkpoint_cfs);
// Delete list of cachefiles in the checkpoint,
this->remove_cachefiles(checkpoint_cfs);
}
下面我们一起看一下checkpoint过程记录的redo日志:
./tdb_logprint < data/log000000000002.tokulog27
begin_checkpoint 'x': lsn=88 timestamp=1455623796540257 last_xid=153 crc=470dd9ea len=37
fassociate 'f': lsn=89 filenum=0 treeflags=0 iname={len=15 data="tokudb.rollback"} unlink_on_close=0 crc=8606e9b1 len=49
fassociate 'f': lsn=90 filenum=1 treeflags=4 iname={len=18 data="tokudb.environment"} unlink_on_close=0 crc=92dc4c1c len=52
fassociate 'f': lsn=91 filenum=3 treeflags=4 iname={len=16 data="tokudb.directory"} unlink_on_close=0 crc=86323b7e len=50
end_checkpoint 'X': lsn=92 lsn_begin_checkpoint=88 timestamp=1455623796541659 num_fassociate_entries=3 num_xstillopen_entries=0 crc=5cde4ff2 len=45