生产级应用
核心理念
每个大型公司最终都会收敛到相同的架构形态:一个边缘节点位于前端,一个可部署的前端应用,一个位于网关后的独立后端,从队列中拉取任务的工作线程,一个关系型数据主库,一个缓存层,一个事件或作业中间件,一个对象存储层,以及贯穿所有环节的可观测性。Shopify、Notion、Figma、Instacart 和 Airbnb 在编程语言和供应商选择上有所差异,但架构形态是相同的。
这种架构形态不会因为是一个周末 MVP 项目还是一个拥有平台团队的大公司而改变。唯一改变的是每个组件背后的提供商。因此,此技能在第一天就使用廉价的管理型提供商构建完整的架构形态,强制执行一小组可移植性规则,并为每个层级保留一份剧本,以便在达到可衡量的触发条件时切换提供商。通过这种方式,从 Vercel 和 Supabase 迁移到 Kubernetes 和 RDS 只是一系列配置更改和数据拷贝操作,而从来不是一次重写。
如何使用此技能
- 阅读项目描述。仅针对描述中未涉及的部分提出下面的接收问题。
- 使用
references/providers-and-tiers.md为每个层级选择一个提供商。除非选择规则另有说明,否则从第一级开始。 - 使用下面的模板编写
docs/PLAN.md,包括根据references/architecture.md调整的图表,并在搭建脚手架之前展示它。 - 按以下顺序搭建脚手架并构建。
- 在启动前,运行
references/launch-checklist.md,仅报告失败的项及其修复方法。 - 当稍后触发了一个条件时,打开
references/migrations.md并迁移该层级。切勿在同一周内迁移两个层级。
如果是现有应用:首先进行审计。将代码库映射到此架构形态上,按风险(数据泄露、宕机、滥用、锁定、成本)列出存在的差距,然后停止。后续的更改由所有者从列表中挑选决定。
接收问题
仅询问描述中未解答的问题,一次性问完。
- 用户是谁,以及什么是租户:拥有数据的单位(一个公司、一栋建筑、一个经销商、一个人)?
- 什么数据量很大:动态消息、消息、上传、视频、事件、搜索?大约是每天多少行或文件,以及增长率。
- 是 Web、iOS、Android 还是混合模式?哪个先发布?
- 实时需求:聊天、在线状态、协作编辑,还是简单的轮询即可?
- 盈利模式:订阅、一次性付款、市场支付,还是没有?
- 是否涉及任何受监管或敏感内容:存储的支付信息、健康数据、未成年人数据、地理位置、政府或企业买家(他们会要求 SOC 2)?
- 受众地域:一个国家还是全球?
- 约束条件:现有的云额度、现有的提供商、必需的语言。
这些答案将驱动 references/providers-and-tiers.md 中的选择规则。
架构形态
每个层级都有一个作用、一个大公司参考案例,以及一个保持其可移植性的规则。每个层级的具体供应商位于 references/providers-and-tiers.md。
- 边缘节点:位于所有主机之前的 DNS、TLS、CDN、DDoS 防护、WAF、速率限制、机器人过滤。大公司:Cloudflare、Akamai、Fastly、CloudFront with AWS WAF。规则:你拥有 DNS 区域;每个提供商都位于 CNAME 之后,因此切换提供商就是一次 DNS 更改。
- 前端:一个单独的可部署项目,静态渲染或服务端渲染,不包含业务逻辑,不访问数据库。大公司:一个 React 或 Next.js 应用,打包成容器并通过 CDN 提供服务。规则:前端打包成一个标准的 Node 容器或静态包,并且仅与公共 API 通信。
- 网关:一个负载均衡器或 API 网关,用于终止来自边缘节点的 TLS、按路径路由流量并强制执行粗略的限制。大公司:ALB、Envoy、NGINX ingress、Kong。规则:API 永远不依赖于网关提供的、其自身中间件无法复制的功能。
- 后端:网关后的一个单体服务,仅在团队发生冲突时才拆分为微服务。大公司:Shopify 和 GitHub 使用 Rails,Instagram 使用 Django,Notion 使用 Node,内部使用 gRPC,外部使用 GraphQL 或 REST。规则:一个 Docker 容器,带有健康检查端点,配置来自环境变量,业务逻辑中不包含供应商 SDK。
- 工作线程和队列:消费作业的独立长时间运行进程;当作业包含多个步骤或需要人工等待时,使用工作流引擎。大公司:Shopify 使用 Sidekiq 和 Kafka 消费者,Netflix 和 Duolingo 使用 Temporal,Notion 使用 Kafka。规则:作业是幂等的,携带 ID 而不携带数据块,队列是标准的 Redis 或 Kafka 协议。
- 关系型数据库:一个作为系统主记录源(system of record)的 Postgres(或 MySQL)主库,用于读取的副本,仅当单台机器达到极限时才进行分片。大公司:Notion 和 Figma 使用 RDS Postgres 和 PgBouncer,Shopify 有超过 100 个 MySQL 分片。规则:仅使用 Postgres wire 协议,SQL 迁移文件保存在代码库中,权限和 RLS 使用 SQL 定义,不使用任何仅在某个供应商 API 上工作的功能。
- 缓存:用于热数据读取、速率限制计数器、锁、会话的 Redis 或 Memcached。规则:标准 Redis 协议,键按租户范围划分并带版本号,应用必须在缓存为空的情况下也能正常运行。
- 对象存储:用于存储所有文件的 S3 或兼容 S3 的存储桶;绝不存储在数据库中。规则:仅使用 S3 API,键按租户添加前缀,通过签名 URL 或 CDN 提供服务。
- 身份认证:一个发布 JWT 的身份提供者,一个由应用拥有的
users表。大公司:内部方案 + 用于企业 SSO 的 Okta 或 Auth0。规则:API 通过 JWKS 自行验证令牌并拥有 users 表,因此身份提供者可以更换。 - 事件和分析:产品分析工具,以及后续通过变更数据捕获(CDC)数据馈送的数仓。大公司:Kafka 到 Snowflake 或 BigQuery。规则:数仓从数据库中读取数据,从不从应用进行双重写入。
- 可观测性:结构化日志、错误追踪、指标、链路追踪、可用性监控、与操作手册关联的告警。大公司:Datadog、Grafana、Honeycomb、Sentry、OpenTelemetry。规则:应用发出 OpenTelemetry 数据和 JSON 格式日志,因此后端只是一个导出器的设置问题。
- 交付:每次推送都进行 CI 构建,基础设施即代码,金丝雀或蓝绿部署,特性标志,快速回滚。大公司:Terraform、Buildkite 或 GitHub Actions、ArgoCD 或 Spinnaker、LaunchDarkly。规则:任何在仪表盘中点击完成的操作都在当天记录到
infra/中,并从第二级开始使用 Terraform。
可移植性规则
这些规则使得后期的迁移成本低廉。它们在 MVP 阶段几乎零成本,并且它们决定了迁移是一个周末就能完成的任务,还是一个需要耗时数季度的重写。
- 从第一次提交开始,前端和后端就是独立的代码仓库和独立部署的项目。它们通过一个版本化的 HTTP API 和共享的 schema 进行通信。
- 后端和工作线程是可以在任何运行容器的地方运行的 Docker 镜像。API 不使用无服务器函数:Vercel Functions、Lambda handlers 和 Edge Functions 是唯一无法在不重写的情况下移植到容器中的东西。
- 仅通过标准协议与基础设施通信:Postgres wire 协议、Redis 协议、S3 API、SMTP 或接口背后的 HTTP 邮件 API、用于令牌的 JWKS、用于遥测的 OpenTelemetry。供应商的客户端库仅允许在可以替换的薄适配器模块中使用。
- 业务逻辑中不包含供应商特定功能:不调用 PostgREST,后端中不使用 Supabase 或 Firebase 客户端,不使用 Vercel 特定的缓存 API,不使用 Workers KV。前端仅可使用供应商的认证 SDK 进行登录。
- 应用拥有其 schema:SQL 迁移文件保存在代码库中,由迁移工具执行,绝不通过仪表盘执行。角色、行级安全(RLS)和函数都是 SQL 文件,可以在任何 Postgres 上重放。
- 应用拥有其用户:一个
users表,在首次登录时从身份提供者镜像过来,将提供者 ID 作为列存储。权限数据存在于应用的表中。 - 配置是启动时验证的环境变量。机密信息存在于平台的密钥存储中,但作为环境变量提供给应用,因此密钥存储可以更换。
- 你拥有 DNS 区域(Cloudflare)。每个提供商都是一个 CNAME 目标。不要将任何域名指向提供商的名称服务器。
- 对象存储的键和存储桶布局在 R2 和 S3 上保持一致;代码使用带有端点配置的 S3 SDK。
- 每个层级都有记录在案的导出方法:数据库使用
pg_dump和逻辑复制,对象使用rclone,永远不需要 Redis 转储,因为缓存是可丢弃的,并且队列会排空。 - 从第一天起就具备健康检查端点、优雅关闭(SIGTERM)和水平无状态特性,因为从 Railway 到 Kubernetes 的每个平台都依赖这些特性。
- 从第一天起遥测和日志就使用 OpenTelemetry 和 JSON 格式;不要在代码中安装供应商代理。
层级
每个层级分为三级。触发条件是可衡量的;完整的表格(包含供应商、成本和选择规则)位于 references/providers-and-tiers.md。
- 第一级,MVP:需要几分钟即可设置完成的管理型提供商,总成本每月低于约 150 美元。前端使用 Vercel,后端容器使用 Railway,Postgres 使用 Supabase,Redis 使用 Railway,对象存储使用 R2,边缘节点使用 Cloudflare,错误监控使用 Sentry 和 Better Stack。此方案一直有效,直到有付费客户询问可用性问题,或者达到以下数值。
- 第二级,增长期:在重要的层级上,在您自己的云账户中使用相同的架构形态:Postgres 使用 RDS 或 Aurora Multi AZ 并搭配 PgBouncer 或 RDS Proxy,容器使用 ECS Fargate 或小型 EKS,缓存使用 ElastiCache,对象存储使用 S3 或 R2,所有基础设施使用 Terraform,增加一个只读副本,工作流使用 Temporal。触发条件:有付费客户要求 SLA 或 SOC 2,或持续 5000 月活跃用户且数据库 CPU 使用率超过 60%,或 PaaS 账单超过 500 美元/月,或有第二名工程师加入。
- 第三级,大规模:Shopify 和 Notion 所使用的架构。Kubernetes(带自动扩缩容),分片或分区主库,使用 Kafka 作为事件中间件,通过 CDC 构建数仓,多区域读取,使用 Datadog 或 Grafana,拥有一个平台工程团队。触发条件:持续每秒 1000 个请求,单个主库接近 1TB 或(在使用副本和缓存后)写入受限,有多区域需求,或存在一个运维团队来管理它。
层级迁移是逐个层级进行的,而不是一次性全部迁移。数据库通常最先迁移,然后是最重要的服务,最后是前端,因为前端留在原处的成本最低。
构建顺序
- Schema、迁移文件、角色、RLS(
references/database.md,references/security.md)。 - 身份验证和租户上下文中间件。
- 核心 API 路由,包含验证和写入操作的幂等性。
- 速率限制和缓存中间件(
references/rate-limiting.md,references/caching.md)。 - 工作线程和队列;任何缓慢或外部操作都移出请求路径(
references/jobs-and-queues.md)。 - 基于 API 开发前端。
- 基于同一个 API 开发移动端。
- 日志、指标、链路追踪、健康检查、告警(
references/observability-and-reliability.md)。 - 启动门禁,以预期峰值三倍的流量进行负载测试,一次恢复测试(
references/launch-checklist.md)。
PLAN.md 模板
# <项目> 计划
## 一句话产品描述(三句内)
## 用户、租户、角色
## 各层级提供商 (每个层级的级别,选择原因,触发其迁移的条件,来自 providers-and-tiers.md)
## 数据模型 (表,标记了 tenant_id,索引,分区表)
## API 接口 (按资源划分的路由,认证级别,速率限制级别)
## 后台任务 (队列,触发条件,幂等性键,重试策略)
## 第三方服务 (服务名称,目的,密钥名称,限制,故障模式)
## 缓存映射 (缓存内容,键,TTL,哪些写入操作使其失效)
## 威胁模型 (前 5 个滥用案例及其控制措施)
## 规模假设 (启动时和达到 10 倍规模时的用户数、数据行数、每分钟请求数)
## 启动成本 (每个提供商,每月费用)
## 各提供商的退出计划 (适用的 migrations.md 剧本,每个一行)
## 架构图
不可协商的规则
- 每个租户拥有的表必须有
tenant_id列;启用并强制行级安全(RLS);API 根据经过验证的成员身份在每个事务中设置租户,而非来自请求输入。 - 客户端永远不直接接触数据库或持有特权密钥。所有操作都通过 API 进行。
- 每个输入都在边界处进行验证,并拒绝未知键;每个查询都使用参数化查询。
- API 和工作线程通过连接池进行连接,池大小较小;按角色设置语句超时。
- 在边缘节点和 API 中按用户、租户和 IP 设置速率限制。缓存键按租户范围划分并带版本号。
- 机密信息存储在平台的密钥存储中,每个环境一套,绝不提交到 Git 或移动端 bundle 中。生产环境和预发布环境是独立的项目或账户。
- 启用 PITR(时间点恢复),并进行一次恢复测试。
- Webhook 签名需验证,支持重放保护,具有幂等性,并在两秒内响应。
- 每个出站调用都要设置超时;仅在幂等工作上使用带抖动的重试机制;失败的任务必须可被人工查看。
- 每条日志行都包含请求 ID 和租户 ID;对错误率、p95 延迟、5xx 数量、队列存在时间、失败任务、数据库连接数进行告警。
移动端
- 已有的 React Native 或 Expo 应用:保持不变。
- 新应用,仅限 iOS 或首选 iOS:使用原生 Swift 和 SwiftUI。大公司在 2026 年回归原生方案(Shopify 回到 Swift 和 Kotlin,Notion 使用 SwiftUI),因为编码代理消除了维护第二套代码库的成本;对于小团队也是如此。仅当实际需要构建 Android 版本时,才使用 Kotlin。
- 新应用,必须由一两个人实现在同一天发布 iOS 和 Android 版本:使用 Expo。
- 无论哪种方式,应用都仅与 API 通信,将令牌存储在安全存储中,并且绝不将任何机密信息打包在 bundle 中。
参考文件
references/providers-and-tiers.md:每个层级的表格(大公司实践、第一/二/三级供应商、触发条件、成本)以及将项目描述转化为具体选择的选择规则。references/migrations.md:将每个层级提升一个级别的逐步操作手册。references/architecture.md:包含层级标签、三种请求路径、代码仓库布局、环境、边缘节点和平台配置的图表。references/security.md:租户管理和行级安全、身份认证、机密信息、HTTP 头、输入验证、文件上传、Webhook、加密、数据删除和导出、移动端、审计日志、安全事件。references/rate-limiting.md:四个层级,包含具体数值和中间件配置。references/caching.md:每个缓存层级的键模式、TTL、失效策略、防止缓存雪崩。references/database.md:Schema 规则、角色、连接池、迁移策略。
...