feat: add distributed owner SSD backing tier for KV cache storage#37
Conversation
| let (source_allocation, source_base, source_addr, response_ssd_stage_len) = | ||
| if local_direct_read { | ||
| let target_capacity = target_allocation.capcity(); | ||
| if target_capacity < ssd_stage_len { |
There was a problem hiding this comment.
[P1] 允许未对齐容量走 scratch 读取
这里把本地 SSD 回填强制要求为 target_capacity >= align(len, 512),但 KvSsdStorage::load_into_addr 已经会在容量不满足 direct I/O 时选择 scratch,并且最终只复制真实的 len。当前 buddy allocator 对 100 B 请求只会给 128 B capacity,因此这类小 value 的内存副本被驱逐后,本地 SSD GET 会在这里直接失败,尽管 target 足以容纳 payload。这里应只要求 target_capacity >= len,并把实际 capacity 传下去,让存储层自行选择 direct/scratch。
| self.submit_ctx(ctx, &mut read_inflight, &mut write_inflight); | ||
| continue; | ||
| } | ||
| if let Err(err) = self.uring.submit_and_wait(1) { |
There was a problem hiding this comment.
[P1] 不要因可重试的 io_uring 错误退出 worker
submit_and_wait 可能瞬时返回 EINTR,CQ overflow 时也可能返回 EBUSY。当前分支只处理已经出现在 CQ 中的项就直接退出;仍在内核中的 SQE 对应的 IoCtx 只剩 user_data 裸指针,其 oneshot 不会完成,随后该 ring 上的读写会挂住或因 receiver 关闭持续失败。请对 EINTR 重试、对 EBUSY drain 后重试;若确实是致命错误,也需要显式完成/取消全部 outstanding context 后再终止 worker。
| avformat_close_input(&fmt_ctx); | ||
| } | ||
| if (avio_ctx != NULL) { | ||
| avio_context_free(&avio_ctx); |
There was a problem hiding this comment.
[P1] 释放 AVIOContext 使用的 buffer
成功调用 avio_alloc_context 后,上面的局部 avio_buffer 被置为 NULL;而 FFmpeg 的 avio_context_free 只释放 context 本身(实现中仅调用 av_freep(ps)),不会释放 avio_ctx->buffer。因此每次 read_frames_numpy 都会泄漏这里分配的 64 KiB。应在 avio_context_free 前调用 av_freep(&avio_ctx->buffer)。
| detail: "kv ssd storage has no active device".to_string(), | ||
| })); | ||
| } | ||
| let idx = self.next_write_device.fetch_add(1, Ordering::Relaxed) % self.devices.len(); |
There was a problem hiding this comment.
[P2] 按 entry 大小选择可承载的 device
这里在不知道 entry_len 的情况下直接 round-robin 到单个 device,而该 device 的 writer 之后只会在自己的 shard_ids 中分配。配置不同大小的多个 root 时,一个只放得进大盘 shard 的 value 若轮询到小盘会直接返回 InvalidArgument,即使其他 device 有足够空间也不会重试,导致 SSD persist 是否成功取决于计数器奇偶。建议先按 aligned length 过滤可承载的 device,并在候选 device 间做 fallback。
| key, | ||
| data_len as u64, | ||
| ); | ||
| self.get_revoke_ssd_source(get_id).await?; |
There was a problem hiding this comment.
[R2][blocking] revoke 前必须等待远端 SSD stage 静默
SsdStageRead RPC 的 60 秒 timeout 只结束 requester 的等待;source 侧 rpc_ssd_stage_read 是独立 spawn,可能仍在 load_and_push_kv_from_ssd 中读写 stage_addr / target_addr。这里立即发 GetRevoke 后,master 会移除 inflight_gets,source 和 target Allocation 的最后一个 Arc 可能被释放并复用,旧 source task 随后就会把数据写进另一个 value;inflight 的 60 秒 TTL 也有同一竞态。请按 get_id 增加 cancel + quiescence ACK(或等价的 ownership barrier),确认 source 不再访问这些裸地址后再释放 allocation。
| put_id: key.put_id, | ||
| }) | ||
| .collect(); | ||
| if let Err(err) = eviction_tx.try_send(replicas) { |
There was a problem hiding this comment.
[major] SSD 淘汰通知不能在队列满时丢弃
这不是可丢的观测事件:本地 ring 已经删除 entry,而 master 仍把它作为有效 SSD replica。队列 Full(例如 master 暂时不可达、batch actor 正在重试)后,这条状态变化没有任何后续恢复来源,get_meta/is_exist 会长期返回 true,GET 也会先路由到不存在的数据后才被动自愈。请对通知施加 backpressure,或用按 key/put_id 合并且不会丢失的 pending 状态;不要让 queue full 永久分裂本地存储和 master route。
| help="FluxonFS username used to sign file RPC tokens.", | ||
| ) | ||
| fluxon_group.add_argument( | ||
| "--fluxon-request-password", |
There was a problem hiding this comment.
[R6][major] 不要通过 argv 接收真实密码
传给这个参数的值会暴露在 shell history、CI 命令日志以及同机进程可读的 /proc//cmdline / ps 中;后面的 sanitized_args 只能保护应用自己写出的结果,无法覆盖这些渠道。请复用项目已有的 secret/config 文件机制,或提供 password-file / 受控 stdin 之类不把秘密放进 argv 的入口,并同步修改 README 示例。
| let (write_tx, write_rx) = tokio_mpsc::channel(DEFAULT_WRITE_QUEUE_DEPTH); | ||
| let (read_tx, read_rx) = tokio_mpsc::channel(DEFAULT_READ_QUEUE_DEPTH); | ||
|
|
||
| task::spawn(ssd_writer_loop( |
There was a problem hiding this comment.
[R2][blocking] SSD worker 必须进入 shutdown completion barrier
这两个原生 tokio::spawn 的 JoinHandle 被直接丢弃,也没有注册到 framework task registry;ClientKvApi::shutdown 又没有关闭读写队列或等待它们。因此 framework shutdown 返回后,worker 仍会阻塞在 recv,UringIoEngine 的线程和 fd 也继续存活。更危险的是 runtime 强制销毁 task 时,in-flight future 持有的 buffer 可能先释放,而内核 I/O 仍保留其裸指针。请由 KvSsdStorage 保存 task handles,按“停止 admission → 关闭队列/取消 → drain → join worker → drop io engine”收敛,并增加 close 返回后无后台活动的测试。
| ) | ||
| .await | ||
| { | ||
| Ok(()) => inner.get_done(req.get_id).await, |
There was a problem hiding this comment.
[R1/R3][blocking] 不要在 holder_id 可交付前不可逆地提交 holding
这里的 GetDone 会先让 master 把 inflight 项移入 get_holding,但 holder_id 还要经过外层 SsdStageReadResp 才能到 requester。若该响应发送失败或 requester 在 60 秒处超时,requester 只会拿 get_id 去 revoke;master 已经找不到 inflight,requester 又从未创建 MemHolder,因而不会发送 DeleteAck,这块 allocation 只能等 member 离开时清理。请为 get_id 保留幂等的完成结果和可补偿的 revoke/query 映射,或把完成提交移到 requester,并确保响应丢失后仍能恢复 holder_id 或回收 holding。
| for ch in raw.chars() { | ||
| if ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.') { | ||
| out.push(ch); | ||
| } else { |
There was a problem hiding this comment.
[R1][blocking] 目录组件必须无碰撞且不能保留 “.” / “..”
当前替换规则会把 owner/a:b、owner_a_b 等不同 instance_key 映射到同一目录,同时 “.” 和 “..” 会原样通过。instance_key 目前只校验非空;一旦两个 owner 共享 large_file_path,前者会让不同实例共用同一批 shard 文件,后者还能让路径跳出实例目录,而 open_cache_files 启动时会 truncate 这些文件。请使用可逆编码或“安全前缀 + 稳定哈希”的一一映射,并显式拒绝 dot segments,补充碰撞和路径逃逸测试。
ActivePeter
left a comment
There was a problem hiding this comment.
Re-review at cc13dcf: prior findings were rechecked. The remaining inline findings below focus on source-owned SSD finalization, raw-address quiescence, bounded backpressure, and avoiding full-table scans.
| let Some(chunk) = inflight.next().await else { | ||
| break; | ||
| }; | ||
| let chunk = chunk?; |
There was a problem hiding this comment.
[R2][blocking] 返回错误前必须收敛所有已提交的 SSD read。
触发场景:任一 chunk read 失败,或下一行 ready_tx.send 因 consumer 提前退出而失败。这里的 ? 会直接 drop FuturesUnordered 中其余 future,但 submit_read_command 已把 ReadCommand 放进 worker;drop oneshot receiver 不会取消 io_uring,worker 仍会向裸 stage_addr 写数据。随后 source 会发 GetRevoke,master 可能释放并复用 source allocation,形成越界写/数据破坏。
请记录首个错误后继续 drain/join 所有已提交 read,再允许 source terminal Revoke;并增加“一个 chunk 失败/ready receiver 关闭后,函数返回前所有 I/O 已完成”的故障注入测试。
| } | ||
| } | ||
| Some(result) = inflight.next(), if !inflight.is_empty() => { | ||
| result?; |
There was a problem hiding this comment.
[R2][blocking] transfer 失败不能通过 drop sibling futures 来证明静默。
这里首个 result? 会丢弃其他已发起的 transfer_data_no_copy future。closed runtime 的 async invoke 是 callback + oneshot:drop receiver 不会撤销 provider 中已经提交的 transfer,所以函数可能先返回,source 随后 GetRevoke 释放 stage/target allocation,而后台 transfer 仍在访问裸地址。
请保留首错但等待全部已提交 transfer 完成,或者实现具有 completion barrier 的 cancel;验收标准是 source 发 Done/Revoke 前不存在仍访问该 get_id 地址的 transfer。
| Ok(()) => match inner.get_done(req.get_id).await { | ||
| Ok(done) => Ok(done), | ||
| Err(done_err) => { | ||
| if let Err(revoke_err) = inner.get_revoke(req.get_id).await { |
There was a problem hiding this comment.
[R2/R3][blocking] source 的 terminal RPC 失败后不能只记录日志并退出。
若 GetDone 失败,fallback GetRevoke 也失败,这个 handler 会返回;下面 transfer-error 分支也一样。requester 随后的 Revoke 在 lifecycle=Active 时只会记 pending,而 inflight_gets 已改为无 TTL,source 又仍是存活成员,因此 allocation 和 durable slot 会永久滞留,没有后续 writer 能完成状态转换。
需要由 source 持有可重试的 terminal-report 状态,直到 master ACK,或进入明确的 shutdown/member-left 回收契约;不要恢复一条无 shutdown 收敛条件的无限循环。
| let inflight_ids = view | ||
| .master_kv_router() | ||
| .inner() | ||
| .take_member_inflight_get_ids(node_id); |
There was a problem hiding this comment.
[R2/R3][blocking] MemberLeft 与 source-owned GetDone 存在漏清理竞态。
当前先 cleanup_node,再处理 inflight index。source 可在 cleanup 之后获得 get transition lock,提交新的 holding 并从 member index 删除 get_id;这里随后 take 到空集合(或拿到 id 后发现 inflight 已消失),新 holding 就永久留给已离群 requester。
请先在每个 get_id 的 transition lock 下建立 requester-left fence,再做最后的 holding cleanup;同时给 holding 建 member_id -> holder_id 索引,避免 cleanup_node 底层对整个 DashMap 做全表扫描。
| if new_tail <= self.shards[shard_id].tail { | ||
| return false; | ||
| } | ||
| let writing_busy = self.entries.values().any(|state| match state { |
There was a problem hiding this comment.
[major] SSD 写入热路径不能扫描全部 entry/read pin。
ring 填满后,几乎每次 allocate_contiguous 都会在全局 inner mutex 下走这里,对 entries 和 read_pins 做 O(N) 扫描;cache key 增长后会把稳定写吞吐退化成 O(N²) 并阻塞并发读写。
请维护按 shard 的 busy frontier/有序计数索引,在 write submit/complete、pin/unpin 时增量更新,使 wrap 判定只查询目标 shard,而不是扫描整张表。
|
|
||
| fn used_bytes_by_shard(&self) -> Vec<u64> { | ||
| let mut out = vec![0u64; self.shards.len()]; | ||
| for state in self.entries.values() { |
There was a problem hiding this comment.
[major] 指标采集不应每 30 秒在全局 storage mutex 下扫描全部 SSD entry。
device_usage_snapshot 由 owner metric loop 周期调用,这里会造成与 key 数量成正比的 I/O 停顿,也违反本 PR 的 no-full-scan 约束。请在 commit/remove/advance_tail 时维护 per-shard used-bytes 计数,snapshot 只读取计数。
| Some(completion) = inflight.next(), if !inflight.is_empty() => { | ||
| finish_write_completion(&inner, &space_notify, completion); | ||
| } | ||
| Some(cmd) = rx.recv() => { |
There was a problem hiding this comment.
[major] pending 会绕过 bounded channel 变成无界的大对象队列。
当队首因 read/persist pin 返回 BlockedByBusyIo 时,本循环仍持续从 rx 接收并把含完整 AlignedBuffer 的命令放进 VecDeque;只要 producer 持续写,原本有容量上限的 channel 会被反复抽空,pending 可无限增长并 OOM。
请在 blocked 状态停止接收,或给 pending 施加与 channel 一致的硬上限,让背压传播回调用者。
| let view_task = view2.clone(); | ||
| let _ = view.spawn("rpc_ssd_replica_commit", async move { | ||
| let t0 = Utc::now().timestamp_micros(); | ||
| let mut ack = handle_ssd_replica_commit(view_task, msg).await; |
There was a problem hiding this comment.
[R1][major] SSD replica 的 owner 身份必须来自已认证的 RPC caller。
当前 handler 丢弃 resp.node_id(),转而信任 request 内可伪造的 node_id。任一 cluster member 都能把 SSD replica 提交到另一个已有 memory replica 的节点,master 随后会把 GET 路由到并不存在该 entry 的 owner;同文件的 eviction handler 已正确使用 caller identity。
请把 resp.node_id() 传给 handle_ssd_replica_commit 并移除冗余字段,或至少强制校验二者相等。
ActivePeter
left a comment
There was a problem hiding this comment.
Re-review at de5dbc2:前一轮的 worker join、淘汰通知、read/transfer drain、全表扫描、writer backpressure、holding MemberLeft 竞态和 commit caller 身份均已复核修复。最新 head 仍有以下 raw-address authority、失败代际、bounded backpressure 与 shutdown completion-barrier 问题;存在 blocking/major finding,当前不建议合并。
| ); | ||
|
|
||
| let view_ext = inner.view.clone_view(); | ||
| RPCHandler::<SsdStageReadReq>::new().regist(inner.view.p2p_module(), move |resp, msg| { |
There was a problem hiding this comment.
[R1][blocking] SsdStageReadReq 的 raw-address descriptor 必须绑定 transport caller 和 master authority。
这里丢弃了 resp.node_id();后续 SsdStageBegin 只让 source 用 get_id 向 master 证明自己是 source,master 没有验证本次 StageRead 的真实 caller 是 inflight.req_node_id,也没有核对 request 中的 key/put_id/stage_addr/stage_len/target_node_id/target_addr/len。任一 cluster member 都可以借 active get_id 让 source 对 request 指定的地址发起 io_uring read,或向任意 member segment 推送数据,并可在失败后让有效 SSD replica 被 revoke。
请把 transport-derived caller 绑定到 inflight requester,并让 master 校验或返回权威 descriptor;source 只能使用 master 授权的 tuple。不要把 caller id 放进可伪造 payload 当作修复。
| }); | ||
|
|
||
| let view_ext = inner.view.clone_view(); | ||
| RPCHandler::<SsdReplicaPersistReq>::new().regist( |
There was a problem hiding this comment.
[R1][blocking] 这个 persist raw-address RPC 必须验证真实调用方。
当前同样丢弃 resp.node_id(),而 handler 会把 payload 中的 target_addr/len 直接交给 persist_from_addr;native backend 在 AlignedBuffer::copy_from_addr 解引用该地址,Foyer backend 用 from_raw_parts(...).to_vec() 解引用。任一 cluster member 因而可以让 owner 读取任意映射地址、申请超大 buffer,甚至触发进程崩溃,并把伪造内容提交为 SSD replica。
该 RPC 目前只有 master 会调用,请至少只接受 transport 识别出的当前 master,并把请求绑定到 master 持有的 put/allocation descriptor;不能信任 payload 自报身份。
| view_task.p2p_module(), | ||
| node_id.clone(), | ||
| req, | ||
| Some(Duration::from_secs(60)), |
There was a problem hiding this comment.
[R2][blocking] RPC timeout 不能作为远端 raw-address reader 已静默的证明。
owner 的 handler 是收到请求后独立 spawn 的;caller 在这里 60 秒超时/重试只停止等待,不会取消那个 handler。尤其 Foyer handler 可能先长时间等待 persist_gate,尚未执行 raw copy。两次 attempt 都超时后本 task 会结束并释放 _allocation_guard;若 route 同时被 delete、替换或 MemberLeft 清理,迟到的 owner handler 随后会解引用已释放/复用的 target_addr。
请为 persist 建立明确的 begin → raw-copy-quiescent terminal 生命周期(或让 owner 持有可验证的 allocation/capability),由 master 在 terminal ack 或 owner MemberLeft 后释放。不要用更长 timeout 或 TTL 替代 quiescence。
| tail: u64, | ||
| used_bytes: u64, | ||
| busy_begins: BTreeMap<u64, usize>, | ||
| order: VecDeque<KvSsdKey>, |
There was a problem hiding this comment.
[major] order 只保存 key,会把同 key 的旧 reservation 和新 reservation 混成一个 generation。
可复现:capacity=2048、每项 512;A@0 写失败后 entry 被删但旧 A 留在 order,提交 B@512,再重试并提交 A@1024、C@1536。wrap 后推进 tail 时,队首旧 A 会通过 entries.get(A) 解析成新 A@1024 并提前 break;下一次覆盖 B 时仍不淘汰它,used_bytes 超 capacity(debug assert),release 下还会让 B 指向已覆盖的数据。
请让 order 保存不可变 generation,例如 (begin, key);pop 时只在当前 entry 的 begin 与该 generation 相同时删除/发布 eviction。不要通过扫描 order 或 entries 修复。
| }; | ||
| let _ = completion.done_tx.send(result); | ||
| } | ||
| Some(cmd) = rx.recv() => { |
There was a problem hiding this comment.
[major] reader 仍会绕过 bounded channel,把 pending 变成无界队列。
当 inflight.len() == max_inflight 时,这个 recv 分支仍然启用,actor 会不断清空容量 16 的 channel 到无界 VecDeque。ReadCommand 在 send 前已经可能分配整块 scratch buffer,并持有 raw target/read pin;并发读取持续到达时,原本的 channel backpressure 因而失效并可 OOM。
请只在 inflight.len() < max_inflight 时 recv;reader 没有 writer 的 Blocked 状态,通常可以直接删除 pending。补一个 inflight 被阻塞时验证 read_tx.capacity()==0 的回归测试。
| Ok(()) => match inner.get_done(req.get_id).await { | ||
| Ok(done) => Ok(done), | ||
| Err(done_err) => { | ||
| if let Err(revoke_err) = inner.finish_ssd_source_revoke(req.get_id, false).await { |
There was a problem hiding this comment.
[R2][blocking] source 的 terminal phase 仍不能被 shutdown 中断。
上面的 get_done 和这里的 fallback 都先调用无 deadline 的 find_or_wait_master_node();finish_ssd_source_revoke 内部的 get_revoke_inner 还使用 timeout=None。它虽然检查 poller/gate,但只会在当前 await 返回后检查。若 master 消失,handler 一直持有 ssd_stage_read 的 ShutdownGuard;ClientKvApi::prepare_shutdown 先 stop admission 再等 gate quiescence,而 framework poller 要等所有 prepare hooks 返回后才置 false,于是形成永久 shutdown deadlock。
请让每次 master lookup/RPC attempt 有有限 deadline或与模块 shutdown 信号竞争,并只对 transient error 重试。仍须保留“master ack 或 source MemberLeft 才能最终释放 raw allocation”的单一 release authority。
| }, | ||
| raw_bytes: Vec::new(), | ||
| }; | ||
| let master_node_id = self |
There was a problem hiding this comment.
[R2][blocking] SSD persist handler 也会在这里把 shutdown completion barrier 卡死。
handle_ssd_replica_persist 持有外层 ShutdownGuard 和已提交 entry 的 persist guard,再进入无 deadline 的 find_or_wait_master_node()。若 master 在本地写盘后离开,ClientKvApi shutdown 会等待这个 guard,但 global poller 尚不会关闭,lookup 也没有事件可以结束。后面的 60 秒 RPC timeout 覆盖不到 master lookup。
请给 lookup/commit attempt 有界 deadline或与模块 shutdown 竞争;失败时不要向 master 发布 replica,释放本地 guard 后让 entry 走正常 eviction/后续回收,不要让外层直接改 storage 内部状态。
| self.view.get().unwrap() | ||
| } | ||
|
|
||
| pub(crate) fn insert_inflight_get(&self, get_id: u64, info: InflightGetInfo) { |
There was a problem hiding this comment.
[R2/R3][blocking] MemberLeft 必须原子关闭该 member 的 inflight admission;当前 take 与这里的 insert 仍可漏项。
两个具体 interleaving:① MemberLeft 先 take 到空集合,正在执行的 GetStart 随后插入;② 这里先把 get_id 放进 member index,MemberLeft take 后拿 transition lock时 main inflight_gets 尚未执行 entry.insert(info),于是看到 None 并丢掉 id,随后 main entry 才发布。inflight_gets 已是无 TTL DashMap,请求方又已离群,这会永久持有 allocation/durable slot。
请像 MasterOwnerMemMgr 一样在 per-member DashMap state 中保存 departed + get_ids,MemberLeft 先关闭 admission,MemberJoined 再重开,并提供 insert_if_active;同时保证 index/main publication 不会出现“id 已被消费但 main 尚不可见”。只按离开的 member 处理其集合,不能回退成全表扫描。
背景
当 KV 工作集超过 DRAM 容量时,内存副本驱逐会增加缓存 miss。本 PR 将 owner 本地 SSD 接入现有 key-version 副本体
系,用作 DRAM 的运行期回填层,从而扩大可命中的工作集。
单机最高Mooncake 6x 吞吐:让 Fluxon KV 支持 DRAM + SSD 多级缓存 https://zhuanlan.zhihu.com/p/2060716360108397913
主要改动
put、get、delete公共 API 不变,get仍返回MemHolder。put在内存副本发布后返回,SSD 持久化在后台异步完成,不增加同步写入路径的等待时间。get优先读取内存;没有可读内存副本时才从 SSD 回填。IO。
O_DIRECT、io_uring、512-byte 对齐、per-device 队列和有界背压。配置
通过
fluxonkv_spec.large_limit_size启用 SSD,其配置项与large_file_paths一一对应。未配置或设置为null时保持纯内存模式。
容量支持整数 bytes 和
"1.5GB"等可读格式。兼容性与边界
验证
package-wheel、文档镜像、双节点 CI 和大规模 MQ GitHub checks 均通过。上述数据是限定环境下的端到端逻辑吞吐,不代表裸 SSD 带宽。
当前分支包含的其他改动
该分支同时包含:
修复与加固(按提交顺序)
SSD IO 正确性与测试覆盖(
b477e02)io_uring补充EINTR重试、EBUSYcompletion drain,以及致命错误下的 outstanding IO 收敛。AVIOContext的 64 KiB buffer 泄漏。KV 语义、etcd 与 MQ 稳定性(
585da30–b43ce92)KeyAlreadyExists与KeyBeingWritten,修复并发创建竞态。SSD shutdown、背压与远端回填生命周期(
8dd0b02–cc13dcf)io_uring”完成退出。
.、..等路径逃逸。GetDone/GetRevoketerminal report,并支持丢失响应后的幂等结果重放。错误收敛与热路径扩展性(
de5dbc2)used_bytes和 busy-offset 索引,移除写入与指标采集热路径中的 SSD entry 全表扫描。代际、地址范围与并发边界(
7039918)(begin, key)generation,避免同 key 的失败 reservation 与后续重试混淆。English
Background
When the KV working set exceeds available DRAM, memory replica eviction increases cache misses. This PR
integrates owner-local SSDs into the existing key-version replica model as a runtime backing tier for DRAM,
expanding the effective cacheable working set.
Key changes
put,get, anddeleteAPIs;getcontinues to returnMemHolder.putafter the memory replica is published, while persisting to SSD asynchronously.IO.
offsets, eviction, and IO.
O_DIRECT,io_uring, 512-byte alignment, per-device queues, and boundedbackpressure.
documentation.
Configuration
SSD storage is enabled through
fluxonkv_spec.large_limit_size, mapped one-to-one tolarge_file_paths.Omitting the field or setting it to
nullkeeps the existing memory-only behavior. Capacity values accept rawbytes and human-readable strings such as
"1.5GB".Compatibility and limitations
empty shards and rings after restart.
published memory replica.
Validation
refills, backpressure, eviction, and lifecycle handling.
topology.
These are scoped end-to-end logical-throughput results, not raw SSD bandwidth.
Other changes currently included in this branch
The branch also contains:
Fixes and hardening, in chronological order
SSD IO correctness and test coverage (
b477e02)EINTR, drain completions onEBUSY, and explicitly settle outstanding IO after fatalio_uringfailures.
AVIOContextbuffer leak in VideoReader.KV semantics, etcd, and MQ stability (
585da30–b43ce92)writes.
retries MQ-offset failures a failure and stop further admission.
examples.
SSD shutdown, backpressure, and remote-refill lifecycle (
8dd0b02–cc13dcf)workers, and then releases
io_uring.a lost
GetDoneresponse.Failure draining and hot-path scalability (
de5dbc2)Generation, address-range, and concurrency edge cases (
7039918)(begin, key)generations in ring order entries.