基础设施:让用户用得上,出问题能恢复

本篇内容

你终于约到一位愿意试用的人。首页打开很快,演示视频也能播放,但他选择表格、点下生成后,一直等不到结果。你从国内电脑再试一遍,却没有复现。此时,“网站已经部署成功”帮不上多少忙:到底是页面、请求、数据,还是某个只有你知道的前提出了问题?

基础设施要解决的,就是让一次使用可以完成,也可以被你解释和恢复。先沿着用户的任务看需要什么,再选工具,会比从一张热门服务清单开始更容易作决定。

本章继续使用周报工具的教学案例:第一轮试用在浏览器里处理文件,不上传、不保存云端历史。后面需要账号与服务端状态的情形是另一个阶段。文中的延迟数字为合成设定;发布与恢复部分则附有本次实际执行的本地隔离演练,不代表海外生产环境已经验收。

先决定,哪些东西现在就需要存在

试用者当前要做的事很具体:打开英文说明,用虚构样例看懂结果,再用自己的表格生成并导出草稿。只要这条路径都在浏览器里完成,就可以先交付一个有域名、有版本记录的静态站点。账号、数据库和登录邮件暂时不在关键路径上。

这能减少初次体验的等待,也减少你要保管的状态。但代价同样明确:没有跨设备历史,刷新或关闭页面前需要导出;不能依靠前端代码保管秘密,也不能把隐藏按钮当成付费授权。若客户真正需要共享报告、保存历史或购买后恢复权益,就要为那些状态增加后端,而不是继续假装所有问题都能留在浏览器里。

中文开发者程小白在 2022 年复盘博客迁移时,遇到的麻烦并不是算力不足。他维护过内容和图片的副本,更新后各处缓存会短暂不一致;同时,构建依赖和手动上传也耗费精力。结合自己的访问情况,他最终取消分流,将数量不多的图片转成 WebP 随仓库管理,收拢到 GitHub 与 Cloudflare Pages 的发布流程。[1]

值得借鉴的是这次减法的依据:内容是静态的,图片规模小,额外副本没有带来足够收益。这不能直接替有账号、订单和用户文件的产品选架构。回到我们的试用样稿,先把页面、脚本和样例作为一个可重复构建的版本交付,确实比先接五个服务更符合当前任务。

同一个产品,两个不同阶段

当前:完成一次本地试用

  1. 域名与页面
    交付固定版本
  2. 浏览器处理
    文件留在本机
  3. 导出草稿
    用户保存结果

↓ 当保存、共享或权益成为必要能力时,再增加下方路径

有服务端状态以后

  1. 浏览器发出请求
    身份与具体动作
  2. API + 数据存储
    授权、记录、恢复
  3. 返回结果
    必要时另发通知
新增后端不意味着 CSV 必须上传。仍可只保存必要的账号或权益状态;每一条新增数据路径都要重新说明用途与恢复方式。

如果进入第二阶段,我会在这个案例里先选一个容易部署、能导出数据的托管后端,并让它与主要数据库靠近。理由是维护者只有业余时间,还没有证明自己需要多地写入或特殊运行环境。托管也有边界:运行限制、数据导出、备份范围和账户恢复,都要在交付前走一次,不能因控制台显示绿色就略过。

自管 VPS 并非天然更差。已经熟悉系统更新、日志、数据库和恢复的人,可以把一个小服务放在自己掌握的环境里;但本例尚未具备这项运维条件,不能只比较月账单就把维护工作算成免费。相反,全球多个后端和多套数据副本现在更难解释:还没有实测瓶颈,却先增加了故障时要核对的状态。出现目标地区的真实延迟或可用性问题,再决定扩在哪里。

页面很快,为什么生成结果仍然很慢

静态资源离用户近,和处理任务的代码离数据库近,是两个不同问题。一个页面可以很快从缓存加载,随后发出的动态请求却连续跨很远的距离读取数据。此时只测首页,恰好绕开了用户真正等待的部分。

Cloudflare 的 Placement 文档就用多次后端往返解释这类取舍:Workers 默认靠近接收请求的位置运行,但访问数据库等后端时,让代码靠近后端可能更快。它支持的是“沿请求路径看位置”的判断,不是某个节点对所有产品都最快的保证。[2]

下面给同一请求安排两条路径。假设它依次查询四次,每次必须等上一次返回,不能并行;数字只计算网络往返,不含查询执行、连接建立、传输大文件或缓存差异。改变次数和距离,看看哪一段开始支配等待。

动手比较 · 合成网络条件

代码应该靠近用户,还是靠近数据?

A · API 靠近用户

  1. 浏览器
  2. 近用户 API
    与浏览器往返 20 毫秒
  3. 远端数据库
    4 次往返,每次 150 毫秒

620 毫秒网络等待

20 + 4 × 150 = 620

B · API 靠近数据库

  1. 浏览器
  2. 近数据 API
    与浏览器往返 160 毫秒
  3. 同处附近的数据库
    4 次往返,每次 5 毫秒

180 毫秒网络等待

160 + 4 × 5 = 180

这组设定下,B 少等待 440 毫秒。减少反复跨距离查询,比缩短最初那一次连接更有影响。

这是两条假设路径的比较,不是地区测速或供应商排名。真实决定还要考虑执行时间、读写一致性、数据所在地与目标用户的实际网络。

因此,有状态版本的第一份位置决定可以很朴素:静态页面单独交付;把完成一次动态任务的代码和主要数据放在容易连通的位置;先减少不必要的串行读取。再从目标用户的真实环境测完整操作,确认结果,而不是把地图上的国家名称当性能数据。

验收时让同一份小样本从头走完:新会话打开页面,生成 North 项目的 5 小时草稿,导出并重新打开文件。随后用一份缺少必填列的样本,看错误能否被理解。记录发生时间、所用版本、失败步骤和请求标识;不要把客户原文件放进排障记录。若加入账号或收费,再增加登录、关闭后重新进入、权益读取等路径,支付事件的一致性见收款方案全景。

这里需要两种视角:用户所在的网络能否完成任务,维护者平时的网络能否发布、查看告警并联系服务商。两边都能打开首页,仍不足以证明上述过程走通。本轮只完成了本地隔离演练,目标用户环境的这一步需要在真实试用时补做。

邮件已经发了,用户为什么还进不来

一旦登录依赖邮件,邮箱就进入了产品的关键路径。对用户来说,他只知道“没有收到”;对你来说,至少要分清请求是否被接受、收件服务器是否接收,以及用户最后有没有完成登录。

以 Resend 的事件定义为例,email.sent 表示 API 请求成功,之后仍会尝试投递;email.delivered 表示送达收件方邮件服务器。后一个状态并没有告诉你邮件位于主收件箱,也没有证明登录链接有效。临时投递延迟与永久退信还有各自的事件。[3]

从发件记录走到一次成功登录

  1. 请求被接受保存消息 ID,确认发件环境、模板和收件地址。未接受 → 查密钥、发件域与请求错误。
  2. 收件服务器接收按同一个消息 ID 查 delivered、延迟或退信。未接收 → 依据实际事件处理,避免盲目重复发送。
  3. 用户完成登录用测试收件箱实际打开链接,确认正确域名、有效期和回跳页面。已经接收但未完成 → 继续查邮箱位置、地址拼写和链接流程。
前两步参考邮件服务事件,第三步需要你自己的登录结果。只有三步连起来,才完成了用户眼中的一次登录。

截至 2026-10-09,Gmail 对个人 Gmail 收件账号的发件要求仍区分一般和批量发送者:一般发送者至少配置 SPF 或 DKIM;批量发送者要求 SPF、DKIM 和 DMARC,营销和订阅邮件还有一键退订要求。Google 建议所有发送者配置三项认证。按发件服务提供的记录配置,再用实际收件验收,不把某一个邮箱提供商的规则概括成所有邮箱都相同。[4]

假设测试记录中,demo-mail-01 的 API 请求成功,随后出现 email.delivery_delayed,用户没有登录。此时可确认的是消息还没有完成服务器投递;没有证据说验证码页面坏了。先查看具体延迟原因,在页面保留清楚的等待与重试入口,并限制重复请求。若后来变成 delivered,再去核对实际收件和链接,排查顺序也随证据改变。这是事件解释示例,没有真实发件结果。

给用户的文字也应和证据一致。可以写 “We’ve requested a sign-in email. It may take a moment to arrive.”,而不是在 API 返回成功时就宣布邮件已经进入收件箱。重要故障的告警也不要只依赖正在排查的那一条邮件路径。

恢复旧版本,和找回用户的工作,是两回事

GitLab 在 2017 年的数据库事故中,先是主库数据被误删,随后发现预期中的备份并不可用。原始复盘记录:备份工具与数据库主版本不匹配,导出任务失败;通知失败的邮件又被拒收,问题没有及时暴露。最终团队使用约六小时前为测试准备的快照恢复,之后一段时间的修改仍然丢失。[5]

这个案例值得看的不是“连大公司都会出错”,而是几个各自看似存在的保护为什么没有接起来:有备份任务,却没有可用产物;有告警配置,却没有收到告警;有旧数据,却没有最近的工作。小产品不需要复制当年的系统规模,但需要把这三件事分别验证。

两座建筑模型作对照:左边多了一间红色附屋,右边仍只有原来的建筑,比喻旧副本缺少后来新增的工作。

就像保留原建筑的模型不含后来加建的部分,能打开的旧副本也可能缺少新工作。恢复后还要核对缺了什么。

一次实际执行的隔离演练

我们为这一章写了一个只在本机运行的练习:两个版本的微型 HTTP 服务,加一份合成 JSON 记录。2026-10-09,在 macOS、Node.js 22.19.0 下实际执行了下面的过程。所有故障只发生在脚本新建的临时目录里,没有连接真实数据库、云平台或客户数据。

North 项目最初有两条记录:Research 为 3 小时,Draft report 为 2 小时。v1 正确求和;故意写错的 v2 只取第一行。恢复部分再增加一条 Review,值为 1 小时,用来观察备份之后的工作。

实际执行的动作观察到的结果这一关说明什么
启动 v1,请求 /reportHTTP 200,5 小时已有一份可核对的基准
停止旧服务,启动故意出错的 v2健康入口正常,报告 HTTP 200,却只有 3 小时只监测状态码会漏过结果错误
停 v2,重新启动 v1回到 5 小时这次代码回退有效,数据格式没有改变
停写并复制备份,再新增 r3新记录被读出,总计 6 小时备份之后出现了新工作
损坏练习数据,再运行原来的 v1HTTP 500,记录无法读取正确的代码不能修复坏数据
将旧备份复制到隔离目录并启动 v1HTTP 200,5 小时,缺少 r3旧副本可读,最新工作尚未完整恢复

最后一步刻意没有记成“恢复成功”:它通过的是旧备份可读检查,未通过的是最新业务记录完整性检查。必须从仍然可用的其他记录补齐,或明确说明丢失范围;不能因为页面重新变绿,就告诉用户所有内容都回来了。

你可以下载 完整演练脚本,在装有 Node.js 22 或更新版本的本机执行:

node infrastructure-drill.mjs

脚本仅监听 127.0.0.1 的临时端口,输出每一步的 HTTP 结果和校验结论,在新建临时目录留下 result.json,最后关闭服务。这是文件与进程层面的教学演练:复制前已停止写入,不能把它的 JSON 文件复制方法照搬成运行中数据库的备份方案,也没有测量真实故障下的恢复时间。

从练习接到自己的服务

真正上线时,先明确分别要恢复什么。页面和程序需要可重新构建的版本;数据库需要受支持的备份与恢复方式;对象文件、环境配置、密钥以及第三方状态还要逐项覆盖。以 Supabase 为例,其文档明确数据库备份不包含 Storage 里的对象文件,数据库只保存相关元数据;恢复旧数据库备份不会找回后来删除的对象。托管备份有用,但名字叫“数据库备份”就不该被理解为“整个产品副本”。[6]

恢复时,先分清哪里出了问题

暂停受影响的写入,保留当前状态与诊断记录

代码结果错,数据仍完整

确认旧版本能读当前数据 → 回退版本 → 重跑固定样本。

做过数据迁移时,必须另查兼容性。

数据损坏或丢失

选恢复点 → 在隔离环境恢复 → 比较记录与文件 → 说明缺口。

旧副本之后发生了什么,需要另找证据。

↓ 两条路径都要回到真实任务:能打开、算正确、记录齐,再决定恢复服务

不要把回退代码、恢复数据和重新开放服务压缩成同一个按钮。恢复检查也应避免触发真实通知或重复交易。

然后给恢复目标写上能检查的边界。比如这次练习可以准确报告“恢复到备份中的两条记录,第三条缺失”,却不能报告“零数据丢失”。自己的服务也应写清最多能接受回到多早的状态,以及准备在多长时间内恢复关键操作;这两个目标分别决定备份间隔和恢复准备,不能从云厂商的可用性数字直接推出来。

发布过程与此相接:为每次构建留下代码版本、配置环境和固定样本结果;先在隔离环境验证,再开放给用户。上线后重复同一个小任务,既检查可达,也检查输出。若正在处理真实付款或发信,演练环境应关闭这些外部动作,不能让一次恢复测试给客户补发旧通知。

把恢复入口留在故障之外

应用还在,域名过期或管理邮箱失效,也会让你无法继续服务。以 Cloudflare Registrar 为例,官方写明默认开启自动续费;默认值不能证明付款方式一直有效,仍要查看实际续费结果。保留域名、DNS 和邮件记录的当前配置,恢复邮箱尽量不要只依赖同一个域名。[7]

对本例,可以把恢复所需资料整理成一个单独保存的小包:当前发布版本、上一可用版本、构建步骤、DNS 记录、备份所在位置、恢复操作说明,以及一份没有客户资料的固定测试样本。写清密钥存放在哪里,不把密钥本身塞进仓库或这份说明。至少从新目录做一次构建和恢复,查出自己平时电脑上那些没有记录的依赖。

密钥与环境也要分开。Cloudflare Workers 文档要求敏感值使用 secrets,不能放进明文 vars;本地 .dev.vars、.env 不应提交到 Git,不同环境需要按其加载和配置规则分别处理。对使用其他平台的服务,同样要核对其实际机制。[8]

一个实用的发布前检查,是把环境、回跳域名、数据存储、支付模式和发件地址放在一起核对。测试邮件跳回正式站,或者测试支付写进正式权益库,单看其中一个服务的配置都可能“没有报错”。发现泄露时需要撤销或轮换凭据,不能只删除眼前文件;账户恢复细节见风控与账户安全。

服务进入中国境内业务时,还要核对什么?

技术位置不能代替业务范围判断。本轮读取的《互联网信息服务管理办法》为 2024 年修订文本,其第二至四条规定境内互联网信息服务的适用范围,以及经营性许可、非经营性备案的区分。2026 年 7 月 3 日网信办公布的另一份文本仍标为修订草案征求意见稿,不能把该草案直接当作已经生效的手续。[9][10]

把真实经营主体、服务内容、面向地区、服务器与网络资源所在地、收费方式和分发渠道一并交给住所地通信管理部门与接入服务商,核对适用事项。对本例,询问至少应说明:“大陆个人提供浏览器内处理表格的 Web 工具,不上传客户文件;拟使用的托管地区与网络资源为已确认的具体配置,预期用户及分发范围为已确认的实际地区。若增加国内分发、账号或收费,分别需要哪些许可、备案或材料?”没有确定的信息先列出,别用“出海”两个字补齐它。

若还有 App,工信部通知针对在境内从事互联网信息服务的 APP 主办者要求备案,已有网站备案的也需补充 APP 信息。具体分发路径见应用商店路线,数据处理与跨境问题见合规底线。[11]

做到这里,你应该能拿出几样互相对应的东西:当前用户路径、每次发布的版本和样本结果、真正恢复过的副本,以及还没有覆盖的缺口。对于本章的初轮试用,先把浏览器内生成与导出交付稳定;当账号、保存或收费成为必要能力,再沿新的关键路径补验证。若加入模型调用或向客户开放接口,继续用 AI、API 与插件专项 检查任务、额度和失败恢复。下一步的定价与订阅,也需要把这些服务与支持成本算进承诺里。

实践案例采用原作者复盘;本地演练只证明所列环境和合成故障下的结果。目标用户网络、真实邮件和生产恢复尚未实测。

Sources

  1. 开发者的窘境 · 程小白 · accessed
  2. Placement · Cloudflare Workers Docs · accessed · archive
  3. Event Types · Resend · accessed
  4. Email sender guidelines · Google Gmail Help · accessed
  5. Postmortem of database outage of January 31 · GitLab · accessed
  6. Database Backups · Supabase Docs · accessed
  7. Cloudflare Registrar · Cloudflare Docs · accessed
  8. Secrets · Cloudflare Workers Docs · accessed
  9. 互联网信息服务管理办法(2024年修订文本) · 国家药品监督管理局 · 国务院法规转载 · accessed
  10. 关于《互联网信息服务管理办法(修订草案征求意见稿)》再次公开征求意见的通知 · 国家互联网信息办公室 · accessed
  11. 工业和信息化部关于开展移动互联网应用程序备案工作的通知 · 工业和信息化部 · 湖南省人民政府转载 · accessed

内容与更新说明

我们使用 AI 开展资料调研、内容编写与维护,目标是通过持续核验和修订,让指南跟上规则与产品的变化。

了解来源、核验与配图
资料来源
关键事实标注出处。价格、政策与准入条件依据相关官方资料核对;实践经验注明原作者及背景,便于回到原文判断适用范围。
核验与更新
核验日期对应已完成的资料检查,具体范围见正文及修订记录。后续规则可能变化,图文和排版调整不会自动更新核验日期。
配图与示例
本站场景插画由 AI 创作,用于辅助解释。教学案例和演示使用明确标注的示例数据;涉及真实操作或结果时,另行说明来源与验证范围。

本主题涉及支付、税务与法律事项,内容为经验整理,不构成专业意见;涉及资金与合规的决定请以官方文件或专业人士意见为准。