每晚入睡前我都会运行的检查
每晚合上笔记本前,我都会运行一条命令。它检查生产站点上的15,000个页面:每篇博文、每个公司页面、每份程序化指南、每个语言版本。对每个页面,它验证HTTP状态码、测量TTFB、检查Cloudflare缓存状态,并记录所有错误。完整检查大约需要6小时,在后台运行——我睡觉的时候,它在工作。
我还会运行一个2分钟内完成的关键路径快速检查:健康端点、站点地图、营收页面、S级公司枢纽页、市场页面、程序化内容索引、i18n博客归档。这个检查我会盯着看。任何一项失败,我都会在入睡前排查清楚。
例行检查会生成这样一份报告:
NIGHTCHECK -- 2026-03-27 (morning)
Overnight comprehensive: 16,228/16,602 ok (97.7%)
Avg TTFB: 715ms
S-tier companies: ALL PASS, ALL HIT
Markets: ALL PASS (89-175ms HIT)
i18n blogs: es 95ms HIT | ja 196ms HIT | de 181ms HIT
没有人要求我这样做。没有工单要求它,没有冲刺计划包含”运行nightcheck”。这套流程之所以存在,是因为我在意——在意当我不盯着屏幕的时候,站点是否依然正常运行。
为什么是每晚
站点每天都在变化。一天之内,我可能部署87个提交:i18n翻译、爬虫提供者更新、CRO实验、性能修复、Logo修复。每个提交都经过独立测试。但87个提交的组合效应,可能产生任何单个提交都无法暴露的故障。
一批翻译可能让博客站点地图突破渲染阈值。一个新的提供者可能引入一个需要14秒才能渲染的公司页面。一次缓存头变更可能让Cloudflare不再缓存某条原本很快的路由。一次CSS重构可能导致某个模板在某一语言环境下报错,但其他语言却一切正常。
nightcheck捕捉的是组合故障——不是”这个提交是否破坏了什么”,而是”今天所有变更落地之后,站点整体是否正常运行”。这个区分至关重要,因为组合故障在CI中是隐形的。每个提交都能通过各自的测试,但87个提交各自通过测试,并不能保证聚合后的系统依然正常。
检查的维度
检查分为四个层级:
P0 基础设施。 健康端点返回正常且数据库已连接。站点地图返回有效XML。Robots.txt和llms.txt存在。RSS订阅有效。这些如果出问题,搜索引擎将无法发现站点。
P0 营收。 首页、简历构建器、分析器和定价页面均可加载。这些页面一旦宕机,直接造成收入损失。简历构建器历来是最慢的页面,其告警阈值设为5秒。
P1 SEO覆盖面。 博客归档、支柱枢纽页、公司目录、职位浏览、全部五个程序化内容索引(简历指南、薪资指南、求职信指南、面试问题、ATS关键词)、四个i18n博客样本、以及全部20个S级公司枢纽页。这些是驱动自然流量的页面。Google公司页面出现404就是一次SEO事故。
P2 全面检查。 博客和公司站点地图中的每一个URL。这就是那个6小时的后台检查。它捕捉长尾故障:某篇博文因格式错误的引用而返回500、某个公司页面因大型公司的N+1查询而超时。
每个页面都使用带有真实User-Agent头的curl进行检查。裸curl会被Cloudflare返回403。缓存状态头与HTTP状态码和TTFB一同记录。一个页面可能返回200,但缓存状态显示DYNAMIC而非应有的HIT——这意味着缓存规则配置有误。TTFB测量的是真实的服务器端延迟,而非浏览器渲染时间。
TTFB趋势
我从2026年3月开始运行这项检查。TTFB趋势讲述了性能优化的完整故事:
| 日期 | 平均TTFB | 最慢页面 | 缓存状态 |
|---|---|---|---|
| 3月21日(缓存清除后) | 1,156ms | Austin市场页 14,290ms | ALL DYNAMIC |
| 3月23日(缓存上线) | 958ms | 市场页 2-3s | Most HIT |
| 3月25日(查询修复) | 715ms | ats-optimization 6.3s | All HIT |
| 3月27日(稳定) | 715ms | zh-hans/blog 3.7s | 34/36 HIT |
这条趋势线完整记录了市场页的性能演进:缓存清除暴露了问题(14.3秒),边缘缓存掩盖了它(89-175ms HIT),查询结构修复才真正解决了根因(108ms源站)。如果没有每晚的趋势数据,我会以为边缘缓存就是最终方案。TTFB数据证明,源站在缓存重新验证期间渲染依然很慢,这为查询重构提供了充分依据。
nightcheck没有修复性能问题。它让性能问题变得可量化,而可量化才能被修复。
无人看见的价值
nightcheck最珍贵之处在于——它在无人注视时运行。不会有Slack通知说”16,228个页面全部通过”,不会有仪表盘变绿。报告存在日志文件里,次日清晨我端着咖啡时阅读。
没有仪式感,这正是要义。nightcheck不是表演性的质量秀,不是站会上的指标。它是一种私人纪律:我睡觉的时候,一切是否正常?如果是,很好。如果不是,我确切知道什么在什么时候出了问题。
纪律的力量在于复利效应。每晚的检查为次日的比较确立基线。某个特定页面TTFB上升50ms就会触发排查——不是因为50ms对用户有感知差异,而是因为上升本身说明有什么发生了变化。也许是新增依赖引入了延迟,也许是缓存规则过期,也许是数据库查询随数据集增长而变慢。每晚的基线让漂移在演变成问题之前就变得可见。
这是复合上下文在运维层面的应用。每晚的检查存入一个数据点,数据点积累成趋势,趋势让那些任何单次检查都无法发现的问题浮出水面。
流程即标准
我完全可以把nightcheck写成定时任务,看看仪表盘就行。但我选择手动运行,因为运行这个动作本身维系着”在意”的习惯。一旦检查变成别人的事,或者变成我随手划掉的通知,或者变成我不再查看的仪表盘,标准就开始侵蚀。
标准不是”站点通过了自动化检查”。标准是”我在入睡前亲自验证了站点的正常运行”。区别在于责任归属。一个静默失败的自动化检查,是自动化的bug。一次我跳过的手动检查,是我对自己在乎什么做出的选择。
我每晚都运行它,因为替代方案是相信一切仍然正常。没有验证的信任就是侥幸。侥幸不是运维策略。
常见问题
完整检查需要多长时间?
关键路径快速检查需要2-3分钟(36个页面,含TTFB和缓存测量)。站点地图全面检查需要5-7小时(15,000+页面)。快速检查同步执行,全面检查在后台运行。
为什么不用可用性监控服务?
可用性监控服务检查的是页面是否返回200。nightcheck检查的是:页面返回200的同时,缓存状态是否正确、TTFB是否在可接受范围内、内容结构是否符合预期。一个返回200但耗时14秒且缓存状态为DYNAMIC的页面,技术上是可用的,运营上是故障的。
检查失败时怎么办?
如果是营收页面或基础设施页面的失败,我会在入睡前排查清楚。对于全面检查中的失败,我会在次日早上审查日志,按页面类型确定优先级。一篇失败的博文优先级低于一个失败的公司枢纽页。高流量页面的DYNAMIC缓存,优先级高于低流量页面的慢TTFB。
这套方案能扩展吗?
当前规模是15,000个页面。全面检查受限于I/O(顺序执行的curl请求),而非计算资源。页面数量翻倍,运行时间也翻倍。到30,000个页面时,检查需要12小时,仍然可以在一夜之内完成。超过这个规模,就需要引入带有速率限制的并行检查。