TiDB 账单爆炸之后:一次跌宕起伏的降本排查,从 Cloudflare 边缘到数据库计费内核,降本$30/月
抱歉,这又是一篇在草稿箱里躺了一个多月的博客。由于时间太久,我的对话已经过期,截图找不回来了,大家将就看。
不过,我敢说这仍然是最值的 WordPress + TiDB Cloud Starter 文章。因为它真的可以帮你省钱。建议所有使用或打算使用 TiDB 的 WordPressR 都收藏这篇文章。
我目前的博客架构是 Next.js(OpenNext + Cloudflare Workers)+ WordPress 当 headless CMS,MySQL 数据库跑在 TiDB Cloud Starter 上。前阵子,我清退了 DigitalOcean 的服务器,开始使用 GCP 免费的 e2-micro。由于硬盘太小,又打算在上面跑 OpenCode,于是就把数据库放到 TiDB Cloud。六月初我发现我的 TiDB 账单要爆了:RU(Request Unit)基线常期 110–120/秒,月消耗 250M+,而免费额度只有 50M,$15 的预算已经快被烧完,后面就是限流,要么充钱要么停半个月博客。
这不是我第一次迁移 TiDB,上次我就铩羽而归,这次,我有 AI,Claude 和 GPT 一左一右跃跃欲试。好,来,把非必要请求都挡掉、加上缓存,这次我要彻底解决问题!
事后证明,AI 的确好用,但是也得我旁边配合,因为整个过程很复杂,就像剥洋葱,每一层都藏着一个意外。
第一层:加强边缘防护,结果发现大门早就关好了
首先,就像以前说过的,我这是个老站,积累了很多机器人昼夜不停的刷我,所以我最开始还是怀疑有人刷我的特定接口,注入垃圾评论之类的,导致高负载。于是我的计划很直接:blog 子域只保留 /wp-json,其它全部挡掉;然后给 /wp-json 配边缘缓存,减轻负载。
动手前先盘点现状,第一个意外就来了:该关的大门其实早就关好了。之前做 SEO 收敛时,旧博客域名的几乎所有路径都已经 301 到主站,wp-json 也已经整体放在 Cloudflare Access 后面(service token,只有我自己的 Worker 能调用)。bot 根本进不来。
那钱花哪儿了?盘点倒是揪出一个真凶:Caddy 的 php_fastcgi 有个经典坑——try_files 会把不存在的文件回落到 index.php。也就是说,扫描器随便探测一个不存在的图片路径,都会启动整个 WordPress、打一串数据库查询。实测一个缺失图片的请求要 15 秒才超时,证明这个问题。
修复方式是把 /wp-content、/wp-includes 拆成纯 file_server,缺失文件立即( 4ms )返回静态 404,零 PHP 零 DB。
把边缘缓存也加上:wp-json 响应 TTL 24 小时(WordPress 对 REST 发 no-store,要用 Cache Rule 的 override_origin 无视它),发文时自动 Purge Everything 保时效。
结果:RU 从 110 降到……还有 60–120,而且波动剧烈。第一次被打脸。
第二层:日志取证,抓到扫描器,但真凶另有其人
第一次尝试失败,不能继续瞎猜,给 Caddy 加上 JSON access log,看看到底什么在打源站。
很快得到结论:45 分钟内,1214 个唯一的垃圾路径——db.old、pg_dump.sql、.env.sh、about.php.bak——漏洞扫描器在扫主站,每个垃圾 URL 被 Next.js 的 catch-all 路由放大成 2–3 个 WP 查询。这解释了尖刺。
但它解释不了基线。日志同时显示一个更扎心的事实:打到源站的请求 95% 是我自己的 Worker 发的,而且几乎全是唯一 URL——每篇文章的 slug、每个标签页、sitemap 分页。缓存失效了:缓存只能吸收重复,而爬虫是把上千个页面摊开爬的,每个 wp-json URL 在 TTL 窗口内根本等不来第二次访问。命中率趋近于零,不管 TTL 设多长。
扫描器好办,WAF 添加一条规则:备份/配置文件后缀(.sql、.bak、.env……)直接 403,在 Worker 之前执行,零渲染零 DB。
但基线问题指向了一个更深的方向:既然请求数压不下去,那就得压单个请求的成本。
第三层:数据库视角,WordPress 的老毛病
打开 TiDB 控制台的 SQL Statements 面板,按 RU 排序,真凶终于露脸:
| SQL |
30 分钟执行次数 |
总 RU |
单次 RU |
|---|---|---|---|
|
autoload 全量读(wp_options) |
880 |
26.7K |
30.3 |
|
逐条读非 autoload 选项 |
19,300 |
11.9K |
0.6 |
|
taxonomy 查询 |
10,500 |
13.8K |
1.3 |
|
postmeta |
9,500 |
9.6K |
1.0 |
880 就是这 30 分钟的请求数。换句话说,每个请求都要付 100+ RU:一次 30 RU 的 autoload 全量读、22 次逐条选项查询、12 次 taxonomy、11 次 postmeta……
这是 WordPress + 远程数据库的经典病:WP 默认没有持久化对象缓存,这些配置和元数据本该躺在内存里,但是每次请求都必须从数据库重新拉一遍。数据库在本机时没症状,数据库换成按请求计费的远程 serverless,每一次冗余读都是真金白银。
药方是手搓一个 100 多行的内存缓存:APCu object-cache drop-in(wp-content/object-cache.php),把这些重复读全部塞进本机内存,进程间共享。
效果立竿见影:SQL 消耗从 92K RU/30min 掉到 1.8K,降了 98%。autoload 查询直接从榜单上消失。
RU 曲线终于断崖了:110 → 25。但是——
第四层:还剩 20,而且它不跟任何东西走
25 RU/s 核算到一个月还是 65M,仍然超免费额度。为了不花钱,我还得继续挖。这一层更有挑战性:
-
SQL 只占 1–3 RU/s,QPS 只有 1–5;
-
SHOW PROCESSLIST:除了诊断连接,空无一人; -
SHOW DATABASES:集群上只有这一个库; -
auto-analyze:一天三四次、每次一秒;
-
RU 曲线和流量在分钟级完全解耦——有请求时它能掉到 0,没请求时它也会稳在 20。
Claude 提出假设——「连接税」:PHP 每个请求都对 TiDB 网关新建一条 TLS 连接,会不会每条新连接的会话初始化都在计费?我们做了个对照实验:单连接发 300 条查询 vs 60 条新连接各发一条,看哪边在 RU 曲线上起尖。
结果:两边都没有任何尖峰。连接税假设告破。(不过顺手改成持久连接之后,赚到一个时延收益:wp-json 从 1.3s 降到 0.3s——省掉的正是每请求的 TLS 握手和会话建立。DB_HOST 加个 p: 前缀就行。)
第四层半:静默实验,一个看似漂亮的收尾
能排除的都排除了,只剩一个办法:让集群彻底安静,看 RU 跟不跟。
用 systemd 定时器预约 11 分钟后自动恢复(防止 SSH 断连把自己锁在外面),然后停掉 PHP-FPM。那 11 分钟里,集群上零连接、零查询,控制台也关着。
[图:静默窗口的 RU 曲线,16:43–16:55 UTC]
RU 纹丝不动,稳稳的在 20 附近波动。
Claude 当时就写下了结论:这 ~20 RU/s 是 TiDB Cloud 的平台后台开销,官方 FAQ 也提到后台查询(schema 同步、权限刷新、统计收集)会消耗 RU。零连接零查询都降不下来,那一定是平台地板,无可优化。
但是我突然想到:不对,我有好多集群,看看其它集群是啥情况呗。
第五层:不比不知道,一比吓一跳
我有好几个 TiDB 集群。逐个看去——流量小的能稳定在 0;有点流量的,也有归零的时刻。 只有 blog 这个集群,就 20 附近游来荡去。
这一下,「平台地板」的结论也不成立。如果是平台对所有 Starter 集群收的「存在税」,别的集群不可能是 0。所以 20 RU/s 不是普适地板,而是 blog 这个集群特有的东西在跑。
小收获有,但只是“小”有收获
重新开查。blog 跟别的集群最大的结构差异,我之前瞄到却没追:它有 10.2 MiB 的列存(Columnar Storage)= TiFlash 副本。TiFlash 会对它复制的表做持续后台同步,完美符合「不需连接也烧 RU」。一查,副本挂在 sample_data.github_events 上——TiDB 给每个集群预置的演示数据集,与我我的应用毫不相关。
ALTER TABLE sample_data.github_events SET TIFLASH REPLICA 0,列存归 0。我以为破案了——但这次我学乖了,先观察 15 分钟再下结论。
RU:纹丝不动,还是 20。TiFlash 不是它。
那就把整个演示库删掉,连同它 85MB 的行数据和统计维护:DROP DATABASE sample_data。再等。
RU:还是 20。演示数据也不是它。
真正的终局:TiDB 工单确定查到边界
到这里,我方能动的旋钮全试过了:
| 假设 |
怎么验证 |
结果 |
|---|---|---|
|
平台对所有集群的地板 |
跨集群对照 |
✗ 别的集群是 0 |
|
外部连接 / 连接税 |
A/B 实验 + 静默测试 |
✗ 无尖峰、停机仍 20 |
|
TiFlash 副本空转 |
删副本,列存归 0 |
✗ RU 不变 |
|
演示数据 |
DROP 整个 sample_data |
✗ RU 不变 |
|
服务器 CLI 定时任务 |
翻遍 crontab / systemd |
✗ 根本不存在 |
剩下的真相只能是:这 20 RU/s 是 TiDB 给 blog 这个集群跑的内部后台开销,无法从我这边消除。 它为什么偏偏在这个集群——最可能因为它是我最老的集群(2023 年建)、历史 churn 最多、内部 region 碎片和 GC 状态最复杂——但这些都不是用户能碰的旋钮。
所以我做了两件事:把这条完整的排除链整理成一份证据扎实的工单丢给 PingCAP(删光所有非应用数据后地板仍 20、零连接仍 20、别的集群为 0——万一又帮他们发现点啥呢);然后,接受它。剩下那点超额是每月一两美元,而我已经把能控制的部分从 110 压到了接近 0。
很快,工单回复:一切符合预期。那好吧,唯一能从我这边彻底终结账单的办法,是把这个只有 7.6MB 的库搬回 VPS 上的本地 MariaDB。算了,回头再说。
账本与心得
最终战果:
| 指标 |
优化前 |
优化后 |
|---|---|---|
|
RU 基线 |
110–120/s |
~20/s(其中我能控制的部分 ~100 → 1–3) |
|
月消耗 |
250M+(账单 $25 量级) |
~60M(超额 <$1–3) |
|
wp-json 时延 |
1.3s |
0.3s |
|
RSS |
坏了一周没发现 |
正常 |
先强调几条值得大家学习的经验:
-
测量先于优化,而且要测对层。我一路上的假设——bot 刷接口、缓存没配好、连接太贵、平台地板、TiFlash、演示数据——几乎全被实际数据推翻。
-
缓存吸收的是重复,不是流量。长尾唯一 URL 的场景下,命中率和 TTL 无关,出路只有两条:把请求挡在更外层,或把单次成本打下来。
-
WordPress 配远程计费数据库,必须上对象缓存。APCu drop-in 一百多行,一杯咖啡的功夫,98% 的降幅。持久连接同理。
-
一个数据点不能外推成一条规律。我用静默实验得到的「平台地板」判断,再次被「对比别的集群」掀翻。下结论前,多想想:有没有反例?
-
承认查到边界,也是一种结论。不是每个问题都能在自己这一侧解决。把排除链整理清楚、定位到「这是对方的事」,本身就是有价值的产出。
常见问题(FAQ)
为什么 WordPress 远程库一上 serverless 就爆账单?
因为 WP 默认没有持久化对象缓存:每个请求都会重读 wp_options autoload、taxonomy、postmeta。数据库在本机时这些读很便宜;换成按 RU 计费的远程库,冗余读就是真金白银。上 APCu(或 Redis)对象缓存,通常比继续拧边缘 TTL 更有效。
边缘缓存 wp-json 为什么压不掉基线?
缓存只吸收重复。文章 slug、标签页、sitemap 分页是长尾唯一 URL,TTL 窗口内几乎等不来第二次命中。边缘缓存能削尖刺,但基线要靠单次请求成本(对象缓存、少打 PHP、挡扫描器)。我是个老站,URL 很多,除非我常年不更新,否则光每日爬虫走一遭就要不少量。
~20 RU/s 的「地板」是不是 TiDB 对所有集群都收?
不一定。跨集群对照后:别的集群可以到 0,只有个别集群常年顶着。删 TiFlash、删演示库、停 PHP 静默实验都消不掉时,更可能是该集群内部后台开销。
TiDB 的官方文档有啥参考不?
你说这篇:Integrate WordPress with TiDB Cloud Starter?
咋说呢,我跟 TiDB 关系不错,有攻击性的话要少说。不过你要是按照这篇文档说的做,嘿嘿,我上个月紧急优化,最后缴了 $18。我觉得你准备每个月 $30 应该差不多了。
延伸阅读
-
[2026 最佳实践:在 Cloudflare Workers 上部署 Next.js](/posts/next-js/best-practice-for-nextjs-on-cloudflare-worker-2026)
-
[Workers + Supabase + Hyperdrive 最佳实践](/posts/cloudflare-worker/best-practice-for-nextjs-supabase-hyperdrive-on-cloudflare-worker)
-
[用 Cloudflare R2 上传文件](/posts/infra/upload-file-via-cloudflare-r2)
-
方案页:[Cloudflare 全栈架构与迁移](/solutions/cloudflare-fullstack)
相关文章
Cloudflare Email Worker 踩坑实录:三个你一定会遇到的问题
记录在用 Cloudflare Email Worker 处理邮件时遇到的三个常见坑:不能转发到同一 Worker、目标地址需验证、以及 message.raw 只能读一次,并给出用 R2 缓存 .e
独立开发周记 · 2026-05-04 → 2026-05-10
一周九个项目并行,共189个 commit。muicv 快速迭代完善语音输入与本地/云同步,free-ai-api 批量接入多家模型并扩展到6语,多个新项目上线 Cloudflare。
Next.js 图片怎么这么贵?!——Cloudflare 原生 Image Resizing 避坑与最佳实践
Why Next.js default image optimization on Cloudflare can spike your bill and how to use a custom loa


