第 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 为例:
- Alice 发 public text + hashtag;
- 检查 status、tag、home feed、public stream;
- Bob 关注 Alice,再验证后续 status 进入 home;
- Alice mention Bob,验证 notification 和 streaming;
- 上传媒体并发布,检查处理状态与 serializer;
- 编辑 status,验证本地 update 和远端 Update;
- 删除 status,验证 feed、search、notification、remote Delete;
- 让远端返回 5xx,验证 retry/backoff/Stoplight;
- 断开 streaming,再重连验证补偿拉取;
- 用无权限 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 时,能迅速判断它在系统中改变的是什么。