-
Notifications
You must be signed in to change notification settings - Fork 521
Open
Description
GC 无法回收空间:所有扇区 dirty 状态为 FALSE 时的问题
问题描述
当 FlashDB KV 数据库空间不足时,垃圾回收(GC)过程无法回收空间,因为 GC 只处理 dirty=2(FDB_SECTOR_DIRTY_TRUE)或 dirty=3(FDB_SECTOR_DIRTY_GC)状态的扇区。但在某些场景下,所有扇区都保持 dirty=1(FDB_SECTOR_DIRTY_FALSE)状态,导致 GC 跳过所有扇区,无法释放空间。
根本原因分析
-
扇区 dirty 状态初始化:当扇区被格式化时(例如 GC 后或初始化时),它们被设置为
FDB_SECTOR_DIRTY_FALSE(dirty=1)。 -
扇区 dirty 标记失败:当调用
fdb_kv_set_blob时,如果new_kv_ex因空间不足而失败,函数会立即返回FDB_SAVED_FULL,而不会调用del_kv来标记扇区为脏。这意味着:- 旧的 KV 可能已被标记为
FDB_KV_PRE_DELETE,但它们的扇区从未被标记为脏 - 包含已删除/垃圾 KV 的扇区保持
dirty=1状态
- 旧的 KV 可能已被标记为
-
GC 处理限制:
do_gc函数只处理dirty=2或dirty=3的扇区:if (sector->check_ok && (sector->status.dirty == FDB_SECTOR_DIRTY_TRUE || sector->status.dirty == FDB_SECTOR_DIRTY_GC)) { // 处理 GC }
这导致所有
dirty=1的扇区被跳过,即使它们包含可以回收的垃圾 KV。
复现步骤
- 用参数填满 KV 数据库,直到接近满载
- 持续更新参数(例如
fdb_kv_set_blob) - 当空间不足时,
new_kv_ex失败 - GC 被触发,但由于所有扇区都是
dirty=1,所有扇区被跳过 - GC 完成但没有释放任何空间
- 后续的
fdb_kv_set_blob调用继续失败,返回FDB_SAVED_FULL(err=7)
预期行为
GC 应该能够从包含垃圾 KV 的扇区回收空间,即使这些扇区没有被标记为脏。这在空间不足且没有脏扇区可用时尤其重要。
实际行为
GC 跳过所有 dirty=1 的扇区,即使它们包含垃圾 KV,导致:
do_gc: SKIP sector=0x..., dirty=1, store=2, DIRTY_TRUE=2, DIRTY_GC=3gc_collect_by_free_size: GC completed, free_size=XX(但实际上没有释放空间)new_kv: FAILED after GC, size=XX, KV full
建议的解决方案
修改 do_gc 函数,使其在空间不足时也能处理处于 USING 或 FULL 状态的非脏扇区,因为它们可能包含垃圾 KV:
static bool do_gc(kv_sec_info_t sector, void *arg1, void *arg2)
{
// ... 现有代码 ...
bool should_gc = false;
/* 处理脏扇区(正常情况) */
if (sector->check_ok && (sector->status.dirty == FDB_SECTOR_DIRTY_TRUE || sector->status.dirty == FDB_SECTOR_DIRTY_GC)) {
should_gc = true;
}
/* 当空间不足时,也处理非脏的 USING/FULL 扇区(它们可能包含垃圾) */
else if (sector->check_ok && sector->status.dirty == FDB_SECTOR_DIRTY_FALSE
&& (sector->status.store == FDB_SECTOR_STORE_USING || sector->status.store == FDB_SECTOR_STORE_FULL)) {
should_gc = true;
}
if (should_gc) {
// ... 现有的 GC 处理代码 ...
}
}这确保了:
- 有效的 KV(
FDB_KV_WRITE或FDB_KV_PRE_DELETE)被正确移动到新空间 - 垃圾 KV 被跳过,扇区被格式化以释放空间
- 空扇区不会被不必要地处理
安全性分析
此修改是安全的,因为:
- 有效的 KV 通过现有的
move_kv逻辑被正确移动 - 只处理
USING或FULL扇区,不处理EMPTY扇区 - 垃圾 KV 被正确跳过和清除
- GC 过程已经受到锁保护
额外说明
此问题是在生产环境中发现的,当时参数更新开始间歇性失败。通过调用恢复默认值函数可以暂时解决问题,但一段时间后会再次出现。
日志示例
显示问题的日志输出示例:
[KV_TRACE] do_gc: SKIP sector=0x00000000, check_ok=1, dirty=1, store=2, DIRTY_TRUE=2, DIRTY_GC=3
[KV_TRACE] do_gc: SKIP sector=0x00001000, check_ok=1, dirty=1, store=1, DIRTY_TRUE=2, DIRTY_GC=3
[KV_TRACE] do_gc: SKIP sector=0x00002000, check_ok=1, dirty=1, store=2, DIRTY_TRUE=2, DIRTY_GC=3
[KV_TRACE] gc_collect_by_free_size: GC completed, free_size=39
[KV_TRACE] new_kv: FAILED after GC, size=39, KV full
相关代码位置
- 文件:
fdb_kvdb.c - 函数:
do_gc(约第 1190 行) - 相关函数:
gc_collect_by_free_size、del_kv、set_kv
Metadata
Metadata
Assignees
Labels
No labels