第 10 章:部署、测试与扩展路线

一、开发与生产是两种拓扑

生产 docker-compose.yml 使用预构建镜像,Web、Streaming、Sidekiq、PostgreSQL、Redis 分开运行;开发环境则需要 Ruby、Node、数据库、Redis、媒体依赖和 Vite 热更新。不要因为生产能 docker compose up 就认为所有源码修改都已被验证。

二、启动前的依赖矩阵

快照 README 给出的主要要求:

依赖作用
Ruby 3.3+Rails Web、service、worker、CLI
PostgreSQL 14+事实数据和关系查询
Redis 7+缓存、队列、Feed、Pub/Sub
Node.js 22+Vite 构建与 Streaming
FFmpeg 5.1+媒体处理

此外,媒体存储、反向代理、邮件服务、可选 Elasticsearch 和对象存储会影响完整功能。

三、生产容器的启动顺序

db / redis health
  └─ web Puma
       ├─ migrations / assets already ready
       └─ REST + ActivityPub + HTML
  ├─ sidekiq
  │    └─ queues + scheduled jobs
  └─ streaming
       └─ WebSocket / SSE + Redis subscriptions

健康检查只说明进程能响应,不说明迁移、队列、联邦或媒体处理全部正常。上线后仍需验证一条完整的用户链路。

四、测试如何映射架构

Mastodon 的测试目录按行为边界拆分:

  • spec/models:关系、scope、validation、领域不变量;
  • spec/services:跨模型用例编排;
  • spec/workers:异步 payload、重试和副作用;
  • spec/requests:REST/ActivityPub HTTP 契约;
  • spec/controllers:controller 层行为;
  • spec/serializers / spec/presenters:输出 schema;
  • spec/system:浏览器/系统级行为;
  • streaming 的测试:Node 服务、Redis、连接和过滤。

修改一条跨边界行为时,优先补最靠近不变量的 spec,再补协议/系统回归。

五、推荐的改动工作流

第 1 步:画调用链

写出入口、service、model、worker、serializer、Redis channel 和前端 action。只要链路中有一个未知,就先读源码/测试,不急着改。

第 2 步:确认同步边界

判断改动是否应在请求内完成,还是入队。如果会访问远端、处理媒体、批量通知、重建 feed 或抓取外部链接,通常应异步。

第 3 步:定义失败语义

明确哪些错误返回 4xx,哪些 retry,哪些安全 return,哪些必须告警/人工处理。

第 4 步:补最小垂直测试

先覆盖 service invariant,然后覆盖 worker/serializer/request。不要只测 happy path。

第 5 步:验证投影

检查 PostgreSQL、Redis feed、notification、search index、ActivityPub delivery、streaming 和 UI 是否都符合预期。

六、扩展点的正确位置

增加 REST 能力

路由 → controller → policy → service/query → serializer → request spec → API docs。

增加 ActivityPub 类型

parser → Activity subclass → service/model → serializer → delivery/receive spec。

增加通知类型

domain event → NotifyService → notification type/serializer → streaming/web push/email → frontend reducer/component。

增加时间线

定义 feed 的事实来源、Redis key、可见性过滤、fan-out worker、streaming channel、REST pagination 和维护/重建命令。

七、数据库迁移与回滚

迁移应考虑:

  • 大表上索引是否需要并发创建;
  • 默认值是否会锁表;
  • 新旧代码在滚动发布期间是否都能工作;
  • worker 是否会看到旧 schema;
  • 回滚时派生索引/Redis 是否仍兼容;
  • 数据清理是否不可逆。

安全的发布通常采用 expand → deploy code → backfill → contract,而不是一次迁移同时删列、改代码和清理数据。

八、可观测性验收清单

上线一个跨进程改动,至少观察:

HTTP latency/error rate
Sidekiq queue latency/retry/dead
PostgreSQL query/lock/pool
Redis latency/memory/pubsub
Streaming connected clients/send failures
ActivityPub per-host delivery
Media processing failures
Search indexing lag

如果只能看到 Web 200,就还没有真正验证这条链路。

九、一次完整的验收脚本

以测试账户 Alice 和本地/远端 Bob 为例:

  1. Alice 发 public text + hashtag;
  2. 检查 status、tag、home feed、public stream;
  3. Bob 关注 Alice,再验证后续 status 进入 home;
  4. Alice mention Bob,验证 notification 和 streaming;
  5. 上传媒体并发布,检查处理状态与 serializer;
  6. 编辑 status,验证本地 update 和远端 Update;
  7. 删除 status,验证 feed、search、notification、remote Delete;
  8. 让远端返回 5xx,验证 retry/backoff/Stoplight;
  9. 断开 streaming,再重连验证补偿拉取;
  10. 用无权限 token/被 block 账户复测可见性。

十、源码阅读的最后一张地图

用户动作
  → route/controller
  → service
  → model + transaction
  → worker
      ├─ local projection: feed/notification/search/media
      ├─ network projection: ActivityPub delivery
      └─ realtime projection: Redis → streaming
  → serializer
  → REST / ActivityPub / React

任何新功能都可以问:“它改变了哪一个事实?生成了哪些投影?需要经过哪些协议和权限边界?失败时如何恢复?”这四个问题比记住某个目录更有迁移性。

十一、源码入口清单

十二、结语

读懂 Mastodon,不需要一次性读完几千个文件。先用发帖这条主线建立进程地图,再沿领域模型、联邦、实时、前端和异步投影逐层展开;最后用测试、日志和维护命令验证每个边界。这样你看到一个新 service、worker 或 serializer 时,能迅速判断它在系统中改变的是什么。