多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Polar Ship Safety:面向非原子化部署的代码评审安全核查实战指南

Polar Ship Safety:面向非原子化部署的代码评审安全核查实战指南 Polar Ship Safety面向非原子化部署的代码评审安全核查实战指南【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar导读本文围绕 Polar 开源仓库Billing platform for the intelligence era中的部署安全评审 Skill——ship-safety见 .agents/skills/ship-safety/SKILL.md展开。它回答一个非常具体的问题在一个迁移先跑、API 先于 Worker 上线、旧前端可能对着新后端运行、队列里还残留旧代码入队任务的非原子化部署环境里一个 diff 在「合并瞬间」到「完全部署完成」之间到底会不会把生产环境打挂。读完本文你将掌握一套可执行的五类检查清单Schema 先于代码、阻塞性 DDL、在途任务、锁与吞吐量、是否拆分 PR并能在合并涉及迁移、任务、模型、端点删除的 PR 之前产出一份规范的 Ship Safety 评审报告。一、为什么需要 Ship SafetyPolar 的部署窗口Ship Safety 的核心句是handle 是 diff证据是「合并那一刻到完全部署完成之间到底什么会坏」见 .agents/skills/ship-safety/SKILL.md。Polar 并不做原子化部署部署窗口里存在四类真实风险迁移先于代码执行migrations 先跑随后 API 才上线。API 先于 Worker 部署API 先于 worker 上线worker 滞后。旧前端可以对着新后端运行前端与后端发布不同步。队列中残留旧名字入队的任务合并时队列里已经躺着由旧代码按旧 actor_name、旧参数入队的任务。因此在最终状态下「逻辑正确」的代码在通往最终状态的路上依然可能把生产环境打崩。Ship Safety 检查的就是这段在路上的窗口。适用范围Scope需要跑 Ship Safety 的 diff 覆盖以下五类.agents/skills/ship-safety/SKILL.md触及server/migrations/versions/的迁移文件触及**/tasks.py的任务代码触及polar/models/的模型触及server/scripts/的运维脚本删除或重命名了某个 endpoint同时跨越server/与clients/且二者存在依赖关系的改动。也就是说只要 PR 动了数据库结构、异步任务、模型、脚本或动了对外 API 契约就应当跑一遍 Ship Safety。与 ADR-0006 的职责边界文档明确划定了Owned elsewhere由其他机制负责、不要重复阐述的部分.agents/skills/ship-safety/SKILL.md迁移规则由ADR-0006覆盖锁超时、nullable → 批量回填脚本run_batched_update→ 跨多个 PR 再收紧为 NOT NULL、enforce 迁移中的无条件UPDATE、迁移 PR 与代码 PR 隔离。CI 通过Migration Isolation Check强制执行adr-check报告违规重复造轮子的 helper由reuse-check负责计费相关的锁与循环规则由billing-review负责。Ship Safety 只负责ADR-0006 没有说到的剩余部分——即上面的部署窗口本身。二、检查 1Schema 先于代码Schema ahead of codeADR-0006 阻止了「代码跑在 schema 之前」但反过来依然会咬人迁移和新代码之间旧代码会跑在新 schema 上.agents/skills/ship-safety/SKILL.md。两条硬规则删除列或表会立即打挂正在运行的应用。原因在源码层面有明确依据SQLAlchemy 在 import 时就会把每个模型映射到表polar/models/下的模型文件会在模块加载阶段建立表映射所以一张被 drop 的表可能在任何查询执行之前就导致导入失败。正确顺序是先停止使用该列/表 → 部署 → 在后续的另一个 PR里再 drop。重命名永远是两步走加新名 → 部署 → 删旧名拆成两个 PR。对每一个 schema 变更都要问同一个问题当前已部署的旧代码在迁移执行后依然正确吗源码佐证Polar 的迁移模板在upgrade()/downgrade()开头都强制执行SET LOCAL lock_timeout 5s见 server/migrations/script.py.mako而 ADR-0006 的完整决策记录在 handbook/engineering/decisions/0006-migration-and-backfill-safety.mdx。生成迁移的入口命令是alembic revision --autogenerate -m your message见 server/migrations/README.md。三、检查 2阻塞性 DDLBlocking DDL针对大表上的结构性变更Ship Safety 要求给出明确的锁行为判断.agents/skills/ship-safety/SKILL.md大表建索引必须带postgresql_concurrentlyTrue且迁移必须放在事务外执行CONCURRENTLY 不能在事务块内跑。大表加 NOT NULL优先CHECK ... NOT VALID再VALIDATE CONSTRAINT让 Postgres 跳过全表锁ACCESS EXCLUSIVE。如果表很小这套操作就是仪式感——评审时要明确说明你认为哪种情况适用。涉及金额money表的新外键必须带ondeleterestrict防止级联删除破坏财务数据完整性。这也是 ADR-0006 决策背景的直接体现阻塞性ALTER或未分批的UPDATE在热表上会拿ACCESS EXCLUSIVE锁可能让线上 API 流量一直 stall 到操作完成。Polar 通过模板层的lock_timeout 5s保证锁等待最多 5 秒快速失败而不是把数据库拖死见 server/migrations/script.py.mako。四、检查 3队列里已经在途的任务Tasks already in flight当 PR 合并时队列里已经存在由旧代码入队的任务。这一节是 Ship Safety 的重头戏.agents/skills/ship-safety/SKILL.md。3.1 重命名 actor会让所有排队任务搁浅Worker 按actor_name查找任务处理器改名字后旧任务全部找不到处理器。SKILL.md 给出的标准操作序列是新增order.invoice.v2并开始用新名字入队部署等待旧队列排空drain删除旧 actor把名字换回。把 actor挪到另一个队列是同样的问题同样适用新旧并存 → 排空 → 退役三步。源码印证Polar 的 order 任务里真实存在这种演进痕迹——order.invoice任务指定了queue_nameTaskQueue.INVOICES_AND_RECEIPTS见 server/polar/order/tasks.py说明「换队列」与「actor 重命名」都是会真实发生的操作。3.2 修改签名破坏用旧参数入队的任务改了函数签名会破坏所有按旧参数入队的任务。新参数必须带默认值让旧任务仍能反序列化执行。源码印证order.trigger_payment的payment_trigger: str | None None就是典型的可空默认参数写法见 server/polar/order/tasks.py。3.3 队列优先级HIGH 是 checkout 专用TaskPriority.HIGH只能用于checkout 路径支付主链路分析analytics、导出exports、回填backfills一律走LOW慢任务放HIGH会饿死 checkout。源码印证checkout.handle_free_success、checkout.expired使用TaskPriority.HIGH而checkout.expire_open_checkouts这种周期清扫用LOW见 server/polar/checkout/tasks.pyorder 域里order.invoice、order_created、order.confirmation_email等非关键路径全部是LOW见 server/polar/order/tasks.py。3.4 Cron actor新增定时任务必须回答漏跑怎么办新增一个cron_trigger前必须回答错过一次运行怎么办。一个正向遍历跳过周期的catch-up 循环通常会算错状态更好的选择是不变量告警invariant alert——直接告警这次运行没发生而不是补跑。源码印证Polar 的 cron actor 使用CronTrigger.from_crontab(...)声明如order.process_dunning每小时、order.enqueue_stale_payment_locks每小时 15 分、checkout.expire_open_checkouts每 15 分钟见 server/polar/order/tasks.py 与 server/polar/checkout/tasks.py。3.5 重试语义新失败路径必须 raise而不是吞掉Polar 依赖自动重试新的失败路径必须raise绝不能swallow异常max_retries0必须是有意为之的决定而不是默认值。源码印证order.trigger_payment对 Stripe 网络类错误APIConnectionError/APIError/RateLimitError用raise Retry()明确重试而对PaymentFailed这类业务失败选择记录日志不重试交给 dunning 流程处理见 server/polar/order/tasks.py。五、检查 4锁与吞吐量Locks and volume新的with_for_update应该放在拥有该工作单元的 task 或 service 里而不是塞进某个 processor 专用的 helper。每一个锁都必须回答如果进程死了谁来释放这把锁只允许在一条路径上释放。一条锁在两条路径上释放就是一个等着爆发的 bug。把每一行匹配数据都加载进内存的做法在线上吞吐量下活不下去——必须用分批/流式查询。扫描整张繁忙表的定时清扫任务必须带索引或限定窗口bounded window。源码印证Polar 为此实现了陈旧支付锁的回收机制——order.enqueue_stale_payment_lockscron每小时 15 分通过stream_stale_payment_lock()流式扫描带锁的订单order.process_stale_payment_lock则用for_updateTrue加行锁、检查is_payment_lock_stale再把过期锁按手动重试失败处理释放见 server/polar/order/tasks.py。这正是「每个锁都要回答进程死后谁来释放」的工程答案由专用 cron 兜底恢复而不是靠人工。六、检查 5拆分这个 PRSplit this PR以下四种情况应标记为需要拆分.agents/skills/ship-safety/SKILL.md删除了后端 endpoint 同时又改了它的前端调用方——旧前端可能还 live 对着新后端跑重命名 actor 时同时删除旧 actor——要等旧队列排空再删原生移动端代码与 TypeScript 一起改——native 会阻断 over-the-air 发布应先把纯 TS 改动发布出去评审中已被标注应该移到另一个模块——后续 PR 是合理答案但要明确说出来而不是默默推迟。七、输出格式一份可直接粘贴的评审报告模板Ship Safety 的产出是一份结构化报告.agents/skills/ship-safety/SKILL.md完整模板如下## Ship Safety ### Blocking - file:line — what breaks, in which window. Fix: fix ### Should fix - file:line — what happens under load. Fix: fix ### Question - file:line — question, including split this PR? ### Notes - run before merge: script path, or none - manual step after deploy: e.g. remove order.invoice v1 after 4h, or none ### Verdict ✅ Safe to ship | ❌ n blocking, n should-fix模板的使用要点分级 Blocking会坏、在哪个窗口坏、怎么修、 Should fix负载下会怎样、 Question包括要不要拆 PRNotes 即使 verdict 是绿色也必须填写——一条必须跑的脚本没写下来就等于这条脚本不会跑部署后的手动步骤例如4 小时后移除 order.invoice v1也必须落到 Notes 里Verdict用✅ Safe to ship或❌ n blocking, n should-fix收尾。这套格式非常适合直接作为 GitHub PR 评论或 Code Review 模板复用它把「部署窗口风险」显式变成可审查、可追踪的条目而不是评审者脑中的模糊担忧。八、结语把 Ship Safety 纳入合并前流程把整个 Skill 串成一条工作流收到 diff 先看 Scope——是否触及migrations/versions/、tasks.py、models/、scripts/、endpoint 契约或server/clients/联动对每个 schema 变更回答旧代码在新 schema 上还正确吗判断 DDL 是否阻塞CONCURRENTLY / NOT VALID / ondeleterestrict审视队列actor 重命名、签名变更、优先级、cron 漏跑、重试语义审查锁与扫描锁的归属、释放路径唯一性、内存加载量、清扫窗口判断是否拆 PR按模板输出报告Notes 与 Verdict 一起提交。这套检查与 Polar 仓库内的 ADR-0006 决策handbook/engineering/decisions/0006-migration-and-backfill-safety.mdx、迁移模板server/migrations/script.py.mako以及真实的 task 实现server/polar/order/tasks.py、server/polar/checkout/tasks.py相互印证——它不是纸面规范而是 Polar 日常发布前真实执行的部署安全守则。在你的项目里即使没有这套 Skill 基础设施把同样的检查清单落到 CI 评论或人工评审模板中也能显著降低非原子化部署引入的事故面。【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表