把 LSM 引擎直接架在对象存储上:slatedb 能不能跑在 RustFS 上?

发布时间:2026/10/25 1:53:07
把 LSM 引擎直接架在对象存储上:slatedb 能不能跑在 RustFS 上?

RocksDB 这类嵌入式 KV 天生绑定一块本地 SSD,副本和跨区复制都得自己补。slatedb 反过来,把 WAL(写前日志)和 SST(排序字符串表)全部写进 S3 兼容对象存储,本地盘只做缓存。它能不能直接架在 RustFS 上?配置层面能:slatedb 用的是 object_store 那套抽象,官方口径支持任何实现 ObjectStore trait 的存储。但官方 Compatibility 页的测试目标里没有 RustFS,双方集成清单也没互相收录,这个组合没有任何一方背书,该自己测的还是得自己测。这篇文章把接法、桶里的布局、以及几个必须自己测的点一次讲清。

为什么有人想把 LSM 搬上对象存储

传统 LSM 引擎(RocksDB 为其一)把 SST 落本地 SSD,靠副本拿耐久度,云上就得配一块 EBS 或本地 NVMe,再自己做跨区复制,耐久度才摸得到对象存储的水平。slatedb 把持久层和容量直接交给对象存储:耐久度白拿,容量跟着桶走没有上限,数据本来就在对象存储里,没有跨区复制流量。代价是对象存储的延迟和每次 PUT/GET 的成本比本地盘高,slatedb 用写批处理(MemTable 攒一批再刷成 SST,很多写合成一次 PUT)和读缓存来摊薄,定位和 RocksDB 的延迟竞争从一开始就没打算赢。所以 slatedb 不是 RocksDB 的平替,它赌的是另一头需求:那些状态不算大、但要无限容量和对象存储耐久、还要节点随时可丢弃的云原生服务。你为它放弃的是微秒级本地延迟,换回来的是再也不用养一套带盘的有状态基础设施。

slatedb 在 RustFS 桶里长什么样

slatedb 在桶下开五个前缀:manifest/(记录有哪些 SST 的清单)、wal/(写前日志)、compacted/(压实后的 SST)、compactions/(压实状态快照)、gc/(GC 协调用的 boundary 文件,让废弃对象只被安全地删一次)。一次db.put先写进内存里的 WAL 和 MemTable,WAL 按flush_interval周期性刷进桶里,MemTable 满了再冻结刷成 SST。有个容易踩的坑:put().await返回只代表写进了内存,官方把落桶确认拆在返回的WriteHandle上:要强耐久就在句柄上调await_durable(),或者显式db.flush();不等它就是低延迟优先,代价是崩溃可能丢掉还没刷进桶的那一段写入。这个模型决定了它不适合 OLTP 写路径,服务的对象是那些本来就能容忍几十毫秒、但想要无限容量和对象存储耐久的工作负载。读的时候 slatedb 先查本地块缓存和 MemTable,未命中才去 RustFS 拉 SST,所以热点数据几乎不打对象存储;冷数据完全躺在桶里,随用随取。这个分层把 PUT 成本和 GET 延迟都压在可控范围。

接 RustFS 要改的那几行

slatedb 通过object_storecrate 的AmazonS3Builder指定后端,把端点指向 RustFS 即可。RustFS 1.0.0(2026-09-16 GA,Apache 2.0)是 100% S3 兼容的分布式对象存储,S3 端点在 9000 端口、走路径风格寻址:

useobject_store::aws::{AmazonS3Builder,S3ConditionalPut};useslatedb::Db;usestd::sync::Arc;#[tokio::main]asyncfnmain()->anyhow::Result<()>{letobject_store=Arc::new(AmazonS3Builder::new().with_endpoint("http://rustfs:9000").with_allow_http(true).with_force_path_style(true).with_access_key_id("slatedb-usr").with_secret_access_key("<secret>").with_bucket_name("slatedb").with_region("us-east-1").with_conditional_put(S3ConditionalPut::ETagMatch).build()?,);letdb=Db::open("/slatedb/ns1",object_store.clone()).await?;db.put(b"k1",b"v1").await?;db.flush().await?;// 显式刷桶,内存里的写入落对象存储再退出db.close().await?;Ok(())}

slatedb 对条件写(CAS,Compare-And-Swap,比较并交换)的依赖是官方明文,不是猜测:Files 页写明gc/目录下的 boundary 文件就用基于 ETag 的条件更新来保证单调推进、防止并发 GC 互踩;官网首页对 manifest fencing 协议的描述是 “formally verified”,同样建立在条件写可用之上。示例里with_conditional_put(ETagMatch)是 object_store 提供这套语义的开关,这个枚举只有ETagMatch和Disabled两个变体。RustFS 支持 S3 条件写,但这个组合没有官方背书,条件写语义建议单独实测一次:万一兼容实现有缺口,slatedb 的 fencing 会跟着失效,两个写者直接脑裂,别只看端点能连上就放心。完整接入步骤见 slatedb 官方的 Connect SlateDB to S3。桶和受限用户记得先在 RustFS 建好,用法见官方 身份与访问管理 文档。这套接法对局域网内的 RustFS 尤其合适:开发者笔记本或 CI 里跑一段 slatedb,数据落在本地 RustFS 实例,换机器不用搬盘。另外这段示例只是 PoC 演示:slatedb 默认开着块缓存,生产上还得按自己的负载调缓存、flush_interval这类参数。

成熟度与几个要自己测的点

slatedb 截至 2026 年 10 月最新是 v0.17.0(2026-09-29 发布),按官方节奏约每两个月、在偶数月末发一版,还没到 1.0。官方只承诺相邻版本间存储格式前后兼容,API 兼容性不在保证范围内,Compatibility 页还给了两条更硬的约束:共享部署一次只升一个版本,多个进程读写同一路径时彼此不能超过一个版本步长;引擎对持久化格式做版本校验,遇到不认识的版本直接 fail fast,压缩 codec 也必须编译进二进制才能读。它的 Adopters 列表里已经能见到 Dropbox、Prisma 这些名字,但这是个仍在快速演进的 0.x 引擎,别拿成熟数据库的预期去套它。它是单写者模型,多写者得自己用 fencing(写者围栏,防止脑裂)协调所有权,两个写者指向同一 namespace 会出乱子。对象存储单次 PUT 的延迟和本地盘不在一个量级,写入路径要围绕批处理和缓存来设计,RustFS 上的具体数字得自己量。

性能这层,公开基准里没有 RustFS 的数字,跑出来的吞吐和延迟得你自己在本机实测,不能拿 AWS S3 或 MinIO 的数据直接外推。真要上生产,先在 RustFS 单节点上量一组你自己的读写混合负载,再谈容量规划。

slatedb 适合什么工作负载

几个典型落点:持久化执行引擎(durable execution)把流程状态存进 slatedb,worker 挂了从桶里恢复;流处理器的中间状态、去重表;还有那些本来想用 Redis 但怕内存贵的计数器和元数据。它不适合做需要次级索引、事务跨 key 强一致、或者每秒上万次小写入的 OLTP 主库,那是别的技术的活。

桶里那几个前缀,正好接上 RustFS 的 IAM 和生命周期

把 slatedb 架在 RustFS 上,价值不只是"找了个 S3 端点"。slatedb 写进桶的那几个前缀,可以直接套 RustFS 的 IAM 做访问控制、套站点复制做异地冗余、套生命周期规则做冷数据沉降,不用在 slatedb 外面再包一层存储管理。一次写入,对象存储那套企业级能力全都接得上。

在 RustFS 上跑 slatedb 的几个实操提醒

单写者这条约束会直接落到部署形态上:一个 slatedb namespace(前面代码里的/slatedb/ns1)同一时刻只能有一个写者,多实例并行时各用各的前缀,别让两个进程指向同一个 ns1,否则要靠 fencing 去抢所有权,平白多一层复杂度。写者只有一个,读者不受限:官方提供只读句柄DbReader,多个读者可以并发打开同一个 namespace。它从 checkpoint 过的 manifest 读取,默认启用 WAL replay——把更新的 WAL SST 重放进 reader 本地的不可变 memtable,所以最新写入对读者可见的速度取决于 checkpoint 轮换和 WAL replay 设置,不是简单等刷成 L0。IAM 账号按桶、按前缀发最小权限就行,写者拿wal/compacted/的读写,只读副本只拿s3:GetObject,这条和前面 K8s 多租户的思路一脉相承。

耐久度这层要讲清楚:slatedb 把耐久交给对象存储,但对象存储这层自己得站得住。AWS S3 标称的 11 个 9 是它自家的宣传数字,slatedb 文档没背书这个数,RustFS 也没有对外承诺过具体的耐久指标。RustFS 单节点靠挂的磁盘、靠 RAID 扛单盘故障;想往上靠耐久,得是分布式 EC 部署、数据打散到多节点,单块盘不够。开发期在笔记本上跑单节点 RustFS 完全没问题,真上生产再把底层换成多节点 EC,应用侧代码一行都不用改。

还有一点容易被忽略:slatedb 会持续往wal/compacted/写新对象,桶里对象总数只增不减。废弃对象的清理由 slatedb 内置 GC 负责:配置在Settings::garbage_collector_options,默认跟着每个 Db 实例跑,也能拆成独立进程,用官方 CLI 的run-garbage-collection或schedule-garbage-collection单独执行,多个 GC 还可以并行;它只删不再被 manifest 或 checkpoint 引用的对象。对象存储的 lifecycle 规则只能做兜底,而且要放得足够保守:万一 lifecycle 把仍被 manifest 引用的对象提前删了,数据库直接损坏,这不是 slatedb 能补救的错。所以在 RustFS 上要盯两件事:桶的对象总数会不会涨到影响ListBucket的返回速度,以及 GC 有没有正常在删、冷数据有没有悄悄堆成账单。

一组可直接跑的动作

  1. 装依赖照官方教程来:cargo add slatedb tokio --features tokio/macros,tokio/rt-multi-thread、cargo add object-store --features object-store/aws、cargo add anyhow,再按上面改AmazonS3Builder指向 RustFS 9000 端口。object_store 不开 aws feature 编译不过;版本要和 slatedb 依赖的 object_store 对齐,或者直接用slatedb::object_store的 re-export,免得出现两份对不上的类型。
  2. 在 RustFS 建slatedb桶并开一个只对该桶有读写权的 IAM 用户。
  3. 要强耐久就在返回的WriteHandle上await_durable(),或显式db.flush();上多写者前先解决 fencing。
  4. 压测别信别人的数字,自己在本机量一遍 RustFS 上的 PUT/GET 延迟。

官方入口:slatedb 仓库 github.com/slatedb/slatedb、文档 slatedb.io;RustFS 文档 docs.rustfs.com、源码 GitHub。