#write-ahead-log #wal #high #garbage-collection #buffer #concurrency #solution

walcraft

轻量级带有垃圾回收的预写日志 (WAL) 解决方案

2 个版本

0.1.1 2024 年 6 月 16 日
0.1.0 2024 年 6 月 16 日

#330文件系统

每月 29 次下载

MIT/Apache

35KB
684

Walcraft

Walcraft 是一个适用于并发环境的预写日志 (WAL) 解决方案。该库通过使用内存缓冲区和只写日志提供高性能。日志存储在多个文件中,并删除较旧的文件以节省空间。

功能

  • 出色的crate名称
  • 简单易用和定制
  • 可配置的存储限制
  • 高写入吞吐量
  • 为并发和并行环境构建
  • 防止高频写入的写入放大

如何

该crate使用一个可调整大小的内存缓冲区,默认大小为4 KB。数据首先添加到缓冲区,当缓冲区满时写入磁盘。

对于高频环境,这种设置通过将几个较小的更新批量合并为几个较大的更新来确保更高的写入吞吐量,从而防止由于写入放大而对 SSD 造成不必要的磨损。

对于低频环境,库提供了一种在缓冲区完全填满之前提前刷新更改的方法(通过使用 .flush() 方法),确保更高的日志恢复保证。

使用方法

写入日志

use serde::{Deserialize, Serialize};
use walcraft::Wal;

// Log to write
#[derive(Serialize, Deserialize, Clone)]
struct Log {
    id: usize,
    value: f64
}

let log = Log {id: 1, value: 5.6234};

// initiate wal and add a log
let wal = Wal::new("./tmp/", None);
wal.write(log); // write a log

// write a log in another thread
let wal2 = wal.clone();
std::thread::spawn(move | | {
let log = Log{id: 2, value: 0.45};
  wal2.write(log);
});

// keep writing logs in current thread
let log = Log{id: 3, value: 123.59};
wal.write(log);

// Flush the logs to the disk manually
// This happens automatically as well after some time. However, it's advised to
// run this method before terminating the program to ensure that no logs are lost.
wal.flush();

读取日志

use serde::{Deserialize, Serialize};
use walcraft::Wal;

// Log to write
#[derive(Serialize, Deserialize, Debug)]
struct Log {
    id: usize,
    value: f64
}
let wal: Wal<Log> = Wal::new("./tmp/", None);
let iterator = wal.read().unwrap();

for log in iterator {
    dbg!(log);
}

限制日志大小

Wal::new 方法接受两个参数。第一个参数是存储日志的目录。第二个(可选)参数是日志应占用的MB数。

一旦日志文件占用的存储空间超过提供的限制,就通过分块删除较旧的日志来释放一些空间。

// Unlimited log storage
let wal = Wal::new("/tmp/logz", None);

// 500 MB of logs storage
let wal = Wal::new("/tmp/logz", Some(500));

// 20 GB of logs storage
let wal = Wal::new("/tmp/logz", Some(20_000));

即将推出的功能

  • 并发读写
  • 可配置的缓冲区大小
  • 支持跳过缓冲区并直接写入磁盘
  • 支持 fsync

怪癖

WAL 只能在读模式或写模式中,不能同时两种模式。

  • 理想:创建时,WAL 处于空闲模式。
  • 读取:调用 .read() 方法将WAL切换到读取模式。在此模式下,您不能写入数据;任何写入尝试都将被忽略。一旦读取完成,WAL将自动恢复到理想模式。
  • 写入:当您开始向WAL写入时,它将切换到写入模式,不能切换回理想或读取模式。

此设计可以防止读取和写入之间的冲突。理想情况下,您应该在启动时读取数据,作为恢复过程的一部分,然后再开始写入。

注意:此行为将在0.2更新中得到修复。

use serde::{Deserialize, Serialize};
use walcraft::Wal;

// Log to write
#[derive(Serialize, Deserialize, Clone)]
struct Log {
    id: usize,
    value: f64
}

// create an instance of WAL
let wal = Wal::new("/tmp/logz", Some(2000));

// recovery: Option A
let all_logs = wal.read().unwrap().into_iter().collect::<Vec<Log> > ();
// recovery: Option B
for log in wal.read().unwrap() {
  // do something with logs 
  dbg!(log);
}

// start writing
wal.write(Log{id: 1, value: 3.14});

依赖项

~0.6–1.2MB
~28K SLoC