我们如何扩展一个用 Rust 编写的开源多租户 Postgres 存储引擎
这篇文章系统解释了 Neon 如何把 Rust 编写的 Pageserver 从“单机承载单租户大数据集”的模式,演进为可面向超大规模数据的多租户分片存储架构。架构背景与目标Neon 采用存储与计算分离:计算层按需扩缩,存储层由 Pageserver 持久化历史数据。初始架构中,同一项目数据集中在单台 Pageserv…
转载声明:本文翻译改编自 Neon 官方博客《How we scale an open source, multi-tenant storage engine for Postgres written in Rust》。原文链接:https://neon.com/blog/how-we-scale-an-open-source-multi-tenant-storage-engine-for-postgres-written-rust 。版权归原作者 John Spray、Raouf Chebri 与 Neon 所有,本文仅用于技术学习与交流。 我们将存储与计算解耦,目标是在云端实现可扩展的 Postgres。计算层运行在虚拟机和 Pod 上,可按需自动扩缩,并把 Postgres 的预写日志(WAL)流式发送到存储引擎。存储层则把数据库历史数据保存在一个用 Rust 编写、支持多租户的引擎中,也就是 Pageserver。 存储引擎的初始实现会把一个项目的所有数据放在同一台服务器上,这意味着在不做人工工程干预的情况下很难支撑超大数据集;同时,大租户和小租户会在同一台 Pageserver 上竞争资源。 为了解决这个问题,我们实现了存储分片(storage sharding),把一个数据库的数据分散到多个 Pageserver 上,从而提供更高的存储容量和吞吐。存储分片是 Neon 实现长期愿景的重要一步,也是走向“底部无界存储(bottomless storage)”的关键基石。 对开发者来说,存储分片是透明的,不需要额外配置,也不需要改变应用开发或部署方式。本文面向希望深入理解 Neon 存储分片的人,重点介绍其概念、实现细节,以及它如何帮助应用扩展规模。Neon 的存储分片 分片是一种分区方式,用来把大型数据库拆分成更小的部分(shard)。在 Neon 中,存储分片会把数据分布到多个物理 Pageserver 上。 在 Neon 中引入分片有几个直接收益:小租户不再与超大租户共享同一台 Pageserver,从而避免不公平的资源竞争。存储 I/O 可以随数据集规模近似线性扩展,帮助在数据增长时维持性能。跨 Pageserver 的迁移等维护操作影响更小,因为我们可以按更小的数据块逐步移动。 对于小体量数据库,你可能几乎感知不到性能差异;但对大数据集,分片可以带来更高的容量与吞吐。它在以下场景中优势更明显:需要更高存储容量的应用。工作集远大于 Postgres buffer cache 的读密集型负载。大表扫描或索引构建等工作负载:数据无法放入 Postgres 内存,瓶颈在于从存储层拉取全量数据的带宽。我们如何实现存储分片 Pageserver 的职责是基于数据库历史,通过 GetPage@LSN 快速重建 Postgres 数据页。Pageserver 从一开始就是多租户设计,其中一个租户对应一个 Postgres 集群(或一个 Neon Project)的数据。 说明: 在 Neon 语境里,一个 tenant 保存一个 Postgres 实例的数据。当你在 Console 创建项目时,Neon 会分配 project-id(也叫 tenant-id)和主分支 branch-id(即 timeline-id)。更多信息可参考 Operations。 下图展示了一个包含 2 个租户的 Pageserver 示例。矩形面积表示租户数据量:面积越大,数据集越大。为简化说明,这里假设每个租户都只有一张表。 Postgres 的 relation(如表和索引)由一段连续的 8KiB 数据块组成,每个块由 block number 标识。存储分片会先用 stripe size 对 block number 分段,再对分段结果做哈希,从而把每个 stripe 分配到一个伪随机选中的 Pageserver。 在实践中,小于 stripe size 的表通常会落在单个 Pageserver 上;而大于 stripe size * shard count 的表,才能充分利用整个系统的吞吐。举例来说:如果 stripe size 是 256MiB(或 32k pages),并且有 8 个 shard,那么表大小至少要达到 2GiB,才能吃满所有 Pageserver 的性能。对于更小的数据库,项目会先以单分片运行,直到超过某个阈值后,Neon 才会透明地启用分片。 在下图中,条纹区域表示能够达到分片 stripe 尺度的数据块。Tenant 2 满足 stripe size,Tenant 1 则不满足。 这种情况下,tenant-1 的数据块会分散到多个 shard。下面是分片后的 Pageserver 形态示意: 目前默认 stripe…
正在初始化 WebAssembly 引擎…
首次编译原生模块可能需要数秒
就绪后,页面交互将以接近原生的速度运行