WAL预写日志格式

概览

预写日志(WAL) 将memtable操作作为日志文件序列化到持久介质。在出现故障时,可以使用WAL文件通过从日志中重构memtable,将数据库恢复到一致状态。 当memtable被安全地清除到持久介质时,相应的WAL日志将被废弃并存档。 最后,经过一段时间后,将归档日志从磁盘中清除。

WAL 管理器

在WAL目录中,随着序列号的增加,将生成WAL文件。为了重建数据库的状态,按序号顺序读取这些文件。 WAL manager提供了将WAL文件作为单个单元读取的抽象。在内部,它使用Reader或Writer抽象打开和读取文件。

Reader/Writer 读取器/写入器

Writer提供了一个将日志记录附加到日志文件的抽象。特定于介质的内部细节由WriteableFile接口处理。 类似地,Reader提供了从日志文件中顺序读取日志记录的抽象。 内部媒体特定的细节由SequentialFile接口处理。

日志文件格式

日志文件由一系列可变长度的记录组成。记录按kBlockSize分组。如果某个记录不能放入剩余空间,那么剩余空间将填充空(null)数据。 写入器以kBlockSize的块进行编写,而读取器以kBlockSize的块进行读取。

  1. +-----+-------------+--+----+----------+------+-- ... ----+
  2. File | r0 | r1 |P | r2 | r3 | r4 | |
  3. +-----+-------------+--+----+----------+------+-- ... ----+
  4. <--- kBlockSize ------>|<-- kBlockSize ------>|
  5. rn = variable size records
  6. P = Padding

记录格式

记录布局格式如下所示。

  1. +---------+-----------+-----------+--- ... ---+
  2. |CRC (4B) | Size (2B) | Type (1B) | Payload |
  3. +---------+-----------+-----------+--- ... ---+
  4. CRC = 32bit hash computed over the payload using CRC
  5. Size = Length of the payload data
  6. Type = Type of record
  7. (kZeroType, kFullType, kFirstType, kLastType, kMiddleType )
  8. The type is used to group a bunch of records together to represent
  9. blocks that are larger than kBlockSize
  10. Payload = Byte stream as long as specified by the payload size

记录格式详情

日志文件内容是由32KB块组成的序列。唯一的例外是文件的尾部可能包含部分块。

每个数据块由一系列记录组成:

  1. block := record* trailer?
  2. record :=
  3. checksum: uint32 // crc32c of type and data[]
  4. length: uint16
  5. type: uint8 // One of FULL, FIRST, MIDDLE, LAST
  6. data: uint8[length]

记录永远不会在块的最后6个字节内开始(因为它不适合)。这里的任何剩余字节都构成预告片,它必须完全由零字节组成,并且必须被读者跳过。

  1. 如果正好七个字节在当前块,并添加一个新的非零长度记录,作家必须先发出记录(包含零字节的用户数据)来填满后7个字节的块,然后发出所有的用户数据在随后的街区。

将来可能会添加更多类型。一些读者可能跳过他们不理解的记录类型,另一些读者可能报告说跳过了一些数据。

  1. FULL == 1
  2. FIRST == 2
  3. MIDDLE == 3
  4. LAST == 4

FULL 完整记录包含整个用户记录的内容。

FIRST, MIDDLE, LAST 是用于被分割成多个片段的用户记录的类型(通常由于块边界)。 FIRST是用户记录的第一个片段的类型,LAST是用户记录的最后一个片段的类型,MID是用户记录的所有内部片段的类型。

例如:考虑一系列用户记录:

  1. A: length 1000
  2. B: length 97270
  3. C: length 8000

A将作为完整的记录存储在第一个块中。

B将被分成三个片段:第一个片段占用第一个块的其余部分,第二个片段占用第二个块的全部,第三个片段占用第三个块的前缀。 这将在第三个块中留下6个字节的空闲空间,这将作为拖车保留为空。

C将作为完整记录存储在第四个块中。

优点

recordio格式的一些好处:

  1. 我们不需要任何启发式的重新同步-只要去下一个块边界和扫描。 如果有损坏,跳到下一个块。另一个好处是,当一个日志文件的部分内容作为记录嵌入到另一个日志文件中时,我们不会感到困惑。
  2. 在近似边界处进行分割(例如,对于mapreduce)很简单: 找到下一个块边界并跳过记录,直到找到完整的或第一个记录。
  3. 对于大型记录,我们不需要额外的缓冲。

缺点

与recordio格式相比,有些缺点:

  1. 没有记录的打包。可以通过添加新的记录类型来解决这个问题,所以这是当前实现的一个缺点,而不一定是格式的问题。
  2. 没有压缩。同样,可以通过添加新的记录类型来解决这个问题。