Skip to content

【Zig 日报】项目分享:Zeno--用 Zig 编写的高性能嵌入式键值存储引擎。 #316

@jiacai2050

Description

@jiacai2050

Zeno 是一款使用纯 Zig 语言编写的高性能、嵌入式键值存储引擎。它专为现代工作负载设计,优先考虑可预测的低延迟、零隐式内存分配和高效的分片并发。其名称(Node/节点)反映了支撑每项操作的核心索引和存储节点,请勿将其与 Node.js 混淆。

Zeno 最初是作为一个研究数据库存储内部原理和自适应基数树(ART)的学习实验。实验结果和性能表现非常出色,因此它演变成了一个独立的引擎。

🚀 核心特性

  • 自适应基数树 (ART) 索引:O(k) 查找时间,通过 SIMD 优化的节点转换(Node4 到 Node256)。
  • 分片并发:256 分片架构,通过顺序锁(seqlock)+ 标记指针(tagged-pointer)ART 实现无锁 GET。并发读取者互不阻塞;写入者按分片序列化。
  • 零隐式分配:遵循严格的 Zig 实践,每个需要分配内存的函数都接受显式的 Allocator
  • 持久化存储
    • WAL (预写日志):批量异步持久化模式,实现高吞吐量写入。
    • 快照:高效的流式快照备份与恢复。
  • 结构化数值:通过 union(enum) 支持复杂数值类型,确保严格的运行时类型安全。

📊 性能基准测试

Zeno 为速度而生。以下数据来自目前在 Ubuntu 24.04.4、AMD Ryzen 7 5700X、32GB DDR4 @ 3200MHz 环境下运行的基准测试套件。

测试方法:稳态测试使用 1,000 个轮转 Key,2,000 次预热迭代,100,000 次测量迭代。延迟列显示 p50/p99 分位数。扩展性测试每个配置运行 1,000,000 次操作。

点操作吞吐量

操作类型 吞吐量 p50 p99
DB PUT (覆盖写, 稳态) 14.75M ops/sec 70 ns 90 ns
DB GET (稳态) 10.71M ops/sec 90 ns 110 ns
DB GET (稳态, TTL 混合) 17.47M ops/sec 50 ns 100 ns
DB PUT Group16 (稳态) 1.18M items/sec 12.98 µs 19.83 µs
ART Lookup (索引查找) 20.98M ops/sec 50 ns 60 ns
ART Insert (索引插入) 30.27M ops/sec 30 ns 40 ns
WAL Append (异步) 0.66M ops/sec 1.38 µs 3.71 µs

分片扩展性

GET 操作通过顺序锁实现无锁化——同一分片上的多个读取者并行进行,无需序列化。PUT 操作通过分片互斥锁序列化写入者;修改 ART 结构的插入操作会额外受顺序锁计数器的保护。

GET —— 无竞争(每个线程位于不同分片):

线程数 1 2 4 8 16
吞吐量 35.58M 67.24M 119.28M 169.73M 203.11M ops/sec
扩展倍率 1.00x 1.89x 3.35x 4.77x 5.71x

GET —— 热点(所有线程访问同一个 Key):

线程数 1 2 4 8 16
吞吐量 34.05M 65.10M 121.84M 226.88M 281.13M ops/sec
扩展倍率 1.00x 1.91x 3.58x 6.66x 8.26x

注:GET 热点表现出超线性扩展,因为多个读取者可以同时遍历相同的缓存 ART 路径而无竞争。

GET —— 均匀分布 10k Keys(真实工作负载):

线程数 1 2 4 8 16
吞吐量 10.83M 18.99M 34.10M 56.15M 89.70M ops/sec
扩展倍率 1.00x 1.75x 3.15x 5.19x 8.29x

PUT —— 无竞争(每个线程位于不同分片):

线程数 1 2 4 8 16
吞吐量 41.76M 68.33M 122.99M 248.82M 203.71M ops/sec
扩展倍率 1.00x 1.64x 2.95x 5.96x 4.88x

PUT —— 均匀分布 10k Keys(真实工作负载):

线程数 1 2 4 8 16
吞吐量 12.02M 16.04M 27.25M 43.20M 55.18M ops/sec
扩展倍率 1.00x 1.33x 2.27x 3.59x 4.59x

高频覆盖写校准

对于频繁覆盖大数值(字符串、数组)的工作负载,Zeno 会累积保留 Arena 字节,直到调用 compact_shard。下表显示了压缩频率、p99 延迟和保留内存之间的权衡(Payload=1KB, Keys=64, Ops=50k):

compact_every_N p50 p99 max 最终保留内存 总耗时
1000 110 ns 6.14 µs 138.54 µs 0 B 106.27 ms
5000 100 ns 4.38 µs 175.05 µs 0 B 48.33 ms
10000 100 ns 3.76 µs 63.37 µs 0 B 39.42 ms
off (关闭) 90 ns 3.69 µs 123.54 µs 48.83 MB 25.10 ms
  • 当需要限制保留字节且能接受中等维护开销时,请使用 5000
  • 仅在追求极致吞吐量且可以接受高内存占用时,请使用 off

要在您的机器上复现这些数据:
zig build bench -Doptimize=ReleaseFast

🛠 使用方法

在您的 build.zig.zon 中添加 zeno:

.{
    .name = "my-project",
    .version = "0.1.0",
    .dependencies = .{
        .zeno = .{
            .url = "https://github.com/zeno-core/zeno/archive/refs/heads/main.tar.gz",
        },
    },
}

然后,在您的 build.zig 中:

const zeno = b.dependency("zeno", .{
    .target = target,
    .optimize = optimize,
});
exe.root_module.addImport("zeno", zeno.module("zeno"));

快速示例

const std = @import("std");
const zeno = @import("zeno");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // 内存模式引擎(不持久化)
    const db = try zeno.public.create(allocator);
    defer db.close() catch {};

    // 写入一个值
    const value = zeno.types.Value{ .string = "Alice" };
    try db.put("user:1", &value);

    // 读取值,调用者拥有返回值的内存所有权
    if (try db.get(allocator, "user:1")) |val| {
        defer val.deinit(allocator);
        std.debug.print("Found: {s}\n", .{val.string});
    }

    // 删除
    _ = try db.delete("user:1");
}

使用 WAL 和快照恢复的持久化引擎:

const db = try zeno.public.open(allocator, .{
    .wal_path      = "./data.wal",
    .snapshot_path = "./data.snapshot",
    .fsync_mode    = .batched_async,
});
defer db.close() catch {};

🏗 架构

Zeno 采用分片优先架构,旨在保持并发环境下热路径的可预测性:

  1. 键空间分区:键空间被划分为 256 个独立的分片(通过 Key 的哈希路由),每个分片拥有自己的 ART 索引、锁、顺序计数器和内存 Arena。
  2. 分片局部操作:点操作在路由后是分片局部的。get 通过顺序锁实现无锁化——同一分片上的并发读取者无需相互序列化。put 获取分片排他锁;覆盖现有 Key 会跳过顺序锁计数器以实现最小延迟。
  3. 读取一致性:通过可见性栅栏和 ReadView 协调,因此扫描和视图内读取可以观察到稳定状态,而其他分片的写入仍可继续。
  4. 持久化机制:由 WAL + 快照处理。WAL 记录实时变更用于崩溃恢复,而快照提供更快的重启和定期状态压缩。

这种设计提供了极强的单 Key 延迟表现、良好的多核扩展性,并允许在吞吐量和持久化策略 (fsync_mode) 之间进行显式权衡。

⚖️ 许可证

基于 MIT 许可证分发。

加入我们

Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文群体中的使用,有多种方式可以参与进来:

  1. 供稿,分享自己使用 Zig 的心得
  2. 改进 ZigCC 组织下的开源项目
  3. 加入微信群Telegram 群组

Metadata

Metadata

Assignees

No one assigned

    Labels

    日报daily report

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions