基础设施与扩容第 4 部分:Valkey、队列和日志,先坏掉的安静部分
NovaCommerce 案例研究第 4 部分:2,854 个 Valkey 超时、一个仍是单点故障的 worker、延迟 merit 卡 115 分钟的队列、说不的 AI 提供商,以及计数建筑而不是学生的速率限制。
我们在 26 分钟窗口中将一个大 MySQL node 移到有 standby 的更便宜 Standard 集群,将读分割到 standby,并为 AI 提供自己的 pgvector PostgreSQL。真实考试晚上显示数据库从不是瓶颈。
Author
Anichur Rahaman

Part 3 of 5
从单服务器到考试日就绪在 10 月 5 日晚上 22:33,我们把两个 load balancer 指向一个空标签。打开网站的人看到了维护页面。在后面,每个学生、答案和结果都在被复制到一个新数据库,我们与业务同意的窗口是 26 分钟。
没人抱怨数据库慢。问题在它的形状。它是一个大 MySQL node,没有 standby,所以那台机器故障的日子就是整个平台故障的日子。它也更大,更昂贵,比需要的工作。
本文是关于 NovaCommerce 的数据层的,那个销售课程和运行定时在线考试的平台。它覆盖 26 分钟的移动、我们忘记的客户端、我们如何分割读写、为什么 AI 得到自己的 PostgreSQL,以及 2026 年 10 月 7 日真实考试晚上关于所有这些说什么。它跟随 第 2 部分,其中应用层学会了扩展。
这是五部分案例研究 "从单服务器到考试日就绪" 的第 3 部分。NovaCommerce 是虚构名称;架构、数字和错误都是真实的。
旧数据库是一个托管 MySQL "Advanced" node,有 8 vCPU 和 32 GB RAM。它很大,就像单个巨大的桥。令人印象深刻,也是唯一穿过的方式。
早期在项目中我们写下了高可用性必须对我们意味着什么。一行说:数据库在 node 故障后存活。一个 node 在任何尺寸都无法达到那行。尺寸和可用性是不同的问题,旧设置只回答了第一个。
数据大约 11 GB。所以我们移到托管 MySQL "Standard" 8.4,有 4 vCPU 和 16 GB 每 node,和两个 node:一个 primary,和如果 primary 故障就接管的 standby。在新集群上缓冲池是 7 GB,max connections 是 1,601,存储 autoscale 是打开的。状态:Implemented,10 月 5 日。
| 之前 | 之后 | |
|---|---|---|
| 计划 | Advanced,单 node | Standard 8.4,两个 node(primary + standby) |
| 尺寸 | 8 vCPU / 32 GB | 4 vCPU / 16 GB 每 node |
| 如果一个 node 故障 | 数据库下线 | Standby 接管 |
| 每月成本 | 更高 | 更低;primary + standby 大约列表价格 $389 |
便宜且高可用性同时是稀有,所以我们拿了它。这里是整个数据层如今看的样子。文章的其余部分走过它。

窗口是计划的并与业务同意的。这是我们在 10 月 5 日跟随的顺序。

匹配行计数是一个弱测试,因为两个表可能拥有相同数量的行和不同内容。所以我们检查了比计数更多,我们保持检查在网站回来后。
| 检查 | 结果 |
|---|---|
| 表和行,旧 vs 新 | 341 表,18,727,244 行,完全匹配 |
| 每个表的内容 | 所有表上的完整内容检查和,在窗口后 |
| 最大的结果表 | 按列对比:2.76 百万行相同 |
| 应用用户的权限 | CREATE TABLE 被拒,如它应该 |
| 前 4 分钟实时 | 约 2,900 请求,0 5xx,110 通知 job,0 失败 |
在 23:24,网站回来后 25 分钟,我们发现了我们没有计划的东西。一个遗留管理面板,运行在一个单独的旧服务器上,仍在写到旧数据库。
我们关闭了我们记得的所有东西:web node、worker、调度器。我们没关闭我们忘记存在的东西。想象搬家。邮局转发你的邮件,但一个仍有旧地址的朋友保持在那里邮寄信件。
到那时 48 行已经登上旧数据库。我们用条件式 upsert 把它们合并到新的:插入行,或仅当旧副本是新的才更新。44 行被合并。4 行新数据库已经保有更新版本,我们保持了那些。然后我们指向遗留面板到新数据库并改变旧集群的应用密码,所以什么都无法写到它。状态:Implemented。
密码改变一样重要如合并。被遗忘的客户端然后大声失败而不是安静地填满没人读的数据库。旧集群保持冻结 48 小时作为我们的回去方式,在那之后被删除。
课程是一个清单项目,不是故事:
"读很重" 的教科书答案是一个只读副本 node。我们 考虑 它。在这个平台一个只读 node 必须至少和 primary 一样大,所以它会再花大约一样多。同时我们已经为等待故障的 standby 付款。
所以我们 实现 了更便宜的东西:standby 也是一个读者,通过它的副本主机名达到。读在 standby 和 primary 间共享,每一个为另一个覆盖。同一硬件,账单上没新线。
| 选项 | 额外成本 | 状态 | 注意 |
|---|---|---|---|
| Primary 上的一切 | 无 | 基准 | 最简单,但 primary 做每个读 |
| 专用只读副本 node | 约和 primary 一样多 | Considered | 必须至少和 primary 一样大 |
| Standby 与 primary 共享读 | 无 | Implemented,10 月 6 日 | 读可能滞后一点;sticky 读覆盖那个 |
我们在 10 月 6 日 00:13 打开分离。草图形式,Laravel 数据库配置说这个:
read hosts = [standby, primary]
write host = primary
sticky = true
connect timeout = 2 s
我们在一个活 node 检查而不是信任配置。我们打开 8 个新连接,两次。第一运行在两个服务器间分离 6 和 2,第二运行分离 4 和 4。Primary 报告 read_only=0 和 standby read_only=1,所以两个被使用,我们知道哪个是哪个。
生产负载从权威类映射加载类。一个全新的类,比如新中间件,不在映射中不作为应用关心的存在,请求返回 500。作为部署的部分把它添加到类映射。
周每周调整大小:考虑。一个想法是在考试日之前调整数据库并在之后调整回下。业务选择了固定的 4 vCPU / 16 GB。下面的考试晚上数字支持那个选择。
最小权限:实现。应用的数据库用户有 SELECT、INSERT、UPDATE 和 DELETE,没有 DDL。它可以改变行,它必须,但它无法创建、修改或掉落一个表。我们在 cutover 期间测试它:CREATE TABLE 被拒。
按标签访问:实现。每个数据库接纳后端标签、worker 和办公和跳转地址。一个新 autoscale 后端 node 连接没手工步骤。
那离开一件算术。每个后端 node 运行 80 个 php-fpm worker,每个可能持有一个 MySQL 连接。总值必须保持在 1,601 允许下。
| 后端 node | php-fpm worker 每 node | 最坏情况连接 | MySQL 限制 |
|---|---|---|---|
| 2 | 80 | 160 | 1,601 |
| 4 | 80 | 320 | 1,601 |
| 6 | 80 | 480 | 1,601 |
| 10(池最大) | 80 | 800 | 1,601 |
即使在池最大我们使用约一半限制,留下房间为 worker 和管理会话。
一个托管 Valkey,有 standby,保有缓存、会话和队列,运行 noeviction 策略。第 4 部分深入。这里仅属于数据层的东西。
在 10 月 6 日 02:28,一个后端负载测试返回了 2,854 个应用 500。每个是 "Operation timed out" 当连接到 Valkey 时,5 秒 connect timeout。每个请求打开了一个新 TLS 连接,大约每个请求 Valkey 的 5.4 ms CPU,针对大约 MySQL 的 3.3 ms。在 4 GB,Valkey 无法快速接受连接。
Implemented:我们在原地调整了它到 8 GB 与 2 node。数据保留,主机不变。重新测试从 25 直到 500 requests/s,开始 2 个后端 node 池扩展出:0 应用错误,0 后端 5xx。在考试晚上它使用约 12.5% 它的内存。它花更多钱,它移除了一个真实故障。
NovaCommerce 有 AI 学习特性:自适应学习、弱点分析和预测问题。它们在 embedding 上工作,那些描述问题意思的长数字列表。找到最相似的叫做向量相似性搜索。
我们保持那些 embedding 在一个单独的托管 PostgreSQL 17 与 pgvector 延伸,在 2 vCPU 和 4 GB。不在 MySQL 中。三个原因:

PostgreSQL node 很小,我们尝试让它更小:1 vCPU 和 2 GB。Tested,然后 reverted。我们把它放回 2 vCPU 和 4 GB 因为在考试期间到达的写。一个小节省不值得一个在重要的一个小时努力的 node。为考试小时调大小,不是为安静日子。
对通用版本这忠告,比如选择索引类型和保持过滤向量搜索准确,看我们的领域指南 负载下的数据层。这案例研究坚持我们做了什么。
一个 standby 保护对抗死机器。它不保护对抗错误:如果某人删除了一个表,standby 片刻后删除它。那是什么备份是。我们使用两个,他们应答不同问题。
| 什么出了问题 | 什么保护我们 | 状态 |
|---|---|---|
| 一个 MySQL node 故障 | Standby 接管(提供商故障转移) | Implemented |
| 数据丢失或损坏 | 托管数据库的每日备份(提供商) | Implemented |
| Cutover 出了问题 | 旧集群保持冻结 48 小时,然后删除 | Implemented |
| Web node 丢失或 rollout 失败 | Web node 的黄金快照;最后 3 个好镜像保留 | Implemented |
| Worker 丢失 | Worker 的一个快照;它仍是单点故障 | 快照 implemented,standby worker planned |
我们没移动数据库因为它慢。我们移动它为了可用性和成本。测量决定了什么 不 花在上面:在 10 月 7 日真实考试晚上,有两个考试背对背,数据库是系统的安静部分。
| 测量,10 月 7 日考试晚上 | 值 |
|---|---|
| MySQL CPU,最大 / 平均 | 24.5% / 13.4% |
| MySQL 运行查询,最大 | 5 |
| MySQL 锁等待 | 0 |
| Valkey 内存 | 12.5% |
| 后端峰值 | 约 69 requests/s |
| 后端 node CPU,2 分钟平均,最大 | 47%(一个 node 碰到 86% 一分钟) |
瓶颈是每 node 后端 CPU,数据库有约 6x 余地。我们的容量模型说 MySQL 变成限制近 450 requests/s,针对那晚峰值 69。那模型来自一个多项选择考试。有 PDF 上传的书面考试加载 worker 更多必须单独测量。
用金钱术语,三个数据存储花约 $689/月在列表价格:MySQL 约 $389,Valkey 约 $240 和 PostgreSQL $60。那是约后端池在 2 node 粗略 $990 月账单的 70%,那是为什么我们仅在测量后增长一个存储。
数据库现在冷静,所以下一个问题是关于在后台运行的一切:单个 worker、队列、日志和监控。第 4 部分覆盖那些:Valkey、worker、日志和可靠性。
Series
从单服务器到考试日就绪About the Author
Anichur Rahaman
Continue Reading