Skip to content

Latest commit

 

History

History
133 lines (104 loc) · 11.7 KB

fastcdc-media.md

File metadata and controls

133 lines (104 loc) · 11.7 KB

FastCDC Media(git-internal / Libra extension)

本文记录 fastcdc feature 下 Media 协议的服务端契约。这不是标准 Git LFS 扩展,也不是标准 Git BLAKE3 互通。media_oid、chunk hash 与 fallback OID 属于 LFS SHA-256 digest domain,不受仓库 monorepo.object_format / Git HashKind 影响。

协议(FC-02 / MF-02)

  • 算法:fastcdc-v2020-32k(fastcdc = "=3.2.1",v2020,Normalization::Level1,seed=0,min/avg/max = 32/64/256 KiB)。旧 fastcdc-v1 / 对象前缀 v1/ 命名空间保留不读写、不自动删除。
  • Manifest:version=1,hash_algorithm=sha256,chunk compression 固定 none 且 encoded_length == length。
  • 块限制:非尾块 32768..=262144;尾块 1..=262144;空文件零块。不设总 chunk 数或全文件字节产品上限。
  • Canonical ID:SHA-256(小写 hex)over JSON of (version, algorithm, hash_algorithm, media_oid, media_size, chunks)。created_by 与 fallback_oid 不进入 identity;分页边界不参与身份(P-01)。
  • 包装上限:Media metadata 单页/摘要/状态请求响应含包装 ≤ 1 MiB(MAX_ENVELOPE_SIZE);单页最多 4096 条且紧凑 entries JSON ≤ 960 KiB(P-01a);chunk payload ≤ 256 KiB;created_by ≤ 4096 bytes。完整 LFS 流不受页限额约束。
  • fallback_oid 若存在必须等于 media_oid。
  • HTTP 形状(路径以 Libra client 为准):
    • capabilities:GET <repo>.git/info/lfs/libra/media/v1/capabilities
    • prepare:POST …/manifests → {manifest_id, missing_chunks}(唯一 hash 差集;exists 并发 ≤16)
    • page:PUT …/manifests/{id}/pages/{page_no}(幂等;内容冲突 409)
    • seal:POST …/manifests/{id}/seal → {manifest_id, seal_generation, page_count}
    • missing:GET …/manifests/{id}/missing?cursor= → {hashes, next_cursor}
    • upload:PUT …/manifests/{id}/chunks/{hash}(application/octet-stream)
    • finalize:POST …/manifests/{id}/finalize → 202 任务(MF-03);GET …/tasks/{task_id} 轮询
    • get:GET …/manifests/by-media/{oid} → {manifest_id, manifest}
    • chunk download:GET …/manifests/by-media/{oid}/chunks/{hash}
    • fixed-id summary:GET …/finalized/{manifest_id} → ManifestSummary
    • fixed-id pages:GET …/finalized/{manifest_id}/pages
    • fixed-id chunk:GET …/finalized/{manifest_id}/chunks/{hash}

Capabilities(共享表)

Capabilities::v1() 同时保留旧字段名并输出共享表字段:

字段 值
version "1"
batch_exists / supports_batch_exists true
range_read / supports_range_read false
standard_lfs_fallback / supports_standard_lfs_fallback true
supports_manifest_id_read true
manifest_paging "v1"
max_page_entries 4096
max_page_bytes / max_manifest_size 1048576
chunk_algorithms ["fastcdc-v2020-32k"]
max_chunk_size 262144

Scope / key(FC-03 / C-08)

  • Namespace 字符串 media 只追加,不改 git/lfs/log/artifact/attachment/oci。
  • Scope = 服务端 actor + canonical 绝对仓库路径;digest = SHA-256(actor || 0x00 || repo)。 Actor 取 AccessTokenUser 的 website_user_id(非空)否则 username。请求体 actor/repository/created_by 不得覆盖。
  • Object key:fastcdc-v2020-32k/{scope_digest}/{pending|chunk|manifest|finalized|page}/{id},存储路径 media/ + key(Media 不走 3-level sharding)。旧 v1/ 对象隔离保留。
    • finalized/{manifest_id}:不可变身份(完整 ManifestResponse)
    • manifest/{media_oid}:可替换 by-media 发现
    • page/{manifest_id}-p{n}:不可变页 blob
  • 对外错误不泄漏 digest、object key 或认证信息。

Upgrade / recovery(C-08 / MF-06)

  • 激活顺序: 先发布本仓 MF-06,再 Libra FL-06,再 MF-05,最后 FL-05。服务端发布只依赖本仓测试与共享静态向量,不等待未来客户端。
  • 命名空间切换: 新写入一律落在 media/fastcdc-v2020-32k/…。既有 media/v1/… 对象不读写、不自动删除;标准 LFS lfs/{oid} 与 lfs_objects 行保留。同 oid 可在新空间重新 finalize,不受旧 v1 finalized 阻止。
  • 补偿恢复(活动数据不可仅 revert 代码): 停写新 Media → 回切到匹配的一对旧二进制与旧 v1 空间语义 → 继续服务保留的 v1 对象与标准 LFS。若活动数据已写入新空间,不得只把代码回退到旧前缀而指望读到新对象。
  • 清理: 旧 v1 空间与布局 GC 属 DEFER-MF-03,须另行显式运维,不在本协议激活范围内。
  • 发布声明边界: 无真 interop(MF-05 / Libra FL 未完成)时,只可声明本仓服务端能力已发布;双仓收口保持 blocked。
  • 演练证据: ceres::lfs::media::tests::c08_upgrade_retains_legacy_v1_and_standard_lfs(feature fastcdc)种植 v1/ 与标准 LFS 对象后做新命名空间 prepare,断言旧对象字节不变且新 pending 落在 fastcdc-v2020-32k/。

持久分页状态(FC-16 / MF-08)

服务库三表(迁移 m20260925_000100_media_paging):

  • media_session:(scope_digest, manifest_id);pending|sealed|finalized;seal_generation 绑定缺块 cursor。
  • media_entry:派生 chunk 索引;跨页 hash→length 冲突拒绝。
  • media_task:异步 finalize lease(MF-03/07)。

prepare 在对象存储写入 pending 的同时 upsert_pending_session;put_page / seal / missing 走 MediaPagingStorage + page blob。

固定 id 读取与成员缓存(FC-04 / MF-04)

  • GET …/finalized/{manifest_id} → ManifestSummary(不可变布局;与 by-media 当前发现无关)。
  • GET …/finalized/{manifest_id}/pages:顺序分页(cursor)或覆盖查询(offset+length 启动,后续 cursor 绑定该范围)。响应含 page_no、绝对 offset_start/offset_end、entries 与 next_cursor;单页包装 ≤1 MiB。
  • GET …/finalized/{manifest_id}/chunks/{hash}:仅当 hash 属于该 id 的 finalized 布局;跨 actor/repo/非成员统一 404;存储块必须重新校验 SHA-256,损坏返回 409,不得返回未校验 payload。
  • 成员/页索引缓存(membership_cache)按 (scope_digest, manifest_id) 计费,总计 ≤128 MiB(含索引开销);满后 LRU 驱逐,不拒绝大文件。缓存不跨 scope;pending TTL 在缓存命中时仍逐请求检查(R-MF-04)。

Prepare / chunk(FC-05)

  • prepare 只接受已 validate 的 manifest(≤1 MiB 包装),并强制 fallback_oid = media_oid;按 P-01a 计算 page_count。
  • pending 对象:media/fastcdc-v2020-32k/{scope}/pending/{manifest_id},逻辑 TTL 24 小时。过期 session 不能继续 upload/get。
  • missing_chunks 按 scoped chunk key 检查存在性(并发 ≤16);重复 chunk_hash 在响应中去重(保序)。
  • chunk 上传必须命中 pending 声明的 hash/length,并校验实际 SHA-256(≤ 256 KiB)。同 scope 同 hash 的正确对象可幂等复用;已存错误内容返回 Conflict。
  • 领域错误:Invalid / NotFound / Conflict / Storage / Io / Json。存储错误对外固定为 media object store error。

Finalize / fallback / multi-layout publication(FC-06 / MF-03 / MF-07)

  • 同时最多 2 个 finalize(semaphore);活跃排队上限 128(超限 429 + Retry-After)。
  • POST …/manifests/{id}/finalize 仅接受 sealed 会话,返回 202 {task_id,manifest_id,state,status_url};status_url 为同前缀 libra/media/v1/tasks/{task_id}(客户端不得跨 origin 跟随或转发 token)。
  • GET …/tasks/{task_id} 每请求重新做 scope 认证,返回 state/bytes_verified/pages_verified/retryable/error_code;complete 另含 oid/size。
  • 验证:按 sealed 页序遍历 media_entry,逐块 scoped 读并校验 length/hash;连续覆盖 media_size;整对象 SHA-256 须等于 media_oid。不要求新鲜 FastCDC 冷切边界相等。
  • 磁盘写与哈希在 spawn_blocking;单次 I/O ≤120s、无进展 ≤10min、lease 60s(约每 20s 续租);持续进展不受整文件墙钟限制。
  • 缺失或损坏 chunk / gap / overlap / overflow:不写 LFS namespace、不写 lfs_objects、不发布 finalized;临时文件与 lease 在成功/失败/取消路径均释放。
  • 通过后的发布顺序(C-04 / ADR-MF-05):
    1. 完整 LFS fallback lfs/{oid}(put_stream_bounded)+ lfs_objects 同 oid 幂等(大小一致才接受);
    2. 不可变身份记录 media/…/finalized/{manifest_id}(put_metadata_atomic,≤1 MiB 完整对象 PUT);
    3. 持久 media_session.state=finalized;
    4. 可替换发现 media/…/manifest/{media_oid}(by-media;任一完整已发布布局可成为当前值,无需胜者 CAS);
    5. 仅在本调用新写入不可变记录且已可读后发 lfs.media.finalized。
  • 同 oid、不同合法布局(不同 manifest_id)可并发成功;同 id 重试返回等价 canonical 身份(created_by 不参与比较)。不得暴露半 JSON / 错 id。
  • GET …/manifests/by-media/{oid} 只返回会话已 finalized 的完整 ManifestResponse。固定 id HTTP 路由见上文「固定 id 读取」;immutable 记录仍可通过存储直读 finalized/{manifest_id}。
  • 不支持原子完整对象 PUT 的后端必须对 put_metadata_atomic 返回明确错误,禁止退化为边写边可见。

出站事件(plan-20260912 / WH-06,已交付)

  • 既有 finalize 路径在本次实际新写入 immutable finalized 记录成功且可读后发一次 lfs.media.finalized(契约见 storage-events.md):scope 只填服务端 MediaScope 的 canonical repo_path,data 为 oid,size,manifest_id,transfer="fastcdc"。
  • prepare / chunk / fallback 中间步骤不发;同 id 重试(immutable 已存在)不发。
  • 认证不改:Media 路由仍全部要求 AccessTokenUser。

HTTP / auth / OpenAPI(FC-07)

  • Feature-on 时挂在当前 LFS mount 下:逻辑前缀 libra/media/v1。OpenAPI 登记为 /api/v1/lfs/libra/media/v1/...。
  • 每一条 Media 路由(含 capabilities)都要求 AccessTokenUser(Bearer)。
  • Body 上限:JSON/metadata 路由 DefaultBodyLimit 1 MiB;chunk PUT 路由 256 KiB。另有 Content-Length 中间件在读 body 前返回 413。
  • 错误:Invalid/Json → 400,NotFound → 404,Conflict → 409,Storage/Io → 500 且 body 固定 media object store error。

固定 fixture:src/ceres/lfs/media/fixtures/(合法 valid_v1.json / 空文件 empty.json / 非法 version 与 fallback)。valid_v1.json 字节 SHA-256:09ae74a7f69da0bbd2b3df12d8f4bb85413b8ba34ec5de123f68141219ac3b96。

双仓 interop gate(FC-15)

默认 cargo test --all 不要求 sibling Libra checkout。真实 client/server 证据只在显式 ignored target 下执行(见既有 integration_fastcdc_libra 说明)。feature-off 时 Media 能力探测为 404,Libra 选择标准 LFS fallback。

MF-05 pin: Libra v0.30.4 / 8c870c4d41eff9bff33dd3adf4b34189fc9ca1fe(FL-04 range export)。 Harness 设置 LIBRA_DIR 为该 tip 的干净 checkout,驱动 Libra monoengine_fastcdc_http_interop(MONOENGINE_FASTCDC_READY_FILE),覆盖 prior→上传→固定 id 范围导出;feature-off 伴生用例验证标准 LFS 回退。 不得缺配置 SKIP-green。C-06 / P-05 大对象效果数字与资源预算见本卡发布证据与 Libra FL-05 收口。

MF-06(v0.40.10 家族发布)状态: 服务端家族(配方、分页协议、覆盖验证、多布局原子发布、固定 id 读取)已按本契约上线;MF-05 真 interop 在独立 patch 收口。