Files
ldb/chainbuffer.plan.md
2026-03-04 07:20:09 +00:00

190 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ChainBuffer + 全量 WAL单一方案slot 驱动)
## 1. 目标与约束
目标:
- 所有命令都落盘(不再区分 update/get
- 主线程 A 尽量不做内存分配,不构造 uring task。
- 主线程 A 只做:收包、解析命令边界、摘取 payload、写入预分配 slot。
- WAL 线程 B 负责:从 slot 取 payload、构造 task、提交 io_uring、归还内存块。
约束:
- 只保留一个实现方案,不引入 v1/v2 双路径。
- 正确性优先半包、多包、pipeline、断连、慢盘都可控。
---
## 2. ChainBuffer 设计(重点)
## 2.1 结构定义
采用“回环 chunk 链表”:
```c
typedef struct cb_chunk {
struct cb_chunk *next;
uint32_t cap; // 固定大小,默认 4096
uint32_t rpos; // 读指针 [0, cap)
uint32_t wpos; // 写指针 [0, cap)
uint32_t used; // 已用字节数 [0, cap]
uint32_t refcnt; // 被 WAL payload 持有的引用计数
uint8_t data[];
} cb_chunk_t;
typedef struct chain_buffer {
cb_chunk_t *head;
cb_chunk_t *tail;
size_t total_len;
uint32_t chunk_size;
// 节点池:避免频繁 malloc/free
cb_chunk_t *free_list;
uint32_t free_count;
uint32_t free_limit;
} chain_buffer_t;
```
说明:
- chunk 内部可回环读写(避免 memmove
- 多 chunk 串联用于扩容。
- 空 chunk 不释放到系统,先回收到 `free_list`
## 2.2 接收路径readv 直写)
主线程不再 `recv -> tmp -> memcpy`,改为:
1. `chain_buffer_prepare_recv_iov(buf, iov, max_iov)`
- 收集可写空闲段(优先尾 chunk不够就从 free_list 取或新建 chunk
2. `readv(fd, iov, iovcnt)`
3. `chain_buffer_commit_recv(buf, nread)`
- 推进 `wpos/used/total_len`
这样可去掉接收中转拷贝。
## 2.3 消费与摘取
提供三个核心能力:
- `chain_buffer_iter_*`:按字节流遍历,供 RESP 解析命令边界。
- `chain_buffer_detach_prefix(buf, len, cb_payload_t *out)`:把前缀字节摘成 payload尽量零拷贝边界切分允许一次小拷贝
- `chain_buffer_release_payload(buf, payload)`WAL 线程用完后归还 chunkrefcnt--,归零后回 free_list
---
## 3. slot 队列设计(主线程零分配)
## 3.1 预分配 ring
定义单生产者单消费者 ringA->B
```c
#define WAL_SLOT_CAP 65536
typedef struct wal_slot {
uint64_t seq;
uint32_t cmd_len;
cb_payload_t payload; // 命令字节
uint8_t in_use;
} wal_slot_t;
typedef struct wal_ring {
wal_slot_t slots[WAL_SLOT_CAP];
_Atomic uint32_t head; // producer write
_Atomic uint32_t tail; // consumer read
_Atomic uint32_t size;
} wal_ring_t;
```
特点:
- slot 全预分配。
- 主线程写 slot 不 malloc。
- WAL 线程消费后清空 slot 并前移 tail。
## 3.2 主线程流程(所有命令都落盘)
对每条完整命令:
1. 从 chainbuffer 解析得到 `cmd_len`
2. 申请一个空 slotring 未满)。
3. `detach_prefix(cmd_len)` 得到 `payload`
4. 写入 slot`seq/cmd_len/payload`
5. 发布 head。
注意:
- 不做命令类型判断。
- 不构造 uring task。
- 不进行额外 payload malloc。
## 3.3 WAL 线程流程
循环:
1. 从 ring 取 slot。
2. 生成长度头4B+ payload iov。
3.`submit_write()`(当前打包拷贝发生在此线程)。
4. `chain_buffer_release_payload()` 归还 chunk。
5. 清 slot推进 tail。
---
## 4. 一致性与回压
## 4.1 日志顺序
- `g_log_off` 只在 WAL 线程维护。
- 单消费者天然保证写入顺序与入队顺序一致。
## 4.2 回压(必须)
当 ring 达到高水位(例如 80%
1. 暂停该连接读事件(优先)。
2. 若持续超时(如 200ms仍高水位关闭连接保护系统。
本方案不再做“回退旧路径”。
## 4.3 异常处理
- `submit_write` 失败:记录错误并触发保护动作(可选停机/拒绝新连接)。
- 连接断开:已入 slot 的 payload 继续由 WAL 线程完成归还。
- 进程退出:先停读,再 drain wal_ring再 shutdown uring。
---
## 5. 实施步骤(实际落地)
1. 重构 `network/chainbuffer.*`
- 回环 chunk + free_list
- `prepare_recv_iov/commit_recv`
- `iter/detach_prefix/release_payload`
2. 修改 `reactor.c`
- `recv_cb` 使用 `readv` + commit
3. 修改 `kvstore.c`
- 按命令边界循环
- 每条命令统一入 wal_slot不分类
4. 新增 `dump/wal_slot_queue.*`(或放 `dump/kvs_oplog.c`
- SPSC ring + WAL worker
5. 调整 `dump/kvs_oplog.c`
- 改为 WAL 线程消费 slot 后调用 `submit_write`
6. 收敛退出与错误路径
- close_conn / shutdown / submit 失败全覆盖
---
## 6. 验收标准
- 功能:
- 半包/多包/pipeline 正确;
- 重启回放后数据一致。
- 性能:
- 主线程不再出现 oplog task 构造热点;
- 接收路径 memcpy 次数下降(去掉 tmp 中转)。
- 稳定性:
- 慢盘压测下无泄漏、无 double free、无 UAF
- 高水位触发时系统行为可预期。