-
Notifications
You must be signed in to change notification settings - Fork 1
feat(storage): MemTable 字节级令牌桶写背压 #125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # M2 · 字节级令牌桶写背压 —— 设计与结果 | ||
|
|
||
| > 承接 M1 发现的「MemTable 写入无背压 → 内存随负载无界增长」。本步给写路径加**字节级令牌桶背压**,并诚实标注它能解决什么、不能解决什么。 | ||
|
|
||
| ## 设计 | ||
|
|
||
| - 新增可复用组件 `pkg/credit`:字节信用池 `Pool`(`Acquire`/`TryAcquire`/`Release`/`Used`)。信用不足时 `Acquire` 阻塞,直到持久化方 `Release` 归还。预算 `<=0` 关闭背压。 | ||
| - `MemTable` 接入: | ||
| - `SkipList` 维护 `byteSize`(覆盖写按新旧 value 差值增量,防漂移)。 | ||
| - `Put`/`Delete` 写入前 `acquireCredit(full)`:`TryAcquire` 失败则先 `StartFlush` 再阻塞 `Acquire`,避免永久阻塞。 | ||
| - 覆盖写实际增量 < 预占额时,归还多占部分(信用与 `byteSize` 对账,不泄漏)。 | ||
| - `Flush` 成功后 `Release(dirty.byteSize)` 归还信用并唤醒被阻塞的写。 | ||
| - `Flush` 失败:保留 dirty(不丢数据)并重新触发刷盘重试;修掉了原 `Flush` 在失败后被下次刷盘覆盖 dirty 的隐患。 | ||
| - 预算配置:`config.MemTableMaxInflightBytes`,默认 64MiB。 | ||
|
|
||
| ## 验证(A 方案:阻塞、0 丢帧) | ||
|
|
||
| ### 背压确实绑定并精确封顶(关掉条数触发,让 MemTable 成为瓶颈) | ||
| `-memtable 1e8 -sat 5s`: | ||
|
|
||
| | | 吞吐 | inflight 峰值 | heap 峰值 | | ||
| |---|---|---|---| | ||
| | 背压关闭 `-budget 0` | 1,768,172 w/s | **784.2 MiB(无界)** | 2,014.7 MiB | | ||
| | 背压 `-budget 16MiB` | 1,225,599 w/s | **16.0 MiB(精确卡在预算)** | 173.4 MiB | | ||
|
|
||
| → 未刷盘字节被**精确限制在 16.0MiB = 预算**,堆从 2GiB 降到 173MiB。代价是吞吐被限到 flush 速率(1.77M→1.23M)——**这正是背压的本意**:源跑赢 flush 时,限速写入而非撑爆内存。`pkg/credit` 有单测覆盖(阻塞/解除、超大单条放行、预算关闭、对账归零)。 | ||
|
|
||
| ### 复现命令 | ||
| ```powershell | ||
| go run ./benchmark/ingest/ -budget 0 -sat 5s -d 1s -rates 1000 -memtable 100000000 | ||
| go run ./benchmark/ingest/ -budget 16777216 -sat 5s -d 1s -rates 1000 -memtable 100000000 | ||
| ``` | ||
|
|
||
| ## 诚实边界:背压不是 M1 那个增长的解药 | ||
|
|
||
| 默认小值配置(`MaxMemTableSize=4096`)下,**按条数刷盘(每 4096 条 ≈ 0.37MiB)远早于字节预算触发**,所以背压平时不绑定。实测 100k/200kHz 跑 60s: | ||
|
|
||
| | 速率 | 丢帧 | inflight 峰值 | heap 峰值 | | ||
| |---|---|---|---| | ||
| | 100 kHz | 0 | 0.4 MiB | 121 MiB | | ||
| | 200 kHz | 3,918 | 0.4 MiB | 265 MiB | | ||
|
|
||
| `inflight_peak` 仅 0.4MiB(≈一个 4096 条的表),**未刷盘量根本不大**。但 heap 仍涨到 265MiB——说明 **M1 看到的内存增长不在 MemTable,而在 SSTable 元数据 / bloom 过滤器随文件数累积**(12M 条 / 4096 ≈ 2900 个 SSTable,每个一份常驻 bloom + 索引)。 | ||
|
|
||
| **结论**: | ||
| - 背压的角色是**内存硬上限 / 安全网**——当 value 很大或 `MaxMemTableSize` 很高、条数触发不及时,它兜底封顶(已证)。 | ||
| - 它**不解决**小值高频下的内存增长,那是 SSTable 元数据/compaction 问题,是**下一条线**(M3 候选):核查 compaction 是否跟得上文件增长、bloom/索引常驻内存能否随 compaction 收敛。 | ||
|
|
||
| ## 200kHz 丢帧说明 | ||
| 开背压后 200kHz/60s 出现 3,918 丢帧,是因为消费者写入被背压/flush 节流,生产者有界队列(qdepth=1024)溢出——属预期的"源超过可持续速率"行为,非引擎崩溃。 |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,66 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Package credit 提供字节级信用池(令牌桶式背压):写入方 Acquire 占用字节信用, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 持久化方 Release 归还信用;信用不足时 Acquire 阻塞,从而把未持久化数据的内存占用 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 限制在预算之内。它本身与具体存储无关,可被任何"写入快、落盘慢"的路径复用。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package credit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import "sync" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Pool 是一个按字节计量的阻塞式信用池。零值不可用,请用 New 构造。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Pool struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mu sync.Mutex | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cond *sync.Cond | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| budget int64 // <=0 表示不限制(背压关闭) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| used int64 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // New 构造一个预算为 budget 字节的信用池;budget <= 0 表示不限制。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func New(budget int64) *Pool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p := &Pool{budget: budget} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.cond = sync.NewCond(&p.mu) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return p | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // fits 报告在已用 used 之上再占 n 是否允许(调用者须持锁)。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 规则:预算关闭、或池空(保证至少一条能写入,避免超大单条永久阻塞)、或不超预算 → 允许。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (p *Pool) fits(n int64) bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return p.budget <= 0 || p.used == 0 || p.used+n <= p.budget | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TryAcquire 不阻塞地尝试占用 n 字节信用,成功返回 true。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (p *Pool) TryAcquire(n int64) bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.mu.Lock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defer p.mu.Unlock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if p.fits(n) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.used += n | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Acquire 占用 n 字节信用;信用不足时阻塞,直到他方 Release 释放出空间。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (p *Pool) Acquire(n int64) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.mu.Lock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defer p.mu.Unlock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for !p.fits(n) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.cond.Wait() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.used += n | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Release 归还 n 字节信用并唤醒所有等待者。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (p *Pool) Release(n int64) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.mu.Lock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.used -= n | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if p.used < 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.used = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.mu.Unlock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.cond.Broadcast() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+29
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reject negative credit amounts at the API boundary.
Suggested fix+func requireNonNegative(n int64) {
+ if n < 0 {
+ panic("credit: negative amount")
+ }
+}
+
// TryAcquire 不阻塞地尝试占用 n 字节信用,成功返回 true。
func (p *Pool) TryAcquire(n int64) bool {
+ requireNonNegative(n)
p.mu.Lock()
defer p.mu.Unlock()
if p.fits(n) {
@@
// Acquire 占用 n 字节信用;信用不足时阻塞,直到他方 Release 释放出空间。
func (p *Pool) Acquire(n int64) {
+ requireNonNegative(n)
p.mu.Lock()
defer p.mu.Unlock()
for !p.fits(n) {
@@
// Release 归还 n 字节信用并唤醒所有等待者。
func (p *Pool) Release(n int64) {
+ requireNonNegative(n)
p.mu.Lock()
p.used -= n📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Used 返回当前已占用字节数。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (p *Pool) Used() int64 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.mu.Lock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defer p.mu.Unlock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return p.used | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't clobber the configured inflight budget when
-budgetis omitted.This flag defaults to a hard-coded
64<<20, and Line 37 writes it back unconditionally, so anyMemTableMaxInflightBytesvalue loaded from config is lost unless the caller passes-budgetexplicitly.Suggested fix
🤖 Prompt for AI Agents