设计探析:在线回收方案为何被搁置)
数据工程数据湖大数据对象存储后端【免费下载链接】lakeFSlakeFS - Data version control for your data lake | Git for data项目地址https://gitcode.com/gh_mirrors/la/lakeFS点击查看免费下载导读本篇文章围绕 lakeFS 设计文档 hard-delete.md 展开剖析 lakeFS 在处理未被引用的未提交uncommitted数据时的完整思考历程包括 5 套候选在线方案staged 数据直写对象存储、对象元数据引用计数、staging manager 引用追踪、staging token 状态机、无拷贝方案各自的流程设计、竞态分析与最终被否决的原因。同时结合当前仓库源码说明 lakeFS 最终选择离线 GC 方案在代码中的落地形态含 uncommitted 回收路径与异步 drop 机制帮助读者理解 lakeFS 数据回收体系的全貌与设计取舍。一、问题背景未提交的垃圾数据从何而来在 lakeFS 中写入一个分支的数据在提交commit之前都会以staging 条目的形式存在。从源码看staging 数据存放在 KV 存储中以 staging token 为分区每条记录是StagedEntryData{Key, Identity, Data}结构见 graveler.pb.go而物理对象本身则写入底层对象存储S3 / GCS / Azure Blob 等。当以下操作发生时会产生不再被任何引用指向的未提交数据删除分支Delete Branch分支的整个 staging 数据被丢弃重置分支Reset Branch调用Reset或ResetHard丢弃当前 staging token 上的全部未提交写入重置单个对象 / 前缀Reset Key / Reset Prefix覆盖或丢弃某条未提交写入。关键问题在于lakeFS 此刻只删除 KV 中的 staging 元数据引用并不会删除对象存储中对应的物理对象。这就导致对象存储中出现孤儿数据其后果是存储成本失控对象持续占用存储空间形成隐性成本合规风险包含敏感数据的未提交对象可能长期残留在对象存储中无法满足数据生命周期管理要求。设计文档 hard-delete.md 明确提出解决该问题有两条路线离线方案由外部进程即 GCGarbage Collection执行批量清理在线方案在 lakeFS 服务内部实时回收。本文档详细记录的是后者——在线方案。值得注意的是文档最终结论是所有候选在线方案都存在未解决的设计问题因此没有选出任何一套方案作为最终实现相关探索被暂时搁置shelve转而集中精力完善离线 GC 方案。三条基础假设无论哪套在线方案都建立在以下假设之上这也是后续所有设计讨论的前提Copy 操作必须改造为完整拷贝方案 3 除外只有保证不存在两个 staging 条目指向同一物理对象删除物理对象才是安全的lakeFS 不负责仓库路径之外数据的保留通过StageObject引入的外部数据ingest 数据的保留责任在用户或专用 GC 进程不在 lakeFS在线方案并非万无一失not bulletproof即使实现了在线回收仍需要一套互补的外部方案来处理边界情况以及历史上已经存在的未提交垃圾。这三条假设在 controller.go 的StageObject实现中可以得到印证当物理地址不在仓库命名空间内时lakeFS 仅校验地址合法性并写入 staging 条目不做保留管理。二、方案 1将 staged 数据直接写入对象存储的固定路径2.1 设计思路这一方案主张把 staging 区域的管理从 ref storeKV迁移到对象存储本身并为对象的物理地址定义结构化路径bucket-name/repo-name/branch-name/staging-token/x/y.z即文件x/y.z被上传到.../branch-name/staging-token/x/y.z。由此得到一个非常优雅的推论未提交的对象 路径位于某个尚未提交的 staging token 之下的所有对象。删除分支时只需要删除该分支当前 staging token 路径下的所有对象即可。2.2 性能瓶颈listing 的严重退化然而该方案有一个致命弱点listing 性能严重退化。Listing 是 lakeFS 的核心流程之一列出分支对象、获取对象、提交 commit 都会用到。设计文档给出了一个实测对比约 240,000 个staged 对象 ./lakectl fs ls lakefs://niro-bench/main/guy-repo/itai-export/medium/ 6.42s user 0.78s system 44% cpu 16.089 total aws s3 ls s3://guy-repo/itai-export/medium/ /dev/null 56.82s user 2.34s system 74% cpu 1:19.79 total直接对对象存储做ls比 lakeFS 自身 listing 慢了约一个数量级因为对象存储的目录列举本质上是前缀扫描且每次操作都要经过网络往返。这一数据直接动摇了方案 1 的可行性。2.3 Blockers 与 Opens阻塞性问题Blocker——提交流程不再原子开始写 staged 对象开始 CommitCommit 流程将当前对象写入 committed 表此时对象写入才完成——数据在已提交之后被改变。即对象写入到对象存储与元数据提交到 committed 表无法原子关联存在数据一致性的竞态窗口。遗留问题Opens如何解决上述提交后数据仍被修改的 blocker如何管理 ingested 数据仓库路径之外的数据如何处理 SymLink 与 StageObject 操作三、方案 2对象元数据上的引用计数器3.1 设计思路该方案主张在底层对象的元数据metadata上维护一个引用计数器reference counter利用各 block adapterAWS、GCS、Azure的能力对计数器执行原子、并发安全的自增操作。staging 数据的处理方式除此之外完全不变。此方案的代价是在选中操作中如果对象位于 repo 命名空间内会额外引入一次元数据读取以及可能的写入。3.2 核心流程Upload Object在对象元数据上设置引用计数器并初始化为 1然后照常写对象。Stage Object对象路径在 repo 命名空间内读取元数据若计数器 0则自增并更新元数据若计数器不存在0按已删除处理。若对象写入失败回滚元数据更新。对象路径在 repo 命名空间外lakeFS 不负责保留需由用户或专用 GC 进程删除。LinkPhysicalAddress假设物理地址在 repo 命名空间内读元数据 → 计数器自增并写回 → 添加 staging 条目。Delete Object读元数据若存在引用计数器则递减若计数器归零硬删除对象若不存在引用计数器则保留不由 lakeFS 管理。Reset / Delete Branch重置分支时丢弃全部未提交数据可复用 KV 的异步 drop 功能顺带检查并硬删除需要的对象。3.3 致命 BlockersAWS S3 不支持条件元数据更新该方案最终被判定为不可行not viable原因在于虽然 Google Storage 和 Azure 提供此能力AWS S3 不提供。各云厂商实现细节云厂商实现方式可行性AWS S3S3 不允许在对象创建后原地更新对象或其元数据要更新元数据必须执行 Copy Object 并携带新元数据。可用的条件仅限对象时间戳和 ETag而ETag 只在对象内容变化时改变不反映元数据变化不可行Azure Blob将引用计数存在 blob 元数据中使用set blob metadataAPI配合 ETag 条件头If-Match原子更新引用计数可行Google Cloud Storage将引用计数存在 blob 元数据中使用patch objectAPI 配合ifMetagenerationMatch条件可行也就是说作为 lakeFS 最重要后端之一的 AWS S3 无法提供条件设置元数据conditional set of metadata能力方案 2 因此无法落地。四、方案 3在 staging manager 中追踪引用4.1 设计思路这一方案转向数据库内追踪Staging manager 维护物理地址 → 引用它的 staging token的映射并引入一个新数据库对象key repo/references/physical_address/staging_token value state enum (staged|deleted|comitted) last_modified timestampUpload object到分支时在 references 路径下新增 key物理地址 当前 staging token标记为stagedDelete object从 staging 区域将对应 reference key 的值更新为deletedCommit将 reference key 的值更新为comitted。last_modified时间戳用于防止待删除引用候选与正在为同一物理地址新增引用的并发操作之间的竞态。其核心假设是当一个物理地址的所有引用都处于deleted状态经过一段时间TBD后该物理地址不可能再被引用所有 staging token 要么已被丢弃、要么已提交。4.2 后台删除任务伪代码扫描reference前缀可使用 after prefix 实现负载均衡对每个physical_address读取全部条目若存在state committed的条目按deleted → staged → committed的顺序删除该物理地址的全部 key若所有条目均为deleted且min(last_modified) TBD硬删除对象并删除该物理地址的全部 key。4.3 Key 更新优先级Update Order of Precedencecommitted状态优先级最高终态覆盖任何其他值——使用Setdeleted状态只能作用于非committed的条目——使用SetIf带条件的写staged状态只能作用于非committed的条目——使用SetIf。4.4 各操作流程Upload Object写 blob → 写 reference 条目为staged→ 添加 staged 条目 → 若当前 staging token 中已存在同名条目将旧引用标记为deleted。遗留问题如何高效处理覆盖/overrideStage Object对象路径在 repo 命名空间内则添加 reference 条目在命名空间外则 lakeFS 不负责保留最后添加 staged 条目。GetPhysicalAddress为生成的唯一物理地址添加一个deleted状态的引用除物理地址外还向用户返回一个有效 token / 过期时间戳用户必须将该 token 传给 LinkPhysicalAddress。LinkPhysicalAddress校验 token 是否有效 / 是否过期将引用状态置为staged添加 reference 条目添加 staging 条目。Delete Object若对象处于 staged 状态读取 reference key若未committed将引用状态改为deleted并更新last_modified。Commit将 staging 区域所有条目标记为committed→ 轮换swapstaging token → 创建 commit → 更新分支的 commit ID。Reset / Delete Branch通过AsyncDrop异步丢弃未提交数据利用该异步任务顺带检查并执行硬删除扫描已删除的 staging token 条目若是tombstone则移除 entry 的 reference key否则将引用状态改为deleted并更新last_modified。这里的AsyncDrop在现有代码中确有对应实现staging manager 的DropAsync会将待清理 token 写入CleanupTokensPartition再通过wakeup通道触发cleanTokens事件由后台asyncLoop循环执行findAndDrop见 manager.go。设计文档中复用 KV drop 异步功能的想法与当前代码的异步清理通道是一脉相承的。五、方案 4Staging Token 状态方案 3 的改进版5.1 核心改进方案 4 将状态从对象引用上移到staging token 本身同时仍保留对引用的追踪但不带状态key repo/references/physical_address value list(staging tokens)对每个被删除 / 被重置的分支的 staging token添加一个 deleted 条目repo/staging/deleted/staging_token对每次 commit将涉及的 staging token 加入 committed 条目追加引用使用set if 重试机制保证一致性key repo/staging/committed/physical_address value list(staging tokens)5.2 各操作流程Upload Object写 blob → 读取 reference 条目 → 对引用执行带新 staging token 的set if→ 添加 staged 条目。注意Delete object 不会移除引用只要 staging token 未被删除或提交引用就一直保留。Delete Object与现状保持一致此流程不删除引用。Stage Object仅允许对 repo 命名空间之外的地址执行 stage object 操作lakeFS 不管理这些对象的保留。GetPhysicalAddress为签发的物理地址附带一个一次性验证 token签发地址只能使用一次因此可假设该地址未被提交、也无其他引用。地址被 LinkPhysicalAddress 使用后从有效 token 列表中删除该 token ID用物理地址签发一个 JWT token在 DB 中保存 token ID向用户返回 token 物理地址。LinkPhysicalAddress校验 token 有效性有效则移除 token ID、添加引用、添加 staging 条目。CopyObject新流程确保后台删除任务移除某已提交对象的所有引用后不再为该对象添加新引用从而防止误删已提交对象在重试未耗尽前循环读取 reference key若 key 存在读取地址的引用列表若列表为空判定地址已被删除返回错误列表为空只可能发生在后台任务正在对该引用执行删除流程时否则将分支的 staging token 追加进引用列表并执行set if谓词失败则重试若 key 不存在假定地址已提交直接执行 copy 而不添加新引用。Commit按现状执行 commit 流程成功后用commit_id与 token 有序列表在committed前缀下创建条目。Reset / Delete Branch按现状执行成功后为所有 staging token 在deleted前缀下创建条目。5.3 后台删除任务扫描 committed tokens对每个已提交对象移除 reference key对 committed 条目中的每个 staging token 移动到 deletedcommitted token 中所有实际未提交的对象都是删除候选因此可执行 deleted token 逻辑或为这些 token 在 deleted 前缀下创建条目——属于实现细节移除staging/committedkey删除 staging 区域。扫描 deleted tokens对每个 token移除其中全部对象的所有引用若某对象因此成为最后一个引用更新后列表为空执行硬删除删除 staging 区域。遗留问题同一 staging token 内 key 覆盖的处理Handling key override on same staging token标记为 TBD未给出最终方案。六、方案 5No Copies无副本拷贝 对象锁6.1 核心思路方案 5 是思路最激进的一版彻底禁止两个 staging 条目指向同一物理对象。只要满足任何物理地址最多被一个 entry 引用就可以放心地在 entry 消失时删除物理地址Upload获取旧条目删除旧的物理地址除非 staging token 已更新Revert分支 / 前缀 / 对象entry 被丢弃时删除物理地址Deleterepo / branch / objectentry 不再存在后删除物理地址。要支撑上述物理删除需要两个前提物理地址不可被随意复用通过给 API 返回的签名链接sign link加入逻辑路径签名——生成的物理地址把逻辑路径作为签名的一部分从而阻止物理地址被用于 repo / branch / entry 之外的地方S3 网关的put操作若要支持 copy将改用底层 adapter 完成数据拷贝——要求每个 adapter 支持 / 模拟一个copy操作可选提供moveAPI 作为 copy 的替代通过copy 删除元数据实现移动copydelete of metadata可轻松支持 lakeFSFS move。实现方式是标记 staging 条目为locked在 move 过程中执行 lock → copy → delete → unlock。由于没有事务任何一步失败都可能留下两个条目或留下一个永远无法删除物理地址的 locked 对象。该方案文档末尾留有 TODO(Barak)列出更多用例及本方案如何应对。说明该方案当时仍是半成品。6.2 对象锁Object Locking在 staging 区域结构中新增一个锁字段type Value struct { Identity []byte Locked bool }当前仓库实际的StagedEntryData为{Key, Identity, Data}三个字段见 graveler.pb.goLocked字段是该提案设想的新增项。利用SetIf实现并发安全的加锁机制。删除 staged entry无论经由DropKey、ResetBranch还是DeleteBranch时仅当 entry未被锁时才执行对象硬删除。6.3 各操作流程Move 操作获取 entry →SetIf加锁 → 在当前 staging token 下把 entry 拷贝到新路径 → 删除旧条目 → 解锁新条目。Upload Object获取 entry → 若存在覆盖场景则SetIf加锁 → 写 blob → 添加或更新staged 条目 → 若之前存在物理地址则删除之。Delete Object获取 entry →SetIf加锁防止 delete-move 竞态→ 删除 staging 条目 → 硬删除对象。6.4 竞态分析与遗留问题该方案面临多个已知竞态文档逐一剖析竞态 1Commit - Move 竞态Move 开始获取 entry、加锁Commit 开始轮换 staging token、写入 commit 数据此时写的是旧 entry 路径Move 继续在新 staging token 上创建新条目、删除旧条目、解锁物理地址。结果存在一个未提交条目指向已提交的物理地址。如果此时重置分支将删除已提交的数据竞态 2Delete - Move 竞态Delete 开始获取 entry、检查未加锁Move 开始获取 entry、加锁、拷贝到新路径Delete 继续从对象存储硬删除对象。结果存在一个未提交条目指向无效的物理地址。文档注明通过在 delete 流程中也加锁可解决。竞态 3并发 Move并发 move 必须被阻止因为它会导致多个指针指向同一物理地址。最直接的办法是依赖锁不存在这个条件。但问题在于永久加锁的地址这是灾难性故障场景下的保护机制永远无法被移动——由此引出竞态 4。竞态 4陈旧锁问题Stale lock problem锁机制被用于保护 Upload覆盖、Move 和 Delete 的竞态。因此带陈旧锁的条目会阻止上述所有操作。可以采用基于时间的锁time based lock来克服但这会给方案带来额外挑战。七、方案横评与最终决策将 5 套方案的核心矛盾归纳如下方案核心机制主要 Blocker / 硬伤1. staged 数据直写对象存储按分支 staging token 结构化路径存放listing 性能数量级退化提交流程非原子已提交数据可能被后续写入篡改2. 对象元数据引用计数借助各 adapter 的原子条件更新能力AWS S3 不支持条件元数据更新GCS / Azure 可行3. staging manager 引用追踪DB 中维护 physical_address → token 映射 状态机后台删除任务与在途操作的竞态依赖last_modified时间窗口override 处理效率未解4. staging token 状态机状态上移到 staging token引用只存列表key 覆盖处理TBDtoken 生命周期管理复杂5. No Copies 对象锁杜绝多 entry 共享物理地址 SetIf加锁Commit-Move / Delete-Move / 并发 Move 竞态陈旧锁问题最终决策没有任何一套方案能够在可接受的代价下覆盖所有场景与边界情况。因此lakeFS 决定暂时搁置在线方案的探索集中精力完善离线方案GC。这也与设计文档开篇的 Decision 完全一致在线删除不是 bulletproof 的它天然需要一套互补的外部方案来处理边界情况与既有垃圾。八、现状验证离线 GC 与异步 drop 的仓库落地形态虽然在线方案被搁置但仓库中确实可以找到与之相关的两条核心机制的实现可作为理解该设计文档的背景佐证。8.1 离线 GC 对 uncommitted 数据的回收路径在 garbage_collection_manager.go 中可以看到 GC 面向未提交数据的落地实现uncommittedPrefixTemplate %s/retention/gc/uncommitted/ uncommittedFilePrefixTemplate uncommittedPrefixTemplate %s/uncommitted/GetUncommittedLocation(runID, ...)返回retention/gc/uncommitted/runID/uncommitted/路径SaveGarbageCollectionUncommitted(...)将未提交数据清单CSV 文件上传到该路径。结合 gc-two-steps-separation.md 中 mark / sweep 分离的演进lakefs.gc.do_mark、lakefs.gc.do_sweep、lakefs.gc.mark_id三个配置项可以确认lakeFS 的正式策略正是设计文档所说的离线方案——外部 GC 进程负责 mark 与 sweep在线服务不介入对象物理删除。8.2 异步 drop在线侧删引用的现有实现在线侧目前只负责删除 KV 中的 staging 引用物理删除交给 GC。这一引用删除本身是异步化的对应设计文档中反复提到的 AsyncDropmanager.goDropAsync把待清理 token 写入CleanupTokensPartition并向wakeup通道发送cleanTokens事件manager.go后台asyncLoop消费事件findAndDrop批量扫描 token每批最多 512 个并通过 shuffle 降低多实例并发处理同一 token 的概率再按前缀逐个删除 staging keygraveler.goReset在BranchUpdate成功后通过dropTokens对旧 staging token 与 sealed tokens 执行异步 dropgraveler.goCommit流程会把当前 staging token 追加到SealedTokens并生成新 token——这与方案 4 中commit 后 token 进入 committed 状态的状态机思路同源。8.3 物理地址签名方案 5 构想的已实现部分方案 5 提到逻辑路径作为签名的一部分阻止物理地址被滥用这在当前代码中已经落地catalog.goGetAddressWithSignature将{repository, branch, path, physicalAddress}的 JSON 做 HMAC 签名拼接到物理地址后返回catalog.goVerifyLinkAddress在LinkPhysicalAddress时校验签名HMAC 比对与地址生成时间LinkAddressTime过期校验controller.goLinkPhysicalAddressAPI 对 repo 命名空间内的相对地址强制走VerifyLinkAddress校验。也就是说方案 5 中签名物理地址 防止地址被 link 到错误位置的设计思路已经在离线方案的配套机制中部分实现这为未来若重启在线方案提供了可复用的基础。九、总结与设计启示删元数据 ≠ 删数据是分布式数据系统的普遍难题lakeFS 的 staging 元数据与物理对象分离引用删除后的物理对象回收天然需要引用计数、路径推断或状态机等机制而这每一项都伴随原子性与竞态问题。云厂商能力差异直接决定方案取舍方案 2 的否决提醒我们跨厂商尤其 AWS S3的条件更新能力缺失会淘汰一批看似优雅的在线设计。在线方案的复杂度不亚于离线方案方案 3、4、5 中反复出现的竞态Commit-Move、Delete-Move、并发 Move、陈旧锁说明在线硬删除需要处理引用可能新增与对象即将删除之间的时间窗口这在无事务的对象存储上难以闭合。务实的分层策略lakeFS 最终选择在线只删引用异步 drop 离线 GC 删对象mark/sweep 分离把难以保证的正确性问题交给可审计、可重试、可人工介入的批处理流程这是工程上稳妥且可运维的选择。对于希望深入了解数据回收细节的读者推荐继续阅读hard-delete.md本篇文章的原始依据、gc-two-steps-separation.md离线 GC mark/sweep 演进、manager.go异步 drop 实现以及 garbage_collection_manager.gouncommitted 回收路径实现。赞分享数据工程数据湖大数据对象存储后端【免费下载链接】lakeFSlakeFS - Data version control for your data lake | Git for data项目地址https://gitcode.com/gh_mirrors/la/lakeFS点击查看免费下载相关推荐lakeFS 未提交数据垃圾回收Uncommitted GC设计解析基于分片扫描与时间令牌的离线回收方案lakeFS 未提交数据垃圾回收Uncommitted GC设计解析基于分片扫描与时间令牌的离线回收方案 导读 lakeFS 的常规 GCGarbage数据工程数据湖大数据对象存储后端lakeFS Staging Compaction 设计剖析为数据湖未提交区域引入的墓碑压缩机制lakeFS Staging Compaction 设计剖析为数据湖未提交区域引入的墓碑压缩机制 导读 本文围绕 lakeFS 仓库中的开放设计文档 desi数据工程数据湖大数据对象存储后端SQLModel 教程使用 DELETE 删除数据SQLModel 教程使用 DELETE 删除数据 在数据库操作中删除数据是常见的需求之一。本文将详细介绍如何在 SQLModel 中使用 DELETE 操ORM数据库后端上一篇SDR 信号接收上手5 分钟从装好到看见频谱下一篇Amiibo 转换 NFC 模拟的 6 步上机实操创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考