1 个不稳定版本

0.3.1 2023年11月26日
0.3.0 2023年10月11日
0.2.0 2023年1月6日
0.1.0 2023年1月3日
0.0.0-reserve.0 2022年8月17日

#1#wal

Download history 1/week @ 2024-05-06 138/week @ 2024-05-13 161/week @ 2024-05-20 201/week @ 2024-05-27 949/week @ 2024-06-03 131/week @ 2024-06-10 101/week @ 2024-06-17 111/week @ 2024-06-24 219/week @ 2024-07-01 67/week @ 2024-07-08 13/week @ 2024-07-15 67/week @ 2024-07-22 313/week @ 2024-07-29 96/week @ 2024-08-05

每月500 次下载
用于 rencfs

MIT/Apache

88KB
1.5K SLoC

OkayWAL

Rust 的预写日志 (WAL) 实现。

有长城,还有这个:一个不错的 WAL。

警告:这个crate处于早期开发阶段。请不要在生产项目中使用,直到它已被集成到 Sediment 并作为 Nebari 的一部分发布。文件格式目前被视为不稳定。

okaywal forbids unsafe code crate version Live Build Status HTML Coverage Report for main branch Documentation

此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相符。如果写入足够的数据以触发检查点,则将文件发送到检查点线程,并激活新的段文件。

无论文件是否已检查点,在提交条目控制返回之前,都会对文件执行fsyncfsync操作被批处理,允许在相同的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