1 个不稳定版本
0.3.1 | 2023年11月26日 |
---|---|
0.3.0 |
|
0.2.0 |
|
0.1.0 |
|
0.0.0-reserve.0 |
|
#1 在 #wal
每月500 次下载
用于 rencfs
88KB
1.5K SLoC
OkayWAL
Rust 的预写日志 (WAL) 实现。
有长城,还有这个:一个不错的 WAL。
警告:这个crate处于早期开发阶段。请不要在生产项目中使用,直到它已被集成到 Sediment 并作为 Nebari 的一部分发布。文件格式目前被视为不稳定。
此crate公开了一个支持
- 来自多个线程的原子性和持久性写入。
- 对先前写入数据的随机访问。
- 自动检查点以允许重用磁盘空间并防止WAL过大。
- 具有基本数据版本支持的基本恢复过程。
基本如何使用
WriteAheadLog::recover()
用于在给定目录中创建或恢复WAL。要打开日志,必须提供 LogManager
的实现者。这是OkayWAL在与恢复或检查点日志通信时与您的代码通信的方式。
基本示例 使用许多注释展示了这个过程,描述了OkayWAL的工作方式。
// Open a log using an Checkpointer that echoes the information passed into each
// function that the Checkpointer trait defines.
let log = WriteAheadLog::recover("my-log", LoggingCheckpointer)?;
// Begin writing an entry to the log.
let mut writer = log.begin_entry()?;
// Each entry is one or more chunks of data. Each chunk can be individually
// addressed using its LogPosition.
let record = writer.write_chunk("this is the first entry".as_bytes())?;
// To fully flush all written bytes to disk and make the new entry
// resilliant to a crash, the writer must be committed.
writer.commit()?;
多线程写入
从多个线程到日志的优化写入是自动处理的。在任何时刻,只能有一个线程访问活动日志文件。因为将数据写入磁盘最慢的部分是 fsync
,所以OkayWAL管理同步多个写入者,以便可以使用单个 fsync
调用执行多个写入。
这可以通过运行基准测试套件来演示: cargo bench -p benchmarks
commit-256B
标签 | 平均 | 最小 | 最大 | 标准差 | 输出百分比 |
---|---|---|---|---|---|
okaywal-01t | 1.001ms | 617.5us | 7.924ms | 557.3us | 0.016% |
okaywal-02t | 1.705ms | 617.3us | 11.38ms | 912.1us | 0.006% |
okaywal-04t | 1.681ms | 622.4us | 9.688ms | 671.4us | 0.021% |
okaywal-08t | 1.805ms | 656.5us | 13.88ms | 1.001ms | 0.014% |
okaywal-16t | 1.741ms | 643.2us | 7.895ms | 796.4us | 0.028% |
提交-1KB
标签 | 平均 | 最小 | 最大 | 标准差 | 输出百分比 |
---|---|---|---|---|---|
okaywal-01t | 959.3us | 621.9us | 7.419ms | 584.4us | 0.012% |
okaywal-02t | 1.569ms | 627.5us | 7.986ms | 1.007ms | 0.028% |
okaywal-04t | 1.856ms | 650.5us | 11.14ms | 1.087ms | 0.017% |
okaywal-08t | 2.054ms | 697.3us | 11.04ms | 1.066ms | 0.021% |
okaywal-16t | 1.875ms | 641.5us | 8.193ms | 674.6us | 0.032% |
提交-4KB
标签 | 平均 | 最小 | 最大 | 标准差 | 输出百分比 |
---|---|---|---|---|---|
okaywal-01t | 1.242ms | 748.8us | 6.902ms | 982.4us | 0.008% |
okaywal-02t | 1.767ms | 761.9us | 8.986ms | 902.1us | 0.016% |
okaywal-04t | 2.347ms | 787.1us | 8.853ms | 1.084ms | 0.016% |
okaywal-08t | 2.798ms | 810.8us | 12.53ms | 1.168ms | 0.014% |
okaywal-16t | 2.151ms | 840.5us | 14.74ms | 1.201ms | 0.008% |
提交-1MB
标签 | 平均 | 最小 | 最大 | 标准差 | 输出百分比 |
---|---|---|---|---|---|
okaywal-01t | 7.018ms | 5.601ms | 9.865ms | 788.2us | 0.027% |
okaywal-02t | 11.06ms | 4.281ms | 20.14ms | 3.521ms | 0.000% |
okaywal-04t | 19.77ms | 5.094ms | 73.21ms | 8.794ms | 0.007% |
okaywal-08t | 25.06ms | 2.871ms | 97.60ms | 17.33ms | 0.002% |
okaywal-16t | 19.01ms | 3.480ms | 58.85ms | 7.195ms | 0.014% |
这些数字是单个线程执行给定大小的原子且持久的写入日志文件所需的时间。尽管使用单文件方法,我们仍然能够保持平均写入时间非常低,即使在大量并发写入者的情况下。
OkayWAL是如何工作的
OkayWAL将传入的数据流式传输到“段”。每个段文件使用以下格式命名:wal-{id}
。段文件的id指的是段文件中可能出现的第一个EntryId
。
段文件预先分配到在Configuration::preallocate_bytes
中配置的长度。预分配文件对于性能至关重要,因为通常覆盖现有字节比在磁盘上分配新字节更便宜。
OkayWAL始终有一个当前段文件。当写入新条目时,它总是写入当前段文件。当条目完成时,检查段文件长度是否与Configuration::checkpoint_after_bytes
相符。如果写入足够的数据以触发检查点,则将文件发送到检查点线程,并激活新的段文件。
无论文件是否已检查点,在提交条目控制返回之前,都会对文件执行fsync
。fsync
操作被批处理,允许在相同的fsync
操作期间由不同的线程写入多个条目。
OkayWAL还跟踪创建新文件或重命名文件的时间。需要时,包含写入前日志的目录也会执行fsync
,以确保必要的文件和目录元数据完全同步。就像文件fsync
批处理一样,OkayWAL还自动跨线程批处理目录fsync
。
检查点段文件(后台线程)
检查点线程保留对WriteAheadLog
数据的弱引用。当线程收到检查点的文件时,它将升级弱引用。如果不能升级,检查点线程将优雅地关闭,恢复过程将在下次日志打开时再次发送文件进行检查点。
线程对文件调用LogManager::checkpoint_to
,允许LogManager
对正在检查点的段中存储的数据进行任何必要的更改以持久化。
在LogManager
完成后,文件会被重命名为包含后缀-cp
。在此步骤之前,读取器可以打开存储在正在检查点文件的中的数据。一旦文件被重命名,新的读取器将开始返回未找到错误。
文件重命名后,检查点器等待所有未完成的读取器完成数据读取。然后,该文件最终通过将其移动到非活动文件列表来回收。
激活新的段文件
如果非活动文件列表中存在任何文件,则将其重复使用。否则,将创建一个新文件,并用0填充到配置的预分配长度。
文件名设置为wal-{next EntryId}
。例如,一个新的预写日志的第一段文件的名称将是wal-1
,第一个写入的EntryId
将是1
。
段文件格式
每个段文件以这个头开始
okw
:三个字节的魔数- OkayWAL 版本:单字节版本号。目前为0。
Configuration::version_info
长度:单字节。嵌入的信息必须小于或等于255字节。- 嵌入的版本信息:版本信息的字节。前一个字节控制这个字段有多长。
在此头部之后,文件是一系列的条目,每个条目包含一系列的数据块。值为1的字节表示一个新条目的开始。任何其他字节都将使读取器停止从文件中读取条目。
条目的前8个字节是其EntryId
的小端表示。
EntryId
之后,应期望一系列的数据块。值为2的字节表示文件中的下一个数据块。值为3的字节表示当前写入的条目结束。任何其他字节将使SegmentReader
返回一个AbortedEntry结果。应该忽略/回滚由LogManager
读取的此条目中的任何已读取的数据块。
数据块的前四个字节是其数据长度的 小端表示。数据块的数据随后。
最后,一个四字节的crc32结束数据块。
如果读取器没有遇到新的数据块标记(2)或条目结束标记(3),则应认为条目已被丢弃,应忽略所有数据块。
开源许可
这个项目,就像所有来自Khonsu Labs的项目一样,是开源的。这个仓库可以在MIT许可或Apache License 2.0下使用。
要了解更多关于贡献的信息,请参阅CONTRIBUTING.md。
依赖关系
~0.6–6MB
~17K SLoC