我们最近尝试了一种新的操作技术,我们称之为“临时数据库(ephemeral DB)”。这很简单:将我们服务的数据分成两个独立的数据库,一个标准的“主”数据库存储大部分内容,是大多数新增功能的默认选择,但还有第二个数据库,即临时数据库,它存储容量较大或经常更改的数据,在紧急情况下,我们可以承受丢失。
将高吞吐量数据与其他所有数据放在一起存在相当大的缺点,这些缺点可能会被忽视,直到出现问题:
大表需要更多的工作来查询,导致运行时间更长的成本更高的查询,并且会与系统中的其他查询竞争。
运行时间更长的查询也意味着保留更多的元组和元组版本,从而导致膨胀,并且在其他地方出现问题的可能性更高。这是 Postgres 的 MVCC 设计的副作用——必须保留记录,直到可能看到它们的最后一个事务完成运行。
它可能会耗尽系统的可用 I/O。再次,与更重要的工作抗衡。
数据库将需要更长的时间来恢复,因为不仅基础备份更大,而且由于频繁的插入/更新,还有更多的WAL需要应用。
在 HA 故障转移的情况下,出于同样的原因,新的 HA 备用服务器将需要更长的时间才能上线,从而在必须发生另一个 HA 故障转移的不幸情况下导致更高的中断风险。
迄今为止,我们已经设法保持主数据库非常小,整个数据库占用不到 150 MB。但是这个话题已经出现了添加更高容量的数据(在这种情况下,指标),并且出现了一个问题,即这应该放在现有数据库中还是单独的数据库中。
我们调查了我们内部多位数据库经验丰富的工程师,一致的反应令人震惊:将其放入新数据库。基本原理因人而异,但都与上面列出的要点一致。因此,临时数据库诞生了。
我们的应用程序代码在上线时会打开两个数据库池,并且编写后每个服务都可以在其中一个上启动事务:
type BaseService struct {
// Begin begins a database transaction. We send this
// in instead of a full connection or connection pool
// to encourage use of transactions in service handlers.
Begin func(context.Context) (*db.Transaction, error)
// BeginEphemeral is similar to Begin, but begins a
// transaction on the ephemeral database line.
BeginEphemeral func(context.Context) (*db.Transaction, error)
}
当然也有一些取舍:
两个数据库比一个数据库更难维护。
数据库之间没有外键。您仍然可以在一个 ID 中引用另一个 ID,但是您在 Mongo 世界中,因为数据层不再保证它们引用的对象的存在。
开发复杂性有所增加,因为我们现在有两条独立的迁移线,工程师在向其中一条或另一条添加更改时必须考虑他们在做什么。但这并不是太重要,因为它几乎可以通过对工具的一点投资来完全解决(我们有一个
make db命令可以让您的本地数据库恢复到当前状态,无论它们从哪里开始)。
关于何时将新表放入临时数据库而不是主数据库,我们提出的一套松散的决策规则是(1)它应该是大容量的,并且(2)它应该是我们能够负担得起的数据失去(例如指标)。如果出现恢复不顺利的重大生产问题,我们愿意完全抛弃临时数据库并启动一个全新的数据库来替换它。目前,关系数约为 50:1,有利于在主数据库中,因此临时 DB 仅被少量使用。
我们仍然有一个单依赖堆栈,因为 Postgres 是我们使用的唯一持久层(即使添加第二个数据库会使方程式复杂化)。因此,在许多方面,临时数据库将扮演 Redis 在其他地方可能扮演的角色1。
我相当有信心地说,拥有类似的东西会让我们免于使用 Heroku 的 API 时遇到的无数操作问题,其中绝大多数是“分析/计费查询在大量数据中运行很长时间” ,导致更多的死元组和 WAL,从而渗入其他所有内容并导致级联故障”。拥有第二个数据库意味着即使我们遇到了同样的问题,这些问题也可以被隔离,并且只有一小部分 API 功能会被降级,而不是让整个事情脱机。
1需要注意的是,与 Redis 相比,Postgres 在操作上的默认安全性要低得多,因此临时数据库将履行一些 Redis 功能,但要小心。
原文标题:Ephemeral DB, a sacrificial database line for high-throughput data
原文链接:https://brandur-org.translate.goog/fragments/ephemeral-db?_x_tr_sl=auto&_x_tr_tl=zh-CN&_x_tr_hl=zh-CN&_x_tr_pto=wapp




