令人惊叹的 PostgreSQL 可伸缩性

原文地址:

  • 原文:How Cloudflare Was Able to Support 55 Million Requests per Second With Only 15 Postgres Clusters.
  • 译文:Cloudflare 如何仅使用 15Postgres 集群每秒支持 5500万 次请求(55M/QPS)。
  • 文章地址:https://newsletter.systemdesign.one/p/postgresql-scalability

2009 年 7 月,美国加州,一个创业团队搞了一个名为 Cloudflare内容分发网络(CDN),用于加速请求,让网络访问更稳定且更快捷。他们在发展初期面临着各种挑战,然而其增长速度却十分惊人。

互联网流量全局概览

互联网流量全局概览

现在他们承载着 20% 的互联网流量,每秒 5500 万个 HTTP 请求。而他们仅仅使用 15PostgreSQL 集群就做到了这一点。

Cloudflare 使用 PostgreSQL 来存储服务元数据,并处理 OLTP 工作负载。然而在同一个集群支持有着多种不同负载类型的租户是一个难题。一个集群(Cluster)是一组数据库服务器,一个租户(tenant)是特定用户或用户组专用的隔离数据空间。

PostgreSQL 的可伸缩性

以下是他们如何将 PostgreSQL 的可伸缩性用到极致的。

1、争用

大多数客户端都会相互争用 Postgres 连接。但是 Postgres 连接的成本很高,因为每个连接都是操作系统级别的独立进程。而且每个租户都有独特的工作负载类型,所以很难创建一个全局阈值进行限流。

而且,人工限制行为不端的租户是一项巨大的工作。某个租户可能会发起一个开销巨大的查询,因而阻塞邻居租户的查询饿着他们。同时,一旦查询到达数据库服务器这儿,再想隔离它就很难了。

pg-争用

使用 Pgbouncer 进行连接池化

因此他们 使用 Pgbouncer 作为 Postgres 前面的连接池PgBouncer 将充当 TCP 代理,池化 Postgres 连接。租户连接到 PgBouncer ,而不是直连 Postgres。因而限制了 Postgres 连接的数量,也能防止连接饥饿现象。

此外,PgBouncer 还通过使用持久连接来规避了创建和销毁数据库连接的高昂开销,也被用于在运行时限流那些发起高开销查询的租户们。

2、惊群

当许多客户端同时查询服务器时就会出现 惊群(Thundering Herd) 的问题,这会导致数据库性能降级。

pg-惊群

惊群

当应用程序被重新部署时,其状态会初始化,应用会一次性创建许多条数据库连接。因而当当租户争抢 Postgres 连接时,就会引起惊群现象,Cloudflare 使用 PgBouncer 来限制特定租户创建的 Postgres 连接数。

3、性能

Cloudflare 没有在云上运行 PostgreSQL,而是 使用没有任何虚拟化开销的裸金属物理机,以实现最好的性能。

pg-性能

在数据库实例之间对流量做负载均衡

Cloudflare 使用 HAProxy 作为四层负载均衡,Pgbouncer 将查询转发至 HAProxy,而 HAProxy 负载均衡器会在集群主实例与只读副本之间对流量进行负载均衡。

4、并发

如果有许多租户发起并发(Concurrent)查询,性能会下降。

pg-并发

拥塞控制限流算法

因而 Cloudflare 使用 TCP Vegas 拥塞控制算法 来对租户限流。这个算法的工作原理是,首先采样每个租户的事务往返 Postgres响应时间(RTT),然后只要 RTT 不降级就持续调整连接池大小,因而在出现资源枯竭前就能实现限流。

5、排队

Cloudflare PgBouncer 层面使用队列对查询进行排队。查询在队列中的顺序取决于它们的历史资源使用情况,换句话说,需要更多资源的查询会排在队列的尾部。

pg-查询排队

使用优先队列排序查询

Cloudflare 只在流量峰时刻启用优先队列以防资源饥饿。换言之在正常流量中,查询不会永远排在队尾。

这种方法改善了绝大多数查询的延迟(Latency),不过在流量峰时发起大开销查询的租户会观察到更高的延迟。

6、高可用

Cloudflare 使用 Stolon 集群管控负责 Postgres 的高可用

pg-高可用

使用 Stolon 负责数据库高可用

Stolon[2] 可用于搭建 Postgres 主从复制,并在出现问题时负责选举 Postgres 集群领导者(主库)并进行故障切换。

这里的每个数据库集群都会复制到两个区域,每个区域内有三个实例。写请求会被路由到主要区域中的主库上,然后异步复制到次要区域,读请求会路由到次要区域中处理。

Cloudflare 会进行组件间连通性测试以便主动发现网络分区问题,也会进行混沌测试以优化系统韧性,还会配置冗余的网络交换机与路由器来避免网络分区。

当故障切换结束,主库实例重新上线时,他们会使用 pg_rewind 工具重放错过的写入变更,来让旧主库重新与集群同步。

CloudflarePostgres 主库实例与从库实例加起来超过 100 台。他们 组合使用了 操作系统资源管理排队理论拥塞控制算法,甚至是 PostgreSQL 统计量 来实现 PostgreSQL 的可伸缩性(Scalability

评价与讨论

这是一篇系统设计经验分享,主要介绍了如何使用 PgBouncer 以解决 PostgreSQL 的可伸缩性(Scalability)问题。55M QPS + 20% 的互联网流量听上去并不小,但从 PostgreSQL 专家的角度看,这里的实践确实还是有些朴素简陋 —— 甚至可以说大惊小怪。不过,是这篇文章确实抛出来了一个有意义的问题 —— 即 PostgreSQL可伸缩性(Scalability) 到底怎么样?

PostgreSQL 的可伸缩性现状

PostgreSQL垂直伸缩水平伸缩 能力上享有盛誉。在读请求上,PostgreSQL 没有什么伸缩性问题 —— 因为读写互不阻塞,所以只读查询的吞吐量上限几乎是随投入的资源(CPU)线性增长的,无论是 垂直增加 CPU/内存 还是 水平扩容拖从库,都可以通过 加资源解决

PostgreSQL 在写入上的伸缩性没有读上那么强,高并发单机 WAL 写入/重放速度达到 100 MB/s ~ 300 MB/s 时,就可能会遇到软件瓶颈了 —— 但对于常规生产 OLTP 负载这已经是一个很大的值了(作为参考,探探这样一个两亿用户/千万日活的应用,所有 PG 的写入加起来也就在 120 MB/s 左右)。

PostgreSQL 社区也正在讨论通过 DIO/AIO 以及 并行 WAL 重放 的方式来进一步拓展此瓶颈。用户也可以考虑使用 Citus 或者其他 分库分表中间 件实现写入的伸缩扩容。—— 《展望 PostgreSQL2024 (Jonathan Katz)

在容量上,PostgreSQL 的可伸缩性主要取决于磁盘,本身并没有瓶颈。在 NVMe SSD 单卡 64TB 的当下,配合 压缩卡 支持 百 TB 级别 的数据容量毫无问题,更大的容量也可以使用 RAID 或使用 多个表空间 的方式进行支持。社区曾经报告过不少 百 TB 量级PG OLTP 实例案例,也有零星 PB 级实例的案例。巨型 PG 实例的面临的挑战主要是 维护 上的(例如 备份管理)而不是性能上的

在过去,PostgreSQL 可伸缩性比较为人诟病的一个问题,就是 对海量连接的支持(高并发)(在 PostgreSQL 14 后得到显著改善)PostgreSQLOracle 默认的模型一样都使用了多进程架构。这种设计有着更好的 可靠性,但在面对海量高并发场景时,这种模型的性能就有些拖后腿了。

互联网场景下数据库访问模式主要是海量短连接:一个查询过来就创建一条连接,执行完后就销毁连接 —— PHP 以前就是这么干的,所以和使用线程模型的搭档 MySQL 很配。但对于 PostgreSQL 而言,海量的后端进程与频繁的进程创建销毁会浪费大量的软硬件资源,因而在这种场景的性能表现上就有些力不从心了。

连接池 —— 解决高并发问题

PostgreSQL 推荐默认使用的连接数量约为 CPU 核数的两倍,通常在几十 ~ 几百的范围内会比较合适。互联网场景下动辄以千/以万计的客户端连接如果直连 PostgreSQL,就会产生显著的额外负担。

连接池便是为了解决这个问题而出现的 —— 可以说,连接池对于在互联网场景下使用 PostgreSQL 是一个必选项,能够起到化腐朽为神奇的效果。

请注意,PostgreSQL 并非不支持高吞吐,问题的关键在于高并发 —— 在《PG 性能有多强》中,我们在 92 vCPU 的服务器上使用约 96 条连接压测出 sysbench 点查吞吐量峰值 233 万。而在超出可用资源后,这一最大吞吐随着并发进一步加大而开始缓慢下降。

PG-性能有多强

使用连接池有一些显著的好处

  • 首先,数万条客户端连接,可以池化缓冲收敛为几条活跃 Server 连接(使用 事务级/Transaction模式 连接池),极大减少了操作系统上的进程数量与开销,也避免了进程创建销毁的开销。
  • 第二点,并发争用的情况因为活跃连接数的减少而大大减小,进一步优化了性能。
  • 第三点,突然出现的负载峰值会在连接池上排队,而不是直接打爆数据库,降低了雪崩概率,从而提高了系统的稳定性。

性能与瓶颈

我在探探时有很多关于 PgBouncer 的最佳实践,我们有一套核心数据库集群,整个集群有着 50万QPS,主库上的客户端连接数为两万,写入 TPS 约为 5 万。这样的负载如果直接打到 Postgres 上会立即打爆数据库。因此在应用与数据库之间,还有一个 PgBouncer 连接池中间件。所有两万条客户端连接经过连接池事务池化模式后,总共只需要 5 ~ 8 条活跃服务器连接就支撑起所有的请求,CPU 使用率约为 20%,这是一个非常巨大的性能改善。

PgBouncer 是一个轻量级连接池,可以部署在用户侧或者数据库侧PgBouncer 本身因为使用了 单进程模式,存在一个 QPS/TPS 瓶颈,约为 3 ~ 5 万。因此为了避免 PgBouncer 本身的 单点问题与瓶颈,在核心主库上我们使用了 4幂等PgBouncer 实例,并通过 HAProxy 均匀分发流量给这四个 PgBouncer 连接池池化后,再转交数据库处理。但是对于绝大多数场景而言,单个 PgBouncer 进程的 3万 QPS 的处理能力已经是绰绰有余了。

管理灵活性

PgBouncer 的一个巨大优势是,它可以提供 User/Database/Instance 级别的 查询响应时间指标(RT)。这是 用于性能衡量的核心指标,对于早些年的 PostgreSQL 老版本,PgBouncer 中的统计值也是获取这类数据的唯一方式。尽管用户可以通过 pg_stat_statements 扩展获取查询组的 RTPostgreSQL 14 以后也可以获取数据库级别的会话活跃时间来计算事务 RT,新出现的 eBPF 也可以完成这一点。但 PgBouncer 提供的性能监控数据对于数据库管理仍然是非常重要的参考依据。

pg-灵活数据管理

PgBouncer 连接池不仅提供了 性能上的改善,还为 精细管理 提供了抓手。例如在数据库在线不停机迁移中,如果在线流量完全通过连接池访问,那么你就可以通过简单修改 PgBouncer 配置文件的方式,将旧集群的读写流量丝滑重定向到新集群中,甚至都不需要业务方即时参与改配置重启服务。你也可以像上面 Cloudflare 的例子一样,在连接池修改 Database/User 的参数,实现限流的能力。如果某一个数据库租户表现不良,影响了整个共享集群,管理员可以在 PgBouncer 上轻松实现限流与阻断的能力。

Pigsty 还提供了一个实用函数 pgb-route,可以将 pgbouncer 数据库流量快速切换至集群中的其他节点,用于 零停机迁移

image.png

# route pgbouncer traffic to another cluster member
function pgb-route() {
  local ip=${1-'\/var\/run\/postgresgl'}
  sed -ie "s/host=[^:space:]]\+/host=${ip}/g" /etc/pgbouncer/pgbouncer.ini
  cat /etc/pgbouncer/pgbouncer.ini
}

其他替代品

PostgreSQL 生态中还有其他的一些 连接池产品。与 PgBouncer 同期的 PGPool-II 也曾经是一个有力竞争者:它提供了更为强大的负载均衡/读写分离等能力,也能充分利用多核的能力,但是对 PostgreSQL 数据库本身有侵入性 —— 需要安装扩展才能用,而且曾经有比较显著的性能折损(30%)。所以在连接池大 PK 中,简单轻量的 PgBouncer 成为了胜利者,占据了 PG 连接池的主流生态位。

pg-连接池产品

根据 TimescaleDB 发起的 PG 社区问卷调查,pgBoucnerPG 生态使用率最高的三方工具。除了 PgBouncer 之外,新的 PostgreSQL 连接池项目也在不断出现,比如 Odyssey,pgcat,pgagroal,ZQPool 等。我非常期待能有一个完全兼容 PgBouncer 的高性能/更易用原位替代出现。

此外,许多编程语言标准库的数据库驱动里,都开始内置了 连接池,加上 PostgreSQL 14 的改进让多个进程的开销减少。以及 硬件性能的指数增长(现在都有 512 vCPU 的服务器了,内存也不是啥稀缺资源了)。所以有时候不用连接池,几千个连接直接干上去也是一个可行选项了。

我能用上 Cloudflare 的实践吗?

随着硬件性能的不断提升,软件架构的不断优化,管理最佳实践的逐渐普及 —— 高可用、高并发、高性能(可伸缩性) 对于 互联网公司来 说属于老生常谈,基本不算什么新鲜技术了。

例如在当下,随便一个初级 DBA/运维,只要使用 Pigsty 部署一套 PostgreSQL 集群都可以轻松做到这一点,包括 Cloudflare 提到的 Pgbouncer 连接池,以及高可用组件 Stolon 的上位替代 Patroni,都已经做到开箱即用了。只要硬件达标,轻松处理好海量并发百万请求不是梦

在本世纪初,一台 Apache 服务器只能处理很可怜的一两百个并发请求。最优秀的软件也很难处理上万的并发 —— 业界有个著名的 C10K 高并发 问题,谁要是能做到几千并发,那就是业界高手。但随着 EpollNginx2003/2004 年相继问世,“高并发” 不再是什么难题了 —— 随便一个小白只要学会配置 Nginx,就可以达到前几年大师们做梦都不敢想的程度 —— 瑞典马工《云厂商眼中的客户:又穷又闲又缺爱》

  • 《云厂商眼中的客户:又穷又闲又缺爱》,https://mp.weixin.qq.com/s/y9IradwxTxOsUGcOHia1XQ

这就跟现在随便哪个新手都可以拿 Nginx 实现以前用 httpd 的大师们想都不敢想的 Web 海量请求与高并发一样。PostgreSQL 的可伸缩性也随着 PgBouncerPigsty 的普及走入千家万户

pg-可伸缩性

例如,在 Pigsty 中,默认为所有 PostgreSQL 1:1 部署了 PgBouncer 实例,使用 事务池化模式,并 纳入监控。而默认的 PrimaryReplica 服务也是通过 PgBouncer 访问 Postgres 数据库的。用户不需要操心太多与 PgBouncer 有关的细节 —— 例如, PgBouncer 的数据库与用户是在通过剧本创建 Postgres 数据库/用户时自动维护的。一些常见的配置注意事项和坑也在预置配置模板中进行了规避,力求做到开箱即用

PgBouncer-数据库

当然,对于 非互联网场景 的应用,PgBouncer 也并非必须品。而且默认的 Transaction Pooling 虽然在性能上非常优秀,但也是以牺牲了一些会话级功能为代价的。所以您也完全可以配置 Primary/Replica 服务直连 Postgres,绕过 PgBouncer;或者使用兼容性最好的 Session Pooling 模式。

总的来说,PgBouncer 确实是一个非常实用的 PostgreSQL 生态工具。如果您的系统对于 PostgreSQL 客户端并发连接数有着较高要求,那么在测试性能时请务必试一试这款中间件。

References

  • [1] 惊群: https://en.wikipedia.org/wiki/The_Thundering_Herd_(1925_film)
  • [2] Stolon: https://github.com/sorintlab/stolon

转载声明:

  • 原文地址:https://mp.weixin.qq.com/s/VMw9CRc2ALJKGwRMSDXPrg
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值