# kvid 设计文档
## 概述
kvid 是一个基于 KVRocks 的分布式自增 ID 生成器,采用双号段设计,实现高性能、低延迟的 ID 分配。
## 核心设计
### 双号段机制
```
┌─────────────┐ ┌─────────────┐
│ 当前号段 │ │ 缓冲号段 │
│ id → max │ │ next: Option│
│ │ │ <Seg> │
└─────────────┘ └─────────────┘
↓ 用尽时切换 ↑ 取走即填充
```
1. 维护一个 `Option<Seg>` 缓冲号段
2. 每次取走缓冲号段后,立即异步填充新的号段
3. 当前号段用尽时,无缝切换到缓冲号段
4. 简单直接:取走就填充,始终保持缓冲可用
### 动态步长调整
根据上次步长和时间间隔动态计算新 step:
```
step = prev_step * PRELOAD_SEC / elapsed
```
- `prev_step`: 上次 fetch 的步长
- `elapsed`: 两次 fetch 之间的时间间隔(秒)
- `PRELOAD_SEC`: 预加载时长(默认 60 秒)
这样可以根据业务负载自动调整,高峰期获取更多 ID,低谷期减少浪费。
## 数据结构
### KvId 成员变量
| `name` | `SmolStr` | ID 名称,用作 KVRocks hash 的 field |
| `fast` | `Fast` | 快速路径状态,原子操作无锁访问 |
| `slow` | `Mutex<Slow>` | 慢路径状态,包含缓冲号段和统计信息 |
### Fast 结构(无锁快速路径)
通过 `Fast::new()` 常量函数初始化,每个 `KvId` 实例拥有独立的原子变量。
| `id` | `AtomicU64` | 当前号段已分配的 ID |
| `max` | `AtomicU64` | 当前号段的最大值 |
| `lock` | `AtomicBool` | 填充锁,防止重复填充 |
### Slow 结构(锁保护慢路径)
通过 `Slow::new()` 常量函数初始化。
| `next` | `Option<Seg>` | 缓冲号段,取走即填充 |
| `step` | `u64` | 当前步长(每次从 KVRocks 获取的 ID 数量) |
| `ts` | `u64` | 上次 fetch 的时间戳 |
### Seg 结构
| `id` | `u64` | 号段起始 ID |
| `max` | `u64` | 号段结束 ID |
## 执行流程
```
next() 调用
│
▼
┌─────────────────┐
│ 快速路径 CAS │ ← 大多数请求走这里,无锁
│ id < max ? │
└────────┬────────┘
│ 是
▼
返回 id+1
缓冲为空? → 异步填充
│
│ 否(号段用尽)
▼
┌─────────────────┐
│ 慢路径(加锁) │
│ 1. 再次尝试 CAS │
│ 2. 切换缓冲号段 │ → 异步填充
│ 3. 同步 fetch │ → 异步填充
└─────────────────┘
```
## 性能优化
1. **无锁快速路径**: 使用 `AtomicU64` + CAS (`compare_exchange_weak`),绝大多数请求无需加锁
2. **缓冲填充机制**: 取走即填充,始终保持缓冲可用,避免同步等待
3. **动态步长**: 根据负载自动调整,平衡性能和 ID 浪费,范围 [1, 1000000]
4. **最小化锁范围**: 只在号段切换时短暂持锁,锁不跨越 await 点
5. **结构分离**: Fast/Slow 分离,快速路径完全无锁
6. **parking_lot**: 使用 `parking_lot::Mutex` 替代标准库,性能更优
## 常量定义
| `PRELOAD_SEC` | 60 | 预加载时长(秒) |
| `STEP_MIN` | 1 | 最小步长 |
| `STEP_MAX` | 1000000 | 最大步长 |
| `KVID_KEY` | "kvid" | Redis hash key |
## 错误处理
| `Empty` | 号段为空(理论上不应发生) |
| `StepOverflow` | step 超出 i64 范围 |
| `Kv` | Redis/KVRocks 操作错误 |
## KVRocks 存储
使用 Hash 结构存储:
```
Key: "kvid"
Field: {name}
Value: {当前最大 ID}
```
通过 `HINCRBY` 原子操作获取新号段,保证分布式环境下的唯一性。