Skip to content

[improvement](fe) Optimize recycle bin lock granularity - #90

Merged
wenzhenghu merged 1 commit into
branch-3.1from
wzh/branch-3.1-backport-recycle-bin-lock-optimization
Aug 31, 2026
Merged

wenzhenghu merged 1 commit into
branch-3.1from
wzh/branch-3.1-backport-recycle-bin-lock-optimization

Conversation

@wenzhenghu

@wenzhenghu wenzhenghu commented Aug 29, 2026 •

Copy link
Copy Markdown

Proposed changes

Issue Number: N/A

Related PR: apache#55753, apache#61366

Production issue

This backport is introduced to resolve a DDL latency incident observed in production. CREATE TABLE and DROP TABLE operations could take several minutes. In one DROP TABLE example, the timing breakdown attributed about 149,520 ms to the unprotectDropTable/getRecycleTimeById section, while the database and table write-lock acquisition itself took 0 ms.

The investigation indicates that CatalogRecycleBin cleanup can hold the recycle-bin lock for a long time while scanning and erasing many recycled objects. A DROP operation may wait for this lock after it has already acquired database or table locks, so the recycle-bin lock wait can be propagated to those metadata locks and delay concurrent DDL operations, including CREATE TABLE.

The purpose of this PR is to reduce both the recycle-bin scan cost and the duration of each exclusive lock hold, resolving the production issue and preventing the same lock-contention amplification from recurring on branch-3.1.

This PR backports the recycle-bin performance and lock-granularity optimizations from Apache Doris PRs apache#55753 and apache#61366 to branch-3.1.

Changes

  • Cache recycled database, table, and partition IDs by name to avoid repeatedly scanning all recycled metadata during each daemon cycle.
  • Replace the CatalogRecycleBin monitor with ConcurrentHashMap and ReentrantReadWriteLock.
  • Collect cleanup candidates under the read lock and erase them one object at a time, releasing the write lock between objects.
  • Rebuild the derived name caches after all branch-3.1 image and Gson deserialization paths.
  • Keep cache entries consistent during recycle, erase, recover, and replay operations.
  • Fix cache cleanup when recovering a database without an explicit ID and when recovering or replaying a renamed partition.

branch-3.1 compatibility

  • Preserve the legacy FeMetaVersion image-reading paths and GsonPostProcessable behavior.
  • Adapt calls to the branch-3.1 two-argument onEraseOlapTable API.
  • Do not include master-only cloud visible-version updates.
  • No journal format, configuration, or external API changes.

Known upstream issues

The following issues exist in the upstream implementation and are inherited by this backport. They are not introduced by the branch-3.1 compatibility adaptations in this PR. Both remain present on the latest Apache Doris master and are tracked in apache/doris#67303.

  1. P1 — stale expired-ID snapshots can erase a newly recycled generation. Apache Doris [Enhancement](recyclebin) Optimize lock granularity in CatalogRecycleBin apache/doris#61366 introduced the microbatch pattern that collects expired IDs under a read lock and later erases the current entry for each ID under a write lock. If an entry is recovered and dropped again between those steps, the same metadata ID can refer to a new Recycle*Info with a fresh recycle timestamp. Because the erase path does not validate the object generation or recheck expiration after reacquiring the write lock, it can erase the newly recycled database, table, or partition and persist the wrong erase operation.
  2. P2 — same-name database cascade cleanup still holds the recycle-bin write lock while erasing all child tables. This lock scope predates [Enhancement](recyclebin) Optimize lock granularity in CatalogRecycleBin apache/doris#61366 and was not fully microbatched by it. eraseDatabaseWithSameName() still calls eraseAllTables() under one write-lock acquisition, including the full table scan, per-table cleanup, and erase journal writes. A database with many recycled tables can therefore still block concurrent recycle/DROP operations for the duration of the whole cascade.

Until the upstream fixes are implemented and backported, this PR reduces the dominant lock contention but does not eliminate these two correctness and lock-amplification risks.

Check List

  • Unit Test: ./run-fe-ut.sh --run org.apache.doris.catalog.CatalogRecycleBinTest
    • 29 tests, 0 failures, 0 errors
  • Checkstyle: mvn -pl fe-core checkstyle:check -DskipTests
    • 0 violations
  • git diff --check

Behavior changed: Yes. Recycle-bin cleanup uses cached same-name groups and releases its write lock between individual objects.

Does this need documentation: No

### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#55753, apache#61366

Problem Summary: Backport same-name recycle-bin caches and microbatch cleanup locking to reduce FE metadata lock contention during recycle-bin cleanup.

### Release note

Reduce FE metadata lock contention during recycle-bin cleanup.

### Check List (For Author)

- Test: Unit Test
    - ./run-fe-ut.sh --run org.apache.doris.catalog.CatalogRecycleBinTest
    - mvn -pl fe-core checkstyle:check -DskipTests
- Behavior changed: Yes. Recycle-bin cleanup now uses cached same-name groups and releases the write lock between individual objects.
- Does this need documentation: No
@wenzhenghu

Copy link
Copy Markdown
Author

静态 Code Review:improvement Optimize recycle bin lock granularity

依据 .claude/skills/code-review/SKILL.md 与仓库 AGENTS.md 规范做的纯静态 review(未运行 UT/回归测试)。已将 PR 版本与 branch-3.1 基线、上游 master、以及 apache#61366 的官方 branch-4.0 backport(apache#61674)逐段对比。

总体结论

✅ 建议合入。 这是对上游 apache#55753 + apache#61366 的高质量、忠实回移植(与官方 branch-4.0 backport apache#61674 逐行对齐),直接解决生产事故根因(回收站单一 monitor 长持锁 → DROP/CREATE 元数据锁等待放大)。无阻断级问题;问题清单中的低危项可作为后续跟进。

问题清单

  • 1【低-中】非公平读写锁,与上游 master 不一致:CatalogRecycleBin.java:71 为 new ReentrantReadWriteLock()(非公平);上游 master 当前为公平锁。非公平 RW 锁在持续读负载下写线程理论上有饥饿风险(write() checkpoint dump、getInfo() 持读锁较久)。缓解:branch-4.0: [Enhancement](recyclebin) Optimize lock granularity in CatalogRecycleBin #61366 apache/doris#61674 同样是非公平锁,即本 PR 与其回移植目标版本一致(公平锁是 master 在 [Enhancement](recyclebin) Optimize lock granularity in CatalogRecycleBin apache/doris#61366 之后的独立演进),且回收站读操作低频。建议作为后续跟进项改为公平锁,与本 PR 合并无阻塞。
  • 2【低】eraseDatabaseInstantly/eraseTableInstantly 的 "Unknown id" 判定失去原子性(:1207/:1263):基线在单个 synchronized 方法内判定;PR 拆成"写锁段 → 读锁收集段 → 判定",判定发生在所有锁释放之后。若收集后、判定前同 dbId 的对象又被并发回收进站,会误抛 DdlException("Unknown database id ...")。触发需要"同 id 二次 drop force"的极窄并发窗口,branch-4.0: [Enhancement](recyclebin) Optimize lock granularity in CatalogRecycleBin #61366 apache/doris#61674 引入相同结构(上游同样接受此权衡)。影响仅偶发误报,无状态损坏。
  • 3【低】recoverPartition(非 replay 路径)异常路径下缓存 key 与对象名短暂不一致(:1045 setName → :1060 remove 之间):若两点间抛异常,partition 已改名但仍留在 idToPartition,缓存 key 是旧名。经核对两点间无现实异常路径(checkPartitionNameExist 已前置检查);即使发生,过期擦除路径(erasePartition 第 1 步直接扫 idToPartition,不依赖缓存)最终会清理,不会永久泄漏。仅作记录。
  • 4【信息】测试覆盖缺口:缺 table 级 same-name keepNum 测试(db/partition 级均有,eraseTableWithSameName 的 dbIdTableNameToIds 分支未被直接覆盖);缺非 replay 的 recoverTable/recoverPartition 带重命名的缓存清理断言(replay 版本 partition 有 testReplayRecoverPartitionWithNewNameClearsNameCache);缺多同名 db 下 dbId == -1 选择最新 + 缓存清理组合断言;testMicrobatchEraseReleasesLockBetweenItems 用 Thread.sleep(50) 而非 latch 精确同步(有 eraseStarted 标志保护,与上游 branch-4.0: [Enhancement](recyclebin) Optimize lock granularity in CatalogRecycleBin #61366 apache/doris#61674 相同写法,可接受但偏弱)。
  • 5【信息】pre-existing / 上游后续演进(非本 PR 引入,不建议在本 PR 修复):replayRecoverDatabase 中 idToDatabase.get(dbId) 为 null 时会 NPE——基线行为完全相同;master 后续演进未含于此 PR 声称范围:三参数 onEraseOlapTable + eraseAllTables 返回 boolean(cloud 场景保护)、可配置 daemon 间隔。branch-3.1 无 cloud erase task 机制,两参数适配正确。

关键检查点结论(逐项)

1. 锁顺序与死锁(最高优先)——无新增死锁环,事故根因被消除。

  • 擦除回调调用点逐一核对:onEraseOlapTable 6 处在回收站写锁内(eraseTable:475、eraseAllTables:412、eraseTableWithSameName:533、eraseTableInstantly:1223、replayEraseTable:572)、onErasePartition 4 处、Env.eraseDatabase 3 处——与上游 [Enhancement](recyclebin) Optimize lock granularity in CatalogRecycleBin apache/doris#61366 设计一致(写锁内每对象 cleanup + map removal + journal 写,对象间释放)。
  • 回调锁行为实证:Env.onEraseOlapTable(Env.java:6586-6603)= TabletInvertedIndex(自带锁)+ ColocateGroupIndex(自带锁)+ eraseTableDropBackendReplicas——后者(InternalCatalog.java:1107-1133)仅构造 DropReplicaTask 批量并 AgentTaskExecutor.submit 异步提交,无同步 RPC、无 db/table 元数据锁;onErasePartition 的 BE 清理为空实现;Env.eraseDatabase = CHM remove + journal,无元数据锁。
  • DROP 路径锁序:dropTableInternal 为 db.writeLock → table.writeLock → recycleTable(回收站写锁)。清理线程持回收站写锁期间不再获取任何 db/table 锁,不构成环。
  • innerRecoverTable:922 在回收站写锁内拿 table.writeLock——但该 table 对象已不在 catalog 中,与 DROP 路径锁的不是同一实例,无环(且为基线 pre-existing 模式);branch-3.1 的 Database.registerTable/getTableNullable 为无锁 CHM 操作。
  • 批量操作:无 eraseAll 类生产方法;clearAll 仅 UT 使用(生产零调用,已 grep 确认);eraseDatabaseInstantly 级联擦除已加 try-catch(:1178-1184/:1254-1260)防并发中断级联——正确。

2. 并发正确性——通过。 裸访问方法(getRecycleTimeById/setRecycleTimeByIdForReplay/isRecycle* 等)全部为 CHM 单操作,原子且弱一致语义可接受(与 apache#61366 的 lock-free 分层一致);setRecycleTimeByIdForReplay 裸 put 仅 replay 线程执行,无并发冲突。TOCTOU(候选收集与逐对象擦除之间对象被 recover/并发擦除)已正确处理——过期擦除路径用 remove() 返回值原子校验(:318-321/:469-472/:601-604),WithSameName 路径用 get() == null → continue。getIdListToEraseByRecycleTime(:720-735)用 getOrDefault(y, 0L) 处理快照与排序之间被移除的 stale id——apache#61366 自述的 NPE 修复已包含。弱一致迭代器:锁外遍历的集合均为 CHM,无 CME(testConcurrentRecycleAndRead 显式验证)。

3. 名字缓存一致性(数据正确性核心)——22 条路径全成对,无泄漏;两个自述修复点属实。 逐路径核对 recycle ↔ erase/recover/replay 各路径的缓存加入与清除全部成对(recycleDatabase:200 ↔ 6 个清除点;recycleTable:230 ↔ 7 个;recyclePartition:255 ↔ 6 个;clearAll 全清;rebuildRecycleBinCaches 全量重建)。多同名场景缓存值为 Set<Long> 集合(非单值覆盖),computeIfPresent + v.isEmpty() ? null : v 正确实现引用清理。两个自述修复点验证:① 重命名 partition 恢复——recoverPartition:1030 在 setName 之前记录 recycledPartitionName 并用于 :1066 清缓存,replay 路径同样先记录再改名后用旧名清(否则旧 key 永久泄漏),修复正确且有测试覆盖 replay 路径;② 无显式 ID 的 db 恢复——recoverDatabase:772-775 的缓存清理对 dbId==-1 与显式 id 均生效,正确。

4. 持久化与回放等价性——通过。 journal 内容与序列零变化(logEraseTable/logErasePartition 写入点与基线一一对应;db 擦除 journal 由 Env.eraseDatabase 内部写,与基线相同)。write()(:1569-1596)序列化格式与基线一致(缓存字段无 @SerializedName,不落盘),image 兼容。反序列化入口全覆盖 3 个:read() VERSION<136 → readFields(:1685-1686 重建);VERSION_136~139 → Gson 构造(gsonPostProcess:1648-1651 自动重建);VERSION≥139 → readFieldsWithGson:1630 显式重建。failover 任意时点回放:缓存是 map 的纯函数(rebuild 遍历重建),各 replay 方法的内存转换与 master 侧逐语句等价。

5. 崩溃一致性——通过,不劣于基线。 单对象擦除窗口内 remove → onErase → journal 写(与 apache#61674 一致);remove 后、journal 写前崩溃:重启后对象经旧 journal/image 回放仍在回收站,下轮重新擦除,各子操作幂等(invertedIndex.deleteTablet、colocate removeTable、重复 DropReplicaTask),无泄漏、无错误终态。minEraseLatency(10 分钟)保留(:67-69)。对象间释放锁的中间可见状态只影响 SHOW RECYCLE BIN 瞬时观感。

6. branch-3.1 兼容性——回移植忠实、无遗漏其声称范围。 与 apache#61674 逐段对比:锁分层(lock-free/读锁/写锁方法集合)、三段式 erase 结构、级联 try-catch、并发测试——全部逐行一致。两参数 onEraseOlapTable(OlapTable, boolean) 适配正确(全文件 6 处调用均两参);保留 branch-3.1 的 GsonPostProcessable 与 FeMetaVersion 三段式 image 读取。

7. 性能确认——直接消除事故根因。 ① getRecycleTimeById 完全无锁(基线 synchronized)——事故中 DROP 路径 149s 等待的直接来源被消除;② keepNum 检查从每轮 O(names×N) 全表扫描变为缓存 O(1) 查询 + 组内排序;③ 写锁持有时长从全量 O(N×T) 降为单对象 O(T)(纯内存遍历 + 异步 task 构造 + journal 写),对象间释放;④ 读锁下收集候选为 O(N) CHM 迭代,不阻塞其他读。

功能与稳定性维度

  • 功能:journal 序列与 image 格式零变化、回放等价、22 条缓存路径成对完备、多同名语义正确,两个自述修复点经代码级验证属实。通过。
  • 稳定性:无新增死锁环(擦除回调不获取 db/table 元数据锁、BE 副本清理异步提交——已逐行核实 Env.java/InternalCatalog.java);锁等待从分钟级降至单对象临界区;崩溃恢复窗口不劣于基线;级联擦除新增 try-catch 更健壮。残留风险为非公平锁理论写饥饿(与官方 backport 一致)和 eraseXxxInstantly 判定的极窄竞态。通过,附低危跟进项。
  • 测试静态评估:29 个 UT 覆盖核心不变量(非空 db 拒收、force drop 立即擦除、同名 keepNum 保留最新、image round-trip 缓存重建、replay 全系列、重命名 partition 缓存清理、CME 防护、microbatch 擦除期间可插入、inverted index 重建);并发测试用 latch/barrier 为主(1 个 sleep+标志与上游相同)。缺口见问题清单 4;死锁场景无法 UT 构造,由静态锁序分析保证。测试实际通过与否需 CI 验证(本次为纯静态 review)。

后续跟进建议(均不阻塞本 PR)

  1. 将 RW 锁改为公平模式,对齐 master;
  2. 补 table 级 same-name 与非 replay 重命名恢复的缓存清理测试;
  3. 如后续 branch-3.1 开启 cloud 化,跟进 eraseAllTables boolean 保护(master 机制)。

@wenzhenghu wenzhenghu added 3.1.4-hydcp-1.3 stability improvement Improvements to system stability, reliability, and failure recovery labels Aug 29, 2026

@Ryan19929 Ryan19929 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复审通过,Approve。

这是对上游 apache#55753 + apache#61366 的高质量忠实回移植,与官方 branch-4.0 backport apache#61674 结构一致。逐点核对结论:

  • 锁分层正确:读锁收集候选 + 逐对象写锁擦除的 microbatch 模式,无读锁→写锁升级路径;写锁内回调(Env.onEraseOlapTable/eraseDatabase 等)不获取 db/table 元数据锁、无同步 RPC,与 DROP 路径(db.writeLock→table.writeLock→回收站锁)不成死锁环。事故根因(getRecycleTimeById 等回收站 monitor)已由无锁 CHM get 消除。
  • 兼容性零风险:缓存与 lock 字段不参与序列化(HiddenAnnotationExclusionStrategy),image 与 journal 格式零变化;readFields/gsonPostProcess/readFieldsWithGson 三条反序列化路径均重建缓存,滚动升级覆盖完整。
  • 并发细节处理正确:过期路径用 remove() 返回值原子校验,WithSameName 路径 get()==null 复查 + isExpireMinLatency,getIdListToEraseByRecycleTime 用 getOrDefault 处理 stale id,无 CME。
  • 测试充分:29 个 UT 覆盖 recycle/recover/replay/force drop/同名 keepNum/image round-trip/并发读不阻塞等。

遗留两项为上游遗留问题(PR body 已如实披露、上游 issue 跟踪中),确认属实但不构成合入阻塞:

  1. 过期擦除路径(eraseDatabase/eraseTable/erasePartition)写锁内不复查 isExpire——RECOVER 后同 id 再 DROP 的极窄窗口内新对象可能被误擦除。后续修复可参照 WithSameName 路径的 isExpireMinLatency 复查。
  2. eraseDatabaseWithSameName → eraseAllTables 在单个写锁内全量扫描+逐表 journal 写,同名 db 下回收表极多时仍会长时间持锁。

建议后续跟进(不阻塞):锁改公平锁对齐 master;recoverPartition 异常窗口下旧名缓存 entry 可能残留至重启;补 table 级 same-name keepNum 测试。

@xuchenhao xuchenhao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:REQUEST_CHANGES。当前重构确实通过同名缓存和逐对象加锁降低了主要扫描与锁等待,但正常过期路径引入了可持久化的 generation/ABA race;此外新增的 microbatch 测试没有实际擦除对象,无法证明核心锁粒度保证。

Part 1.3 检查结论:

  1. 目标与证明:性能目标部分达成;ABA 破坏回收保留语义,核心 microbatch 测试未有效覆盖。
  2. 范围:两文件改动集中于 CatalogRecycleBin 重构与测试,规模大但主题一致。
  3. 并发:CHM、读写锁及多数缓存更新合理;expired-ID 两阶段处理存在 P1 generation race,未发现新的锁序死锁。
  4. 生命周期/SIOF:纯 Java 实例字段,无 SIOF;缓存的 recycle/erase/recover/replay/clear 生命周期基本闭合。
  5. 配置:未新增配置;修复时需在写锁下按当前 retention 配置重新判断过期。
  6. 兼容性:无 journal、存储格式或外部 API 变化;branch-3.1 两参数 onEraseOlapTable 适配正确。
  7. 并行路径:database/table/partition 三条普通过期路径都有同型 ABA,需一致修复;same-name 路径会重查最小保留时间,不等同于该问题。
  8. 条件:keepNum/min-latency 语义基本保留;普通过期写锁区缺 generation 与 expiration 重验。
  9. 测试:新增 29 个 UT 覆盖缓存、恢复、replay 和 image,但缺三类 ABA 确定性测试,microbatch 锁测试当前无效。
  10. 可观测性:现有 erase 日志包含对象 ID,内部优化暂不强制新增 metric。
  11. 事务与持久化:journal 类型及 replay 对称;P1 会把错误 generation 的 erase 持久化,是阻断点。
  12. 写入/原子性/崩溃:单对象 callback/remove/journal 顺序基本沿用基线,但 ABA 的错误结果会在 failover 后保留。
  13. FE-BE 参数:无新增 thrift 或 FE-BE 变量传递。
  14. 性能:正常清理明显改善;same-name DB cascade 长写锁为基线已有且本 PR 未消除的残余风险,不作为本 diff 的新回归。
  15. 其他:image 三条加载路径均会重建派生缓存;重命名 partition 使用旧名称清缓存,未发现额外 Gson/cache 阻断问题。

验证边界:固定审查 head 2dd68f1、base 59380e3;完整检查两处 changed files、base 对照、调用链、历史意见、上游 issue 与 workflow steps,git diff --check 通过。独立 worktree 缺 .worktree_initialized、hooks/setup_worktree.sh 和 thirdparty/installed,因此未运行 FE 构建或 UT;作者声明的 29 个 UT/checkstyle 未在本地复现。CI 中 formatter×2、large-file×2、license 成功;Sonar Java 失败于 Setup thrift,Sonar C++ 失败于 Set up job。

for (Long dbId : expiredIds) {
writeLock();
try {
RecycleDatabaseInfo dbInfo = idToDatabase.remove(dbId);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] 写锁下需校验快照对应的 generation。这里在读锁阶段只保存 dbId,释放锁后直接 remove 当前映射。可达交错为:daemon 快照旧的过期条目 → 用户 recover → 同一对象再次 drop,以相同 metadata ID 写入新的 RecycleDatabaseInfo 和新时间戳 → daemon 根据旧 ID 删除当前的新条目。随后 eraseDatabase(..., true) 会持久化错误擦除,使刚 drop 的数据库绕过保留期且无法恢复;table/partition 的 line 469/601 存在相同问题。候选需同时保存 Recycle*Info identity 和 recycleTime,重新获得写锁后确认两者仍匹配并再次校验过期,再执行 callback/remove/journal;三条路径应一致修复。

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

范围纠正:这条技术判断本身成立,但 PR 描述已明确将其列为 apache#61366 继承、由 apache#67303 跟踪的 upstream 已知问题,并要求本次回移植审查不以此阻断。我之前忽略了该审查边界,现撤回本条作为 P1 blocker;已重新提交 APPROVE。本线程仅保留为已知风险记录。


Thread eraseThread = new Thread(() -> {
eraseStarted.set(true);
recycleBin.runAfterCatalogReady();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 让测试真正进入逐对象擦除区间。这里运行 daemon 时,前面创建的 partition 都是新鲜条目且名称互不相同:默认 catalog_trash_expire_second 为 86400,每个 same-name group 大小为 1、低于默认 keepNum 3;runningUnitTest 只绕过 isExpireMinLatency,不会让 isExpire 成立。因此该调用不会擦除任何 item,50ms 后 recyclePartition 成功不能证明写锁在 item 间释放。请构造真实 eligible 条目,并用 latch/hook 在第一次擦除 callback 内确定性阻塞后验证并发 writer。

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

优先级纠正:该测试确实没有证明逐对象擦除期间释放锁,但这只构成测试证明力不足,未显示当前回移植存在新的生产缺陷。在排除 PR 描述已接受的 upstream 问题后,它不足以阻断合入,现降为非阻断 P3,并已重新提交 APPROVE。

@xuchenhao xuchenhao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

更正上一次 review:重新按 PR 描述的 “Known upstream issues” 审查边界核对后,结论应为 APPROVE。

我此前把 stale expired-ID generation race 作为本次 P1 blocker,实际重复了 PR 描述已明确列出、由 apache#61366 引入并由 apache#67303 跟踪的 upstream 已知问题。它并非 branch-3.1 兼容适配新增问题,应按本 PR 已声明的范围排除。原 microbatch 测试确实没有证明逐项释放锁,但这只是测试证明力不足;在生产实现忠实回移植且已有其他验证的前提下,降为非阻断 P3。

按 Part 1.3 重新结论:

  1. 目标:同名缓存和逐对象锁显著降低主要扫描/锁等待,符合回移植目标。
  2. 范围:两文件集中于 CatalogRecycleBin 与测试。
  3. 并发:CHM、读写锁及缓存更新与 upstream 实现一致;已声明 upstream ABA 不在本次阻断范围。
  4. 生命周期:recycle/erase/recover/replay/clear 的派生缓存生命周期闭合。
  5. 配置:未新增配置,保留现有 retention 行为。
  6. 兼容性:无 journal/API 格式变化,branch-3.1 两参数回调适配正确。
  7. 平行路径:database/table/partition、same-name、recover/replay 均已核对。
  8. 条件:keepNum、min latency 与旧行为保持;已知 generation 风险按描述另行跟踪。
  9. 测试:缓存、恢复、replay/image 有覆盖;microbatch 锁测试未命中真实 erase,作为 P3 后续补强。
  10. 可观测性:erase 日志含对象 ID,无新增 blocker。
  11. 持久化:journal/replay 格式与路径保持兼容。
  12. 写入/崩溃:未发现 branch-3.1 适配新增风险。
  13. FE/BE 参数:不涉及。
  14. 性能:缓存与 microbatch 改善主要锁竞争;same-name cascade 长锁是描述中已知残余。
  15. 其他:三条 image 加载路径重建缓存,rename 清理正确;无其他 P0–P3。

此前两条 inline 将分别回复并 resolve:generation 问题改为已接受 upstream 限制,测试意见降为 P3。静态完整文件、base 与 diff 已核对;作者 UT/checkstyle 属作者证据,本轮未重跑 FE 构建或 UT。

@wenzhenghu
wenzhenghu merged commit c2f3499 into branch-3.1 Aug 31, 2026
8 of 12 checks passed
@wenzhenghu

Copy link
Copy Markdown
Author

公平锁偏差分析(P3,comment 级,非阻塞)

基于 branch-3.1@28387f7203b(本 PR 合入后)的实际代码复核。

代码事实(CatalogRecycleBin.java)

  • L71:private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); —— 默认非公平模式
  • 读锁持有方(读读并发):
    • write(DataOutput) L1570 —— image 序列化(Env.java:2724 全量 dump 回收站 4 个 map,持锁时长与回收站元数据量成正比)
    • getDbToRecycleSize() L1523 —— 被 ShowDataStmt(SHOW DATA)与 PrometheusMetricVisitor:345(指标抓取,生产环境周期性高频)调用
    • getInfo() L1400(SHOW PROC)、allTabletsInRecycledStatus() L117(DECOMMISSION 路径)
  • 写锁方:recycleTable / recover / erase 等全部 DDL 突变 + 回收清理 daemon

偏差分析

  1. 写者最坏等待变长但无饥饿:旧 synchronized 下写者只等当前单个持锁者;新非公平 RW 锁下,写者需等当前所有并发读者退出。JDK 非公平 ReentrantReadWriteLock 在写者排队后会禁止新读者插队(NonFairSync.readerShouldBlock() = apparentlyFirstQueuedIsExclusive()),因此无写饥饿,最坏等待 ≈ image dump 时长——与旧 coarse mutex 的等待等价,无回归。
  2. 读流量与写者的竞争偏差:Prometheus 周期抓取 + image dump 两类长读并存时,回收清理 daemon(默认 30s 周期)的写锁获取延迟波动增大;因清理本身非延迟敏感(有 minEraseLatency 10 分钟下限),实际影响可忽略。
  3. 可选优化(非必须):若超大回收站 + 高频抓取成为常态,getDbToRecycleSize() 可改为无锁快照/缓存,缩短读持有时间。

结论

非公平锁选择合理(与旧 synchronized 公平性语义等价,均无饥饿风险),无 P0/P1/P2 问题,记录为 P3 观察项。


  • 严重程度:P3
  • 解决方案:comment(无需代码修改)
  • 回归分析:无回归——写者最坏等待与旧 coarse mutex 等价;非公平模式存在 JDK 级防饥饿保护(排队写者阻塞新读者插队)

wenzhenghu added a commit that referenced this pull request Sep 10, 2026
…ns (#103)

### What problem does this PR solve?

Issue Number: None

Related PR: #90

Problem Summary: Recycle-bin expiration scans retained only object IDs. A recover and re-drop between the scan and per-item erase could make a stale candidate erase the newly recycled generation.

### Release note

Prevent stale recycle-bin expiration candidates from erasing newly recycled databases, tables, or partitions.

### Check List (For Author)

- Test: Unit Test
    - CatalogRecycleBinTest: 33 tests passed; the three new ABA interleaving tests also passed in a focused rerun.
- Behavior changed: Yes. Stale expiration candidates are skipped after a recycle generation changes.
- Does this need documentation: No.
wenzhenghu pushed a commit that referenced this pull request Oct 8, 2026
…8689)

### What problem does this PR solve?

Related PR: apache#68687

Pin lance-c to merged upstream main commit
`cd63420bfbe27f6f0a1edcc873b9191af7d52852`, including the
distance-bounded search (#90), independent batch nearest search (#92),
and boolean scalar-segment predicates (#93) changes. Update the archive
checksum and refresh the existing Foyer patch against that base with an
unchanged implementation payload.

Foyer PR
[lance-format/lance-c#73](lance-format/lance-c#73)
is still open. Its patch must remain temporarily because Doris already
calls its cache APIs; it can be removed after upstream merge.

This PR contains the dependency pin/Foyer refresh and its build
integration: `build.sh`, `thirdparty/build-thirdparty.sh`,
`thirdparty/lance-install.sh`, and the installation regression harness.
The build checks a source/archive/patch fingerprint before reusing
Lance, invalidates it during publication, and rejects stale external
build output. Before removing an existing installation, it verifies
required rebuild inputs and rejects external definitions that differ
from the checkout. The third-party CI script job runs the installation
harness under the existing workflow path filters. FE predicate changes
and SQL fixtures/tests from apache#68687 are not included.

### Validation

- Verified the downloaded archive checksum and exact patch application.
- Third-party extraction, cached-source reuse/refresh, and invalid-patch
rejection tests passed on master.
- The updated dependency plus the unchanged Foyer patch passed 75 Rust
unit tests, 376 C API tests, and all 3 native C/C++/static OSS consumer
tests.
- The same dependency passed 27 FE-generated Substrait integration
scenarios as part of apache#68687 validation.
- The installation harness passed on master and branch-4.1 using
independent external source definitions: legacy/matching installs,
missing helper/vars/patch/downloader/builder, mismatched and
synchronized pin/patch updates, incomplete artifacts, stale builder
output, interrupted publication, and retry. Rejected preflight cases
verify that the entire installation and builder invocation count remain
unchanged. The missing-helper regression failed before the fix. Shell
syntax, workflow YAML, and CI path-filter checks passed.
- Full Doris compilation and BE UT for this revision are pending CI.

### Release note

Update the Lance C dependency to merged upstream search and
scalar-predicate improvements.

### Check List (For Author)

- Test: Dependency unit and integration tests described above.
- Behavior changed: Yes; incorporates upstream lance-c fixes and
additive APIs.
- Does this need documentation: No new Doris user interface in this
dependency-only update.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3.1.4-hydcp-1.3 stability improvement Improvements to system stability, reliability, and failure recovery

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants