OpenAI 如何利用单主多副本架构的 PostgreSQL 支持了 8 亿 ChatGPT 用户
正文#
最近在 OpenAI 官网看了一篇博客 Scaling PostgreSQL to power 800 million ChatGPT users ,讲述了他们如何使用并优化 PostgreSQL 来支持 ChatGPT 的海量用户。我觉得有意思的是他们遇到的问题也是很多开发者都会遇到的问题。而唯一不同的是,他们面对的是 8 亿用户(并且还在快速增长),而我们面对的可能只是几千、几万用户。同样的问题,在不同规模下,解决方法可能会有所不同,所以这篇博客值得学习。
首先,OpenAI 的 PostgreSQL 集群架构是单主多副本架构,主库负责写操作(也有一些无法分离的读请求),副本库负责读操作。
即使这种架构满足日常的请求规模,但是也会遇到一些问题,比如:新功能上线导致流量激增,主库面临写风暴,比如大量缓存失效,比如执行大量复杂的多表连接查询,导致消耗大量的 CPU 资源,导致数据库 CPU 饱和,影响正常请求的处理等等。 另外 PostgreSQL 的多版本并发控制(MVCC)在写入密集场景下会导致写入放大、表膨胀和复杂的清理(Vacuum)问题,这些问题在高并发场景下会更加明显,从而导致主库的性能下降,影响整体的用户体验。
但是即使是这样,OpenAI 首先仍然没有选择数据库分片,因为他们认为这样做太复杂了,可能需要修改应用程序端点,增加了架构的复杂度,也非常耗时,对应短期的目标来说,这样不划算,也不是最高优先级事项。
更重要的是,在当前架构下,他们做了很多优化已经想到好了,能够支撑现有的流量。
优化#
1. 减轻主库负载#
- 尽可能将读请求从主库分离到从节点(Replica)
- 优化慢查询
- 尽可能将可分片、写入密集型工作负载迁移到 Azure Cosmos DB 等分片系统(PostgreSQL 仍然不算分片)
2. 优化异常查询#
持续优化 PostgreSQL 查询,避免多表连接等 OLTP 反模式,并将复杂连接逻辑转移到应用程序层。 很多这类查询来源于 ORM, 所示使用 ORM 时需要注意查询的性能,避免使用复杂的查询和连接,尽量使用原生 SQL
配置连接超时,防止空闲连接长时间占用资源。
3. 单节点故障转移#
虽然单个副本节点出现故障可以马上切换到其他副节点,但是如果主节点挂了呢?因为是单主节点,所以此时没有替代节点转移故障。
解决办法是:首先主节点设置为高可用模式(HA)。将主数据库运行在带有热备的高可用性模式下,并部署多个副本以应对读取副本故障。
大多数关键请求仅涉及读取查询。为了减少主节点的单点故障,将这些读取从主节点转移到副本,确保即使主节点宕机,这些请求也能继续服务。虽然写入作仍会失败,但影响会减少;由于读取仍然可用,它不再是 SEV0。
为了应对读副本失败,在每个区域部署多个副本,并保持足够的容量余量,确保单个副本故障不会导致区域性中断。
4. 工作负载隔离#
有些请求会消耗数据节点过多的资源,而有些则很少。这意味着可能某些数据库节点中高消耗的请求会影响其余的请求。
解决方法就是将请求分为高优先级和低优先级,并路由到不同的实例,以防止资源密集型请求影响其他工作负载
5. 连接管理#
每个 PostgreSQL 实例的连接数限制为 5,000 (Azure PostgreSQL),如果不加控制很容易消耗完并造成严重的事故。
在 PostgreSQL 中,可以使用 PgBouncer 作为代理层进行连接池管理,以提高连接的复用率和性能。
另外跨区域连接和请求可能成本高昂,因此将代理、客户端和副本共置在同一区域,以最大限度减少网络开销和连接使用时间。
6. 缓存#
最怕的是缓存失效,因为缓存失效会导致大量的读请求,从而增加主库的负载。
对于 OpenAI 来说,数据库的流量是读密集型的。对于大部分读流量,他们又专门增加了一层缓存,但是如果缓存命中率突然意外下降,则大量请求直接打到数据库实例上,也是吃不消的。 为此他们又专门加了一种缓存锁机制:当多个请求未命中同一个缓存 key 时,只有一个请求获得锁并继续检索数据并重新补充缓存。其他所有请求都是等待这个缓存 key 更新,而不是一次性全部发送到 PostgreSQL 实例。这种方式也是值得借鉴的。
7. 扩展读复制#
对于 OpenAI 的这种架构,主节点需要同时向近 50 个副本发送 WAL 数据,这会消耗大量的网络带宽和 CPU 资源。 比如,如果主节点每秒产生 100MB 的日志,发送给 50 个副本就意味着每秒要消耗 5GB 的网络带宽。另外维护这 50 个网络连接并分发数据会消耗主节点大量的 CPU 资源。如果主节点太忙,日志发不过去,从节点的数据就会落后于主节点,导致用户读到旧数据。
为了减少主节点的负载,他们与 Azure PostgreSQL 团队合作,研究级联复制,以支持更多副本并减轻主数据库的 WAL 流式传输压力。级联复制通过将主节点的 WAL 数据流式传输到多个副本节点,从而减少主节点的负载。
8. 限流#
在应用程序、连接池、代理和查询层实施限速,以防止流量高峰导致数据库过载。这属于常规方法了。
9. Schema 管理#
对于 ChatGPT 这种拥有 8 亿用户、数据量极其庞大的系统中,需要特别避免全表重写。 但是即使是小的 Schema 更改,比如更改列类型,也可能触发完整的表重写。因此,需要谨慎地应用 Schema 更改,限制它们只做轻量级操作,避免重写整个表。
例如:
- 添加或删除某些不会触发完整表重写的列
- 对 Schema 更改强制执行严格的超时,避免全表重写
- 允许同时创建和丢弃索引
- 模式变更仅限于现有表,如果新功能需要额外表,必须在其他分片系统中,如 Azure CosmosDB,而非 PostgreSQL