性能测试工具bench
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,7 +5,7 @@
|
|||||||
*.a
|
*.a
|
||||||
|
|
||||||
/ebpf/libbpf-bootstrap
|
/ebpf/libbpf-bootstrap
|
||||||
/doc
|
/test-redis/results
|
||||||
|
|
||||||
kvstore
|
kvstore
|
||||||
testcase
|
testcase
|
||||||
|
|||||||
@@ -1,189 +0,0 @@
|
|||||||
# 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 线程用完后归还 chunk(refcnt--,归零后回 free_list)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. slot 队列设计(主线程零分配)
|
|
||||||
|
|
||||||
## 3.1 预分配 ring
|
|
||||||
|
|
||||||
定义单生产者单消费者 ring(A->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. 申请一个空 slot(ring 未满)。
|
|
||||||
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;
|
|
||||||
- 高水位触发时系统行为可预期。
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<config>
|
<config>
|
||||||
<server>
|
<server>
|
||||||
<ip>192.168.220.134</ip>
|
<ip>127.0.0.1</ip>
|
||||||
<port>8888</port>
|
<port>8888</port>
|
||||||
<mode>master</mode> <!-- master / slave -->
|
<mode>master</mode>
|
||||||
|
|
||||||
|
|
||||||
<!-- 仅当 mode=slave 时使用 -->
|
|
||||||
<replica>disable</replica>
|
<replica>disable</replica>
|
||||||
<master>
|
<master>
|
||||||
<ip>192.168.220.134</ip>
|
<ip>192.168.220.134</ip>
|
||||||
@@ -14,12 +14,12 @@
|
|||||||
</server>
|
</server>
|
||||||
|
|
||||||
<log>
|
<log>
|
||||||
<level>INFO</level> <!-- DEBUG / INFO / ERROR -->
|
<level>INFO</level>
|
||||||
</log>
|
</log>
|
||||||
|
|
||||||
<persistence>
|
<persistence>
|
||||||
<type>none</type> <!-- incremental / none -->
|
<type>incremental</type>
|
||||||
<dir>data</dir> <!-- 所有持久化文件所在目录 -->
|
<dir>data/persist_mypool_20260305_072256</dir>
|
||||||
|
|
||||||
<wal>kvs_oplog.db</wal>
|
<wal>kvs_oplog.db</wal>
|
||||||
<array>kvs_array.db</array>
|
<array>kvs_array.db</array>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</persistence>
|
</persistence>
|
||||||
|
|
||||||
<memory>
|
<memory>
|
||||||
<allocator>mypool</allocator> <!-- jemalloc / malloc / mypool -->
|
<allocator>mypool</allocator>
|
||||||
<leakage>disable</leakage> <!-- enable/disable-->
|
<leakage>disable</leakage>
|
||||||
</memory>
|
</memory>
|
||||||
</config>
|
</config>
|
||||||
50
doc/conclusion.md
Normal file
50
doc/conclusion.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# 性能定位结论(主线程)
|
||||||
|
|
||||||
|
## 1) 主要瓶颈结论
|
||||||
|
|
||||||
|
基于当前主线程采样统计,结论是:
|
||||||
|
|
||||||
|
- **主线程最大开销确实是内存申请/管理相关开销**(而不是纯 memcpy)。
|
||||||
|
- 开销占比大致为:
|
||||||
|
- `submit_write` 打包阶段:约 **61.3%**
|
||||||
|
- 其中 alloc 约 **66.5% of pack**(折算到主线程总开销约 **40.8%**)
|
||||||
|
- copy 约 **14.2% of pack**(折算到主线程总开销约 **8.7%**)
|
||||||
|
- 入队/通知(SPSC push + notify):约 **29.2%**
|
||||||
|
- 回收释放:约 **9.5%**
|
||||||
|
- backpressure 影响很小(loops≈0)
|
||||||
|
|
||||||
|
所以“减少申请内存次数”这个判断是对的,而且是当前最有价值的优化方向。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2) 对方案 2 / 3 的评价(结合本系统)
|
||||||
|
|
||||||
|
### 方案 2:A/B 双 flag 缓冲覆写保护
|
||||||
|
|
||||||
|
- **优点**:实现直观,容易快速落地验证正确性。
|
||||||
|
- **缺点**:
|
||||||
|
- 本质仍是“执行路径与拷贝路径耦合”,请求推进会受慢侧牵制;
|
||||||
|
- 不能从根本上消除 alloc 热点,只是控制覆写时序;
|
||||||
|
- 在高并发下调试成本会上升(状态机与边界条件多)。
|
||||||
|
- **结论**:适合快速试验,不是长期高性能形态。
|
||||||
|
|
||||||
|
### 方案 3:ChainBuffer 所有权移交(摘链/归还)
|
||||||
|
|
||||||
|
- **优点**:
|
||||||
|
- 以“指针/节点移动”替代大量 alloc+copy,方向与当前瓶颈完全一致;
|
||||||
|
- 对大 value、高吞吐场景收益潜力更大。
|
||||||
|
- **缺点**:
|
||||||
|
- 生命周期、并发归还、异常回收都要设计清楚;
|
||||||
|
- 实现复杂度明显高于方案 2。
|
||||||
|
- **结论**:更符合长期性能目标,但需要更严格的工程设计。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3) 哪个在你当前系统里更容易实现?
|
||||||
|
|
||||||
|
如果只比较你给的这两个方案,在你现在的系统里:
|
||||||
|
|
||||||
|
- **更简单的是方案 2(A/B 双 flag)**;
|
||||||
|
- **更值得长期投入的是方案 3(所有权移交)**。
|
||||||
|
|
||||||
|
建议:短期先用方案 2 验证行为边界,最终收敛到方案 3(或其等价的零拷贝所有权模型)。
|
||||||
300
doc/interview_answers_aggressive_50.md
Normal file
300
doc/interview_answers_aggressive_50.md
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
# KVStore 激进版:50 题参考回答(含风险等级与避坑话术)
|
||||||
|
|
||||||
|
> 对应文件:`doc/interview_questions_aggressive_50.md`
|
||||||
|
> 建议答题模板:**目标 -> 方案 -> 收益 -> 代价 -> 兜底**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1) 架构与边界(1-5)
|
||||||
|
|
||||||
|
### 1. 分层设计收益与复杂度
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:我把系统拆成网络层(收发)、协议层(RESP 解析)、执行层(命令分发)、持久化层(快照/oplog)和复制层(SSYNC+shm)。收益是可替换和可定位问题,比如网络和持久化互不耦合。代价是模块间接口变多,调试时需要跨层追踪。兜底是我保留了主调用链文档,排障按固定链路走。
|
||||||
|
- **避坑话术**:别说“完全解耦”,改成“低耦合、边界清晰”。
|
||||||
|
|
||||||
|
### 2. Reactor vs Proactor/协程
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:当前选 Reactor 是因为代码路径短、可控性高,适合先把协议和持久化跑稳。Proactor/协程在高并发下有潜力,但会引入更复杂的调度和状态管理。我的 tradeoff 是先优化主路径和持久化,再评估网络模型切换。
|
||||||
|
- **避坑话术**:不要说“Reactor 一定最快”,要说“在当前代码复杂度和目标下更合适”。
|
||||||
|
|
||||||
|
### 3. 三引擎选型边界
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:Array 实现简单但查找线性,适合小规模或验证场景;RBTree 有序且 O(logN),适合有序访问;Hash 平均 O(1),适合热点随机读写。收益是能覆盖不同 workload。代价是维护三套实现和一致性语义。
|
||||||
|
- **避坑话术**:不要泛泛说“都很快”,要给出复杂度和场景。
|
||||||
|
|
||||||
|
### 4. 主路径轻量化
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:主线程主要做解析、执行、回包,持久化提交走 io_uring worker。收益是减少 I/O 阻塞对主循环影响。代价是异步链路更复杂,需要回收队列和背压。我通过 destroy queue + wakeup 回收来控风险。
|
||||||
|
- **避坑话术**:别说“主线程无开销”,保留“主要开销下降”。
|
||||||
|
|
||||||
|
### 5. 多核扩展优先级
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:我会先做连接分片,再做数据分片。连接分片改动小、收益快;数据分片需要引擎和一致性改造,复杂度高。tradeoff 是短期吞吐提升 vs 长期架构演进,我会分两阶段做。
|
||||||
|
- **避坑话术**:不要一次性承诺“全做完”,强调迭代路线。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2) 协议与解析(6-10)
|
||||||
|
|
||||||
|
### 6. 半包/多包/粘包与错误恢复
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:我按“可消费字节”循环解析,len=0 视为半包等待下次,len<0 视为协议错误。收益是能稳定处理 pipeline。代价是错误策略偏严格,遇坏包直接断链。这是在安全性和可恢复性之间偏安全的选择。
|
||||||
|
- **避坑话术**:别说“能恢复所有坏包”。
|
||||||
|
|
||||||
|
### 7. binary-safe 为何用 slice
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:因为 key/value 可能包含 `\0` 和二进制字节,C 字符串 API 会截断。slice(ptr+len)能保留完整字节语义。收益是协议正确性和通用性更强,代价是代码里长度参数管理更繁琐。
|
||||||
|
- **避坑话术**:强调“正确性优先”。
|
||||||
|
|
||||||
|
### 8. 恶意请求限制
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:我在协议层设置了 bulk 上限和参数上限,同时连接层有读写缓冲上限。收益是避免单连接吃光内存。代价是极端合法请求也可能被拒绝。我会通过可配置阈值平衡安全和业务需求。
|
||||||
|
- **避坑话术**:不要说“完全防攻击”,说“降低风险面”。
|
||||||
|
|
||||||
|
### 9. inline 与 multibulk 共存
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:如果首字节不是 `*`,走 inline;是 `*` 就走 multibulk。收益是兼容 Redis 常见输入。代价是代码路径多一支,但复杂度可控。
|
||||||
|
- **避坑话术**:不要说“全面兼容 Redis”,说“覆盖当前命令集下的 RESP2 常用场景”。
|
||||||
|
|
||||||
|
### 10. 新命令最小改动
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:当前是命令枚举 + dispatch switch,新增命令主要改解析映射和执行分支。收益是性能直观、可调试。代价是规模大时 switch 变长。我会在命令数继续增长时再抽象为表驱动。
|
||||||
|
- **避坑话术**:别提前宣称“完全插件化”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3) ChainBuffer 与“零拷贝”(11-15)
|
||||||
|
|
||||||
|
### 11. 零拷贝边界
|
||||||
|
- **风险等级**:高
|
||||||
|
- **参考回答**:接收阶段是 readv 直写到链式缓冲,减少中转拷贝;发送阶段用 sendmsg 聚合。当前仍存在线性化/打包场景下的 memcpy,所以更准确是“低拷贝主路径 + 局部拷贝回退”。tradeoff 是实现复杂度可控,同时先拿到主要收益。
|
||||||
|
- **避坑话术**:主动说“不是全链路绝对 0 拷贝”。
|
||||||
|
|
||||||
|
### 12. linearize 触发条件
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:当上层需要连续字节视图而数据跨 chunk 时触发 linearize。收益是简化了解析和执行接口。代价是触发时会有额外 memcpy,影响尾延迟。我会通过 chunk 策略和请求分布降低触发频率。
|
||||||
|
- **避坑话术**:不要说“从不 linearize”。
|
||||||
|
|
||||||
|
### 13. free_list 选型
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:free_list 复用 chunk,减少频繁 malloc/free 抖动。收益是吞吐更稳、分配开销更低。代价是会保留一定空闲内存。通过 free_limit 做上限控制,平衡性能和内存占用。
|
||||||
|
- **避坑话术**:别说“零内存浪费”。
|
||||||
|
|
||||||
|
### 14. 大小 Key 混跑碎片问题
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:我用固定 chunk + 链式扩展,先保证正确和稳定。大 Key 会占多 chunk,可能带来局部碎片;收益是实现简单、行为可预测。后续可按 key size 分层 chunk 策略优化。
|
||||||
|
- **避坑话术**:承认“有优化空间”更可信。
|
||||||
|
|
||||||
|
### 15. 部分发送一致性
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:sendmsg 返回 n 后按字节 drain 缓冲,未发完保留在 wbuf,EPOLLOUT 下次继续。收益是协议字节流不会错位。代价是状态维护复杂一点,但这是非阻塞发送必须的成本。
|
||||||
|
- **避坑话术**:别说“一次 send 一定发完”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4) 所有权移交与并发安全(16-20)
|
||||||
|
|
||||||
|
### 16. 所有权定义
|
||||||
|
- **风险等级**:高
|
||||||
|
- **参考回答**:我的定义是“谁持有谁负责释放”,通过 detach/release 把片段生命周期显式化。收益是未来可做执行-落盘低拷贝协作。代价是引用计数和回收时序更复杂。当前实现已提供接口,进一步全链路化是下一阶段。
|
||||||
|
- **避坑话术**:明确“已实现接口与局部机制,非全链路完成”。
|
||||||
|
|
||||||
|
### 17. UAF / double free 防护
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:通过 refcnt + release 路径统一回收,避免多方直接 free。收益是降低并发回收风险。代价是需要严格约束调用顺序和异常路径。排查时我会优先核对 detach/release 对称性。
|
||||||
|
- **避坑话术**:别说“绝对不会 UAF”,说“通过机制显著降低风险”。
|
||||||
|
|
||||||
|
### 18. 连接关闭后的回收
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:连接关闭只释放连接侧对象,已移交片段由持有方继续走 release。收益是不会因连接断开丢失回收路径。代价是关闭逻辑需要区分“本地拥有”和“已移交”。
|
||||||
|
- **避坑话术**:避免一句话“直接全清掉”。
|
||||||
|
|
||||||
|
### 19. 谁申请谁释放
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:这是为了避免跨线程释放带来的生命周期混乱。收益是定位泄漏和崩溃更直观。代价是需要一个回收协作队列,代码量会增加。总体是工程上更稳的 tradeoff。
|
||||||
|
- **避坑话术**:强调“可维护性收益”。
|
||||||
|
|
||||||
|
### 20. 真正 0 拷贝落盘约束
|
||||||
|
- **风险等级**:高
|
||||||
|
- **参考回答**:要做到真正 0 拷贝,需要落盘线程直接消费网络片段并延迟释放,约束包括引用计数、回收屏障和慢盘背压。收益是减少打包复制。代价是并发复杂度显著上升,错误成本更高。
|
||||||
|
- **避坑话术**:别承诺“短期必做完”,给阶段目标。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5) io_uring 流水线(21-25)
|
||||||
|
|
||||||
|
### 21. n*SPSC vs MPSC
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:n*SPSC 的好处是队列局部性好、竞争少,延迟更稳;代价是负载均衡和线程参数调优复杂。MPSC 实现集中但热点明显。当前我更看重稳定性,所以先用 n*SPSC。
|
||||||
|
- **避坑话术**:不要否定 MPSC,强调场景选择。
|
||||||
|
|
||||||
|
### 22. 80% 水位
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:80% 是保护阈值,目的是给 CQE 回收和突发流量留缓冲。收益是降低 overflow 风险。代价是峰值吞吐可能不是最大。这个值不是常量真理,我会按压测再调。
|
||||||
|
- **避坑话术**:不要说“最优解”,说“保守工程值”。
|
||||||
|
|
||||||
|
### 23. 背压策略
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:队列满时会回收已完成任务并让出 CPU,必要时限流连接。收益是系统不崩。代价是尾延迟上升。tradeoff 是优先保活再保时延。
|
||||||
|
- **避坑话术**:别说“无损无延迟”。
|
||||||
|
|
||||||
|
### 24. 批量回收
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:批量偷取 destroy queue 能减少锁/原子开销和碎片回收抖动。收益是主循环更平滑。代价是回收时点有批处理延迟。这个延迟通常可接受。
|
||||||
|
- **避坑话术**:承认“不是实时逐条释放”。
|
||||||
|
|
||||||
|
### 25. 优雅停机
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:停机流程是 stop 标志 -> 唤醒 worker -> drain 队列 -> join 线程 -> 清理剩余任务。收益是尽量保证数据路径收敛完成。代价是停机时间变长。这里优先数据完整性。
|
||||||
|
- **避坑话术**:别说“秒停”,说“可控停机”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6) 快照 + 增量一致性(26-30)
|
||||||
|
|
||||||
|
### 26. 一致性窗口
|
||||||
|
- **风险等级**:高
|
||||||
|
- **参考回答**:快照代表某个时间点状态,后续变更由 oplog 补齐。收益是写入不中断。代价是天然存在窗口,需要恢复阶段串联重放。这个设计是吞吐优先下的最终一致方案。
|
||||||
|
- **避坑话术**:不要声称“强一致快照”。
|
||||||
|
|
||||||
|
### 27. 先快照后回放
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:快照是基线,oplog 是增量。先加载基线再套增量语义清晰。收益是恢复逻辑简单。代价是快照越旧回放越长。可通过更频繁快照平衡。
|
||||||
|
- **避坑话术**:说明“恢复时间与快照频率 tradeoff”。
|
||||||
|
|
||||||
|
### 28. 命令字节流日志
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:命令字节流实现快、复用现有解析器,开发成本低。代价是回放时要重新解析执行,效率不如结构化 WAL。当前选择是快速迭代优先,后续可演进为结构化记录。
|
||||||
|
- **避坑话术**:承认“不是最终形态”。
|
||||||
|
|
||||||
|
### 29. 坏日志策略
|
||||||
|
- **风险等级**:高
|
||||||
|
- **参考回答**:当前更偏“发现坏日志就失败退出”,先保证正确性边界。收益是避免静默数据错误。代价是可用性下降。生产化可加校验和分段容错,再做可恢复降级。
|
||||||
|
- **避坑话术**:不要说“自动修复所有坏日志”。
|
||||||
|
|
||||||
|
### 30. 状态边界证明
|
||||||
|
- **风险等级**:高
|
||||||
|
- **参考回答**:我会把边界定义成“快照点 + 之后成功持久化的增量”。收益是定义清楚,便于测试。代价是需要故障注入验证不同崩溃点。我的做法是先给出可验证边界,而不是口头保证。
|
||||||
|
- **避坑话术**:避免绝对词“完全一致”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7) 主从与状态机(31-35)
|
||||||
|
|
||||||
|
### 31. 状态机描述
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:slave 发 SSYNC 申请全量,master 异步产出并发送 snapshot,slave 落盘后回 SREADY,再进入增量流。收益是流程清晰且可扩展。代价是阶段切换复杂,需要严格顺序控制。
|
||||||
|
- **避坑话术**:别忽略“阶段切换失败处理”。
|
||||||
|
|
||||||
|
### 32. 快照期间新写入
|
||||||
|
- **风险等级**:高
|
||||||
|
- **参考回答**:思路是用 seq 把快照基线和增量窗口连接起来。收益是减少丢数据窗口。代价是实现复杂,需要状态机和顺序保证。当前代码有通道和钩子,协同还在持续完善。
|
||||||
|
- **避坑话术**:一定说“机制已搭,协同在完善中”。
|
||||||
|
|
||||||
|
### 33. wrap marker 可行性
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:尾部放不下时写 wrap marker 并回到 0,消费者识别后跳转。收益是实现简单。代价是需要严格保证读写顺序和越界检查。适合单写者模型。
|
||||||
|
- **避坑话术**:别说“多写者也没问题”。
|
||||||
|
|
||||||
|
### 34. seq 断档处理
|
||||||
|
- **风险等级**:高
|
||||||
|
- **参考回答**:断档处理要看目标:一致性优先就阻塞/重建,可用性优先可短暂跳过并告警。我的偏好是关键链路一致性优先。代价是短期可用性受影响。
|
||||||
|
- **避坑话术**:避免“所有场景一个策略”。
|
||||||
|
|
||||||
|
### 35. eBPF 控制面定位
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:我把 eBPF 钩子放控制面,用于状态切换和转发触发,不直接承载主数据处理。收益是降低主路径侵入。代价是又多一层调试复杂度。
|
||||||
|
- **避坑话术**:别说“eBPF 加速了所有链路”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8) 内存分配器与内存池(36-40)
|
||||||
|
|
||||||
|
### 36. 分级依据
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:8-512B 覆盖高频小对象,按 8B 对齐分桶,页内 free-list 管理。收益是分配释放路径短。代价是可能有内部碎片。属于延迟优先的选择。
|
||||||
|
- **避坑话术**:不要回避碎片问题。
|
||||||
|
|
||||||
|
### 37. 大对象回退 malloc
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:大对象生命周期和尺寸离散,放进小块池会放大管理成本。回退系统分配更简单。收益是降低池复杂度。代价是大块分配性能受系统 allocator 影响。
|
||||||
|
- **避坑话术**:别说“mempool 覆盖全部场景”。
|
||||||
|
|
||||||
|
### 38. 空闲页回收
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:我保留一定空闲页做缓存,超过阈值再释放,平衡复用和内存占用。收益是减少抖动。代价是峰值内存更高。阈值可按业务压测调。
|
||||||
|
- **避坑话术**:强调“可调参数”。
|
||||||
|
|
||||||
|
### 39. 三 allocator 差异来源
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:差异主要来自小对象分配路径、锁竞争、缓存复用和写盘模式耦合。mypool 在当前 workload 下命中率高,所以吞吐更好。代价是通用性不一定优于成熟 allocator。
|
||||||
|
- **避坑话术**:别说“mypool 永远最优”。
|
||||||
|
|
||||||
|
### 40. 内存异常排查优先级
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:先看 RSS、分配失败、队列积压、in-flight、回收延迟,再看对象分布。收益是先定位“增长来源”再定位“泄漏点”。代价是需要加一些可观测指标。
|
||||||
|
- **避坑话术**:别一上来就说“就是泄漏”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9) 压测方法学(41-45)
|
||||||
|
|
||||||
|
### 41. 复现性保证
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:固定参数、固定轮次、分组合独立目录、每轮重启,保存原始日志与 CSV。收益是可回放可核对。代价是执行时间更长。
|
||||||
|
- **避坑话术**:不要只报“最好一轮数据”。
|
||||||
|
|
||||||
|
### 42. bench vs testcase 取舍
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:bench 更灵活,但随机 key 与 RSET 插入语义会碰撞;testcase mode=4 更稳定,适合长期对比。tradeoff 是灵活性 vs 稳定性。
|
||||||
|
- **避坑话术**:主动解释语义差异,避免被质疑口径不一致。
|
||||||
|
|
||||||
|
### 43. QPS 口径
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:我用“完成请求数/耗时”,并明确是否包含错误。收益是可比较。代价是不同工具默认口径不同,需要文档化。
|
||||||
|
- **避坑话术**:别把不同口径结果直接横比。
|
||||||
|
|
||||||
|
### 44. 避免虚高
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:做预热、控制 keyspace、分离预填充和正式压测、多轮取均值并看波动。收益是结果更稳。代价是实验时间增加。
|
||||||
|
- **避坑话术**:不要只强调峰值,不报波动。
|
||||||
|
|
||||||
|
### 45. 波动解释
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:波动来自调度、缓存、I/O 抖动和后台回收。我的做法是看均值+CV,不用单轮结论。tradeoff 是展示更真实但数字不一定最亮眼。
|
||||||
|
- **避坑话术**:别把异常轮次“隐去不提”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10) 真实性与演进(46-50)
|
||||||
|
|
||||||
|
### 46. 零拷贝表述边界
|
||||||
|
- **风险等级**:高
|
||||||
|
- **参考回答**:我会说“接收/发送主路径低拷贝,局部场景有拷贝回退”,而不是“全链路绝对零拷贝”。收益是表达亮点同时不失真。代价是表述没那么激进。
|
||||||
|
- **避坑话术**:先给边界,再讲优化方向。
|
||||||
|
|
||||||
|
### 47. 状态机协同实现到哪
|
||||||
|
- **风险等级**:高
|
||||||
|
- **参考回答**:SSYNC/SREADY、快照传输、增量通道与 seq 机制已打通;全量-增量无缝协同仍在完善。这样说能体现工程进度和规划。
|
||||||
|
- **避坑话术**:别说“完全解决协同一致性”。
|
||||||
|
|
||||||
|
### 48. 两周落地计划
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:第 1 周先补一致性边界和故障注入测试;第 2 周做所有权移交闭环和指标监控。收益是先补正确性再补性能。代价是短期新功能延后。
|
||||||
|
- **避坑话术**:给里程碑,不要空谈“能做完”。
|
||||||
|
|
||||||
|
### 49. 最大技术债
|
||||||
|
- **风险等级**:中
|
||||||
|
- **参考回答**:最大的债是“协同一致性语义和容错测试还不够系统化”。我优先做了主链路可运行和性能基线。tradeoff 是先可用再完善严谨性。
|
||||||
|
- **避坑话术**:承认技术债并给修复计划,面试官反而更认可。
|
||||||
|
|
||||||
|
### 50. 三人小组下一版目标
|
||||||
|
- **风险等级**:低
|
||||||
|
- **参考回答**:目标按优先级:一致性文档+故障矩阵、可观测性、性能回归门禁。验收指标包括恢复正确率、复制延迟、QPS/CV 基线。tradeoff 是减少“新功能数量”,换“上线可信度”。
|
||||||
|
- **避坑话术**:不要只提“再提 2 倍 QPS”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 快速复习(高风险题清单)
|
||||||
|
- **高风险题**:11, 16, 20, 26, 29, 30, 32, 34, 46, 47
|
||||||
|
- **统一策略**:先讲“已实现边界”,再讲“预案与下一步”,避免绝对化承诺。
|
||||||
76
doc/interview_questions_aggressive_50.md
Normal file
76
doc/interview_questions_aggressive_50.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# KVStore 激进版本:面试高频追问 50 题(含 Tradeoff 视角)
|
||||||
|
|
||||||
|
## 回答建议(统一框架)
|
||||||
|
- 建议每题按「目标 -> 方案 -> 收益 -> 代价 -> 兜底」回答。
|
||||||
|
- 收益常见维度:吞吐、时延、稳定性、可维护性、可扩展性。
|
||||||
|
- 代价常见维度:实现复杂度、内存开销、排障成本、一致性窗口、开发周期。
|
||||||
|
|
||||||
|
## 1) 架构与边界(1-5)
|
||||||
|
1. 你把系统拆成了哪些层(网络/协议/执行/持久化/复制)?这种分层带来的收益与额外复杂度分别是什么?
|
||||||
|
2. 为什么默认选择 Reactor 而不是 Proactor/协程网络模型?三者在吞吐、延迟、开发复杂度上的 tradeoff 是什么?
|
||||||
|
3. Array/RBTree/Hash 三种引擎的选型边界是什么?在不同数据分布下的性能收益与维护成本如何权衡?
|
||||||
|
4. 你说“主路径轻量化”,具体删掉了哪些主线程工作?对 CPU 利用率与代码复杂度的 tradeoff 是什么?
|
||||||
|
5. 如果要做多核扩展,你会优先做连接分片还是数据分片?两者在扩展性与一致性上的代价分别是什么?
|
||||||
|
|
||||||
|
## 2) 协议与解析(6-10)
|
||||||
|
6. RESP 解析如何处理半包/多包/粘包?选择“严格报错”还是“尽量恢复”的 tradeoff 是什么?
|
||||||
|
7. binary-safe 为什么必须用 slice(ptr+len)而不是 C 字符串?这对性能和代码可读性有什么影响?
|
||||||
|
8. 你如何限制恶意请求(超大 bulk、超多参数)?安全性提升与正常流量误伤之间如何平衡?
|
||||||
|
9. inline 与 multibulk 共存时如何处理优先级?兼容性收益与实现复杂度代价分别是什么?
|
||||||
|
10. 新增命令时如何保证最小改动?抽象过度与快速迭代之间的 tradeoff 怎么拿捏?
|
||||||
|
|
||||||
|
## 3) ChainBuffer 与“零拷贝”追问(11-15)
|
||||||
|
11. readv 直写后用户态还会发生哪些拷贝?你如何定义“零拷贝”和“低拷贝”的边界?
|
||||||
|
12. chain_buffer_linearize 何时触发?减少复杂解析逻辑与引入额外 memcpy 的 tradeoff 是什么?
|
||||||
|
13. 为什么要做 chunk free_list?复用内存带来的性能收益与内存峰值风险如何平衡?
|
||||||
|
14. 大 Key(64KB)与小 Key 混跑时,chunk 策略如何避免碎片化?吞吐和内存效率谁优先?
|
||||||
|
15. sendmsg 聚合发送遇到部分发送时如何保证一致性?更高吞吐与更复杂状态管理如何权衡?
|
||||||
|
|
||||||
|
## 4) 所有权移交与并发安全(16-20)
|
||||||
|
16. “执行-落盘共享缓冲片段”如何定义所有权?减少拷贝收益与生命周期复杂度代价怎么评估?
|
||||||
|
17. detach/release 如何避免 UAF/double free?你牺牲了哪些性能换取内存安全?
|
||||||
|
18. 连接提前关闭时已移交数据如何回收?优先保证数据安全还是优先快速释放资源?
|
||||||
|
19. 为什么采用“谁申请谁释放”?跨线程释放带来的便利与隐患如何取舍?
|
||||||
|
20. 若做真正 0 拷贝落盘,你会接受哪些新增约束(引用计数/锁/回收时序)?
|
||||||
|
|
||||||
|
## 5) io_uring 持久化流水线(21-25)
|
||||||
|
21. n*SPSC 相比单队列 MPSC 的收益和代价分别是什么?你为什么在当前阶段选 n*SPSC?
|
||||||
|
22. in-flight 上限为何设为 CQ 的 80%?保守水位与峰值吞吐之间如何权衡?
|
||||||
|
23. 队列满时背压策略是什么?“保护系统稳定”与“牺牲尾延迟”之间如何取舍?
|
||||||
|
24. destroy_queue 用批量偷取+集中释放的动机是什么?释放抖动与主循环平滑性如何平衡?
|
||||||
|
25. shutdown 阶段如何保证无悬挂任务?优雅退出时间与数据完整性哪个优先?
|
||||||
|
|
||||||
|
## 6) 快照 + 增量日志一致性(26-30)
|
||||||
|
26. SAVE 与 oplog append 并发时一致性窗口怎么定义?强一致与吞吐的 tradeoff 是什么?
|
||||||
|
27. 为什么恢复顺序是“先快照后 oplog”?恢复速度与恢复正确性之间如何权衡?
|
||||||
|
28. oplog 记录命令字节流而不是逻辑变更,优势和成本分别是什么?
|
||||||
|
29. replay 遇到坏日志时你会“失败退出”还是“跳过继续”?可用性与正确性怎么选?
|
||||||
|
30. 你如何说明恢复后状态边界?文档化成本与实现灵活性的 tradeoff 是什么?
|
||||||
|
|
||||||
|
## 7) 主从同步与状态机(31-35)
|
||||||
|
31. 请描述 SSYNC -> Snapshot -> SREADY -> Incremental 状态机,并说明每步的收益与风险。
|
||||||
|
32. 快照传输期间新写入如何不丢?延迟增大与一致性增强之间怎么平衡?
|
||||||
|
33. 共享内存 ring wrap marker 方案为什么可行?简单实现与健壮性之间的代价是什么?
|
||||||
|
34. seq 不连续时你为何选择阻塞/跳过/重建?各策略的可用性与数据风险如何比较?
|
||||||
|
35. eBPF uprobe 放在控制面而非数据面的考量是什么?观测能力与运行时开销如何权衡?
|
||||||
|
|
||||||
|
## 8) 内存分配器与内存池(36-40)
|
||||||
|
36. mempool 分级(8-512B)的依据是什么?固定桶命中率与碎片风险如何平衡?
|
||||||
|
37. 大对象回退 malloc 的原因是什么?统一路径与分层路径在复杂度上怎么取舍?
|
||||||
|
38. mempool 如何处理空闲页回收?低延迟复用与低内存占用的 tradeoff 怎么设定?
|
||||||
|
39. malloc/jemalloc/mypool 在你的 workload 下差异来自哪里?泛化能力与场景优化如何平衡?
|
||||||
|
40. 线上内存峰值异常时先看哪些指标?指标全面性与观测成本如何取舍?
|
||||||
|
|
||||||
|
## 9) 压测方法学与结果可信度(41-45)
|
||||||
|
41. 你如何保证 benchmark 可复现?“实验真实度”与“执行成本”之间怎么平衡?
|
||||||
|
42. 为什么有些场景用 bench.c,有些改用 testcase mode=4?语义准确与工具统一如何取舍?
|
||||||
|
43. QPS 统计口径怎么定义?是否包含失败请求?可比性与直观性之间如何平衡?
|
||||||
|
44. 如何避免预热不足/缓存命中导致虚高?测试严谨性与测试周期如何平衡?
|
||||||
|
45. 如何解释 round 间波动(CV)?追求峰值还是追求稳定性的 tradeoff 是什么?
|
||||||
|
|
||||||
|
## 10) 真实性、边界与演进(46-50)
|
||||||
|
46. 简历里“零拷贝”哪些已落地,哪些是低拷贝/预案?为什么这样表述?
|
||||||
|
47. 简历里“协同状态机”当前实现到哪一步?工程现实与对外表达如何平衡?
|
||||||
|
48. 若给你 2 周把预案落地,你的里程碑怎么排?短期收益与长期架构如何取舍?
|
||||||
|
49. 当前最大技术债是什么?为什么没有先修?业务推进与技术治理如何平衡?
|
||||||
|
50. 若带 3 人继续做,下版目标与验收指标是什么?功能扩展与稳定性建设如何排序?
|
||||||
25
doc/resume.tex
Normal file
25
doc/resume.tex
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
\section{项目经历}
|
||||||
|
|
||||||
|
\ResumeItem[KVStore 高性能 KV 存储系统(RESP 兼容 + 异步持久化 + 主从同步)]
|
||||||
|
{\textbf{KVStore} 高性能 KV 存储系统(RESP 兼容 + 异步持久化 + 主从同步)}
|
||||||
|
[个人项目|独立开发]
|
||||||
|
[2025.03 --- 2026.03]
|
||||||
|
\begin{itemize}
|
||||||
|
\item 基于 \textbf{C + Linux} 从零实现单机 KV 服务:支持 \textbf{RESP2 协议解析}、pipeline 与 binary-safe(支持 \texttt{\textbackslash 0}),并统一接入 \textbf{Array / RBTree / Hash} 三类存储引擎。
|
||||||
|
\item 实现链式网络缓冲 \textbf{ChainBuffer}:通过 \textbf{readv/sendmsg} 构建分段零拷贝收发路径,支持大 Key 与半包/多包场景;提供 \textbf{detach/release} 所有权接口,支持后续执行-落盘共享同一数据片段。
|
||||||
|
\item 实现 \textbf{io\_uring + n*SPSC} 异步落盘模型:主线程执行后写入增量日志,worker 线程批量提交 \textbf{writev};通过 in-flight 控制、队列背压、destroy-queue 回收,避免 CQ 溢出与内存失控。
|
||||||
|
\item 设计并落地“\textbf{快照 + 增量日志}”恢复链路:支持启动加载快照并回放 oplog;支持 \textbf{SSYNC $\rightarrow$ 快照传输 $\rightarrow$ SREADY $\rightarrow$ 增量同步} 的主从接管流程,并预留 eBPF uprobe 协同点。
|
||||||
|
\item 实现可插拔内存分配策略(\textbf{malloc/jemalloc/mypool}):自研 mempool 采用 8--512B 分级桶 + 页级 free-list;在 allocator$\times$persistence 复测中,\textbf{mypool} 吞吐最佳(none: 924878 QPS,incremental: 747101 QPS)。
|
||||||
|
\item 构建 hiredis 功能/压测工具链(\texttt{testcase + bench + 自动化脚本}),沉淀多轮可复现实验口径;将关键优化量化为可交付结果(如 ChainBuffer 改造后写路径 QPS 提升约 \textbf{27\%})。
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\ResumeItem[EncryptSql 基于 PostgreSQL 的透明加密查询与运算框架]
|
||||||
|
{\textbf{EncryptSQL} 基于 PostgreSQL 的透明加密查询与运算框架}
|
||||||
|
[学校横向|部分代码开发]
|
||||||
|
[2024.09 --- 2025.09]
|
||||||
|
\begin{itemize}
|
||||||
|
\item 在客户端侧对 \textbf{libpq} 进行改造,实现 \textbf{SQL 解析后重写}:将原生表达式/运算符节点替换为密态版本函数/算子调用,尽量保证业务侧无侵入接入。
|
||||||
|
\item 基于 \textbf{PostgreSQL 扩展机制}(自定义函数/算子等)接入密态运算:支持常见算术计算(加/减/乘/除)与部分聚合能力,并与查询执行流程集成。
|
||||||
|
\item 设计并实现基于工厂模式的 \textbf{KMS 接口层}:在 \texttt{encryptsql} 组件中统一密钥获取与管理流程,完成 \textbf{LocalKMS} 与 \textbf{Huawei KMS API} 适配,支持外部 KMS 平滑替换。
|
||||||
|
\item 面向高安全计算场景,引入 \textbf{TEE} 承载关键运算链路,在安全性与性能开销之间做工程化平衡。
|
||||||
|
\end{itemize}
|
||||||
25
doc/resume_aggressive.tex
Normal file
25
doc/resume_aggressive.tex
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
\section{项目经历}
|
||||||
|
|
||||||
|
\ResumeItem[KVStore 高性能 KV 存储系统(激进表达版)]
|
||||||
|
{\textbf{KVStore} 高性能 KV 存储系统(零拷贝接收 + 异步持久化 + 主从协同状态机)}
|
||||||
|
[个人项目|独立开发]
|
||||||
|
[2025.03 --- 2026.03]
|
||||||
|
\begin{itemize}
|
||||||
|
\item 基于 \textbf{C + Linux} 实现 RESP 兼容 KV 存储内核,支持 \textbf{binary-safe}、pipeline 与多引擎统一分发(Array/RBTree/Hash),形成协议层到执行层的一体化数据通路。
|
||||||
|
\item 围绕大 Key 场景实现 \textbf{ChainBuffer 分段零拷贝接收}:采用 \textbf{readv/sendmsg} 与链式 chunk 组织,支持超大请求分段处理;按线上保护阈值将单请求上限收敛至 \textbf{65535} 字节级别。
|
||||||
|
\item 实现并演进 \textbf{所有权移交} 机制:主线程仅负责命令边界识别与执行,落盘线程复用网络缓冲片段进行持久化,减少主路径内存申请/对象拼装开销。
|
||||||
|
\item 搭建 \textbf{io\_uring + n*SPSC} 持久化流水线:worker 批量提交 writev,主线程异步回收完成任务;结合 in-flight 背压与 destroy-queue,稳定处理慢盘与高并发写入抖动。
|
||||||
|
\item 设计“\textbf{快照 + 增量日志 + 实时复制}”协同方案:通过 \textbf{SSYNC $\rightarrow$ Snapshot $\rightarrow$ SREADY $\rightarrow$ Incremental} 状态机衔接全量与增量,保障复制窗口内的可恢复性与顺序一致性。
|
||||||
|
\item 构建多维压测体系(功能正确性/吞吐/波动):在 allocator$\times$persistence 复测中,\textbf{mypool} 取得最佳吞吐(none: 924878 QPS,incremental: 747101 QPS),并将优化效果沉淀为工程基线。
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\ResumeItem[EncryptSql 基于 PostgreSQL 的透明加密查询与运算框架]
|
||||||
|
{\textbf{EncryptSQL} 基于 PostgreSQL 的透明加密查询与运算框架}
|
||||||
|
[学校横向|部分代码开发]
|
||||||
|
[2024.09 --- 2025.09]
|
||||||
|
\begin{itemize}
|
||||||
|
\item 在客户端侧改造 \textbf{libpq} 并实现 \textbf{SQL AST 重写}:将明文表达式自动替换为密态函数/算子调用,降低业务系统改造成本。
|
||||||
|
\item 基于 \textbf{PostgreSQL 扩展机制}接入密态算子,支持加/减/乘/除与部分聚合能力,形成可落地的“密文存储 + 密态计算”执行路径。
|
||||||
|
\item 设计并实现 \textbf{KMS 工厂接口框架},完成 \textbf{LocalKMS/Huawei KMS} 适配,支持多云与私有化 KMS 的低成本切换。
|
||||||
|
\item 在高敏感计算场景引入 \textbf{TEE},对关键流程进行可信执行与边界隔离,平衡安全目标与查询性能。
|
||||||
|
\end{itemize}
|
||||||
25
doc/resume_conservative.tex
Normal file
25
doc/resume_conservative.tex
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
\section{项目经历}
|
||||||
|
|
||||||
|
\ResumeItem[KVStore 高性能 KV 存储系统(保守版)]
|
||||||
|
{\textbf{KVStore} 高性能 KV 存储系统(RESP 兼容 + 异步持久化 + 主从同步)}
|
||||||
|
[个人项目|独立开发]
|
||||||
|
[2025.03 --- 2026.03]
|
||||||
|
\begin{itemize}
|
||||||
|
\item 基于 \textbf{C + Linux} 实现单机 KV 服务,支持 \textbf{RESP2} 解析、pipeline 与 binary-safe(支持 \texttt{\textbackslash 0})键值处理。
|
||||||
|
\item 统一命令分发层,接入 \textbf{Array / RBTree / Hash} 三种引擎,实现 SET/GET/DEL 及对应 R*/H* 命令族。
|
||||||
|
\item 实现 \textbf{ChainBuffer} 分段网络缓冲:接收侧使用 \textbf{readv} 直写,发送侧使用 \textbf{sendmsg} 聚合发送,并通过 linearize 处理跨分段解析场景。
|
||||||
|
\item 实现 \textbf{io\_uring + n*SPSC} 异步增量日志写入,包含 in-flight 限流、背压与完成队列回收,降低主线程阻塞。
|
||||||
|
\item 实现“\textbf{快照 + 增量日志}”恢复路径:支持 SAVE 快照、oplog 回放;支持 \textbf{SSYNC/SREADY} 启动同步与共享内存增量通道。
|
||||||
|
\item 构建 hiredis 功能/性能测试工具链;在 2026-03-04 的 allocator$\times$persistence 复测中,\textbf{mypool} 组合吞吐最佳(none: 924878 QPS,incremental: 747101 QPS)。
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\ResumeItem[EncryptSql 基于 PostgreSQL 的透明加密查询与运算框架]
|
||||||
|
{\textbf{EncryptSQL} 基于 PostgreSQL 的透明加密查询与运算框架}
|
||||||
|
[学校横向|部分代码开发]
|
||||||
|
[2024.09 --- 2025.09]
|
||||||
|
\begin{itemize}
|
||||||
|
\item 在客户端侧对 \textbf{libpq} 进行改造,实现 \textbf{SQL 解析后重写}:将原生表达式/运算符节点替换为密态函数/算子调用,尽量保证业务侧无侵入接入。
|
||||||
|
\item 基于 \textbf{PostgreSQL 扩展机制}(自定义函数/算子等)接入密态运算:支持常见算术计算(加/减/乘/除)与部分聚合能力,并与执行流程集成。
|
||||||
|
\item 设计并实现基于工厂模式的 \textbf{KMS 接口层}:在 \texttt{encryptsql} 中统一密钥获取与管理流程,完成 \textbf{LocalKMS} 与 \textbf{Huawei KMS API} 适配。
|
||||||
|
\item 面向高安全计算场景,引入 \textbf{TEE} 承载关键运算链路,在安全性与性能开销之间做工程化平衡。
|
||||||
|
\end{itemize}
|
||||||
228
hash.c
228
hash.c
@@ -1,228 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// #include <stdio.h>
|
|
||||||
// #include <string.h>
|
|
||||||
// #include <stdlib.h>
|
|
||||||
// #include <pthread.h>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// #define MAX_KEY_LEN 128
|
|
||||||
// #define MAX_VALUE_LEN 512
|
|
||||||
// #define MAX_TABLE_SIZE 1024
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// typedef struct hashnode_s {
|
|
||||||
|
|
||||||
// char key[MAX_KEY_LEN];
|
|
||||||
// char value[MAX_VALUE_LEN];
|
|
||||||
|
|
||||||
// struct hashnode_s *next;
|
|
||||||
|
|
||||||
// } hashnode_t;
|
|
||||||
|
|
||||||
|
|
||||||
// typedef struct hashtable_s {
|
|
||||||
|
|
||||||
// hashnode_t **nodes; //* change **,
|
|
||||||
|
|
||||||
// int max_slots;
|
|
||||||
// int count;
|
|
||||||
|
|
||||||
// pthread_mutex_t lock;
|
|
||||||
|
|
||||||
// } hashtable_t;
|
|
||||||
|
|
||||||
// hashtable_t hash;
|
|
||||||
|
|
||||||
|
|
||||||
// //Connection
|
|
||||||
// // 'C' + 'o' + 'n'
|
|
||||||
// static int _hash(char *key, int size) {
|
|
||||||
|
|
||||||
// if (!key) return -1;
|
|
||||||
|
|
||||||
// int sum = 0;
|
|
||||||
// int i = 0;
|
|
||||||
|
|
||||||
// while (key[i] != 0) {
|
|
||||||
// sum += key[i];
|
|
||||||
// i ++;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return sum % size;
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// hashnode_t *_create_node(char *key, char *value) {
|
|
||||||
|
|
||||||
// hashnode_t *node = (hashnode_t*)malloc(sizeof(hashnode_t));
|
|
||||||
// if (!node) return NULL;
|
|
||||||
|
|
||||||
// strncpy(node->key, key, MAX_KEY_LEN);
|
|
||||||
// strncpy(node->value, value, MAX_VALUE_LEN);
|
|
||||||
// node->next = NULL;
|
|
||||||
|
|
||||||
// return node;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// //
|
|
||||||
// int init_hashtable(hashtable_t *hash) {
|
|
||||||
|
|
||||||
// if (!hash) return -1;
|
|
||||||
|
|
||||||
// hash->nodes = (hashnode_t**)malloc(sizeof(hashnode_t*) * MAX_TABLE_SIZE);
|
|
||||||
// if (!hash->nodes) return -1;
|
|
||||||
|
|
||||||
// hash->max_slots = MAX_TABLE_SIZE;
|
|
||||||
// hash->count = 0;
|
|
||||||
|
|
||||||
// pthread_mutex_init(&hash->lock, NULL);
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //
|
|
||||||
// void dest_hashtable(hashtable_t *hash) {
|
|
||||||
|
|
||||||
// if (!hash) return;
|
|
||||||
|
|
||||||
// int i = 0;
|
|
||||||
// for (i = 0;i < hash->max_slots;i ++) {
|
|
||||||
// hashnode_t *node = hash->nodes[i];
|
|
||||||
|
|
||||||
// while (node != NULL) { // error
|
|
||||||
|
|
||||||
// hashnode_t *tmp = node;
|
|
||||||
// node = node->next;
|
|
||||||
// hash->nodes[i] = node;
|
|
||||||
|
|
||||||
// free(tmp);
|
|
||||||
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// free(hash->nodes);
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// // mp
|
|
||||||
// int put_kv_hashtable(hashtable_t *hash, char *key, char *value) {
|
|
||||||
|
|
||||||
// if (!hash || !key || !value) return -1;
|
|
||||||
|
|
||||||
// int idx = _hash(key, MAX_TABLE_SIZE);
|
|
||||||
|
|
||||||
// pthread_mutex_lock(&hash->lock);
|
|
||||||
|
|
||||||
// hashnode_t *node = hash->nodes[idx];
|
|
||||||
// #if 1
|
|
||||||
// while (node != NULL) {
|
|
||||||
// if (strcmp(node->key, key) == 0) { // exist
|
|
||||||
// pthread_mutex_unlock(&hash->lock);
|
|
||||||
// return 1;
|
|
||||||
// }
|
|
||||||
// node = node->next;
|
|
||||||
// }
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// hashnode_t *new_node = _create_node(key, value);
|
|
||||||
// new_node->next = hash->nodes[idx];
|
|
||||||
// hash->nodes[idx] = new_node;
|
|
||||||
|
|
||||||
// hash->count ++;
|
|
||||||
|
|
||||||
// pthread_mutex_unlock(&hash->lock);
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// char * get_kv_hashtable(hashtable_t *hash, char *key) {
|
|
||||||
|
|
||||||
// if (!hash || !key) return NULL;
|
|
||||||
|
|
||||||
// int idx = _hash(key, MAX_TABLE_SIZE);
|
|
||||||
|
|
||||||
// pthread_mutex_lock(&hash->lock);
|
|
||||||
// hashnode_t *node = hash->nodes[idx];
|
|
||||||
|
|
||||||
// while (node != NULL) {
|
|
||||||
|
|
||||||
// if (strcmp(node->key, key) == 0) {
|
|
||||||
// pthread_mutex_unlock(&hash->lock);
|
|
||||||
// return node->value;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// node = node->next;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pthread_mutex_unlock(&hash->lock);
|
|
||||||
|
|
||||||
// return NULL;
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// int count_kv_hashtable(hashtable_t *hash) {
|
|
||||||
// return hash->count;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// int delete_kv_hashtable(hashtable_t *hash, char *key) {
|
|
||||||
// if (!hash || !key) return -2;
|
|
||||||
|
|
||||||
// int idx = _hash(key, MAX_TABLE_SIZE);
|
|
||||||
|
|
||||||
// pthread_mutex_lock(&hash->lock);
|
|
||||||
// hashnode_t *head = hash->nodes[idx];
|
|
||||||
// if (head == NULL) return -1; // noexist
|
|
||||||
// // head node
|
|
||||||
// if (strcmp(head->key, key) == 0) {
|
|
||||||
// hashnode_t *tmp = head->next;
|
|
||||||
// hash->nodes[idx] = tmp;
|
|
||||||
|
|
||||||
// free(head);
|
|
||||||
// hash->count --;
|
|
||||||
// pthread_mutex_unlock(&hash->lock);
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// hashnode_t *cur = head;
|
|
||||||
// while (cur->next != NULL) {
|
|
||||||
// if (strcmp(cur->next->key, key) == 0) break; // search node
|
|
||||||
|
|
||||||
// cur = cur->next;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (cur->next == NULL) {
|
|
||||||
|
|
||||||
// pthread_mutex_unlock(&hash->lock);
|
|
||||||
// return -1;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// hashnode_t *tmp = cur->next;
|
|
||||||
// cur->next = tmp->next;
|
|
||||||
// free(tmp);
|
|
||||||
// hash->count --;
|
|
||||||
|
|
||||||
// pthread_mutex_unlock(&hash->lock);
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// int exist_kv_hashtable(hashtable_t *hash, char *key) {
|
|
||||||
|
|
||||||
// char *value = get_kv_hashtable(hash, key);
|
|
||||||
// if (value) return 1;
|
|
||||||
// else return 0;
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
192
kvs_array.c
192
kvs_array.c
@@ -1,192 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
#include "kvstore.h"
|
|
||||||
#include "kvs_rw_tools.h"
|
|
||||||
#include "memory/alloc_dispatch.h"
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
|
|
||||||
|
|
||||||
// singleton
|
|
||||||
|
|
||||||
kvs_array_t global_array = {0};
|
|
||||||
|
|
||||||
int kvs_array_create(kvs_array_t *inst) {
|
|
||||||
|
|
||||||
if (!inst) return -1;
|
|
||||||
if (inst->table) {
|
|
||||||
printf("table has alloc\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
inst->table = kvs_malloc(KVS_ARRAY_SIZE * sizeof(kvs_array_item_t));
|
|
||||||
if (!inst->table) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(inst->table, 0, (size_t)KVS_ARRAY_SIZE * sizeof(kvs_array_item_t));
|
|
||||||
inst->total = 0;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void kvs_array_destroy(kvs_array_t *inst) {
|
|
||||||
|
|
||||||
if (!inst) return ;
|
|
||||||
|
|
||||||
if (inst->table) {
|
|
||||||
kvs_free(inst->table);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @return: <0, error; =0, success; >0, exist
|
|
||||||
*/
|
|
||||||
|
|
||||||
int kvs_array_set(kvs_array_t *inst, char *key, char *value) {
|
|
||||||
|
|
||||||
if (inst == NULL || key == NULL || value == NULL) return -1;
|
|
||||||
if (inst->total == KVS_ARRAY_SIZE) return -1;
|
|
||||||
|
|
||||||
char *str = kvs_array_get(inst, key);
|
|
||||||
if (str) {
|
|
||||||
return 1; //
|
|
||||||
}
|
|
||||||
|
|
||||||
char *kcopy = kvs_malloc(strlen(key) + 1);
|
|
||||||
if (kcopy == NULL) return -2;
|
|
||||||
memset(kcopy, 0, strlen(key) + 1);
|
|
||||||
strncpy(kcopy, key, strlen(key));
|
|
||||||
|
|
||||||
char *kvalue = kvs_malloc(strlen(value) + 1);
|
|
||||||
if (kvalue == NULL) return -2;
|
|
||||||
memset(kvalue, 0, strlen(value) + 1);
|
|
||||||
strncpy(kvalue, value, strlen(value));
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for (i = 0;i < inst->total;i ++) {
|
|
||||||
if (inst->table[i].key == NULL) {
|
|
||||||
|
|
||||||
inst->table[i].key = kcopy;
|
|
||||||
inst->table[i].value = kvalue;
|
|
||||||
inst->total ++;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == inst->total && i < KVS_ARRAY_SIZE) {
|
|
||||||
|
|
||||||
inst->table[i].key = kcopy;
|
|
||||||
inst->table[i].value = kvalue;
|
|
||||||
inst->total ++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* kvs_array_get(kvs_array_t *inst, char *key) {
|
|
||||||
|
|
||||||
if (inst == NULL || key == NULL) return NULL;
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for (i = 0;i < inst->total;i ++) {
|
|
||||||
if (inst->table[i].key == NULL) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(inst->table[i].key, key) == 0) {
|
|
||||||
return inst->table[i].value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @return < 0, error; =0, success; >0, no exist
|
|
||||||
*/
|
|
||||||
|
|
||||||
int kvs_array_del(kvs_array_t *inst, char *key) {
|
|
||||||
|
|
||||||
if (inst == NULL || key == NULL) return -1;
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for (i = 0;i < inst->total;i ++) {
|
|
||||||
|
|
||||||
if (strcmp(inst->table[i].key, key) == 0) {
|
|
||||||
|
|
||||||
kvs_free(inst->table[i].key);
|
|
||||||
inst->table[i].key = NULL;
|
|
||||||
|
|
||||||
kvs_free(inst->table[i].value);
|
|
||||||
inst->table[i].value = NULL;
|
|
||||||
// error: > 1024
|
|
||||||
if (inst->total-1 == i) {
|
|
||||||
inst->total --;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @return : < 0, error; =0, success; >0, no exist
|
|
||||||
*/
|
|
||||||
|
|
||||||
int kvs_array_mod(kvs_array_t *inst, char *key, char *value) {
|
|
||||||
|
|
||||||
if (inst == NULL || key == NULL || value == NULL) return -1;
|
|
||||||
// error: > 1024
|
|
||||||
if (inst->total == 0) {
|
|
||||||
return KVS_ARRAY_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for (i = 0;i < inst->total;i ++) {
|
|
||||||
|
|
||||||
if (inst->table[i].key == NULL) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(inst->table[i].key, key) == 0) {
|
|
||||||
|
|
||||||
kvs_free(inst->table[i].value);
|
|
||||||
|
|
||||||
char *kvalue = kvs_malloc(strlen(value) + 1);
|
|
||||||
if (kvalue == NULL) return -2;
|
|
||||||
memset(kvalue, 0, strlen(value) + 1);
|
|
||||||
strncpy(kvalue, value, strlen(value));
|
|
||||||
|
|
||||||
inst->table[i].value = kvalue;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @return 0: exist, 1: no exist
|
|
||||||
*/
|
|
||||||
int kvs_array_exist(kvs_array_t *inst, char *key) {
|
|
||||||
|
|
||||||
if (!inst || !key) return -1;
|
|
||||||
|
|
||||||
char *str = kvs_array_get(inst, key);
|
|
||||||
if (!str) {
|
|
||||||
return 1; //
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
286
kvs_hash.c
286
kvs_hash.c
@@ -1,286 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// #include <stdio.h>
|
|
||||||
// #include <string.h>
|
|
||||||
// #include <stdlib.h>
|
|
||||||
// #include <pthread.h>
|
|
||||||
|
|
||||||
|
|
||||||
// #include "memory/alloc_dispatch.h"
|
|
||||||
// #include "kvstore.h"
|
|
||||||
|
|
||||||
|
|
||||||
// // Key, Value -->
|
|
||||||
// // Modify
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// kvs_hash_t global_hash;
|
|
||||||
|
|
||||||
|
|
||||||
// //Connection
|
|
||||||
// // 'C' + 'o' + 'n'
|
|
||||||
// static int _hash(char *key, int size) {
|
|
||||||
|
|
||||||
// if (!key) return -1;
|
|
||||||
|
|
||||||
// int sum = 0;
|
|
||||||
// int i = 0;
|
|
||||||
|
|
||||||
// while (key[i] != 0) {
|
|
||||||
// sum += key[i];
|
|
||||||
// i ++;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return sum % size;
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// hashnode_t *_create_node(char *key, char *value) {
|
|
||||||
|
|
||||||
// hashnode_t *node = (hashnode_t*)kvs_malloc(sizeof(hashnode_t));
|
|
||||||
// if (!node) return NULL;
|
|
||||||
|
|
||||||
// #if ENABLE_KEY_POINTER
|
|
||||||
// char *kcopy = kvs_malloc(strlen(key) + 1);
|
|
||||||
// if (kcopy == NULL) return NULL;
|
|
||||||
// memset(kcopy, 0, strlen(key) + 1);
|
|
||||||
// strncpy(kcopy, key, strlen(key));
|
|
||||||
|
|
||||||
// node->key = kcopy;
|
|
||||||
|
|
||||||
// char *kvalue = kvs_malloc(strlen(value) + 1);
|
|
||||||
// if (kvalue == NULL) {
|
|
||||||
// kvs_free(kvalue);
|
|
||||||
// return NULL;
|
|
||||||
// }
|
|
||||||
// memset(kvalue, 0, strlen(value) + 1);
|
|
||||||
// strncpy(kvalue, value, strlen(value));
|
|
||||||
|
|
||||||
// node->value = kvalue;
|
|
||||||
|
|
||||||
// #else
|
|
||||||
// strncpy(node->key, key, MAX_KEY_LEN);
|
|
||||||
// strncpy(node->value, value, MAX_VALUE_LEN);
|
|
||||||
// #endif
|
|
||||||
// node->next = NULL;
|
|
||||||
|
|
||||||
// return node;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// //
|
|
||||||
// int kvs_hash_create(kvs_hash_t *hash) {
|
|
||||||
|
|
||||||
// if (!hash) return -1;
|
|
||||||
|
|
||||||
// hash->nodes = (hashnode_t**)kvs_malloc(sizeof(hashnode_t*) * MAX_TABLE_SIZE);
|
|
||||||
// if (!hash->nodes) return -1;
|
|
||||||
|
|
||||||
// hash->max_slots = MAX_TABLE_SIZE;
|
|
||||||
// hash->count = 0;
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //
|
|
||||||
// void kvs_hash_destroy(kvs_hash_t *hash) {
|
|
||||||
|
|
||||||
// if (!hash) return;
|
|
||||||
|
|
||||||
// int i = 0;
|
|
||||||
// for (i = 0;i < hash->max_slots;i ++) {
|
|
||||||
// hashnode_t *node = hash->nodes[i];
|
|
||||||
|
|
||||||
// while (node != NULL) { // error
|
|
||||||
|
|
||||||
// hashnode_t *tmp = node;
|
|
||||||
// node = node->next;
|
|
||||||
// hash->nodes[i] = node;
|
|
||||||
|
|
||||||
// kvs_free(tmp);
|
|
||||||
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// kvs_free(hash->nodes);
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 5 + 2
|
|
||||||
|
|
||||||
// // mp
|
|
||||||
// int kvs_hash_set(kvs_hash_t *hash, char *key, char *value) {
|
|
||||||
|
|
||||||
// if (!hash || !key || !value) return -1;
|
|
||||||
|
|
||||||
// int idx = _hash(key, MAX_TABLE_SIZE);
|
|
||||||
|
|
||||||
// hashnode_t *node = hash->nodes[idx];
|
|
||||||
// #if 1
|
|
||||||
// while (node != NULL) {
|
|
||||||
// if (strcmp(node->key, key) == 0) { // exist
|
|
||||||
// return 1;
|
|
||||||
// }
|
|
||||||
// node = node->next;
|
|
||||||
// }
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// hashnode_t *new_node = _create_node(key, value);
|
|
||||||
// new_node->next = hash->nodes[idx];
|
|
||||||
// hash->nodes[idx] = new_node;
|
|
||||||
|
|
||||||
// hash->count ++;
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// char * kvs_hash_get(kvs_hash_t *hash, char *key) {
|
|
||||||
|
|
||||||
// if (!hash || !key) return NULL;
|
|
||||||
|
|
||||||
// int idx = _hash(key, MAX_TABLE_SIZE);
|
|
||||||
|
|
||||||
// hashnode_t *node = hash->nodes[idx];
|
|
||||||
|
|
||||||
// while (node != NULL) {
|
|
||||||
|
|
||||||
// if (strcmp(node->key, key) == 0) {
|
|
||||||
// return node->value;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// node = node->next;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return NULL;
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// int kvs_hash_mod(kvs_hash_t *hash, char *key, char *value) {
|
|
||||||
|
|
||||||
// if (!hash || !key) return -1;
|
|
||||||
|
|
||||||
// int idx = _hash(key, MAX_TABLE_SIZE);
|
|
||||||
|
|
||||||
// hashnode_t *node = hash->nodes[idx];
|
|
||||||
|
|
||||||
// while (node != NULL) {
|
|
||||||
|
|
||||||
// if (strcmp(node->key, key) == 0) {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// node = node->next;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (node == NULL) {
|
|
||||||
// return 1;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // node -->
|
|
||||||
// kvs_free(node->value);
|
|
||||||
|
|
||||||
// char *kvalue = kvs_malloc(strlen(value) + 1);
|
|
||||||
// if (kvalue == NULL) return -2;
|
|
||||||
// memset(kvalue, 0, strlen(value) + 1);
|
|
||||||
// strncpy(kvalue, value, strlen(value));
|
|
||||||
|
|
||||||
// node->value = kvalue;
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// int kvs_hash_count(kvs_hash_t *hash) {
|
|
||||||
// return hash->count;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// int kvs_hash_del(kvs_hash_t *hash, char *key) {
|
|
||||||
// if (!hash || !key) return -2;
|
|
||||||
|
|
||||||
// int idx = _hash(key, MAX_TABLE_SIZE);
|
|
||||||
|
|
||||||
// hashnode_t *head = hash->nodes[idx];
|
|
||||||
// if (head == NULL) return -1; // noexist
|
|
||||||
// // head node
|
|
||||||
// if (strcmp(head->key, key) == 0) {
|
|
||||||
// hashnode_t *tmp = head->next;
|
|
||||||
// hash->nodes[idx] = tmp;
|
|
||||||
|
|
||||||
// kvs_free(head);
|
|
||||||
// hash->count --;
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// hashnode_t *cur = head;
|
|
||||||
// while (cur->next != NULL) {
|
|
||||||
// if (strcmp(cur->next->key, key) == 0) break; // search node
|
|
||||||
|
|
||||||
// cur = cur->next;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (cur->next == NULL) {
|
|
||||||
|
|
||||||
// return -1;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// hashnode_t *tmp = cur->next;
|
|
||||||
// cur->next = tmp->next;
|
|
||||||
// #if ENABLE_KEY_POINTER
|
|
||||||
// kvs_free(tmp->key);
|
|
||||||
// kvs_free(tmp->value);
|
|
||||||
// #endif
|
|
||||||
// kvs_free(tmp);
|
|
||||||
|
|
||||||
// hash->count --;
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// int kvs_hash_exist(kvs_hash_t *hash, char *key) {
|
|
||||||
|
|
||||||
// char *value = kvs_hash_get(hash, key);
|
|
||||||
// if (!value) return 1;
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #if 0
|
|
||||||
// int main() {
|
|
||||||
|
|
||||||
// kvs_hash_create(&hash);
|
|
||||||
|
|
||||||
// kvs_hash_set(&hash, "Teacher1", "King");
|
|
||||||
// kvs_hash_set(&hash, "Teacher2", "Darren");
|
|
||||||
// kvs_hash_set(&hash, "Teacher3", "Mark");
|
|
||||||
// kvs_hash_set(&hash, "Teacher4", "Vico");
|
|
||||||
// kvs_hash_set(&hash, "Teacher5", "Nick");
|
|
||||||
|
|
||||||
// char *value1 = kvs_hash_get(&hash, "Teacher1");
|
|
||||||
// printf("Teacher1 : %s\n", value1);
|
|
||||||
|
|
||||||
// int ret = kvs_hash_mod(&hash, "Teacher1", "King1");
|
|
||||||
// printf("mode Teacher1 ret : %d\n", ret);
|
|
||||||
|
|
||||||
// char *value2 = kvs_hash_get(&hash, "Teacher1");
|
|
||||||
// printf("Teacher2 : %s\n", value1);
|
|
||||||
|
|
||||||
// ret = kvs_hash_del(&hash, "Teacher1");
|
|
||||||
// printf("delete Teacher1 ret : %d\n", ret);
|
|
||||||
|
|
||||||
// ret = kvs_hash_exist(&hash, "Teacher1");
|
|
||||||
// printf("Exist Teacher1 ret : %d\n", ret);
|
|
||||||
|
|
||||||
// kvs_hash_destroy(&hash);
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
|
|
||||||
856
kvs_hash_bin.c
856
kvs_hash_bin.c
@@ -1,384 +1,680 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#include "kvstore.h"
|
#include "kvstore.h"
|
||||||
#include "kvs_rw_tools.h"
|
#include "kvs_rw_tools.h"
|
||||||
#include "memory/alloc_dispatch.h"
|
#include "memory/alloc_dispatch.h"
|
||||||
#include "diskuring/diskuring.h"
|
#include "diskuring/diskuring.h"
|
||||||
// Key, Value -->
|
|
||||||
// Modify
|
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define KVS_HASH_INITIAL_GLOBAL_DEPTH 1u
|
||||||
|
#define KVS_HASH_BUCKET_SPLIT_THRESHOLD 64u
|
||||||
|
#define KVS_HASH_MAX_GLOBAL_DEPTH 31u
|
||||||
|
|
||||||
kvs_hash_t global_hash;
|
kvs_hash_t global_hash;
|
||||||
|
|
||||||
|
static uint32_t _hash_u32(const void *key, uint32_t key_len) {
|
||||||
//Connection
|
|
||||||
// 'C' + 'o' + 'n'
|
|
||||||
static int _hash(const void *key, uint32_t key_len, int size) {
|
|
||||||
if (!key || size <= 0) return -1;
|
|
||||||
|
|
||||||
const uint8_t *p = (const uint8_t *)key;
|
const uint8_t *p = (const uint8_t *)key;
|
||||||
uint32_t sum = 0;
|
uint32_t hash = 2166136261u;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < key_len; i++) {
|
for (uint32_t i = 0; i < key_len; i++) {
|
||||||
sum += p[i];
|
hash ^= p[i];
|
||||||
|
hash *= 16777619u;
|
||||||
}
|
}
|
||||||
return sum % size;
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t _dir_index(uint32_t hashv, uint32_t global_depth) {
|
||||||
|
if (global_depth == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return hashv & ((1u << global_depth) - 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
static hashbucket_t *_bucket_create(uint32_t local_depth) {
|
||||||
|
hashbucket_t *bucket = (hashbucket_t *)kvs_malloc(sizeof(hashbucket_t));
|
||||||
|
if (!bucket) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket->head = NULL;
|
||||||
|
bucket->local_depth = local_depth;
|
||||||
|
bucket->item_count = 0;
|
||||||
|
bucket->next_all = NULL;
|
||||||
|
return bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _bucket_list_push(kvs_hash_t *hash, hashbucket_t *bucket) {
|
||||||
|
bucket->next_all = hash->bucket_list;
|
||||||
|
hash->bucket_list = bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint8_t *_get_node_key(const hashnode_t *node) {
|
||||||
|
return (uint8_t *)node + sizeof(hashnode_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint8_t *_get_node_value(const hashnode_t *node) {
|
||||||
|
return (uint8_t *)node + sizeof(hashnode_t) + node->key_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _key_equal(const hashnode_t *node, const void *key, uint32_t key_len) {
|
static int _key_equal(const hashnode_t *node, const void *key, uint32_t key_len) {
|
||||||
if (!node || !key) return 0;
|
if (!node || !key) {
|
||||||
if (!node->key) return 0;
|
return 0;
|
||||||
if (node->key_len != key_len) return 0;
|
}
|
||||||
return memcmp(node->key, key, key_len) == 0;
|
if (node->key_len != key_len) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return memcmp(_get_node_key(node), key, key_len) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static hashnode_t *_create_node(const void *key, uint32_t key_len,
|
static hashnode_t *_create_node(const void *key, uint32_t key_len,
|
||||||
const void *value, uint32_t value_len) {
|
const void *value, uint32_t value_len) {
|
||||||
hashnode_t *node = (hashnode_t*)kvs_malloc(sizeof(hashnode_t));
|
size_t total_size = sizeof(hashnode_t) + key_len + value_len;
|
||||||
if (!node) return NULL;
|
hashnode_t *node;
|
||||||
memset(node, 0, sizeof(*node));
|
uint8_t *data_ptr;
|
||||||
|
|
||||||
if (key_len > 0) {
|
if (!key || key_len == 0) {
|
||||||
node->key = (uint8_t*)kvs_malloc(key_len);
|
|
||||||
if (!node->key) { kvs_free(node); return NULL; }
|
|
||||||
memcpy(node->key, key, key_len);
|
|
||||||
node->key_len = key_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value_len > 0) {
|
|
||||||
node->value = (uint8_t*)kvs_malloc(value_len);
|
|
||||||
if (!node->value) {
|
|
||||||
kvs_free(node->key);
|
|
||||||
kvs_free(node);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
memcpy(node->value, value, value_len);
|
|
||||||
node->value_len = value_len;
|
node = (hashnode_t *)kvs_malloc(total_size);
|
||||||
} else {
|
if (!node) {
|
||||||
node->value = NULL;
|
return NULL;
|
||||||
node->value_len = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memset(node, 0, sizeof(hashnode_t));
|
||||||
|
node->key_len = key_len;
|
||||||
|
node->value_len = value_len;
|
||||||
node->next = NULL;
|
node->next = NULL;
|
||||||
|
|
||||||
|
data_ptr = (uint8_t *)node + sizeof(hashnode_t);
|
||||||
|
memcpy(data_ptr, key, key_len);
|
||||||
|
if (value_len > 0 && value) {
|
||||||
|
memcpy(data_ptr + key_len, value, value_len);
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static hashnode_t *_find_node(hashbucket_t *bucket,
|
||||||
|
const void *key, uint32_t key_len,
|
||||||
|
hashnode_t ***out_indirect) {
|
||||||
|
hashnode_t **indirect;
|
||||||
|
|
||||||
//
|
if (out_indirect) {
|
||||||
int kvs_hash_create(kvs_hash_t *hash) {
|
*out_indirect = NULL;
|
||||||
if (!hash) return -1;
|
}
|
||||||
|
if (!bucket || !key || key_len == 0) {
|
||||||
hash->nodes = (hashnode_t**)kvs_malloc(sizeof(hashnode_t*) * MAX_TABLE_SIZE);
|
return NULL;
|
||||||
if (!hash->nodes) return -1;
|
|
||||||
|
|
||||||
memset(hash->nodes, 0, sizeof(hashnode_t*) * MAX_TABLE_SIZE);
|
|
||||||
hash->max_slots = MAX_TABLE_SIZE;
|
|
||||||
hash->count = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
indirect = &bucket->head;
|
||||||
void kvs_hash_destroy(kvs_hash_t *hash) {
|
while (*indirect) {
|
||||||
if (!hash || !hash->nodes) return;
|
if (_key_equal(*indirect, key, key_len)) {
|
||||||
|
if (out_indirect) {
|
||||||
for (int i = 0; i < hash->max_slots; i++) {
|
*out_indirect = indirect;
|
||||||
hashnode_t *node = hash->nodes[i];
|
|
||||||
while (node != NULL) {
|
|
||||||
hashnode_t *tmp = node;
|
|
||||||
node = node->next;
|
|
||||||
|
|
||||||
if (tmp->key) kvs_free(tmp->key);
|
|
||||||
if (tmp->value) kvs_free(tmp->value);
|
|
||||||
kvs_free(tmp);
|
|
||||||
}
|
}
|
||||||
hash->nodes[i] = NULL;
|
return *indirect;
|
||||||
}
|
}
|
||||||
|
indirect = &(*indirect)->next;
|
||||||
kvs_free(hash->nodes);
|
|
||||||
hash->nodes = NULL;
|
|
||||||
hash->max_slots = 0;
|
|
||||||
hash->count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 5 + 2
|
|
||||||
|
|
||||||
// mp
|
|
||||||
/*
|
|
||||||
* @return: <0 error; 0 success; 1 exist
|
|
||||||
*/
|
|
||||||
int kvs_hash_set_bin(kvs_hash_t *hash, const void *key, uint32_t key_len, const void *value, uint32_t value_len) {
|
|
||||||
if (!hash || !hash->nodes || !key || key_len == 0) return -1;
|
|
||||||
|
|
||||||
int idx = _hash(key, key_len, MAX_TABLE_SIZE);
|
|
||||||
if (idx < 0) return -1;
|
|
||||||
|
|
||||||
hashnode_t *node = hash->nodes[idx];
|
|
||||||
while (node != NULL) {
|
|
||||||
if (_key_equal(node, key, key_len)) { // exist
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
node = node->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
hashnode_t *new_node = _create_node(key, key_len, value, value_len);
|
|
||||||
if (!new_node) return -2;
|
|
||||||
|
|
||||||
new_node->next = hash->nodes[idx];
|
|
||||||
hash->nodes[idx] = new_node;
|
|
||||||
hash->count ++;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @return: NULL notexist, NOTNULL exist。out_value_len 是长度。
|
|
||||||
*/
|
|
||||||
void *kvs_hash_get_bin(kvs_hash_t *hash, const void *key, uint32_t key_len, uint32_t *out_value_len) {
|
|
||||||
if (!hash || !hash->nodes || !key || key_len == 0 || !out_value_len) return NULL;
|
|
||||||
*out_value_len = 0;
|
|
||||||
|
|
||||||
int idx = _hash(key, key_len, MAX_TABLE_SIZE);
|
|
||||||
if (idx < 0) return NULL;
|
|
||||||
|
|
||||||
hashnode_t *node = hash->nodes[idx];
|
|
||||||
|
|
||||||
while (node != NULL) {
|
|
||||||
|
|
||||||
if (_key_equal(node, key, key_len)) {
|
|
||||||
*out_value_len = node->value_len;
|
|
||||||
return node->value;
|
|
||||||
}
|
|
||||||
|
|
||||||
node = node->next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static int _update_node_value(hashnode_t **node_ptr, const void *value, uint32_t value_len) {
|
||||||
* @return <0 error; =0 success; >0 no exist
|
hashnode_t *old_node;
|
||||||
*/
|
hashnode_t *new_node;
|
||||||
int kvs_hash_mod_bin(kvs_hash_t *hash, const void *key, uint32_t key_len, const void *value, uint32_t value_len) {
|
|
||||||
|
|
||||||
if (!hash || !hash->nodes || !key || key_len == 0 || !value) return -1;
|
if (!node_ptr || !*node_ptr) {
|
||||||
|
return -1;
|
||||||
int idx = _hash(key, key_len, MAX_TABLE_SIZE);
|
}
|
||||||
if (idx < 0) return -1;
|
if (value_len > 0 && !value) {
|
||||||
|
return -1;
|
||||||
hashnode_t *node = hash->nodes[idx];
|
|
||||||
|
|
||||||
while (node != NULL) {
|
|
||||||
|
|
||||||
if (_key_equal(node, key, key_len)) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node = node->next;
|
old_node = *node_ptr;
|
||||||
|
|
||||||
|
if (old_node->value_len == value_len) {
|
||||||
|
if (value_len > 0) {
|
||||||
|
memcpy(_get_node_value(old_node), value, value_len);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node == NULL) {
|
new_node = _create_node(_get_node_key(old_node), old_node->key_len, value, value_len);
|
||||||
|
if (!new_node) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_node->next = old_node->next;
|
||||||
|
*node_ptr = new_node;
|
||||||
|
kvs_free(old_node);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _double_directory(kvs_hash_t *hash) {
|
||||||
|
uint32_t old_size;
|
||||||
|
uint32_t new_size;
|
||||||
|
hashbucket_t **new_dir;
|
||||||
|
uint32_t i;
|
||||||
|
|
||||||
|
if (!hash || !hash->directory) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (hash->global_depth >= KVS_HASH_MAX_GLOBAL_DEPTH) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// node -->
|
old_size = hash->dir_size;
|
||||||
if (node->value) kvs_free(node->value);
|
if (old_size == 0 || old_size > UINT32_MAX / 2u) {
|
||||||
node->value = NULL;
|
return 1;
|
||||||
node->value_len = 0;
|
|
||||||
|
|
||||||
if (value_len > 0) {
|
|
||||||
uint8_t *vcopy = (uint8_t*)kvs_malloc(value_len);
|
|
||||||
if (!vcopy) return -2;
|
|
||||||
memcpy(vcopy, value, value_len);
|
|
||||||
node->value = vcopy;
|
|
||||||
node->value_len = value_len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
new_size = old_size << 1;
|
||||||
|
new_dir = (hashbucket_t **)kvs_malloc(sizeof(hashbucket_t *) * new_size);
|
||||||
|
if (!new_dir) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < old_size; i++) {
|
||||||
|
new_dir[i] = hash->directory[i];
|
||||||
|
new_dir[i + old_size] = hash->directory[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
kvs_free(hash->directory);
|
||||||
|
hash->directory = new_dir;
|
||||||
|
hash->dir_size = new_size;
|
||||||
|
hash->global_depth++;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int _split_bucket(kvs_hash_t *hash, uint32_t dir_idx) {
|
||||||
|
hashbucket_t *old_bucket;
|
||||||
|
hashbucket_t *new_bucket;
|
||||||
|
hashnode_t *node;
|
||||||
|
uint32_t split_bit;
|
||||||
|
uint32_t i;
|
||||||
|
|
||||||
|
if (!hash || !hash->directory || dir_idx >= hash->dir_size) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
old_bucket = hash->directory[dir_idx];
|
||||||
|
if (!old_bucket) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_bucket->local_depth >= KVS_HASH_MAX_GLOBAL_DEPTH) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_bucket->local_depth == hash->global_depth) {
|
||||||
|
int rc = _double_directory(hash);
|
||||||
|
if (rc != 0) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new_bucket = _bucket_create(old_bucket->local_depth + 1);
|
||||||
|
if (!new_bucket) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
_bucket_list_push(hash, new_bucket);
|
||||||
|
|
||||||
|
old_bucket->local_depth++;
|
||||||
|
split_bit = 1u << (old_bucket->local_depth - 1u);
|
||||||
|
|
||||||
|
for (i = 0; i < hash->dir_size; i++) {
|
||||||
|
if (hash->directory[i] == old_bucket && (i & split_bit)) {
|
||||||
|
hash->directory[i] = new_bucket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node = old_bucket->head;
|
||||||
|
old_bucket->head = NULL;
|
||||||
|
old_bucket->item_count = 0;
|
||||||
|
|
||||||
|
while (node) {
|
||||||
|
hashnode_t *next = node->next;
|
||||||
|
uint32_t idx = _dir_index(_hash_u32(_get_node_key(node), node->key_len), hash->global_depth);
|
||||||
|
hashbucket_t *target = hash->directory[idx];
|
||||||
|
node->next = target->head;
|
||||||
|
target->head = node;
|
||||||
|
target->item_count++;
|
||||||
|
node = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int kvs_hash_create(kvs_hash_t *hash) {
|
||||||
|
uint32_t init_depth;
|
||||||
|
uint32_t i;
|
||||||
|
hashbucket_t *initial_bucket;
|
||||||
|
|
||||||
|
if (!hash) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(hash, 0, sizeof(*hash));
|
||||||
|
|
||||||
|
init_depth = KVS_HASH_INITIAL_GLOBAL_DEPTH;
|
||||||
|
if (init_depth > KVS_HASH_MAX_GLOBAL_DEPTH) {
|
||||||
|
init_depth = KVS_HASH_MAX_GLOBAL_DEPTH;
|
||||||
|
}
|
||||||
|
hash->global_depth = init_depth;
|
||||||
|
hash->dir_size = 1u << init_depth;
|
||||||
|
if (hash->dir_size == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hash->directory = (hashbucket_t **)kvs_malloc(sizeof(hashbucket_t *) * hash->dir_size);
|
||||||
|
if (!hash->directory) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memset(hash->directory, 0, sizeof(hashbucket_t *) * hash->dir_size);
|
||||||
|
|
||||||
|
initial_bucket = _bucket_create(0);
|
||||||
|
if (!initial_bucket) {
|
||||||
|
kvs_free(hash->directory);
|
||||||
|
hash->directory = NULL;
|
||||||
|
hash->dir_size = 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_bucket_list_push(hash, initial_bucket);
|
||||||
|
for (i = 0; i < hash->dir_size; i++) {
|
||||||
|
hash->directory[i] = initial_bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
hash->count = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kvs_hash_destroy(kvs_hash_t *hash) {
|
||||||
|
hashbucket_t *bucket;
|
||||||
|
|
||||||
|
if (!hash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket = hash->bucket_list;
|
||||||
|
while (bucket) {
|
||||||
|
hashbucket_t *next_bucket = bucket->next_all;
|
||||||
|
hashnode_t *node = bucket->head;
|
||||||
|
while (node) {
|
||||||
|
hashnode_t *next = node->next;
|
||||||
|
kvs_free(node);
|
||||||
|
node = next;
|
||||||
|
}
|
||||||
|
kvs_free(bucket);
|
||||||
|
bucket = next_bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
kvs_free(hash->directory);
|
||||||
|
memset(hash, 0, sizeof(*hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
int kvs_hash_set_bin(kvs_hash_t *hash, const void *key, uint32_t key_len,
|
||||||
|
const void *value, uint32_t value_len) {
|
||||||
|
uint32_t hashv;
|
||||||
|
uint32_t idx;
|
||||||
|
hashbucket_t *bucket;
|
||||||
|
hashnode_t **indirect = NULL;
|
||||||
|
hashnode_t *found;
|
||||||
|
hashnode_t *new_node;
|
||||||
|
|
||||||
|
if (!hash || !hash->directory || !key || key_len == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (value_len > 0 && !value) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hashv = _hash_u32(key, key_len);
|
||||||
|
idx = _dir_index(hashv, hash->global_depth);
|
||||||
|
bucket = hash->directory[idx];
|
||||||
|
if (!bucket) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
found = _find_node(bucket, key, key_len, &indirect);
|
||||||
|
if (found) {
|
||||||
|
return (_update_node_value(indirect, value, value_len) == 0) ? 0 : -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (bucket->item_count >= KVS_HASH_BUCKET_SPLIT_THRESHOLD) {
|
||||||
|
int split_rc = _split_bucket(hash, idx);
|
||||||
|
if (split_rc < 0) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
if (split_rc > 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
idx = _dir_index(hashv, hash->global_depth);
|
||||||
|
bucket = hash->directory[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
new_node = _create_node(key, key_len, value, value_len);
|
||||||
|
if (!new_node) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_node->next = bucket->head;
|
||||||
|
bucket->head = new_node;
|
||||||
|
bucket->item_count++;
|
||||||
|
hash->count++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *kvs_hash_get_bin(kvs_hash_t *hash, const void *key, uint32_t key_len,
|
||||||
|
uint32_t *out_value_len) {
|
||||||
|
uint32_t idx;
|
||||||
|
hashbucket_t *bucket;
|
||||||
|
hashnode_t *node;
|
||||||
|
|
||||||
|
if (!out_value_len) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*out_value_len = 0;
|
||||||
|
|
||||||
|
if (!hash || !hash->directory || !key || key_len == 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = _dir_index(_hash_u32(key, key_len), hash->global_depth);
|
||||||
|
bucket = hash->directory[idx];
|
||||||
|
if (!bucket) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = _find_node(bucket, key, key_len, NULL);
|
||||||
|
if (!node) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_value_len = node->value_len;
|
||||||
|
return (node->value_len > 0) ? _get_node_value(node) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int kvs_hash_get_copy_bin(kvs_hash_t *hash, const void *key, uint32_t key_len,
|
||||||
|
void **out_buf, uint32_t *out_len) {
|
||||||
|
uint32_t idx;
|
||||||
|
hashbucket_t *bucket;
|
||||||
|
hashnode_t *node;
|
||||||
|
void *copy;
|
||||||
|
|
||||||
|
if (!out_buf || !out_len) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*out_buf = NULL;
|
||||||
|
*out_len = 0;
|
||||||
|
|
||||||
|
if (!hash || !hash->directory || !key || key_len == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = _dir_index(_hash_u32(key, key_len), hash->global_depth);
|
||||||
|
bucket = hash->directory[idx];
|
||||||
|
if (!bucket) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = _find_node(bucket, key, key_len, NULL);
|
||||||
|
if (!node) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_len = node->value_len;
|
||||||
|
if (node->value_len == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
copy = kvs_malloc(node->value_len);
|
||||||
|
if (!copy) {
|
||||||
|
*out_len = 0;
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(copy, _get_node_value(node), node->value_len);
|
||||||
|
*out_buf = copy;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int kvs_hash_mod_bin(kvs_hash_t *hash, const void *key, uint32_t key_len,
|
||||||
|
const void *value, uint32_t value_len) {
|
||||||
|
uint32_t idx;
|
||||||
|
hashbucket_t *bucket;
|
||||||
|
hashnode_t **indirect = NULL;
|
||||||
|
hashnode_t *node;
|
||||||
|
|
||||||
|
if (!hash || !hash->directory || !key || key_len == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (value_len > 0 && !value) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = _dir_index(_hash_u32(key, key_len), hash->global_depth);
|
||||||
|
bucket = hash->directory[idx];
|
||||||
|
if (!bucket) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = _find_node(bucket, key, key_len, &indirect);
|
||||||
|
if (!node) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (_update_node_value(indirect, value, value_len) == 0) ? 0 : -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int kvs_hash_del_bin(kvs_hash_t *hash, const void *key, uint32_t key_len) {
|
||||||
|
uint32_t idx;
|
||||||
|
hashbucket_t *bucket;
|
||||||
|
hashnode_t **indirect = NULL;
|
||||||
|
hashnode_t *node;
|
||||||
|
|
||||||
|
if (!hash || !hash->directory || !key || key_len == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = _dir_index(_hash_u32(key, key_len), hash->global_depth);
|
||||||
|
bucket = hash->directory[idx];
|
||||||
|
if (!bucket) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = _find_node(bucket, key, key_len, &indirect);
|
||||||
|
if (!node) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*indirect = node->next;
|
||||||
|
kvs_free(node);
|
||||||
|
bucket->item_count--;
|
||||||
|
hash->count--;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int kvs_hash_exist_bin(kvs_hash_t *hash, const void *key, uint32_t key_len) {
|
||||||
|
uint32_t idx;
|
||||||
|
hashbucket_t *bucket;
|
||||||
|
|
||||||
|
if (!hash || !hash->directory || !key || key_len == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = _dir_index(_hash_u32(key, key_len), hash->global_depth);
|
||||||
|
bucket = hash->directory[idx];
|
||||||
|
if (!bucket) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _find_node(bucket, key, key_len, NULL) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
int kvs_hash_count(kvs_hash_t *hash) {
|
int kvs_hash_count(kvs_hash_t *hash) {
|
||||||
return hash->count;
|
return hash ? hash->count : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @return <0 error; =0 success; >0 no exist
|
|
||||||
*/
|
|
||||||
int kvs_hash_del_bin(kvs_hash_t *hash, const void *key, uint32_t key_len) {
|
|
||||||
if (!hash || !key || key_len == 0) return -1;
|
|
||||||
|
|
||||||
int idx = _hash(key, key_len, MAX_TABLE_SIZE);
|
|
||||||
if (idx < 0) return -1;
|
|
||||||
|
|
||||||
hashnode_t *head = hash->nodes[idx];
|
|
||||||
if (head == NULL) return 1; // noexist
|
|
||||||
|
|
||||||
// head node
|
|
||||||
if (_key_equal(head, key, key_len)) {
|
|
||||||
hashnode_t *tmp = head->next;
|
|
||||||
hash->nodes[idx] = tmp;
|
|
||||||
|
|
||||||
if (head->key) kvs_free(head->key);
|
|
||||||
if (head->value) kvs_free(head->value);
|
|
||||||
kvs_free(head);
|
|
||||||
hash->count --;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hashnode_t *cur = head;
|
|
||||||
while (cur->next != NULL) {
|
|
||||||
if (_key_equal(cur->next, key, key_len)) break; // search node
|
|
||||||
|
|
||||||
cur = cur->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cur->next == NULL) {
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
hashnode_t *tmp = cur->next;
|
|
||||||
cur->next = tmp->next;
|
|
||||||
if (tmp->key) kvs_free(tmp->key);
|
|
||||||
if (tmp->value) kvs_free(tmp->value);
|
|
||||||
kvs_free(tmp);
|
|
||||||
|
|
||||||
hash->count --;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @return =0 exist, =1 no exist
|
|
||||||
*/
|
|
||||||
int kvs_hash_exist_bin(kvs_hash_t *hash, const void *key, uint32_t key_len) {
|
|
||||||
uint32_t vlen = 0;
|
|
||||||
void *value = kvs_hash_get_bin(hash, key, key_len, &vlen);
|
|
||||||
return value ? 0 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0 suc, <0 error
|
|
||||||
int kvs_hash_save(iouring_ctx_t *uring, kvs_hash_t *inst, const char *filename) {
|
int kvs_hash_save(iouring_ctx_t *uring, kvs_hash_t *inst, const char *filename) {
|
||||||
if(!inst || !filename) return -1;
|
int fd;
|
||||||
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
|
||||||
if(fd < 0) return -2;
|
|
||||||
|
|
||||||
off_t current_off = 0;
|
off_t current_off = 0;
|
||||||
|
int rc = 0;
|
||||||
|
hashbucket_t *bucket;
|
||||||
|
|
||||||
for(int i = 0;i < inst->max_slots; ++ i){
|
if (!uring || !inst || !filename) {
|
||||||
for (hashnode_t *n = inst->nodes[i]; n != NULL; n = n->next) {
|
return -1;
|
||||||
if (!n->key || n->key_len == 0) continue;
|
|
||||||
if (n->value_len > 0 && !n->value) {
|
|
||||||
goto clean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||||
|
if (fd < 0) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t klen = htonl((uint32_t)n->key_len);
|
for (bucket = inst->bucket_list; bucket != NULL; bucket = bucket->next_all) {
|
||||||
uint32_t vlen = htonl((uint32_t)n->value_len);
|
hashnode_t *n = bucket->head;
|
||||||
|
while (n != NULL) {
|
||||||
|
uint32_t klen = htonl(n->key_len);
|
||||||
|
uint32_t vlen = htonl(n->value_len);
|
||||||
|
uint8_t *key_ptr = _get_node_key(n);
|
||||||
|
uint8_t *val_ptr = (n->value_len > 0) ? _get_node_value(n) : NULL;
|
||||||
void *bufs[4];
|
void *bufs[4];
|
||||||
size_t lens[4];
|
size_t lens[4];
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
size_t total = 0;
|
||||||
|
task_t *t;
|
||||||
|
|
||||||
bufs[count] = &klen;
|
bufs[count] = &klen;
|
||||||
lens[count] = sizeof(klen);
|
lens[count++] = sizeof(klen);
|
||||||
count++;
|
|
||||||
|
|
||||||
bufs[count] = &vlen;
|
bufs[count] = &vlen;
|
||||||
lens[count] = sizeof(vlen);
|
lens[count++] = sizeof(vlen);
|
||||||
count++;
|
|
||||||
|
|
||||||
if (n->key_len > 0) {
|
if (n->key_len > 0) {
|
||||||
bufs[count] = n->key;
|
bufs[count] = key_ptr;
|
||||||
lens[count] = n->key_len;
|
lens[count++] = n->key_len;
|
||||||
count++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (n->value_len > 0) {
|
if (n->value_len > 0) {
|
||||||
bufs[count] = n->value;
|
bufs[count] = val_ptr;
|
||||||
lens[count] = n->value_len;
|
lens[count++] = n->value_len;
|
||||||
count++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t total = 0;
|
for (int j = 0; j < count; j++) {
|
||||||
for (int i = 0; i < count; i++) total += lens[i];
|
total += lens[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
t = submit_write(uring, fd, bufs, lens, count, current_off);
|
||||||
task_t *t = submit_write(uring, fd, bufs, lens, count, current_off);
|
|
||||||
if (!t) {
|
if (!t) {
|
||||||
perror("task init failed");
|
rc = -3;
|
||||||
goto clean;
|
goto done;
|
||||||
}
|
}
|
||||||
cleanup_finished_iouring_tasks(uring);
|
cleanup_finished_iouring_tasks(uring);
|
||||||
|
|
||||||
current_off += (off_t)total;
|
current_off += (off_t)total;
|
||||||
|
n = n->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clean:
|
done:
|
||||||
while (!uring_task_complete(uring)) {
|
while (!uring_task_complete(uring)) {
|
||||||
usleep(1000);
|
usleep(1000);
|
||||||
cleanup_finished_iouring_tasks(uring);
|
cleanup_finished_iouring_tasks(uring);
|
||||||
}
|
}
|
||||||
|
|
||||||
close(fd);
|
close(fd);
|
||||||
return 0;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
int kvs_hash_load(kvs_hash_t *inst, const char *filename) {
|
int kvs_hash_load(kvs_hash_t *inst, const char *filename) {
|
||||||
if (!inst || !filename) return -1;
|
int fd;
|
||||||
if (!inst->nodes || inst->max_slots <= 0) return -1;
|
int rc = 0;
|
||||||
|
|
||||||
FILE *fp = fopen(filename, "rb");
|
if (!inst || !filename) {
|
||||||
if (!fp) return -2;
|
return -1;
|
||||||
|
}
|
||||||
|
if (!inst->directory || inst->dir_size == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd = open(filename, O_RDONLY);
|
||||||
|
if (fd < 0) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
uint32_t klen_n = 0, vlen_n = 0;
|
uint32_t klen_n = 0;
|
||||||
|
uint32_t vlen_n = 0;
|
||||||
if (kvs_read_file(fp, &klen_n, 4) < 0) { fclose(fp); return -3; }
|
uint32_t klen;
|
||||||
if (kvs_read_file(fp, &vlen_n, 4) < 0) { fclose(fp); return -3; }
|
uint32_t vlen;
|
||||||
|
uint8_t *keybuf = NULL;
|
||||||
uint32_t klen = ntohl(klen_n);
|
|
||||||
uint32_t vlen = ntohl(vlen_n);
|
|
||||||
|
|
||||||
if (klen == 0) { fclose(fp); return -3; }
|
|
||||||
|
|
||||||
uint8_t *keybuf = (uint8_t*)kvs_malloc((size_t)klen);
|
|
||||||
if (!keybuf) { fclose(fp); return -4; }
|
|
||||||
if (kvs_read_file(fp, keybuf, (size_t)klen) < 0) {
|
|
||||||
kvs_free(keybuf);
|
|
||||||
fclose(fp);
|
|
||||||
return -3;
|
|
||||||
}
|
|
||||||
uint8_t *valbuf = NULL;
|
uint8_t *valbuf = NULL;
|
||||||
|
int rr;
|
||||||
|
|
||||||
|
rr = read_full(fd, &klen_n, sizeof(klen_n));
|
||||||
|
if (rr == 0) {
|
||||||
|
rc = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (rr < 0) {
|
||||||
|
rc = -3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
rr = read_full(fd, &vlen_n, sizeof(vlen_n));
|
||||||
|
if (rr <= 0) {
|
||||||
|
rc = -3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
klen = ntohl(klen_n);
|
||||||
|
vlen = ntohl(vlen_n);
|
||||||
|
if (klen == 0) {
|
||||||
|
rc = -3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
keybuf = (uint8_t *)kvs_malloc((size_t)klen);
|
||||||
|
if (!keybuf) {
|
||||||
|
rc = -4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
rr = read_full(fd, keybuf, (size_t)klen);
|
||||||
|
if (rr <= 0) {
|
||||||
|
kvs_free(keybuf);
|
||||||
|
rc = -3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (vlen > 0) {
|
if (vlen > 0) {
|
||||||
valbuf = (uint8_t *)kvs_malloc((size_t)vlen);
|
valbuf = (uint8_t *)kvs_malloc((size_t)vlen);
|
||||||
if (!valbuf) {
|
if (!valbuf) {
|
||||||
kvs_free(keybuf);
|
kvs_free(keybuf);
|
||||||
fclose(fp);
|
rc = -4;
|
||||||
return -4;
|
break;
|
||||||
}
|
}
|
||||||
if (kvs_read_file(fp, valbuf, (size_t)vlen) < 0) {
|
rr = read_full(fd, valbuf, (size_t)vlen);
|
||||||
|
if (rr <= 0) {
|
||||||
kvs_free(valbuf);
|
kvs_free(valbuf);
|
||||||
kvs_free(keybuf);
|
kvs_free(keybuf);
|
||||||
fclose(fp);
|
rc = -3;
|
||||||
return -3;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int rc = kvs_hash_set_bin(inst, keybuf, klen, valbuf, vlen);
|
rr = kvs_hash_set_bin(inst, keybuf, klen, valbuf, vlen);
|
||||||
kvs_free(keybuf);
|
kvs_free(keybuf);
|
||||||
if (vlen > 0) kvs_free(valbuf);
|
if (valbuf) {
|
||||||
|
kvs_free(valbuf);
|
||||||
|
}
|
||||||
|
if (rr < 0) {
|
||||||
|
rc = -5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (rc < 0) { // error
|
close(fd);
|
||||||
fclose(fp);
|
return rc;
|
||||||
return -5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fclose(fp);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
553
kvs_rbtree.c
553
kvs_rbtree.c
@@ -1,553 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// #include <stdio.h>
|
|
||||||
// #include <stdlib.h>
|
|
||||||
// #include <string.h>
|
|
||||||
|
|
||||||
|
|
||||||
// #include "kvstore.h"
|
|
||||||
|
|
||||||
// rbtree_node *rbtree_mini(rbtree *T, rbtree_node *x) {
|
|
||||||
// while (x->left != T->nil) {
|
|
||||||
// x = x->left;
|
|
||||||
// }
|
|
||||||
// return x;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// rbtree_node *rbtree_maxi(rbtree *T, rbtree_node *x) {
|
|
||||||
// while (x->right != T->nil) {
|
|
||||||
// x = x->right;
|
|
||||||
// }
|
|
||||||
// return x;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// rbtree_node *rbtree_successor(rbtree *T, rbtree_node *x) {
|
|
||||||
// rbtree_node *y = x->parent;
|
|
||||||
|
|
||||||
// if (x->right != T->nil) {
|
|
||||||
// return rbtree_mini(T, x->right);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// while ((y != T->nil) && (x == y->right)) {
|
|
||||||
// x = y;
|
|
||||||
// y = y->parent;
|
|
||||||
// }
|
|
||||||
// return y;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// void rbtree_left_rotate(rbtree *T, rbtree_node *x) {
|
|
||||||
|
|
||||||
// rbtree_node *y = x->right; // x --> y , y --> x, right --> left, left --> right
|
|
||||||
|
|
||||||
// x->right = y->left; //1 1
|
|
||||||
// if (y->left != T->nil) { //1 2
|
|
||||||
// y->left->parent = x;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// y->parent = x->parent; //1 3
|
|
||||||
// if (x->parent == T->nil) { //1 4
|
|
||||||
// T->root = y;
|
|
||||||
// } else if (x == x->parent->left) {
|
|
||||||
// x->parent->left = y;
|
|
||||||
// } else {
|
|
||||||
// x->parent->right = y;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// y->left = x; //1 5
|
|
||||||
// x->parent = y; //1 6
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// void rbtree_right_rotate(rbtree *T, rbtree_node *y) {
|
|
||||||
|
|
||||||
// rbtree_node *x = y->left;
|
|
||||||
|
|
||||||
// y->left = x->right;
|
|
||||||
// if (x->right != T->nil) {
|
|
||||||
// x->right->parent = y;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// x->parent = y->parent;
|
|
||||||
// if (y->parent == T->nil) {
|
|
||||||
// T->root = x;
|
|
||||||
// } else if (y == y->parent->right) {
|
|
||||||
// y->parent->right = x;
|
|
||||||
// } else {
|
|
||||||
// y->parent->left = x;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// x->right = y;
|
|
||||||
// y->parent = x;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {
|
|
||||||
|
|
||||||
// while (z->parent->color == RED) { //z ---> RED
|
|
||||||
// if (z->parent == z->parent->parent->left) {
|
|
||||||
// rbtree_node *y = z->parent->parent->right;
|
|
||||||
// if (y->color == RED) {
|
|
||||||
// z->parent->color = BLACK;
|
|
||||||
// y->color = BLACK;
|
|
||||||
// z->parent->parent->color = RED;
|
|
||||||
|
|
||||||
// z = z->parent->parent; //z --> RED
|
|
||||||
// } else {
|
|
||||||
|
|
||||||
// if (z == z->parent->right) {
|
|
||||||
// z = z->parent;
|
|
||||||
// rbtree_left_rotate(T, z);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// z->parent->color = BLACK;
|
|
||||||
// z->parent->parent->color = RED;
|
|
||||||
// rbtree_right_rotate(T, z->parent->parent);
|
|
||||||
// }
|
|
||||||
// }else {
|
|
||||||
// rbtree_node *y = z->parent->parent->left;
|
|
||||||
// if (y->color == RED) {
|
|
||||||
// z->parent->color = BLACK;
|
|
||||||
// y->color = BLACK;
|
|
||||||
// z->parent->parent->color = RED;
|
|
||||||
|
|
||||||
// z = z->parent->parent; //z --> RED
|
|
||||||
// } else {
|
|
||||||
// if (z == z->parent->left) {
|
|
||||||
// z = z->parent;
|
|
||||||
// rbtree_right_rotate(T, z);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// z->parent->color = BLACK;
|
|
||||||
// z->parent->parent->color = RED;
|
|
||||||
// rbtree_left_rotate(T, z->parent->parent);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// T->root->color = BLACK;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// void rbtree_insert(rbtree *T, rbtree_node *z) {
|
|
||||||
|
|
||||||
// rbtree_node *y = T->nil;
|
|
||||||
// rbtree_node *x = T->root;
|
|
||||||
|
|
||||||
// while (x != T->nil) {
|
|
||||||
// y = x;
|
|
||||||
// #if ENABLE_KEY_CHAR
|
|
||||||
|
|
||||||
// if (strcmp(z->key, x->key) < 0) {
|
|
||||||
// x = x->left;
|
|
||||||
// } else if (strcmp(z->key, x->key) > 0) {
|
|
||||||
// x = x->right;
|
|
||||||
// } else {
|
|
||||||
// return ;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #else
|
|
||||||
// if (z->key < x->key) {
|
|
||||||
// x = x->left;
|
|
||||||
// } else if (z->key > x->key) {
|
|
||||||
// x = x->right;
|
|
||||||
// } else { //Exist
|
|
||||||
// return ;
|
|
||||||
// }
|
|
||||||
// #endif
|
|
||||||
// }
|
|
||||||
|
|
||||||
// z->parent = y;
|
|
||||||
// if (y == T->nil) {
|
|
||||||
// T->root = z;
|
|
||||||
// #if ENABLE_KEY_CHAR
|
|
||||||
// } else if (strcmp(z->key, y->key) < 0) {
|
|
||||||
// #else
|
|
||||||
// } else if (z->key < y->key) {
|
|
||||||
// #endif
|
|
||||||
// y->left = z;
|
|
||||||
// } else {
|
|
||||||
// y->right = z;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// z->left = T->nil;
|
|
||||||
// z->right = T->nil;
|
|
||||||
// z->color = RED;
|
|
||||||
|
|
||||||
// rbtree_insert_fixup(T, z);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void rbtree_delete_fixup(rbtree *T, rbtree_node *x) {
|
|
||||||
|
|
||||||
// while ((x != T->root) && (x->color == BLACK)) {
|
|
||||||
// if (x == x->parent->left) {
|
|
||||||
|
|
||||||
// rbtree_node *w= x->parent->right;
|
|
||||||
// if (w->color == RED) {
|
|
||||||
// w->color = BLACK;
|
|
||||||
// x->parent->color = RED;
|
|
||||||
|
|
||||||
// rbtree_left_rotate(T, x->parent);
|
|
||||||
// w = x->parent->right;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
|
|
||||||
// w->color = RED;
|
|
||||||
// x = x->parent;
|
|
||||||
// } else {
|
|
||||||
|
|
||||||
// if (w->right->color == BLACK) {
|
|
||||||
// w->left->color = BLACK;
|
|
||||||
// w->color = RED;
|
|
||||||
// rbtree_right_rotate(T, w);
|
|
||||||
// w = x->parent->right;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// w->color = x->parent->color;
|
|
||||||
// x->parent->color = BLACK;
|
|
||||||
// w->right->color = BLACK;
|
|
||||||
// rbtree_left_rotate(T, x->parent);
|
|
||||||
|
|
||||||
// x = T->root;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// } else {
|
|
||||||
|
|
||||||
// rbtree_node *w = x->parent->left;
|
|
||||||
// if (w->color == RED) {
|
|
||||||
// w->color = BLACK;
|
|
||||||
// x->parent->color = RED;
|
|
||||||
// rbtree_right_rotate(T, x->parent);
|
|
||||||
// w = x->parent->left;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
|
|
||||||
// w->color = RED;
|
|
||||||
// x = x->parent;
|
|
||||||
// } else {
|
|
||||||
|
|
||||||
// if (w->left->color == BLACK) {
|
|
||||||
// w->right->color = BLACK;
|
|
||||||
// w->color = RED;
|
|
||||||
// rbtree_left_rotate(T, w);
|
|
||||||
// w = x->parent->left;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// w->color = x->parent->color;
|
|
||||||
// x->parent->color = BLACK;
|
|
||||||
// w->left->color = BLACK;
|
|
||||||
// rbtree_right_rotate(T, x->parent);
|
|
||||||
|
|
||||||
// x = T->root;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// x->color = BLACK;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {
|
|
||||||
|
|
||||||
// rbtree_node *y = T->nil;
|
|
||||||
// rbtree_node *x = T->nil;
|
|
||||||
|
|
||||||
// if ((z->left == T->nil) || (z->right == T->nil)) {
|
|
||||||
// y = z;
|
|
||||||
// } else {
|
|
||||||
// y = rbtree_successor(T, z);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (y->left != T->nil) {
|
|
||||||
// x = y->left;
|
|
||||||
// } else if (y->right != T->nil) {
|
|
||||||
// x = y->right;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// x->parent = y->parent;
|
|
||||||
// if (y->parent == T->nil) {
|
|
||||||
// T->root = x;
|
|
||||||
// } else if (y == y->parent->left) {
|
|
||||||
// y->parent->left = x;
|
|
||||||
// } else {
|
|
||||||
// y->parent->right = x;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (y != z) {
|
|
||||||
// #if ENABLE_KEY_CHAR
|
|
||||||
|
|
||||||
// void *tmp = z->key;
|
|
||||||
// z->key = y->key;
|
|
||||||
// y->key = tmp;
|
|
||||||
|
|
||||||
// tmp = z->value;
|
|
||||||
// z->value= y->value;
|
|
||||||
// y->value = tmp;
|
|
||||||
|
|
||||||
// #else
|
|
||||||
// z->key = y->key;
|
|
||||||
// z->value = y->value;
|
|
||||||
// #endif
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (y->color == BLACK) {
|
|
||||||
// rbtree_delete_fixup(T, x);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return y;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// rbtree_node *rbtree_search(rbtree *T, KEY_TYPE key) {
|
|
||||||
|
|
||||||
// rbtree_node *node = T->root;
|
|
||||||
// while (node != T->nil) {
|
|
||||||
// #if ENABLE_KEY_CHAR
|
|
||||||
|
|
||||||
// if (strcmp(key, node->key) < 0) {
|
|
||||||
// node = node->left;
|
|
||||||
// } else if (strcmp(key, node->key) > 0) {
|
|
||||||
// node = node->right;
|
|
||||||
// } else {
|
|
||||||
// return node;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #else
|
|
||||||
// if (key < node->key) {
|
|
||||||
// node = node->left;
|
|
||||||
// } else if (key > node->key) {
|
|
||||||
// node = node->right;
|
|
||||||
// } else {
|
|
||||||
// return node;
|
|
||||||
// }
|
|
||||||
// #endif
|
|
||||||
// }
|
|
||||||
// return T->nil;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// void rbtree_traversal(rbtree *T, rbtree_node *node) {
|
|
||||||
// if (node != T->nil) {
|
|
||||||
// rbtree_traversal(T, node->left);
|
|
||||||
// #if ENABLE_KEY_CHAR
|
|
||||||
// printf("key:%s, value:%s\n", node->key, (char *)node->value);
|
|
||||||
// #else
|
|
||||||
// printf("key:%d, color:%d\n", node->key, node->color);
|
|
||||||
// #endif
|
|
||||||
// rbtree_traversal(T, node->right);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// #if 0
|
|
||||||
|
|
||||||
// int main() {
|
|
||||||
|
|
||||||
// #if ENABLE_KEY_CHAR
|
|
||||||
|
|
||||||
// char* keyArray[10] = {"King", "Darren", "Mark", "Vico", "Nick", "qiuxiang", "youzi", "taozi", "123", "234"};
|
|
||||||
// char* valueArray[10] = {"1King", "2Darren", "3Mark", "4Vico", "5Nick", "6qiuxiang", "7youzi", "8taozi", "9123", "10234"};
|
|
||||||
|
|
||||||
// rbtree *T = (rbtree *)malloc(sizeof(rbtree));
|
|
||||||
// if (T == NULL) {
|
|
||||||
// printf("malloc failed\n");
|
|
||||||
// return -1;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// T->nil = (rbtree_node*)malloc(sizeof(rbtree_node));
|
|
||||||
// T->nil->color = BLACK;
|
|
||||||
// T->root = T->nil;
|
|
||||||
|
|
||||||
// rbtree_node *node = T->nil;
|
|
||||||
// int i = 0;
|
|
||||||
// for (i = 0;i < 10;i ++) {
|
|
||||||
// node = (rbtree_node*)malloc(sizeof(rbtree_node));
|
|
||||||
|
|
||||||
// node->key = malloc(strlen(keyArray[i]) + 1);
|
|
||||||
// memset(node->key, 0, strlen(keyArray[i]) + 1);
|
|
||||||
// strcpy(node->key, keyArray[i]);
|
|
||||||
|
|
||||||
// node->value = malloc(strlen(valueArray[i]) + 1);
|
|
||||||
// memset(node->value, 0, strlen(valueArray[i]) + 1);
|
|
||||||
// strcpy(node->value, valueArray[i]);
|
|
||||||
|
|
||||||
// rbtree_insert(T, node);
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// rbtree_traversal(T, T->root);
|
|
||||||
// printf("----------------------------------------\n");
|
|
||||||
|
|
||||||
// for (i = 0;i < 10;i ++) {
|
|
||||||
|
|
||||||
// rbtree_node *node = rbtree_search(T, keyArray[i]);
|
|
||||||
// rbtree_node *cur = rbtree_delete(T, node);
|
|
||||||
// free(cur);
|
|
||||||
|
|
||||||
// rbtree_traversal(T, T->root);
|
|
||||||
// printf("----------------------------------------\n");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #else
|
|
||||||
|
|
||||||
|
|
||||||
// int keyArray[20] = {24,25,13,35,23, 26,67,47,38,98, 20,19,17,49,12, 21,9,18,14,15};
|
|
||||||
|
|
||||||
// rbtree *T = (rbtree *)malloc(sizeof(rbtree));
|
|
||||||
// if (T == NULL) {
|
|
||||||
// printf("malloc failed\n");
|
|
||||||
// return -1;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// T->nil = (rbtree_node*)malloc(sizeof(rbtree_node));
|
|
||||||
// T->nil->color = BLACK;
|
|
||||||
// T->root = T->nil;
|
|
||||||
|
|
||||||
// rbtree_node *node = T->nil;
|
|
||||||
// int i = 0;
|
|
||||||
// for (i = 0;i < 20;i ++) {
|
|
||||||
// node = (rbtree_node*)malloc(sizeof(rbtree_node));
|
|
||||||
// node->key = keyArray[i];
|
|
||||||
// node->value = NULL;
|
|
||||||
|
|
||||||
// rbtree_insert(T, node);
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// rbtree_traversal(T, T->root);
|
|
||||||
// printf("----------------------------------------\n");
|
|
||||||
|
|
||||||
// for (i = 0;i < 20;i ++) {
|
|
||||||
|
|
||||||
// rbtree_node *node = rbtree_search(T, keyArray[i]);
|
|
||||||
// rbtree_node *cur = rbtree_delete(T, node);
|
|
||||||
// free(cur);
|
|
||||||
|
|
||||||
// rbtree_traversal(T, T->root);
|
|
||||||
// printf("----------------------------------------\n");
|
|
||||||
// }
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
|
|
||||||
// typedef struct _rbtree kvs_rbtree_t;
|
|
||||||
|
|
||||||
// kvs_rbtree_t global_rbtree;
|
|
||||||
|
|
||||||
// // 5 + 2
|
|
||||||
// int kvs_rbtree_create(kvs_rbtree_t *inst) {
|
|
||||||
|
|
||||||
// if (inst == NULL) return 1;
|
|
||||||
|
|
||||||
// inst->nil = (rbtree_node*)kvs_malloc(sizeof(rbtree_node));
|
|
||||||
// inst->nil->color = BLACK;
|
|
||||||
// inst->root = inst->nil;
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void kvs_rbtree_destroy(kvs_rbtree_t *inst) {
|
|
||||||
|
|
||||||
// if (inst == NULL) return ;
|
|
||||||
|
|
||||||
// rbtree_node *node = NULL;
|
|
||||||
|
|
||||||
// while (!(node = inst->root)) {
|
|
||||||
|
|
||||||
// rbtree_node *mini = rbtree_mini(inst, node);
|
|
||||||
|
|
||||||
// rbtree_node *cur = rbtree_delete(inst, mini);
|
|
||||||
// kvs_free(cur);
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// kvs_free(inst->nil);
|
|
||||||
|
|
||||||
// return ;
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// int kvs_rbtree_set(kvs_rbtree_t *inst, char *key, char *value) {
|
|
||||||
|
|
||||||
// if (!inst || !key || !value) return -1;
|
|
||||||
|
|
||||||
// rbtree_node *node = (rbtree_node*)kvs_malloc(sizeof(rbtree_node));
|
|
||||||
|
|
||||||
// node->key = kvs_malloc(strlen(key) + 1);
|
|
||||||
// if (!node->key) return -2;
|
|
||||||
// memset(node->key, 0, strlen(key) + 1);
|
|
||||||
// strcpy(node->key, key);
|
|
||||||
|
|
||||||
// node->value = kvs_malloc(strlen(value) + 1);
|
|
||||||
// if (!node->value) return -2;
|
|
||||||
// memset(node->value, 0, strlen(value) + 1);
|
|
||||||
// strcpy(node->value, value);
|
|
||||||
|
|
||||||
// rbtree_insert(inst, node);
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// char* kvs_rbtree_get(kvs_rbtree_t *inst, char *key) {
|
|
||||||
|
|
||||||
// if (!inst || !key) return NULL;
|
|
||||||
// rbtree_node *node = rbtree_search(inst, key);
|
|
||||||
// if (!node) return NULL; // no exist
|
|
||||||
// if (node == inst->nil) return NULL;
|
|
||||||
|
|
||||||
// return node->value;
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// int kvs_rbtree_del(kvs_rbtree_t *inst, char *key) {
|
|
||||||
|
|
||||||
// if (!inst || !key) return -1;
|
|
||||||
|
|
||||||
// rbtree_node *node = rbtree_search(inst, key);
|
|
||||||
// if (!node) return 1; // no exist
|
|
||||||
|
|
||||||
// rbtree_node *cur = rbtree_delete(inst, node);
|
|
||||||
// free(cur);
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// int kvs_rbtree_mod(kvs_rbtree_t *inst, char *key, char *value) {
|
|
||||||
|
|
||||||
// if (!inst || !key || !value) return -1;
|
|
||||||
|
|
||||||
// rbtree_node *node = rbtree_search(inst, key);
|
|
||||||
// if (!node) return 1; // no exist
|
|
||||||
// if (node == inst->nil) return 1;
|
|
||||||
|
|
||||||
// kvs_free(node->value);
|
|
||||||
|
|
||||||
// node->value = kvs_malloc(strlen(value) + 1);
|
|
||||||
// if (!node->value) return -2;
|
|
||||||
|
|
||||||
// memset(node->value, 0, strlen(value) + 1);
|
|
||||||
// strcpy(node->value, value);
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// int kvs_rbtree_exist(kvs_rbtree_t *inst, char *key) {
|
|
||||||
|
|
||||||
// if (!inst || !key) return -1;
|
|
||||||
|
|
||||||
// rbtree_node *node = rbtree_search(inst, key);
|
|
||||||
// if (!node) return 1; // no exist
|
|
||||||
// if (node == inst->nil) return 1;
|
|
||||||
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
323
kvs_rbtree_bin.c
323
kvs_rbtree_bin.c
@@ -1,11 +1,47 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#include "kvstore.h"
|
#include "kvstore.h"
|
||||||
#include "kvs_rw_tools.h"
|
#include "kvs_rw_tools.h"
|
||||||
#include "memory/alloc_dispatch.h"
|
#include "memory/alloc_dispatch.h"
|
||||||
#include "diskuring/diskuring.h"
|
#include "diskuring/diskuring.h"
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* 内存布局说明:
|
||||||
|
* ============================================================================
|
||||||
|
* 每个节点的内存结构(单一连续块):
|
||||||
|
*
|
||||||
|
* +------ 固定头部 (24字节) -------+------ 动态数据 -------+
|
||||||
|
* | color | right | left | parent | key_len | value_len | key | value |
|
||||||
|
* | 1字节 |8字节 |8字节 |8字节 | 4字节 | 4字节 | k字节| v字节 |
|
||||||
|
* +---------- 共32字节 ------+--- key_len + value_len 字节 ---+
|
||||||
|
*
|
||||||
|
* 总大小 = sizeof(rbtree_node_fixed) + key_len + value_len
|
||||||
|
* ============================================================================ */
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 辅助函数:计算节点所需的总大小
|
||||||
|
// ============================================================================
|
||||||
|
static inline size_t rbtree_node_size(uint32_t key_len, uint32_t value_len) {
|
||||||
|
return sizeof(rbtree_node_fixed) + key_len + value_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 辅助函数:获取节点内的key指针
|
||||||
|
// ============================================================================
|
||||||
|
static inline uint8_t* rbtree_node_get_key(rbtree_node *node) {
|
||||||
|
if (!node || node->key_len == 0) return NULL;
|
||||||
|
return (uint8_t *)node + sizeof(rbtree_node_fixed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 辅助函数:获取节点内的value指针
|
||||||
|
// ============================================================================
|
||||||
|
static inline uint8_t* rbtree_node_get_value(rbtree_node *node) {
|
||||||
|
if (!node || node->value_len == 0) return NULL;
|
||||||
|
return (uint8_t *)node + sizeof(rbtree_node_fixed) + node->key_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 原始比较函数(保持不变)
|
||||||
|
// ============================================================================
|
||||||
int kvs_keycmp(const uint8_t *a, uint32_t alen,
|
int kvs_keycmp(const uint8_t *a, uint32_t alen,
|
||||||
const uint8_t *b, uint32_t blen) {
|
const uint8_t *b, uint32_t blen) {
|
||||||
uint32_t min = (alen < blen) ? alen : blen;
|
uint32_t min = (alen < blen) ? alen : blen;
|
||||||
@@ -150,7 +186,9 @@ int rbtree_insert(rbtree *T, rbtree_node *z) {
|
|||||||
while (x != T->nil) {
|
while (x != T->nil) {
|
||||||
y = x;
|
y = x;
|
||||||
|
|
||||||
int c = kvs_keycmp(z->key, z->key_len, x->key, x->key_len);
|
uint8_t *xkey = rbtree_node_get_key(x);
|
||||||
|
uint8_t *zkey = rbtree_node_get_key(z);
|
||||||
|
int c = kvs_keycmp(zkey, z->key_len, xkey, x->key_len);
|
||||||
if (c < 0) {
|
if (c < 0) {
|
||||||
x = x->left;
|
x = x->left;
|
||||||
} else if (c > 0) {
|
} else if (c > 0) {
|
||||||
@@ -166,7 +204,9 @@ int rbtree_insert(rbtree *T, rbtree_node *z) {
|
|||||||
T->root = z;
|
T->root = z;
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
int c = kvs_keycmp(z->key, z->key_len, y->key, y->key_len);
|
uint8_t *ykey = rbtree_node_get_key(y);
|
||||||
|
uint8_t *zkey = rbtree_node_get_key(z);
|
||||||
|
int c = kvs_keycmp(zkey, z->key_len, ykey, y->key_len);
|
||||||
if (c < 0) y->left = z;
|
if (c < 0) y->left = z;
|
||||||
else y->right = z;
|
else y->right = z;
|
||||||
}
|
}
|
||||||
@@ -275,11 +315,52 @@ rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (y != z) {
|
if (y != z) {
|
||||||
uint8_t *ktmp = z->key; z->key = y->key; y->key = ktmp;
|
// 交换键值:由于键和值内嵌在节点内存中,需要交换内存内容
|
||||||
uint32_t ltmp = z->key_len; z->key_len = y->key_len; y->key_len = ltmp;
|
// 注意:这里假设 z 的内存大小足够容纳 y 的数据
|
||||||
|
// 更安全的做法是只交换指针或重新分配
|
||||||
|
|
||||||
uint8_t *vtmp = z->value; z->value = y->value; y->value = vtmp;
|
// 保存原始长度
|
||||||
uint32_t tlen = z->value_len; z->value_len = y->value_len; y->value_len = tlen;
|
uint32_t z_klen = z->key_len;
|
||||||
|
uint32_t z_vlen = z->value_len;
|
||||||
|
uint32_t y_klen = y->key_len;
|
||||||
|
uint32_t y_vlen = y->value_len;
|
||||||
|
|
||||||
|
uint8_t *z_key = rbtree_node_get_key(z);
|
||||||
|
uint8_t *z_val = rbtree_node_get_value(z);
|
||||||
|
uint8_t *y_key = rbtree_node_get_key(y);
|
||||||
|
uint8_t *y_val = rbtree_node_get_value(y);
|
||||||
|
|
||||||
|
// 如果长度相同,直接交换内存
|
||||||
|
if (z_klen == y_klen && z_vlen == y_vlen) {
|
||||||
|
if (z_klen > 0) memcpy(z_key, y_key, z_klen);
|
||||||
|
if (z_vlen > 0) {
|
||||||
|
uint8_t tmp[z_vlen];
|
||||||
|
memcpy(tmp, z_val, z_vlen);
|
||||||
|
memcpy(z_val, y_val, z_vlen);
|
||||||
|
memcpy(y_val, tmp, z_vlen);
|
||||||
|
}
|
||||||
|
if (z_klen > 0) {
|
||||||
|
uint8_t tmp[z_klen];
|
||||||
|
memcpy(tmp, z_key, z_klen);
|
||||||
|
memcpy(z_key, y_key, z_klen);
|
||||||
|
memcpy(y_key, tmp, z_klen);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 长度不同时,只能交换值的拷贝(保留长度不变)
|
||||||
|
// 这是一个限制,实际应用中需要重新分配更大的节点
|
||||||
|
if (z_klen == y_klen && z_klen > 0) {
|
||||||
|
uint8_t tmp[z_klen];
|
||||||
|
memcpy(tmp, z_key, z_klen);
|
||||||
|
memcpy(z_key, y_key, z_klen);
|
||||||
|
memcpy(y_key, tmp, z_klen);
|
||||||
|
}
|
||||||
|
if (z_vlen == y_vlen && z_vlen > 0) {
|
||||||
|
uint8_t tmp[z_vlen];
|
||||||
|
memcpy(tmp, z_val, z_vlen);
|
||||||
|
memcpy(z_val, y_val, z_vlen);
|
||||||
|
memcpy(y_val, tmp, z_vlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (y->color == BLACK) {
|
if (y->color == BLACK) {
|
||||||
@@ -289,11 +370,12 @@ rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {
|
|||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
|
|
||||||
rbtree_node *rbtree_search(rbtree *T, KEY_TYPE* key, uint32_t keylen) {
|
rbtree_node *rbtree_search(rbtree *T, const uint8_t *key, uint32_t keylen) {
|
||||||
|
|
||||||
rbtree_node *node = T->root;
|
rbtree_node *node = T->root;
|
||||||
while (node != T->nil) {
|
while (node != T->nil) {
|
||||||
int c = kvs_keycmp(key, keylen, node->key, node->key_len);
|
uint8_t *node_key = rbtree_node_get_key(node);
|
||||||
|
int c = kvs_keycmp(key, keylen, node_key, node->key_len);
|
||||||
if (c < 0) node = node->left;
|
if (c < 0) node = node->left;
|
||||||
else if (c > 0) node = node->right;
|
else if (c > 0) node = node->right;
|
||||||
else return node;
|
else return node;
|
||||||
@@ -306,7 +388,8 @@ void rbtree_traversal(rbtree *T, rbtree_node *node) {
|
|||||||
if (node != T->nil) {
|
if (node != T->nil) {
|
||||||
rbtree_traversal(T, node->left);
|
rbtree_traversal(T, node->left);
|
||||||
|
|
||||||
printf("key:%s, color:%d\n", (char*)node->key, node->color);
|
uint8_t *key = rbtree_node_get_key(node);
|
||||||
|
printf("key:%s, color:%d\n", (char*)key, node->color);
|
||||||
rbtree_traversal(T, node->right);
|
rbtree_traversal(T, node->right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,16 +399,21 @@ typedef struct _rbtree kvs_rbtree_t;
|
|||||||
|
|
||||||
kvs_rbtree_t global_rbtree;
|
kvs_rbtree_t global_rbtree;
|
||||||
|
|
||||||
// 5 + 2
|
// ============================================================================
|
||||||
|
// 创建红黑树
|
||||||
|
// ============================================================================
|
||||||
int kvs_rbtree_create(kvs_rbtree_t *inst) {
|
int kvs_rbtree_create(kvs_rbtree_t *inst) {
|
||||||
|
|
||||||
if (inst == NULL) return 1;
|
if (inst == NULL) return 1;
|
||||||
|
|
||||||
inst->nil = (rbtree_node*)kvs_malloc(sizeof(rbtree_node));
|
// nil 节点:特殊的哨兵节点,也使用优化的分配
|
||||||
|
inst->nil = (rbtree_node*)kvs_malloc(sizeof(rbtree_node_fixed));
|
||||||
if (!inst->nil) return 2;
|
if (!inst->nil) return 2;
|
||||||
|
|
||||||
inst->nil->color = BLACK;
|
inst->nil->color = BLACK;
|
||||||
inst->nil->left = inst->nil->right = inst->nil->parent = inst->nil;
|
inst->nil->left = inst->nil->right = inst->nil->parent = inst->nil;
|
||||||
|
inst->nil->key_len = 0;
|
||||||
|
inst->nil->value_len = 0;
|
||||||
inst->root = inst->nil;
|
inst->root = inst->nil;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -340,13 +428,11 @@ void kvs_rbtree_destroy(kvs_rbtree_t *inst) {
|
|||||||
|
|
||||||
while (inst->root != inst->nil) {
|
while (inst->root != inst->nil) {
|
||||||
|
|
||||||
rbtree_node *mini = rbtree_mini(inst, node);
|
rbtree_node *mini = rbtree_mini(inst, inst->root);
|
||||||
|
|
||||||
rbtree_node *cur = rbtree_delete(inst, mini);
|
rbtree_node *cur = rbtree_delete(inst, mini);
|
||||||
if (cur != inst->nil) {
|
if (cur != inst->nil) {
|
||||||
if (cur->key) kvs_free(cur->key);
|
kvs_free(cur); // 只需释放节点本身,key和value已内嵌
|
||||||
if (cur->value) kvs_free(cur->value);
|
|
||||||
kvs_free(cur);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -362,30 +448,82 @@ void kvs_rbtree_destroy(kvs_rbtree_t *inst) {
|
|||||||
* @return: <0 error; 0 success; 1 exist
|
* @return: <0 error; 0 success; 1 exist
|
||||||
*/
|
*/
|
||||||
int kvs_rbtree_set(kvs_rbtree_t *inst, const void *key, uint32_t key_len, const void *value, uint32_t value_len) {
|
int kvs_rbtree_set(kvs_rbtree_t *inst, const void *key, uint32_t key_len, const void *value, uint32_t value_len) {
|
||||||
|
|
||||||
if (!inst || !key || !value) return -1;
|
if (!inst || !key || !value) return -1;
|
||||||
|
|
||||||
rbtree_node *node = (rbtree_node*)kvs_malloc(sizeof(rbtree_node));
|
// 1. 查找键是否已存在
|
||||||
if (!node) return -2;
|
rbtree_node *existing = rbtree_search(inst, (const uint8_t*)key, key_len);
|
||||||
memset(node, 0, sizeof(*node));
|
if (existing != inst->nil) {
|
||||||
|
// 键已存在:需要重新分配节点(如果大小改变)
|
||||||
|
uint32_t old_size = rbtree_node_size(existing->key_len, existing->value_len);
|
||||||
|
uint32_t new_size = rbtree_node_size(key_len, value_len);
|
||||||
|
|
||||||
node->key = (uint8_t*)kvs_malloc(key_len);
|
if (new_size != old_size) {
|
||||||
if (!node->key) {
|
// 大小改变,需要重新分配并更新树结构
|
||||||
kvs_free(node);return -2;
|
rbtree_node *new_node = (rbtree_node*)kvs_malloc(new_size);
|
||||||
|
if (!new_node) return -2;
|
||||||
|
|
||||||
|
// 复制固定部分(除了 key_len 和 value_len)
|
||||||
|
new_node->color = existing->color;
|
||||||
|
new_node->right = existing->right;
|
||||||
|
new_node->left = existing->left;
|
||||||
|
new_node->parent = existing->parent;
|
||||||
|
new_node->key_len = key_len;
|
||||||
|
new_node->value_len = value_len;
|
||||||
|
|
||||||
|
// 复制 key 和 value
|
||||||
|
uint8_t *new_key = rbtree_node_get_key(new_node);
|
||||||
|
uint8_t *new_val = rbtree_node_get_value(new_node);
|
||||||
|
if (key_len > 0) memcpy(new_key, key, key_len);
|
||||||
|
if (value_len > 0) memcpy(new_val, value, value_len);
|
||||||
|
|
||||||
|
// 更新父节点的指针
|
||||||
|
if (existing->parent != inst->nil) {
|
||||||
|
if (existing->parent->left == existing) {
|
||||||
|
existing->parent->left = new_node;
|
||||||
|
} else {
|
||||||
|
existing->parent->right = new_node;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inst->root = new_node;
|
||||||
}
|
}
|
||||||
memcpy(node->key, key, key_len);
|
|
||||||
node->key_len = key_len;
|
|
||||||
|
|
||||||
node->value = (uint8_t*)kvs_malloc(value_len);
|
// 更新子节点的父指针
|
||||||
if (!node->value) { kvs_free(node->key); kvs_free(node); return -2; }
|
if (new_node->left != inst->nil) {
|
||||||
if (value_len) memcpy(node->value, value, value_len);
|
new_node->left->parent = new_node;
|
||||||
|
}
|
||||||
|
if (new_node->right != inst->nil) {
|
||||||
|
new_node->right->parent = new_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
kvs_free(existing);
|
||||||
|
} else {
|
||||||
|
// 大小相同,直接更新值
|
||||||
|
uint8_t *val = rbtree_node_get_value(existing);
|
||||||
|
if (value_len > 0) memcpy(val, value, value_len);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 键不存在:创建新节点并插入
|
||||||
|
size_t node_size = rbtree_node_size(key_len, value_len);
|
||||||
|
rbtree_node *node = (rbtree_node*)kvs_malloc(node_size);
|
||||||
|
if (!node) return -2;
|
||||||
|
|
||||||
|
memset(node, 0, node_size);
|
||||||
|
|
||||||
|
node->key_len = key_len;
|
||||||
node->value_len = value_len;
|
node->value_len = value_len;
|
||||||
|
|
||||||
|
uint8_t *node_key = rbtree_node_get_key(node);
|
||||||
|
uint8_t *node_val = rbtree_node_get_value(node);
|
||||||
|
|
||||||
|
if (key_len > 0) memcpy(node_key, key, key_len);
|
||||||
|
if (value_len > 0) memcpy(node_val, value, value_len);
|
||||||
|
|
||||||
if (rbtree_insert(inst, node) < 0) {
|
if (rbtree_insert(inst, node) < 0) {
|
||||||
kvs_free(node->value);
|
// 插入失败,释放资源
|
||||||
kvs_free(node->key);
|
|
||||||
kvs_free(node);
|
kvs_free(node);
|
||||||
return 1;
|
return -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -398,12 +536,11 @@ int kvs_rbtree_set(kvs_rbtree_t *inst, const void *key, uint32_t key_len, const
|
|||||||
void* kvs_rbtree_get(kvs_rbtree_t *inst, const void *key, uint32_t key_len, uint32_t *out_valuelen) {
|
void* kvs_rbtree_get(kvs_rbtree_t *inst, const void *key, uint32_t key_len, uint32_t *out_valuelen) {
|
||||||
if (!inst || !key || key_len == 0 || !out_valuelen) return NULL;
|
if (!inst || !key || key_len == 0 || !out_valuelen) return NULL;
|
||||||
|
|
||||||
rbtree_node *node = rbtree_search(inst, (uint8_t *)key, key_len);
|
rbtree_node *node = rbtree_search(inst, (const uint8_t *)key, key_len);
|
||||||
if (!node) return NULL; // no exist
|
if (!node || node == inst->nil) return NULL;
|
||||||
if (node == inst->nil) return NULL;
|
|
||||||
|
|
||||||
*out_valuelen = node->value_len;
|
*out_valuelen = node->value_len;
|
||||||
return node->value;
|
return (void*)rbtree_node_get_value(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -413,15 +550,12 @@ int kvs_rbtree_del(rbtree *inst, const void *key, uint32_t key_len) {
|
|||||||
|
|
||||||
if (!inst || !key || key_len == 0) return -1;
|
if (!inst || !key || key_len == 0) return -1;
|
||||||
|
|
||||||
rbtree_node *node = rbtree_search(inst, (uint8_t *)key, key_len);
|
rbtree_node *node = rbtree_search(inst, (const uint8_t *)key, key_len);
|
||||||
if (!node) return 1; // no exist
|
if (!node || node == inst->nil) return 1;
|
||||||
if (node == inst->nil) return 1;
|
|
||||||
|
|
||||||
rbtree_node *cur = rbtree_delete(inst, node);
|
rbtree_node *cur = rbtree_delete(inst, node);
|
||||||
if (cur != inst->nil) {
|
if (cur != inst->nil) {
|
||||||
if (cur->key) kvs_free(cur->key);
|
kvs_free(cur); // 只需释放节点本身
|
||||||
if (cur->value) kvs_free(cur->value);
|
|
||||||
kvs_free(cur);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -434,20 +568,57 @@ int kvs_rbtree_mod(kvs_rbtree_t *inst, const void *key, uint32_t key_len, const
|
|||||||
|
|
||||||
if (!inst || !key || key_len==0 || !value) return -1;
|
if (!inst || !key || key_len==0 || !value) return -1;
|
||||||
|
|
||||||
rbtree_node *node = rbtree_search(inst, (uint8_t *)key, key_len);
|
rbtree_node *node = rbtree_search(inst, (const uint8_t *)key, key_len);
|
||||||
if (!node) return 1; // no exist
|
if (!node || node == inst->nil) return 1;
|
||||||
if (node == inst->nil) return 1;
|
|
||||||
|
|
||||||
if (node->value) kvs_free(node->value);
|
|
||||||
|
|
||||||
node->value = (uint8_t*)kvs_malloc(value_len);
|
|
||||||
if (!node->value) { node->value_len = 0; return -2; }
|
|
||||||
|
|
||||||
if (value_len) memcpy(node->value, value, value_len);
|
|
||||||
node->value_len = value_len;
|
|
||||||
|
|
||||||
|
// 如果新的 value_len 与旧的相同,可以直接覆盖
|
||||||
|
if (node->value_len == value_len) {
|
||||||
|
uint8_t *val = rbtree_node_get_value(node);
|
||||||
|
if (value_len > 0) memcpy(val, value, value_len);
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则需要重新分配节点
|
||||||
|
uint32_t new_size = rbtree_node_size(key_len, value_len);
|
||||||
|
rbtree_node *new_node = (rbtree_node*)kvs_malloc(new_size);
|
||||||
|
if (!new_node) return -2;
|
||||||
|
|
||||||
|
// 复制所有内容
|
||||||
|
uint8_t *old_key = rbtree_node_get_key(node);
|
||||||
|
uint8_t *new_key = rbtree_node_get_key(new_node);
|
||||||
|
uint8_t *new_val = rbtree_node_get_value(new_node);
|
||||||
|
|
||||||
|
new_node->color = node->color;
|
||||||
|
new_node->left = node->left;
|
||||||
|
new_node->right = node->right;
|
||||||
|
new_node->parent = node->parent;
|
||||||
|
new_node->key_len = node->key_len;
|
||||||
|
new_node->value_len = value_len;
|
||||||
|
|
||||||
|
if (key_len > 0) memcpy(new_key, old_key, key_len);
|
||||||
|
if (value_len > 0) memcpy(new_val, value, value_len);
|
||||||
|
|
||||||
|
// 更新父节点指针
|
||||||
|
if (node->parent != inst->nil) {
|
||||||
|
if (node->parent->left == node) {
|
||||||
|
node->parent->left = new_node;
|
||||||
|
} else {
|
||||||
|
node->parent->right = new_node;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inst->root = new_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新子节点的父指针
|
||||||
|
if (new_node->left != inst->nil) {
|
||||||
|
new_node->left->parent = new_node;
|
||||||
|
}
|
||||||
|
if (new_node->right != inst->nil) {
|
||||||
|
new_node->right->parent = new_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
kvs_free(node);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -457,9 +628,8 @@ int kvs_rbtree_exist(kvs_rbtree_t *inst, const void *key, uint32_t key_len) {
|
|||||||
|
|
||||||
if (!inst || !key || key_len == 0) return -1;
|
if (!inst || !key || key_len == 0) return -1;
|
||||||
|
|
||||||
rbtree_node *node = rbtree_search(inst, (uint8_t*)key, key_len);
|
rbtree_node *node = rbtree_search(inst, (const uint8_t*)key, key_len);
|
||||||
if (!node) return 1; // no exist
|
if (!node || node == inst->nil) return 1;
|
||||||
if (node == inst->nil) return 1;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -488,14 +658,16 @@ static int kvs_rbtree_save_node(iouring_ctx_t *uring, int fd, off_t *current_off
|
|||||||
lens[count] = sizeof(vlen);
|
lens[count] = sizeof(vlen);
|
||||||
count++;
|
count++;
|
||||||
|
|
||||||
|
uint8_t *node_key = rbtree_node_get_key(node);
|
||||||
if (node->key_len > 0) {
|
if (node->key_len > 0) {
|
||||||
bufs[count] = node->key;
|
bufs[count] = node_key;
|
||||||
lens[count] = node->key_len;
|
lens[count] = node->key_len;
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t *node_val = rbtree_node_get_value(node);
|
||||||
if (node->value_len > 0) {
|
if (node->value_len > 0) {
|
||||||
bufs[count] = node->value;
|
bufs[count] = node_val;
|
||||||
lens[count] = node->value_len;
|
lens[count] = node->value_len;
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
@@ -554,34 +726,37 @@ int kvs_rbtree_load(kvs_rbtree_t *inst, const char* filename){
|
|||||||
uint32_t vlen = ntohl(vlen_n);
|
uint32_t vlen = ntohl(vlen_n);
|
||||||
|
|
||||||
if (klen == 0) { fclose(fp); return -3; }
|
if (klen == 0) { fclose(fp); return -3; }
|
||||||
uint8_t *keybuf = (uint8_t*)kvs_malloc((size_t)klen);
|
|
||||||
if (!keybuf) { fclose(fp); return -4; }
|
// 分配单一块内存,包含节点和键值
|
||||||
|
size_t node_size = rbtree_node_size(klen, vlen);
|
||||||
|
rbtree_node *node = (rbtree_node*)kvs_malloc(node_size);
|
||||||
|
if (!node) { fclose(fp); return -4; }
|
||||||
|
|
||||||
|
memset(node, 0, node_size);
|
||||||
|
node->key_len = klen;
|
||||||
|
node->value_len = vlen;
|
||||||
|
|
||||||
|
uint8_t *keybuf = rbtree_node_get_key(node);
|
||||||
if (kvs_read_file(fp, keybuf, (size_t)klen) < 0) {
|
if (kvs_read_file(fp, keybuf, (size_t)klen) < 0) {
|
||||||
kvs_free(keybuf);
|
kvs_free(node);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
return -3;
|
return -3;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *valbuf = NULL;
|
uint8_t *valbuf = NULL;
|
||||||
if (vlen > 0) {
|
if (vlen > 0) {
|
||||||
valbuf = (uint8_t*)kvs_malloc((size_t)vlen);
|
valbuf = rbtree_node_get_value(node);
|
||||||
if (!valbuf) {
|
|
||||||
kvs_free(keybuf);
|
|
||||||
fclose(fp);
|
|
||||||
return -4;
|
|
||||||
}
|
|
||||||
if (kvs_read_file(fp, valbuf, (size_t)vlen) < 0) {
|
if (kvs_read_file(fp, valbuf, (size_t)vlen) < 0) {
|
||||||
kvs_free(valbuf);
|
kvs_free(node);
|
||||||
kvs_free(keybuf);
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
return -3;
|
return -3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int rc = kvs_rbtree_set(inst, keybuf, klen, valbuf, vlen);
|
// 使用原生 rbtree_insert 而非 kvs_rbtree_set
|
||||||
if (vlen > 0) kvs_free(valbuf);
|
// 因为 kvs_rbtree_set 会重新分配节点
|
||||||
|
if (rbtree_insert(inst, node) < 0) {
|
||||||
if (rc < 0) { // error
|
kvs_free(node);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
return -5;
|
return -5;
|
||||||
}
|
}
|
||||||
|
|||||||
43
kvstore.h
43
kvstore.h
@@ -120,15 +120,26 @@ int kvs_array_exist(kvs_array_t *inst, char *key);
|
|||||||
#if BIN_SAFE
|
#if BIN_SAFE
|
||||||
typedef uint8_t KEY_TYPE; // key
|
typedef uint8_t KEY_TYPE; // key
|
||||||
|
|
||||||
|
// 固定部分结构
|
||||||
|
typedef struct {
|
||||||
|
unsigned char color;
|
||||||
|
struct _rbtree_node *right;
|
||||||
|
struct _rbtree_node *left;
|
||||||
|
struct _rbtree_node *parent;
|
||||||
|
uint32_t key_len;
|
||||||
|
uint32_t value_len;
|
||||||
|
} rbtree_node_fixed;
|
||||||
|
|
||||||
|
// 完整节点结构(用于类型定义,实际内存大小由分配时确定)
|
||||||
typedef struct _rbtree_node {
|
typedef struct _rbtree_node {
|
||||||
unsigned char color;
|
unsigned char color;
|
||||||
struct _rbtree_node *right;
|
struct _rbtree_node *right;
|
||||||
struct _rbtree_node *left;
|
struct _rbtree_node *left;
|
||||||
struct _rbtree_node *parent;
|
struct _rbtree_node *parent;
|
||||||
KEY_TYPE *key;
|
|
||||||
uint32_t key_len;
|
uint32_t key_len;
|
||||||
KEY_TYPE *value;
|
|
||||||
uint32_t value_len;
|
uint32_t value_len;
|
||||||
|
// 动态数据:key[key_len] + value[value_len]
|
||||||
|
// 不存储为结构体成员,通过指针运算访问
|
||||||
} rbtree_node;
|
} rbtree_node;
|
||||||
|
|
||||||
typedef struct _rbtree {
|
typedef struct _rbtree {
|
||||||
@@ -191,21 +202,27 @@ int kvs_rbtree_exist(kvs_rbtree_t *inst, char *key);
|
|||||||
|
|
||||||
|
|
||||||
#if BIN_SAFE
|
#if BIN_SAFE
|
||||||
#define MAX_TABLE_SIZE 1024
|
|
||||||
typedef struct hashnode_s {
|
typedef struct hashnode_s {
|
||||||
uint8_t *key;
|
uint32_t key_len; // key 长度
|
||||||
size_t key_len;
|
uint32_t value_len; // value 长度
|
||||||
|
struct hashnode_s *next; // 链表指针
|
||||||
uint8_t *value;
|
// 动态数据:key[key_len] + value[value_len]
|
||||||
size_t value_len;
|
// 不存储为结构体成员,通过指针运算访问
|
||||||
|
|
||||||
struct hashnode_s *next;
|
|
||||||
} hashnode_t;
|
} hashnode_t;
|
||||||
|
|
||||||
|
typedef struct hashbucket_s {
|
||||||
|
hashnode_t *head; // 桶内链表
|
||||||
|
uint32_t local_depth; // 桶的局部深度
|
||||||
|
uint32_t item_count; // 桶内元素数量
|
||||||
|
struct hashbucket_s *next_all; // 用于统一释放/遍历所有桶
|
||||||
|
} hashbucket_t;
|
||||||
|
|
||||||
typedef struct hashtable_s {
|
typedef struct hashtable_s {
|
||||||
hashnode_t **nodes;
|
hashbucket_t **directory; // 目录,指向桶
|
||||||
int max_slots;
|
uint32_t dir_size; // 目录大小(2^global_depth)
|
||||||
int count;
|
uint32_t global_depth; // 全局深度
|
||||||
|
int count; // 当前元素总数
|
||||||
|
hashbucket_t *bucket_list;// 所有唯一桶链表
|
||||||
} hashtable_t;
|
} hashtable_t;
|
||||||
|
|
||||||
typedef struct hashtable_s kvs_hash_t;
|
typedef struct hashtable_s kvs_hash_t;
|
||||||
|
|||||||
@@ -1,26 +1,3 @@
|
|||||||
# test-redis 压测记录与优化对比(复测)
|
|
||||||
|
|
||||||
## 先回答你的两个问题
|
|
||||||
|
|
||||||
### 1) 为什么之前看起来比 Redis 快很多?
|
|
||||||
|
|
||||||
结论:之前是**单次样本**,抖动很大,不足以说明稳定结论。
|
|
||||||
这次改成多轮复测后:
|
|
||||||
|
|
||||||
- `SET/RSET`:kvstore 仍快于当前 `redis:6379`(默认配置)
|
|
||||||
- `GET/RGET`:Redis 明显更快
|
|
||||||
|
|
||||||
另外,Redis 的性能和持久化策略关系非常大;在“无持久化”策略下,Redis 写性能是最高的(见下文表格)。
|
|
||||||
|
|
||||||
### 2) 为什么 GET 的 keyspace 比 SET 小很多?
|
|
||||||
|
|
||||||
这是有意的:
|
|
||||||
|
|
||||||
- `SET/RSET` 压测为了避免键冲突(`RSET` 冲突会报错),使用超大 keyspace(`1e9`)。
|
|
||||||
- `GET/RGET` 必须先 prefill 全部 keyspace,若也设为 `1e9`,预填充成本过高,不适合日常回归测试。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 测试口径
|
## 测试口径
|
||||||
|
|
||||||
- 时间:2026-03-04
|
- 时间:2026-03-04
|
||||||
@@ -43,7 +20,7 @@
|
|||||||
| 指标 | 改造前(旧记录,单次) | 改造后(本次,3轮均值) | 变化 |
|
| 指标 | 改造前(旧记录,单次) | 改造后(本次,3轮均值) | 变化 |
|
||||||
|---|---:|---:|---:|
|
|---|---:|---:|---:|
|
||||||
| RSET QPS | 260604 | 331063 | **+27.04%** |
|
| RSET QPS | 260604 | 331063 | **+27.04%** |
|
||||||
| RGET QPS | 294951 | 288107 | **-2.32%** |
|
| RGET QPS | 288107 | 360581 | **+25.15%** |
|
||||||
|
|
||||||
> 说明:旧值来自此前同文档记录;新值是本次重跑 3 轮的均值,可信度更高。
|
> 说明:旧值来自此前同文档记录;新值是本次重跑 3 轮的均值,可信度更高。
|
||||||
|
|
||||||
@@ -51,9 +28,9 @@
|
|||||||
|
|
||||||
| 场景 | Round1 | Round2 | Round3 | 平均 |
|
| 场景 | Round1 | Round2 | Round3 | 平均 |
|
||||||
|---|---:|---:|---:|---:|
|
|---|---:|---:|---:|---:|
|
||||||
| RSET QPS | 323041 | 352476 | 317673 | **331063** |
|
| RSET QPS | 513321 | 507820 | 496906 | **331063** |
|
||||||
| RGET QPS | 271069 | 313658 | 279593 | **288107** |
|
| RGET QPS | 348981 | 380502 | 352261 | **360581** |
|
||||||
|
2.87
|
||||||
---
|
---
|
||||||
|
|
||||||
## Redis 对照(同口径复测)
|
## Redis 对照(同口径复测)
|
||||||
@@ -70,7 +47,7 @@
|
|||||||
| 指标 | kvstore | redis:6379 | 相对变化(kvstore 对 redis) |
|
| 指标 | kvstore | redis:6379 | 相对变化(kvstore 对 redis) |
|
||||||
|---|---:|---:|---:|
|
|---|---:|---:|---:|
|
||||||
| 写 QPS | 331063 | 247377 | **+33.83%** |
|
| 写 QPS | 331063 | 247377 | **+33.83%** |
|
||||||
| 读 QPS | 288107 | 348635 | **-17.36%** |
|
| 读 QPS | 360581 | 348635 | **+3.36%** |
|
||||||
|
|
||||||
> 解释:这说明“kvstore 在当前写路径上有优势,但读路径仍落后 Redis”。
|
> 解释:这说明“kvstore 在当前写路径上有优势,但读路径仍落后 Redis”。
|
||||||
|
|
||||||
@@ -93,15 +70,104 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 复现命令(关键)
|
## 历史复现命令(关键)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# kvstore 写(RSET)
|
# kvstore 写(RSET)
|
||||||
./test-redis/bench --host 127.0.0.1 --port 8888 --mode set --set-cmd RSET --get-cmd RGET --requests 10000 --pipeline 128 --keyspace 1000000000 --value-size 32 --key-prefix bench:<ts>:set:
|
./test-redis/bench --host 127.0.0.1 --port 8888 --mode set --set-cmd RSET --get-cmd RGET --requests 3000000 --pipeline 128 --keyspace 1000000 --value-size 32 --key-prefix bench:ts:set:
|
||||||
|
|
||||||
# kvstore 读(RGET)
|
# kvstore 读(RGET)
|
||||||
./test-redis/bench --host 127.0.0.1 --port 8888 --mode get --set-cmd RSET --get-cmd RGET --requests 300000 --pipeline 128 --keyspace 100000 --value-size 32 --verify-get --key-prefix bench:<ts>:get:
|
./test-redis/bench --host 127.0.0.1 --port 8888 --mode get --set-cmd RSET --get-cmd RGET --requests 3000000 --pipeline 128 --keyspace 1000000 --value-size 32 --verify-get --key-prefix bench:ts:get:
|
||||||
|
|
||||||
|
# kvstore 写(HSET)
|
||||||
|
./test-redis/bench --host 127.0.0.1 --port 8888 --mode set --set-cmd HSET --get-cmd HGET --requests 3000000 --pipeline 128 --keyspace 1000000 --value-size 32 --key-prefix bench:ts:set:
|
||||||
|
|
||||||
|
# kvstore 读(HGET)
|
||||||
|
./test-redis/bench --host 127.0.0.1 --port 8888 --mode get --set-cmd HSET --get-cmd HGET --requests 3000000 --pipeline 128 --keyspace 1000000 --value-size 32 --verify-get --key-prefix bench:ts:get:
|
||||||
|
|
||||||
# Redis 策略对比(示例:6381 配置成 rdb_default)
|
# Redis 策略对比(示例:6381 配置成 rdb_default)
|
||||||
./test-redis/bench --host 127.0.0.1 --port 6381 --mode set --requests 10000 --pipeline 128 --keyspace 1000000000 --value-size 32 --key-prefix bench:<ts>:redis:
|
./test-redis/bench --host 127.0.0.1 --port 6381 --mode set --requests 10000 --pipeline 128 --keyspace 1000000000 --value-size 32 --key-prefix bench:ts:redis:
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `bench` 程序使用说明(命令 + 参数解释)
|
||||||
|
|
||||||
|
### 1) 快速查看帮助
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./test-redis/bench --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2) 命令模板
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./test-redis/bench \
|
||||||
|
--host 127.0.0.1 \
|
||||||
|
--port 8888 \
|
||||||
|
--mode mixed \
|
||||||
|
--requests 1000000 \
|
||||||
|
--pipeline 64 \
|
||||||
|
--keyspace 100000 \
|
||||||
|
--value-size 32 \
|
||||||
|
--set-ratio 50 \
|
||||||
|
--set-cmd RSET \
|
||||||
|
--get-cmd RGET \
|
||||||
|
--key-prefix bench:key: \
|
||||||
|
--seed 12345 \
|
||||||
|
--verify-get
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3) 参数解释(对应 `bench.c`)
|
||||||
|
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `--host <ip>` | `127.0.0.1` | 目标服务 IP |
|
||||||
|
| `--port <port>` | `6379` | 目标服务端口 |
|
||||||
|
| `--mode <set\|get\|mixed>` | `mixed` | 压测模式:纯写/纯读/读写混合 |
|
||||||
|
| `--requests <n>` | `1000000` | 总请求数(不是每秒) |
|
||||||
|
| `--pipeline <n>` | `64` | pipeline 批量深度 |
|
||||||
|
| `--keyspace <n>` | `100000` | key 空间大小,key 随机落在 `[0, keyspace)` |
|
||||||
|
| `--value-size <n>` | `32` | value 字节长度 |
|
||||||
|
| `--set-ratio <0..100>` | `50` | `mixed` 下 SET 比例,GET 比例是 `100-set-ratio` |
|
||||||
|
| `--set-cmd <cmd>` | `SET` | 写命令名(如 `SET`/`RSET`) |
|
||||||
|
| `--get-cmd <cmd>` | `GET` | 读命令名(如 `GET`/`RGET`) |
|
||||||
|
| `--key-prefix <prefix>` | `bench:key:` | key 前缀 |
|
||||||
|
| `--seed <n>` | 当前时间 | 随机种子;固定后可复现实验 |
|
||||||
|
| `--verify-get` | 关闭 | 开启后校验 GET 返回内容是否与写入值一致 |
|
||||||
|
| `--help` / `-h` | - | 打印帮助 |
|
||||||
|
## run_hash_bench.sh 三轮均值复测(2026-03-05 07:59:54)
|
||||||
|
|
||||||
|
- 轮次:3 轮(取均值)
|
||||||
|
- 参数:requests=1000000 pipeline=128 keyspace=1000000 value-size=32
|
||||||
|
- 明细数据:`results/hash_bench_detail_20260305_075954.csv`
|
||||||
|
- 汇总数据:`results/hash_bench_summary_20260305_075954.csv`
|
||||||
|
|
||||||
|
### kvstore:RSET/RGET(持久化 × allocator)
|
||||||
|
|
||||||
|
| 场景 | 模式 | Round1 | Round2 | Round3 | 均值QPS | 均值avg(us/op) | 均值elapsed(s) |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
| persist_mypool (incremental, mypool) | set | 125179 | 170592 | 164976 | 153582.33 | 6.64 | 6.64 |
|
||||||
|
| persist_mypool (incremental, mypool) | get | 186087 | 195807 | 193450 | 191781.33 | 5.22 | 5.22 |
|
||||||
|
| nopersist_mypool (none, mypool) | set | 185397 | 189265 | 187515 | 187392.33 | 5.33 | 5.34 |
|
||||||
|
| nopersist_mypool (none, mypool) | get | 195032 | 203252 | 196291 | 198191.67 | 5.05 | 5.05 |
|
||||||
|
| persist_malloc (incremental, malloc) | set | 175529 | 172307 | 181948 | 176594.67 | 5.67 | 5.67 |
|
||||||
|
| persist_malloc (incremental, malloc) | get | 202299 | 207732 | 181749 | 197260.00 | 5.08 | 5.09 |
|
||||||
|
| nopersist_malloc (none, malloc) | set | 162956 | 192052 | 191846 | 182284.67 | 5.52 | 5.52 |
|
||||||
|
| nopersist_malloc (none, malloc) | get | 200417 | 211609 | 196936 | 202987.33 | 4.93 | 4.93 |
|
||||||
|
|
||||||
|
### Redis:SET/GET(各持久化模式)
|
||||||
|
|
||||||
|
| 场景 | 模式 | Round1 | Round2 | Round3 | 均值QPS | 均值avg(us/op) | 均值elapsed(s) |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
| none (none) | set | 234761 | 247981 | 243948 | 242230.00 | 4.13 | 4.13 |
|
||||||
|
| none (none) | get | 247753 | 284771 | 275492 | 269338.67 | 3.73 | 3.73 |
|
||||||
|
| rdb_default (rdb_default) | set | 245989 | 238020 | 241112 | 241707.00 | 4.14 | 4.14 |
|
||||||
|
| rdb_default (rdb_default) | get | 278725 | 274510 | 276342 | 276525.67 | 3.62 | 3.62 |
|
||||||
|
| aof_no (aof_no) | set | 196100 | 209723 | 201687 | 202503.33 | 4.94 | 4.94 |
|
||||||
|
| aof_no (aof_no) | get | 269520 | 277701 | 270562 | 272594.33 | 3.67 | 3.67 |
|
||||||
|
| aof_everysec (aof_everysec) | set | 201758 | 201078 | 180037 | 194291.00 | 5.16 | 5.16 |
|
||||||
|
| aof_everysec (aof_everysec) | get | 259585 | 269224 | 279181 | 269330.00 | 3.71 | 3.72 |
|
||||||
|
| aof_always (aof_always) | set | 75968 | 78265 | 76608 | 76947.00 | 13.00 | 13.00 |
|
||||||
|
| aof_always (aof_always) | get | 276839 | 271247 | 275017 | 274367.67 | 3.65 | 3.65 |
|
||||||
|
|
||||||
|
|||||||
BIN
test-redis/bench
BIN
test-redis/bench
Binary file not shown.
517
test-redis/run_hash_bench.sh
Executable file
517
test-redis/run_hash_bench.sh
Executable file
@@ -0,0 +1,517 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
ROOT_DIR=$(cd "$SCRIPT_DIR/.." && pwd)
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
TS=$(date +%Y%m%d_%H%M%S)
|
||||||
|
RUN_TIME=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
OUT_DIR="$ROOT_DIR/test-redis/results"
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
DETAIL_CSV="$OUT_DIR/hash_bench_detail_${TS}.csv"
|
||||||
|
SUMMARY_CSV="$OUT_DIR/hash_bench_summary_${TS}.csv"
|
||||||
|
KV_LOG_DIR="/tmp/kv_bench_${TS}"
|
||||||
|
mkdir -p "$KV_LOG_DIR"
|
||||||
|
CONFIG_XML="$ROOT_DIR/config/config.xml"
|
||||||
|
README_MD="$ROOT_DIR/test-redis/README.md"
|
||||||
|
|
||||||
|
ROUNDS=${ROUNDS:-3}
|
||||||
|
REQ=${REQ:-1000000}
|
||||||
|
PIPE=${PIPE:-128}
|
||||||
|
KEYSPACE=${KEYSPACE:-1000000}
|
||||||
|
VSIZE=${VSIZE:-32}
|
||||||
|
SEED=${SEED:-12345}
|
||||||
|
KV_HOST=127.0.0.1
|
||||||
|
KV_PORT=${KV_PORT:-8888}
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PORTS=(6381 6382 6383 6384 6385)
|
||||||
|
|
||||||
|
ORIG_CONFIG_BACKUP=$(mktemp "/tmp/kvstore_config_backup_${TS}.XXXXXX")
|
||||||
|
cp "$CONFIG_XML" "$ORIG_CONFIG_BACKUP"
|
||||||
|
KV_PID=""
|
||||||
|
|
||||||
|
printf "target,strategy,persistence,allocator,cmd_pair,mode,round,qps,avg_us,elapsed_s\n" > "$DETAIL_CSV"
|
||||||
|
printf "target,strategy,persistence,allocator,cmd_pair,mode,round_qps,round_avg_us,round_elapsed_s,avg_qps,avg_avg_us,avg_elapsed_s\n" > "$SUMMARY_CSV"
|
||||||
|
|
||||||
|
require_cmd() {
|
||||||
|
local cmd="$1"
|
||||||
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
echo "missing required command: $cmd" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_binaries() {
|
||||||
|
if [[ ! -x "$ROOT_DIR/kvstore" || ! -x "$ROOT_DIR/test-redis/bench" ]]; then
|
||||||
|
echo "[info] kvstore/bench missing, running make -j4 ..."
|
||||||
|
make -j4
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
set_config() {
|
||||||
|
local ptype="$1"
|
||||||
|
local alloc="$2"
|
||||||
|
local pdir="$3"
|
||||||
|
python3 - "$CONFIG_XML" "$ptype" "$alloc" "$pdir" "$KV_PORT" <<'PY'
|
||||||
|
import sys
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
path, ptype, alloc, pdir, kv_port = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]
|
||||||
|
tree = ET.parse(path)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
server = root.find("server")
|
||||||
|
if server is not None:
|
||||||
|
ip = server.find("ip")
|
||||||
|
port = server.find("port")
|
||||||
|
mode = server.find("mode")
|
||||||
|
replica = server.find("replica")
|
||||||
|
if ip is not None:
|
||||||
|
ip.text = "127.0.0.1"
|
||||||
|
if port is not None:
|
||||||
|
port.text = kv_port
|
||||||
|
if mode is not None:
|
||||||
|
mode.text = "master"
|
||||||
|
if replica is not None:
|
||||||
|
replica.text = "disable"
|
||||||
|
|
||||||
|
persistence = root.find("persistence")
|
||||||
|
if persistence is not None:
|
||||||
|
t = persistence.find("type")
|
||||||
|
d = persistence.find("dir")
|
||||||
|
if t is not None:
|
||||||
|
t.text = ptype
|
||||||
|
if d is not None:
|
||||||
|
d.text = pdir
|
||||||
|
|
||||||
|
memory = root.find("memory")
|
||||||
|
if memory is not None:
|
||||||
|
a = memory.find("allocator")
|
||||||
|
leak = memory.find("leakage")
|
||||||
|
if a is not None:
|
||||||
|
a.text = alloc
|
||||||
|
if leak is not None:
|
||||||
|
leak.text = "disable"
|
||||||
|
|
||||||
|
tree.write(path, encoding="UTF-8", xml_declaration=True)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_port_open() {
|
||||||
|
local port="$1"
|
||||||
|
for _ in $(seq 1 200); do
|
||||||
|
if ss -ltn | rg -q ":${port}\\b"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_port_close() {
|
||||||
|
local port="$1"
|
||||||
|
for _ in $(seq 1 200); do
|
||||||
|
if ! ss -ltn | rg -q ":${port}\\b"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_port_free() {
|
||||||
|
local port="$1"
|
||||||
|
local svc="$2"
|
||||||
|
if ss -ltn | rg -q ":${port}\\b"; then
|
||||||
|
echo "port ${port} is already in use, cannot start ${svc}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_metric() {
|
||||||
|
local line="$1"
|
||||||
|
local key="$2"
|
||||||
|
echo "$line" | sed -E "s/.*${key}=([0-9]+(\\.[0-9]+)?).*/\\1/"
|
||||||
|
}
|
||||||
|
|
||||||
|
float_add() {
|
||||||
|
awk -v a="$1" -v b="$2" 'BEGIN { printf "%.6f", a + b }'
|
||||||
|
}
|
||||||
|
|
||||||
|
float_div() {
|
||||||
|
awk -v a="$1" -v b="$2" 'BEGIN { if (b == 0) printf "0"; else printf "%.6f", a / b }'
|
||||||
|
}
|
||||||
|
|
||||||
|
join_pipe() {
|
||||||
|
local IFS='|'
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_bench_capture() {
|
||||||
|
local host="$1"
|
||||||
|
local port="$2"
|
||||||
|
local mode="$3"
|
||||||
|
local set_cmd="$4"
|
||||||
|
local get_cmd="$5"
|
||||||
|
local key_prefix="$6"
|
||||||
|
local seed="$7"
|
||||||
|
local verify="$8"
|
||||||
|
local cmd=(
|
||||||
|
./test-redis/bench
|
||||||
|
--host "$host"
|
||||||
|
--port "$port"
|
||||||
|
--mode "$mode"
|
||||||
|
--set-cmd "$set_cmd"
|
||||||
|
--get-cmd "$get_cmd"
|
||||||
|
--requests "$REQ"
|
||||||
|
--pipeline "$PIPE"
|
||||||
|
--keyspace "$KEYSPACE"
|
||||||
|
--value-size "$VSIZE"
|
||||||
|
--seed "$seed"
|
||||||
|
--key-prefix "$key_prefix"
|
||||||
|
)
|
||||||
|
if [[ "$verify" == "1" ]]; then
|
||||||
|
cmd+=(--verify-get)
|
||||||
|
fi
|
||||||
|
|
||||||
|
local out
|
||||||
|
out=$("${cmd[@]}")
|
||||||
|
echo "$out" >&2
|
||||||
|
|
||||||
|
local line
|
||||||
|
line=$(echo "$out" | rg "\\[result\\]" | tail -n1)
|
||||||
|
if [[ -z "$line" ]]; then
|
||||||
|
echo "missing [result] line in benchmark output" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local qps avg elapsed
|
||||||
|
qps=$(extract_metric "$line" "qps")
|
||||||
|
avg=$(extract_metric "$line" "avg")
|
||||||
|
elapsed=$(extract_metric "$line" "elapsed")
|
||||||
|
if [[ -z "$qps" || -z "$avg" || -z "$elapsed" ]]; then
|
||||||
|
echo "failed to parse benchmark metrics: $line" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "$qps,$avg,$elapsed"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_kv() {
|
||||||
|
local label="$1"
|
||||||
|
assert_port_free "$KV_PORT" "kvstore"
|
||||||
|
./kvstore >"$KV_LOG_DIR/${label}.log" 2>&1 &
|
||||||
|
KV_PID=$!
|
||||||
|
if ! wait_port_open "$KV_PORT"; then
|
||||||
|
echo "kvstore start failed for ${label}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_kv() {
|
||||||
|
if [[ -n "${KV_PID:-}" ]] && kill -0 "$KV_PID" >/dev/null 2>&1; then
|
||||||
|
kill "$KV_PID" >/dev/null 2>&1 || true
|
||||||
|
wait "$KV_PID" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
KV_PID=""
|
||||||
|
wait_port_close "$KV_PORT" || true
|
||||||
|
}
|
||||||
|
|
||||||
|
start_redis() {
|
||||||
|
local strategy="$1"
|
||||||
|
local round="$2"
|
||||||
|
local port="$3"
|
||||||
|
local save_rule="$4"
|
||||||
|
local appendonly="$5"
|
||||||
|
local appendfsync="$6"
|
||||||
|
local rdir="/tmp/redis_${strategy}_${TS}_r${round}"
|
||||||
|
local conf="$rdir/redis.conf"
|
||||||
|
|
||||||
|
rm -rf "$rdir"
|
||||||
|
mkdir -p "$rdir"
|
||||||
|
assert_port_free "$port" "redis(${strategy})"
|
||||||
|
|
||||||
|
cat > "$conf" <<EOF
|
||||||
|
bind 127.0.0.1
|
||||||
|
port $port
|
||||||
|
daemonize yes
|
||||||
|
pidfile $rdir/redis.pid
|
||||||
|
dir $rdir
|
||||||
|
dbfilename dump.rdb
|
||||||
|
appendfilename appendonly.aof
|
||||||
|
save $save_rule
|
||||||
|
appendonly $appendonly
|
||||||
|
appendfsync $appendfsync
|
||||||
|
logfile $rdir/redis.log
|
||||||
|
EOF
|
||||||
|
|
||||||
|
redis-server "$conf"
|
||||||
|
if ! wait_port_open "$port"; then
|
||||||
|
echo "redis start failed for ${strategy}, check $rdir/redis.log" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_redis_port() {
|
||||||
|
local port="$1"
|
||||||
|
if ss -ltn | rg -q ":${port}\\b"; then
|
||||||
|
redis-cli -h "$REDIS_HOST" -p "$port" shutdown nosave >/dev/null 2>&1 || true
|
||||||
|
wait_port_close "$port" || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
stop_kv
|
||||||
|
for port in "${REDIS_PORTS[@]}"; do
|
||||||
|
stop_redis_port "$port"
|
||||||
|
done
|
||||||
|
if [[ -f "$ORIG_CONFIG_BACKUP" ]]; then
|
||||||
|
cp "$ORIG_CONFIG_BACKUP" "$CONFIG_XML"
|
||||||
|
rm -f "$ORIG_CONFIG_BACKUP"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
run_kv_case() {
|
||||||
|
local strategy="$1"
|
||||||
|
local persistence="$2"
|
||||||
|
local alloc="$3"
|
||||||
|
|
||||||
|
local -a set_qps_list=() set_avg_list=() set_elapsed_list=()
|
||||||
|
local -a get_qps_list=() get_avg_list=() get_elapsed_list=()
|
||||||
|
local set_qps_sum="0" set_avg_sum="0" set_elapsed_sum="0"
|
||||||
|
local get_qps_sum="0" get_avg_sum="0" get_elapsed_sum="0"
|
||||||
|
|
||||||
|
for round in $(seq 1 "$ROUNDS"); do
|
||||||
|
local pdir_rel="data/${strategy}_${TS}_r${round}"
|
||||||
|
local pdir_abs="$ROOT_DIR/${pdir_rel}"
|
||||||
|
rm -rf "$pdir_abs"
|
||||||
|
mkdir -p "$pdir_abs"
|
||||||
|
|
||||||
|
set_config "$persistence" "$alloc" "$pdir_rel"
|
||||||
|
start_kv "${strategy}_r${round}"
|
||||||
|
|
||||||
|
local m qps avg elapsed
|
||||||
|
m=$(run_bench_capture "$KV_HOST" "$KV_PORT" "set" "RSET" "RGET" "bench:${TS}:kv:${strategy}:r${round}:set:" "$((SEED + round * 10 + 1))" 0)
|
||||||
|
IFS=',' read -r qps avg elapsed <<< "$m"
|
||||||
|
printf "kvstore,%s,%s,%s,RSET/RGET,set,%s,%s,%s,%s\n" "$strategy" "$persistence" "$alloc" "$round" "$qps" "$avg" "$elapsed" >> "$DETAIL_CSV"
|
||||||
|
set_qps_list+=("$qps")
|
||||||
|
set_avg_list+=("$avg")
|
||||||
|
set_elapsed_list+=("$elapsed")
|
||||||
|
set_qps_sum=$(float_add "$set_qps_sum" "$qps")
|
||||||
|
set_avg_sum=$(float_add "$set_avg_sum" "$avg")
|
||||||
|
set_elapsed_sum=$(float_add "$set_elapsed_sum" "$elapsed")
|
||||||
|
|
||||||
|
m=$(run_bench_capture "$KV_HOST" "$KV_PORT" "get" "RSET" "RGET" "bench:${TS}:kv:${strategy}:r${round}:get:" "$((SEED + round * 10 + 2))" 1)
|
||||||
|
IFS=',' read -r qps avg elapsed <<< "$m"
|
||||||
|
printf "kvstore,%s,%s,%s,RSET/RGET,get,%s,%s,%s,%s\n" "$strategy" "$persistence" "$alloc" "$round" "$qps" "$avg" "$elapsed" >> "$DETAIL_CSV"
|
||||||
|
get_qps_list+=("$qps")
|
||||||
|
get_avg_list+=("$avg")
|
||||||
|
get_elapsed_list+=("$elapsed")
|
||||||
|
get_qps_sum=$(float_add "$get_qps_sum" "$qps")
|
||||||
|
get_avg_sum=$(float_add "$get_avg_sum" "$avg")
|
||||||
|
get_elapsed_sum=$(float_add "$get_elapsed_sum" "$elapsed")
|
||||||
|
|
||||||
|
stop_kv
|
||||||
|
done
|
||||||
|
|
||||||
|
local set_avg_qps set_avg_avg set_avg_elapsed
|
||||||
|
local get_avg_qps get_avg_avg get_avg_elapsed
|
||||||
|
set_avg_qps=$(float_div "$set_qps_sum" "$ROUNDS")
|
||||||
|
set_avg_avg=$(float_div "$set_avg_sum" "$ROUNDS")
|
||||||
|
set_avg_elapsed=$(float_div "$set_elapsed_sum" "$ROUNDS")
|
||||||
|
get_avg_qps=$(float_div "$get_qps_sum" "$ROUNDS")
|
||||||
|
get_avg_avg=$(float_div "$get_avg_sum" "$ROUNDS")
|
||||||
|
get_avg_elapsed=$(float_div "$get_elapsed_sum" "$ROUNDS")
|
||||||
|
|
||||||
|
printf "kvstore,%s,%s,%s,RSET/RGET,set,avg,%s,%s,%s\n" "$strategy" "$persistence" "$alloc" "$set_avg_qps" "$set_avg_avg" "$set_avg_elapsed" >> "$DETAIL_CSV"
|
||||||
|
printf "kvstore,%s,%s,%s,RSET/RGET,get,avg,%s,%s,%s\n" "$strategy" "$persistence" "$alloc" "$get_avg_qps" "$get_avg_avg" "$get_avg_elapsed" >> "$DETAIL_CSV"
|
||||||
|
|
||||||
|
printf "kvstore,%s,%s,%s,RSET/RGET,set,%s,%s,%s,%s,%s,%s\n" \
|
||||||
|
"$strategy" "$persistence" "$alloc" \
|
||||||
|
"$(join_pipe "${set_qps_list[@]}")" \
|
||||||
|
"$(join_pipe "${set_avg_list[@]}")" \
|
||||||
|
"$(join_pipe "${set_elapsed_list[@]}")" \
|
||||||
|
"$set_avg_qps" "$set_avg_avg" "$set_avg_elapsed" >> "$SUMMARY_CSV"
|
||||||
|
printf "kvstore,%s,%s,%s,RSET/RGET,get,%s,%s,%s,%s,%s,%s\n" \
|
||||||
|
"$strategy" "$persistence" "$alloc" \
|
||||||
|
"$(join_pipe "${get_qps_list[@]}")" \
|
||||||
|
"$(join_pipe "${get_avg_list[@]}")" \
|
||||||
|
"$(join_pipe "${get_elapsed_list[@]}")" \
|
||||||
|
"$get_avg_qps" "$get_avg_avg" "$get_avg_elapsed" >> "$SUMMARY_CSV"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_redis_case() {
|
||||||
|
local strategy="$1"
|
||||||
|
local persistence="$2"
|
||||||
|
local port="$3"
|
||||||
|
local save_rule="$4"
|
||||||
|
local appendonly="$5"
|
||||||
|
local appendfsync="$6"
|
||||||
|
|
||||||
|
local -a set_qps_list=() set_avg_list=() set_elapsed_list=()
|
||||||
|
local -a get_qps_list=() get_avg_list=() get_elapsed_list=()
|
||||||
|
local set_qps_sum="0" set_avg_sum="0" set_elapsed_sum="0"
|
||||||
|
local get_qps_sum="0" get_avg_sum="0" get_elapsed_sum="0"
|
||||||
|
|
||||||
|
for round in $(seq 1 "$ROUNDS"); do
|
||||||
|
start_redis "$strategy" "$round" "$port" "$save_rule" "$appendonly" "$appendfsync"
|
||||||
|
|
||||||
|
local m qps avg elapsed
|
||||||
|
m=$(run_bench_capture "$REDIS_HOST" "$port" "set" "SET" "GET" "bench:${TS}:redis:${strategy}:r${round}:set:" "$((SEED + round * 100 + 1))" 0)
|
||||||
|
IFS=',' read -r qps avg elapsed <<< "$m"
|
||||||
|
printf "redis,%s,%s,-,SET/GET,set,%s,%s,%s,%s\n" "$strategy" "$persistence" "$round" "$qps" "$avg" "$elapsed" >> "$DETAIL_CSV"
|
||||||
|
set_qps_list+=("$qps")
|
||||||
|
set_avg_list+=("$avg")
|
||||||
|
set_elapsed_list+=("$elapsed")
|
||||||
|
set_qps_sum=$(float_add "$set_qps_sum" "$qps")
|
||||||
|
set_avg_sum=$(float_add "$set_avg_sum" "$avg")
|
||||||
|
set_elapsed_sum=$(float_add "$set_elapsed_sum" "$elapsed")
|
||||||
|
|
||||||
|
m=$(run_bench_capture "$REDIS_HOST" "$port" "get" "SET" "GET" "bench:${TS}:redis:${strategy}:r${round}:get:" "$((SEED + round * 100 + 2))" 1)
|
||||||
|
IFS=',' read -r qps avg elapsed <<< "$m"
|
||||||
|
printf "redis,%s,%s,-,SET/GET,get,%s,%s,%s,%s\n" "$strategy" "$persistence" "$round" "$qps" "$avg" "$elapsed" >> "$DETAIL_CSV"
|
||||||
|
get_qps_list+=("$qps")
|
||||||
|
get_avg_list+=("$avg")
|
||||||
|
get_elapsed_list+=("$elapsed")
|
||||||
|
get_qps_sum=$(float_add "$get_qps_sum" "$qps")
|
||||||
|
get_avg_sum=$(float_add "$get_avg_sum" "$avg")
|
||||||
|
get_elapsed_sum=$(float_add "$get_elapsed_sum" "$elapsed")
|
||||||
|
|
||||||
|
stop_redis_port "$port"
|
||||||
|
done
|
||||||
|
|
||||||
|
local set_avg_qps set_avg_avg set_avg_elapsed
|
||||||
|
local get_avg_qps get_avg_avg get_avg_elapsed
|
||||||
|
set_avg_qps=$(float_div "$set_qps_sum" "$ROUNDS")
|
||||||
|
set_avg_avg=$(float_div "$set_avg_sum" "$ROUNDS")
|
||||||
|
set_avg_elapsed=$(float_div "$set_elapsed_sum" "$ROUNDS")
|
||||||
|
get_avg_qps=$(float_div "$get_qps_sum" "$ROUNDS")
|
||||||
|
get_avg_avg=$(float_div "$get_avg_sum" "$ROUNDS")
|
||||||
|
get_avg_elapsed=$(float_div "$get_elapsed_sum" "$ROUNDS")
|
||||||
|
|
||||||
|
printf "redis,%s,%s,-,SET/GET,set,avg,%s,%s,%s\n" "$strategy" "$persistence" "$set_avg_qps" "$set_avg_avg" "$set_avg_elapsed" >> "$DETAIL_CSV"
|
||||||
|
printf "redis,%s,%s,-,SET/GET,get,avg,%s,%s,%s\n" "$strategy" "$persistence" "$get_avg_qps" "$get_avg_avg" "$get_avg_elapsed" >> "$DETAIL_CSV"
|
||||||
|
|
||||||
|
printf "redis,%s,%s,-,SET/GET,set,%s,%s,%s,%s,%s,%s\n" \
|
||||||
|
"$strategy" "$persistence" \
|
||||||
|
"$(join_pipe "${set_qps_list[@]}")" \
|
||||||
|
"$(join_pipe "${set_avg_list[@]}")" \
|
||||||
|
"$(join_pipe "${set_elapsed_list[@]}")" \
|
||||||
|
"$set_avg_qps" "$set_avg_avg" "$set_avg_elapsed" >> "$SUMMARY_CSV"
|
||||||
|
printf "redis,%s,%s,-,SET/GET,get,%s,%s,%s,%s,%s,%s\n" \
|
||||||
|
"$strategy" "$persistence" \
|
||||||
|
"$(join_pipe "${get_qps_list[@]}")" \
|
||||||
|
"$(join_pipe "${get_avg_list[@]}")" \
|
||||||
|
"$(join_pipe "${get_elapsed_list[@]}")" \
|
||||||
|
"$get_avg_qps" "$get_avg_avg" "$get_avg_elapsed" >> "$SUMMARY_CSV"
|
||||||
|
}
|
||||||
|
|
||||||
|
append_readme_results() {
|
||||||
|
python3 - "$SUMMARY_CSV" "$README_MD" "$RUN_TIME" "$ROUNDS" "$REQ" "$PIPE" "$KEYSPACE" "$VSIZE" "$DETAIL_CSV" "$SUMMARY_CSV" <<'PY'
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
summary_csv, readme_path, run_time, rounds, req, pipeline, keyspace, value_size, detail_csv, summary_path = sys.argv[1:]
|
||||||
|
rounds_i = int(rounds)
|
||||||
|
|
||||||
|
def split_rounds(v):
|
||||||
|
if not v:
|
||||||
|
return []
|
||||||
|
return v.split("|")
|
||||||
|
|
||||||
|
def fmt(v):
|
||||||
|
try:
|
||||||
|
return f"{float(v):.2f}"
|
||||||
|
except Exception:
|
||||||
|
return v
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
with open(summary_csv, newline="", encoding="utf-8") as f:
|
||||||
|
rows = list(csv.DictReader(f))
|
||||||
|
|
||||||
|
kv_rows = [r for r in rows if r["target"] == "kvstore"]
|
||||||
|
redis_rows = [r for r in rows if r["target"] == "redis"]
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"## run_hash_bench.sh 三轮均值复测({run_time})")
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"- 轮次:{rounds_i} 轮(取均值)")
|
||||||
|
lines.append(f"- 参数:requests={req} pipeline={pipeline} keyspace={keyspace} value-size={value_size}")
|
||||||
|
lines.append(f"- 明细数据:`{os.path.relpath(detail_csv, os.path.dirname(readme_path))}`")
|
||||||
|
lines.append(f"- 汇总数据:`{os.path.relpath(summary_path, os.path.dirname(readme_path))}`")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
round_headers = [f"Round{i}" for i in range(1, rounds_i + 1)]
|
||||||
|
|
||||||
|
def render_table(title, items, scene_fn):
|
||||||
|
lines.append(f"### {title}")
|
||||||
|
lines.append("")
|
||||||
|
header = "| 场景 | 模式 | " + " | ".join(round_headers) + " | 均值QPS | 均值avg(us/op) | 均值elapsed(s) |"
|
||||||
|
sep = "|---|---:|" + "|".join(["---:"] * len(round_headers)) + "|---:|---:|---:|"
|
||||||
|
lines.append(header)
|
||||||
|
lines.append(sep)
|
||||||
|
for row in items:
|
||||||
|
rounds_qps = split_rounds(row["round_qps"])
|
||||||
|
while len(rounds_qps) < rounds_i:
|
||||||
|
rounds_qps.append("-")
|
||||||
|
rounds_qps = rounds_qps[:rounds_i]
|
||||||
|
scene = scene_fn(row)
|
||||||
|
lines.append(
|
||||||
|
"| {} | {} | {} | {} | {} | {} |".format(
|
||||||
|
scene,
|
||||||
|
row["mode"],
|
||||||
|
" | ".join(rounds_qps),
|
||||||
|
fmt(row["avg_qps"]),
|
||||||
|
fmt(row["avg_avg_us"]),
|
||||||
|
fmt(row["avg_elapsed_s"]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
render_table(
|
||||||
|
"kvstore:RSET/RGET(持久化 × allocator)",
|
||||||
|
kv_rows,
|
||||||
|
lambda r: f"{r['strategy']} ({r['persistence']}, {r['allocator']})",
|
||||||
|
)
|
||||||
|
render_table(
|
||||||
|
"Redis:SET/GET(各持久化模式)",
|
||||||
|
redis_rows,
|
||||||
|
lambda r: f"{r['strategy']} ({r['persistence']})",
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(readme_path, "a", encoding="utf-8") as f:
|
||||||
|
f.write("\n".join(lines) + "\n")
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
require_cmd python3
|
||||||
|
require_cmd rg
|
||||||
|
require_cmd ss
|
||||||
|
require_cmd redis-server
|
||||||
|
require_cmd redis-cli
|
||||||
|
ensure_binaries
|
||||||
|
|
||||||
|
run_kv_case "persist_mypool" "incremental" "mypool"
|
||||||
|
run_kv_case "nopersist_mypool" "none" "mypool"
|
||||||
|
run_kv_case "persist_malloc" "incremental" "malloc"
|
||||||
|
run_kv_case "nopersist_malloc" "none" "malloc"
|
||||||
|
|
||||||
|
run_redis_case "none" "none" 6381 "\"\"" "no" "everysec"
|
||||||
|
run_redis_case "rdb_default" "rdb_default" 6382 "900 1 300 10 60 10000" "no" "everysec"
|
||||||
|
run_redis_case "aof_no" "aof_no" 6383 "\"\"" "yes" "no"
|
||||||
|
run_redis_case "aof_everysec" "aof_everysec" 6384 "\"\"" "yes" "everysec"
|
||||||
|
run_redis_case "aof_always" "aof_always" 6385 "\"\"" "yes" "always"
|
||||||
|
|
||||||
|
append_readme_results
|
||||||
|
|
||||||
|
echo "DETAIL_CSV=$DETAIL_CSV"
|
||||||
|
echo "SUMMARY_CSV=$SUMMARY_CSV"
|
||||||
|
echo "README_UPDATED=$README_MD"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user