Lore 0.8.7+ AWS 不可变存储迁移:用 lore-aws-migrate 将片段元数据从 DynamoDB 迁移到 S3 对象头
版本控制后端【免费下载链接】loreLore is a next-generation, open source version control system项目地址https://gitcode.com/gh_mirrors/lore6/lore点击查看免费下载Lore 从 v0.8.7 起AWS 不可变存储immutable store中的片段fragment不再由 DynamoDB 单独维护一行元数据而是由承载载荷的 S3 对象通过对象头object metadata自描述DynamoDB 仅保留记录哈希存在的状态行state row。contrib/aws-migrate-0.9.0是仓库提供的独立迁移工具独立于 Lore workspace 之外的独立 crate专用于把 v0.8.7 之前写入的存量存储改写为新布局。读完本文你将掌握旧/新两种布局的差异与迁移必要性、如何构建含 Oodle 特性开关、如何用--dry-run排演并计算维护窗口、生产迁移的完整步骤与验证/清理流程、关键统计量与命令行参数的含义以及中断续跑、多机分片、本地栈测试等实战细节。背景片段描述信息为什么从 DynamoDB 行搬到了 S3 对象头在旧布局中一个片段fragment由两部分组成其元数据flags、payload 大小、content 大小等描述信息存放在 DynamoDB 的元数据表fragment metadata table中而载荷payload本身存放在 S3。自 v0.8.7 起Lore 改为把片段描述信息直接写到 S3 对象的元数据头上——即lore-fragment这个 metadata key——DynamoDB 中只剩一张状态表fragment state table其中的状态行只负责记录该哈希存在并携带毁坏obliteration状态。这一改动在仓库的 ADR-00020Store fragment metadata as S3 object metadata 中有完整论证把描述信息与载荷放到同一个对象版本上读写都遵循 S3 全对象 PUT 的原子性从结构上消灭了对象字节与记录描述互不一致这类永久性损坏同时哈希是否存在仍然只需一次GetItem即可回答无需 S3 请求。因此凡是在 v0.8.7 之前写入、对象头还不带自描述元数据的存储都需要逐个对象重写一遍把元数据挂到对象头上并为每个哈希发布一条状态行。这正是lore-aws-migrate的职责。迁移器的核心实现在 lore-aws/src/store/immutable_store/metadata_migrator.rs本工具是它的运维前端operator front-end负责把命令行参数组装成 store、覆盖元数据表的全部分段segment、报告进度并在所有分段都完成时以 0 退出。工具本身位于contrib/aws-migrate-0.9.0/是一个独立 crate它通过路径依赖../../lore-aws与../../vendor/quinn-proto并在自身Cargo.toml中提交了锁定的Cargo.lock不与 Lore 主 workspace 一起构建与测试见 Cargo.toml 的[workspace]空表声明与注释。迁移前的铁律存储必须停止对外服务迁移期间必须让所有能触达该存储的节点进入维护模式直到运行结束。注意时机是在排演rehearsal之后、正式运行之前进入维护模式——排演的作用正是用来估算维护窗口大小的。这不是单纯的负载考虑而是正确性问题。一次转换的流程是读取片段的 state → 一次 GET → 一次解压 → 一次重压 → 一次 PUT → 写状态行。如果在这个窗口内恰好有某哈希的毁坏obliteration操作完成那么载荷仍会被上传墓碑tombstone会被复活回Stored状态毁坏被撤销、载荷回到桶里、哈希重新可读且可被去重。这类事件不会被任何统计量计数存储层只在 info 级别记录一条Payload revives a tombstoned hash——这正是后面 Verify 阶段要 grep 的日志。维护模式的进入方式设置环境变量LORE_SERVER_MAINTENANCE1即可让节点进入维护模式。进入后节点仍然通过 HTTP 提供/health_check返回200 OK让负载均衡器继续把节点视为已注册而不是反复重启任务提供 gRPC environment 服务在节点正常暴露的所有端口上返回UNAVAILABLE让探针看到的是一个降级节点而不是连接被拒不注册任何存储storage、复制replication或管理admin服务。在contrib/aws拓扑中需要在两个任务定义上都设置该环境变量。因为边缘节点edge只能通过主节点primary触达存储——durable store 走 QUIC 的replicated、mutable store 走remote——所以让主节点静默quiesce就能挡住写往 S3 和 DynamoDB 的流量同时也要让边缘节点静默否则它还会继续接收客户端流量、写入自己的本地存储。进入维护模式前可以检查是否还有任务停留在旧版本使用默认name lore前缀产生的名称aws ecs describe-services --cluster lore-cluster --services lore lore-edge \ --query services[].deployments[].{taskDefinition:taskDefinition,running:runningCount} \ --region us-west-2前置条件Rust 1.85 或更高需要 edition 2024。仓库中没有任何代码使用 nightly 特性也没有固定工具链toolchain本仓库的检出crate 通过路径依赖../../lore-aws和../../vendor/quinn-proto首次构建需要网络访问或者有一个温暖的 cargo 缓存并加--offline标准 AWS 凭证链standard chain。构建cd contrib/aws-migrate-0.9.0 cargo build --release产物位于target/release/lore-aws-migrate。必须使用 release 构建一次迁移要读取、解压并重写存储中的每一个载荷。Oodle 载荷需要 oodle 特性如果存储中保存了 Oodle 载荷需要 Oodle 库不在本仓库中。没有这些库时每个 Oodle 片段都会被报告为unreadable并留在原地OODLE_LIB_DIR/path/to/oodle cargo build --release --features oodle该特性通过lore-storage/oodle传递开启见 Cargo.toml 的[features]段构建时需要OODLE_LIB_DIR指向 Oodle 库所在目录。运行一次完整迁移覆盖元数据表的全部分段这也是默认行为./target/release/lore-aws-migrate \ --s3-bucket lore-fragments-abc123 \ --fragments-table lore-fragments \ --fragment-state-table lore-fragment-state \ --fragment-metadata-table lore-metadata \ --region us-west-2如果某个部署从未有过单独的状态表——状态行与遗留元数据行共用一张表靠行的形状shape区分——那么把同一个表名同时传给--fragment-state-table和--fragment-metadata-table即可。几个要点迁移会读取 S3 对象、其背后的元数据表以及状态表写入的是对象和一条状态行。关联associations既不被读也不被写因此--fragments-table只是为了让存储定义完整运行时会对其做存在性检查四个存储参数也可以用环境变量提供LORE_MIGRATE_S3_BUCKET、LORE_MIGRATE_FRAGMENTS_TABLE、LORE_MIGRATE_FRAGMENT_STATE_TABLE、LORE_MIGRATE_FRAGMENT_METADATA_TABLE--region对应AWS_REGION这些在 src/main.rs 的Args结构体中通过 clap 的env属性声明。先排演--dry-run排演是必须的步骤。--dry-run会分析每一个片段并报告每个片段将得到的结果不写任何东西因此与真实运行不同它对在线存储是安全的./target/release/lore-aws-migrate ... --dry-run把结果当作下限来读在未迁移的存储上除了上传和状态写入之外它做了真实运行会做的所有事。排演时必须使用与真实运行相同的--total-segments和--consumers否则这个数字对真实运行没有任何参考意义。并行扫描与多机分片--total-segments决定表被划分为多少个并行扫描scan段--consumers决定每个段一次并行转换多少个片段。要把一次迁移分摊到多台机器就给每台机器相同的--total-segments和各自的--segment# machine 0 of 4 ./target/release/lore-aws-migrate ... --total-segments 4 --segment 0不指定--segment时运行全部分段源码segments()返回0..total_segments见 src/main.rs指定的段号必须在0..total_segments范围内重复与乱序会被排序去重退出码为 0 仅当本次调用覆盖的每个分段都完成了。每 30 秒记录一次进度汇总--progress-interval-secs 0可静默RUST_LOG控制日志级别。大存储或异常存储上值得关注的四个旋钮参数默认值用途--timeout-millis30000单次 S3 或 DynamoDB 操作超时。一次载荷重写算一次操作因此它限定了迁移能搬动的最大片段--max-retries5首次尝试之后的额外重试次数作用于扫描页、片段转换、以及转换内部的载荷写入。后两者嵌套所以一个持续失败的写入会被尝试 (1 max)² 次--retry-base-delay-ms200按尝试次数相乘、封顶五秒。DynamoDB 节流时调大它--scan-limit未设置每个扫描页的条目数用于让发现不要跑得比转换还快--help会列出其余全部参数Args结构体的 doc 注释即为--help文本见 src/main.rs。中断与续跑重复执行同一条命令即可续跑一个既有状态行、对象又能自描述自己的片段会被跳过对应ConvertOutcome::SkippedMigrated。Ctrl-C会设置一个在每个片段顶部检查的 abort 标志因此正在进行中的片段会执行完毕再停止stop_on_interrupt在 src/main.rs 中把首个中断转成 abort 标志续跑不是免费的发现过程不保留检查点checkpoint所以续跑会重读每一行并对每个已迁移片段额外花费一次状态查询和一次HeadObject然后才到达新的工作。失败模式一个读或写持续失败、耗尽重试的片段会停掉整个运行、所有分段一个持续失败的扫描页影响面更窄它只结束自己那个段的发现运行报告不完整incomplete其他段仍完成各自的扫描没有任何编解码器能恢复的载荷不会停任何东西——它被计为unreadable并跳过在--dry-run下什么都不会停住运行所以一次排演就能把存储的所有问题都报告出来。统计量totals的含义迁移器维护一套运行计数RewriteStats见 metadata_migrator.rs每个统计量都有明确语义统计量含义scanned/metadata_rows从元数据表读取的行数以及其中解析出哈希的行数maintained声明的编解码器正确且不是 Oodle载荷原样重新上传以挂上其元数据recompressed_oodleOodle 载荷重新压缩为 Zstdrecompressed_mismatch声明的编解码器与实际存储字节不符重新压缩为 Zstdstored_uncompressed重压缩不划算压缩无效因此载荷以未压缩形式存储payloads_deduced编解码器必须通过探测确定而非信任声明already_migrated状态行存在且对象已自描述无事可做state_with_no_head找到了状态行但对象不自描述因此片段仍会被转换。当状态表与元数据表是同一张表时这会是每一个片段因为状态读取找到的就是遗留行本身obliterated行表明载荷已被毁坏因此跳过、不写状态行。预期行为——见下文oversized对象的长度或行声明的尺寸超过阈值因此被拒。长度本身就能暴露问题的对象甚至不会被读取检查发生在Content-Length早于 body 到达之时unreadable没有编解码器能复现该哈希载荷无法恢复不写状态行errored转换在重试后仍然失败。对象已消失的行会落在这里——这正是毁坏连载荷一起删掉之后留下的东西obliterated是一种结果不是失败被毁坏的片段有意不写状态行它的载荷已被销毁、关联已消失没有任何东西能取回它状态行只会把这个哈希重新放回存储当前读取的布局里。因此在一个发生过毁坏的存储上obliterated计数非零是预期的。只有载荷仍在桶里的行才会进入该计数——即被中断、尚未删除对象的毁坏操作。真正删掉了对象的那种毁坏会留下一个迁移器无法加载的行计入errored。成功是按分段计不是按片段计退出码 0 意味着每个分段都走到了扫描末尾不代表每个片段都迁移了。有两种结果会留下未迁移的片段却仍以 0 退出unreadable——没有编解码器能复现哈希。未带oodle特性构建、而存储中又有 Oodle 载荷时所有 Oodle 片段都会落在这里oversized——尺寸超过阈值片段被拒绝而不是转换。一次迁移只有在这两项都为 0时才算是完整的。在此之前部署仍需保留元数据表的配置——[plugins.aws.immutable_store]下的dynamodb_fragment_metadata_table或contrib/aws中的fragment_metadata_table变量——因为遗留片段只能通过该表读取。Verify验证迁移验证要在节点仍处于维护模式时进行。1. 阅读统计量从运行日志的final行看unreadable、oversized、errored必须为 0。每一项都代表一个没有状态行、只能通过元数据表读取的遗留片段scanned与metadata_rows应一致。有差距意味着存在解析不出哈希的行运行会逐条记录它们obliterated可以是任意值——这些片段本来就该没有状态行。然后用 grep 检查日志中的Payload revives a tombstoned hash。没有任何统计量会计数它而且它比迁移不完整更糟运行为一个标记为已毁坏的哈希上传了载荷并把该哈希重新置回了Stored。这意味着在一次转换读取 state 与发布之间完成了一次毁坏——正是让存储静默所要防止的竞态——所以它根本不应该出现。如果出现了在重新放行流量之前要查清是哪些哈希。2. 以 dry run 再跑一遍这是验证通道verify pass。它重读每一行并报告每个片段现在的状态不写任何东西因此即使存储已经重新开始对外服务也是安全的./target/release/lore-aws-migrate ... --dry-runalready_migrated与obliterated之和应等于metadata_rows其余全部为 0。already_migrated意味着状态行 携带自身元数据的对象这一对新布局读取所需的组合——这正是为什么这一趟能确立迁移完成而单纯去数状态表不行。任何落在maintained、recompressed_*或stored_uncompressed下的都是运行没有转换的片段unreadable或oversized则是它无法转换的。注意这一趟会重新扫描整张表、head 每个对象并下载任何未迁移片段的载荷所以要像真实运行一样为它预留窗口。3. 抽查一个对象一个已迁移对象通过单个元数据头携带其完整片段信息aws s3api head-object --bucket lore-fragments-abc123 \ --key the 64 hex characters of the hash --region us-west-2 --query Metadata{ lore-fragment: 8:4096:16384 }格式为flags:size_payload:size_contentflags 是十六进制因为它是位域十六进制最便于读位两个大小是十进制因为它们是量级。编码实现在 lore-aws/src/store/object_metadata.rs刻意采用纯文本而不是紧凑编码让对象形态无需 lore 工具也能从aws s3api head-object直接读懂写入时 flags 会被约简到载荷相关位PAYLOAD_FLAGS让对象只描述载荷本身。没有lore-fragmentkey 的对象就是没被迁移无论状态表对它的哈希怎么说。Clean up清理仅在 Verify 报告存储已完全迁移之后进行。1. 让节点退出维护模式但先保留元数据表配置。已迁移的存储永远不会回退到该表而保留它能让存储即使验证有所遗漏也仍然可读。这并非完全免费——只要表还被命名批量查询batch query就还会顺带读状态表一旦配置移除它就跳过状态表。2. 停止命名元数据表。从[plugins.aws.immutable_store]移除dynamodb_fragment_metadata_table并作为一次普通部署滚动出去。在contrib/aws拓扑上对应fragment_metadata_table变量它会在两个任务定义上设置LORE__PLUGINS__AWS__IMMUTABLE_STORE__DYNAMODB_FRAGMENT_METADATA_TABLE早于该改名的部署可能写成dynamodb_metadata_table仍作为别名被接受。取消它等于声明不存在没有自身元数据的对象这样的对象将报告为损坏damaged而不是从某行来描述。这一步是可以回滚的——如果读开始失败就回滚此时还没有销毁任何东西。3. 删除元数据表如果它是独立的一张表。在第 2 步运行足够长、可以信任之后aws dynamodb delete-table --table-name lore-metadata --region us-west-2当--fragment-state-table与--fragment-metadata-table指向同一张表时没有可删的东西删了反而会毁掉整个存储。两种行形态共用它而迁移到那里的片段保留了自己已有的行——状态写入发现行已存在就放着不动——所以遗留形态的行就是状态表。在这种情况下第 2 步就是清理的全部。4. 归还权限。见下节其中dynamodb:Scan尤其没有授予任何其他角色。5. 移除构建产物。工具不安装任何东西、不留下任何状态删除contrib/aws-migrate-0.9.0/target可回收数 GB 空间。权限要求工具运行所用的凭证需要比服务器自身任务角色更多的权限元数据表上的dynamodb:Scan——服务器从不扫描因此它的策略没有授予此项元数据表与状态表上的dynamodb:GetItem状态表上的dynamodb:PutItem三张表上的dynamodb:DescribeTable与桶上的s3:ListBucketDescribeTable与HeadBucket都据此授权。两者在启动时发出因此名字写错会在运行触碰任何数据之前就失败片段桶上的s3:GetObject覆盖跳过检查所用的HeadObject与s3:PutObject用于重写。可以把--s3-endpoint-url与--dynamodb-endpoint-url指向本地栈local stack来排演需要时再加--s3-force-path-style按路径寻址桶某些 S3 兼容存储要求如此。附录针对本地栈的端到端测试这不是迁移存储的一部分而是工具本身——或其背后的lore-aws中的迁移器——发生改动时的检查方式。tests/migrate_local_stack.rs用旧布局的形态播种了 1000 多个片段运行本二进制、中途用SIGINT打断、再次运行然后检查状态行、对象头、验证通道的统计量以及在元数据表被移除的情况下通过存储回读数据。每个片段轮流采用五种遗留形态之一覆盖每一条恢复路径其中包括以 16 个分片保存的 1 MB 内容及其命名列表测试随后将其重组以及一个被毁坏的片段必须无状态行地通过、并读取为不存在。从仓库根目录运行docker compose --file lore-integration-tests/compose.yaml up --detach minio dynamodb cd contrib/aws-migrate-0.9.0 cargo test --features integration_testsintegration_tests特性默认关闭所以裸cargo test只跑参数测试、不需要任何服务。测试首次使用时创建桶、按运行命名建表通过时删除自己创建的东西失败运行会留下以lore-migrate-test-*命名的表。工作流速查把上述步骤收敛为一次生产迁移的完整顺序构建release 构建存储含 Oodle 载荷时加oodle特性排演以--dry-run对在线存储运行不写任何东西用与真实运行相同的--total-segments/--consumers估算维护窗口停服为整个窗口让存储退出服务——这是硬性要求。在contrib/aws拓扑上LORE_SERVER_MAINTENANCE1设置在主、边两个任务定义上并用aws ecs describe-services确认没有任务停留在旧版本运行执行迁移可多机分片、可中断续跑每 30 秒观察进度日志验证退出码 0 不等于迁移完成——检查final行统计量unreadable/oversized/errored必须为 0、scanned与metadata_rows一致、grepPayload revives a tombstoned hash、以--dry-run复跑确认already_migratedobliteratedmetadata_rows再aws s3api head-object抽查lore-fragment头清理节点退出维护模式 → 移除dynamodb_fragment_metadata_table配置可回滚步骤→ 删除独立元数据表 → 收回权限 → 删除target构建产物。迁移的核心是把 ADR-00020 确定的新布局落到存量数据上让对象自我描述、让状态表只回答存在性把描述信息与载荷的不一致从结构上消除。lore-aws-migrate只是把这一步做成可排演、可续跑、可分片、可验证的运维操作。赞分享版本控制后端【免费下载链接】loreLore is a next-generation, open source version control system项目地址https://gitcode.com/gh_mirrors/lore6/lore点击查看免费下载相关推荐从VSCode到Vimvimsheet教你打造高效编辑器环境从VSCode到Vimvimsheet教你打造高效编辑器环境 vimsheet是一份从入门到进阶的Vim速查表帮助VSCode用户轻松过渡到Vim高效编辑环版本控制后端3个时间管理痛点与一个优雅解决方案FlipIt翻页时钟屏保如何重新定义Windows闲置屏幕3个时间管理痛点与一个优雅解决方案FlipIt翻页时钟屏保如何重新定义Windows闲置屏幕 想象一下这样的场景你在深夜赶工需要频繁查看国际团队的时间但版本控制后端Compiler Explorer 短链接迁移实战从本地文件存储平滑迁移到 AWS S3/DynamoDBCompiler Explorer 短链接迁移实战从本地文件存储平滑迁移到 AWS S3/DynamoDB Compiler Explorer 的短链接sh后端前端开发工具上一篇Horovod终极资源利用率优化指南10个策略最大化GPU硬件性能下一篇揭秘wasm-service工作原理ServiceWorker如何拦截HTTP请求并驱动WASM渲染创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考