从维基百科的ACID
词条,我们可以看到:
ACID,是指数据库管理系统(DBMS)在写入或更新资料的过程中,为保证事务(transaction)是正确可靠的,所必须具备的四个特性:原子性(atomicity,或称不可分割性)、一致性(consistency)、隔离性(isolation,又称独立性)、持久性(durability)。
特别地,为了保证事务的原子性和持久性,在对数据库内存中维护的各种数据结构修改之前,会将该事务的对数据库的所有操作信息先写入磁盘中日志文件,这个过程被称为预写日志(Write-Ahead Logging,缩写为WAL)。当数据库发生崩溃时,预写日志也可以作为故障恢复的依据。
而假如每次提交事务需要调用一次fsync将日志刷入磁盘,而一次fsync本身是开销是比较大的,那么事务提交将是数据库的一个瓶颈。而当好几个事务要提交时,将它们合并一次fsync来做,即可以显著提高数据库系统的TPS,这也是Group Commit的含义。
MyRocks的写入过程分成以下三步:
其中,每个WriteBatch代表一个事务,可以包含多条操作,可以通过调用WriteBatch::Put/Delete等操作将对应多条的key/value记录加入WriteBatch中。
同样地,为了提高提交的性能,RocksDB引擎也使用Group Commit的机制。
每个写线程都会生成一个WriteThread::Write的实例,关联到对应的一个WriteBatch。
Write的数据结构如下:
struct Writer {
WriteBatch* batch;
bool sync;
bool no_slowdown;
bool disable_wal;
bool disable_memtable;
uint64_t log_used; // log number that this batch was inserted into
uint64_t log_ref; // log number that memtable insert should reference
WriteCallback* callback;
bool made_waitable; // records lazy construction of mutex and cv
std::atomic<uint8_t> state; // write under StateMutex() or pre-link
WriteGroup* write_group;
SequenceNumber sequence; // the sequence number to use for the first key
Status status; // status of memtable inserter
Status callback_status; // status returned by callback->Callback()
std::aligned_storage<sizeof(std::mutex)>::type state_mutex_bytes;
std::aligned_storage<sizeof(std::condition_variable)>::type state_cv_bytes;
Writer* link_older; // read/write only before linking, or as leader
Writer* link_newer; // lazy, read/write only before linking, or as leader
}
可以看到它也是一个链表的结构,待提交的事务可以通过JoinBatchGroup(&w)函数将本WriteBatch对应的Write实例加到Write链表中。自然地,Write链表中的一个元素代表着一个待提交的写线程。写到WAL文件中的内容有先后顺序,这里也只需要按照链表中的先后顺序写入即可。多个Write对象的实例同样合并成一个写WAL操作,由一个线程负责进行fsync即可。
这里存在一个问题,由哪个线程来负责进行fsync操作将操作记录写入WAL文件中?
RocksDB将待提交阶段的线程分成两种: leader线程和follower线程。存在leader线程的Group Commit如下:
这里存在另一个问题,leader是怎么选出来并且是怎么进行Group Commit?
当写线程要提交事务时会将自己对应的Write实例添加到Write链表的尾部。 此时存在一种特殊情况,即当前待提交的线程是加入Write链表的第一个线程。在RocksDB的逻辑中,第一个加入链表的线程将成为leader线程。
当线程成为leader线程之后,将开始进入提交逻辑(以下简略部份逻辑):
writer对象在Group Commit过程中有如下几种状态:
以下为简化的writer的状态变化图
本文介绍RocksDB存储引擎在写入数据时Group Commit的机制。
ACID: https://zh.wikipedia.org/wiki/ACID