Figma 数据库架构的初始状态
2020 年,Figma 仍使用单个大型 Amazon RDS 数据库来存储大部分元数据。虽然它处理得相当好,但单机总有其极限。
在流量高峰期,CPU 利用率超过 65%,导致数据库延迟不可预测。
虽然完全饱和还远未到来,但 Figma 的基础设施团队希望主动识别并修复任何可扩展性问题。他们从一些战术性修复开始:
- 将数据库升级到可用的最大实例(从 r5.12xlarge 升级到 r5.24xlarge)
- 创建多个只读副本来扩展读流量
- 为新的用例建立新数据库,以限制原始数据库的增长
- 添加 PgBouncer 作为连接池,以减少连接数增长带来的影响
这些修复为他们争取了额外一年的时间,但仍有局限性:
- 写操作仍受限于单机的性能
- 某些表已增长到数 TB,包含数十亿行数据
很明显,他们需要一个更长期的解决方案。
第一步:垂直分区
当 Figma 的基础设施团队意识到需要扩展数据库时,他们不能简单地关闭所有服务从头开始。他们需要在解决问题的同时保持 Figma 平稳运行。
这就是垂直分区发挥作用的地方。
将垂直分区想象成整理衣柜。与其让所有东西堆成一团糟,不如将它们分成不同的区域。用数据库术语来说,就是将某些表移动到独立的数据库中。
对 Figma 而言,垂直分区是救星。它允许他们将高流量的相关表(如”Figma 文件”和”组织”表)迁移到独立的数据库中。这为他们争取了急需的喘息空间。
为了识别需要分区的表,Figma 考虑了两个因素:
- 影响(Impact):分区能带来多大好处
- 隔离(Isolation):表能否被干净地分离,而不会导致复杂的跨数据库查询
为了衡量影响,他们查看查询的平均活动会话数(AAS)。这个指标描述了在特定时间点专用于给定查询的活动线程的平均数量。
衡量隔离性则更为棘手。他们使用了运行时验证器,这些验证器钩入他们的 Ruby ORM——ActiveRecord。验证器将生产环境的查询和事务信息发送到 Snowflake 进行分析,帮助他们根据查询模式和表关系识别适合分区的表。
一旦确定了表,Figma 需要在不停机的情况下将它们迁移到不同的数据库之间。他们为迁移解决方案设定了以下目标:
- 零停机时间
- 保持数据一致性
- 能够回滚
- 对应用代码的影响最小
由于找不到能满足这些要求的现成解决方案,Figma 构建了一个内部解决方案。大体而言,其工作原理如下:
- 设置新的目标数据库
- 使用逻辑复制将数据从源数据库复制到目标数据库
- 在复制追赶上后,短暂切换流量
- 验证一切正常运行
为了使向分区数据库的迁移更顺利,他们创建了独立的 PgBouncer 服务来虚拟地分流流量。实施了安全组以确保只有 PgBouncer 可以直接访问数据库。
首先对 PgBouncer 层进行分区,为客户机提供了一定的缓冲空间,因为所有 PgBouncer 实例最初都指向同一个目标数据库,这样可以路由错误的查询。在此期间,团队还可以检测路由不匹配并进行必要的纠正。
下图展示了这个迁移过程:

实现复制
数据复制是扩展数据库读操作的好方法。在垂直分区的数据复制方面,Figma 在 Postgres 中有两个选项:流复制(streaming replication)或逻辑复制(logical replication)。
他们选择逻辑复制主要有 3 个原因:
- 可以选择性地复制特定表(流复制会复制整个数据库)
- 支持不同版本的 Postgres
- 提供更大的灵活性
然而,逻辑复制速度很慢。初始数据复制可能需要数天甚至数周才能完成。
Figma 非常希望避免这个漫长的过程,不仅要最小化复制失败的时间窗口,还要减少出现问题时重新启动的成本。
但是什么让这个过程如此缓慢呢?
罪魁祸首是 Postgres 在目标数据库中维护索引的方式。虽然复制过程批量复制行,但它也会逐行更新索引。通过删除目标数据库中的索引并在数据复制后重建它们,Figma 将复制时间缩短到了几个小时。
水平扩展的需求
随着 Figma 用户群和功能集的增长,对其数据库的需求也随之增加。
尽管他们尽了最大努力,垂直分区仍有局限性,尤其是对于 Figma 最大的表。有些表包含数 TB 的数据和数十亿行,使它们对单个数据库来说太大了。
有两个问题尤为突出:
- 写入扩展:垂直分区无法帮助扩展写流量
- 跨分片查询:随着表分布到更多数据库,跨表查询变得更加复杂
为了更好地理解,想象一个藏书迅速增长的图书馆。最初,图书馆可能会通过添加更多书架(垂直分区)来应对。但最终,建筑物本身会耗尽空间。无论你如何高效地安排书架,你都无法在单个建筑物中容纳无限数量的书籍。这时你就需要考虑开设分馆了。
这就是水平分片的方法。
对于 Figma 来说,水平分片是一种将大表拆分到多个物理数据库的方法,使他们能够超越单机的限制进行扩展。
下图展示了这种方法:

然而,水平分片是一个复杂的过程,伴随着自身的挑战:
- 跨分片查询更加复杂
- 需要管理数据库拓扑
- 负载必须均匀分布
- 事务跨多个分片更加困难
探索替代方案
Figma 的工程团队评估了其他 SQL 选项,如 CockroachDB、TiDB、Spanner 和 Vitess,以及 NoSQL 数据库。
然而,最终他们决定在现有的垂直分区 RDS Postgres 基础设施之上构建水平分片解决方案。
做出这个决定有多个原因:
- 团队熟悉度:团队已经对 Postgres 有丰富的经验
- 现有投资:他们已经为 Postgres 构建了大量工具和工作流程
- 控制力:自建方案让他们对架构有完全控制
- 成本效益:与托管替代方案相比更便宜
Figma 独特的分片实现
Figma 的水平分片方法是针对其特定需求和现有架构量身定制的。他们做出了一些不同寻常的设计选择,使其实现与其他常见解决方案区分开来。
让我们看看 Figma 分片方法的关键组件:
Colos(数据定位)用于分组相关表
Figma 引入了”colos”或数据定位的概念,这是一组共享相同分片键和物理分片布局的相关表。
为了创建 colo,他们选择了一些分片键,如 UserId、FileId 或 OrgID。Figma 的几乎每个表都可以使用这些键之一进行分片。
这为开发者与水平分片表交互提供了一个友好的抽象。
当限制在单个分片键时,colo 内的表支持跨表连接和完整事务。大多数应用代码已经以类似方式与数据库交互,这最小化了使表准备好进行水平分片所需的应用工作量。
下图展示了 colos 的概念:

逻辑分片与物理分片
Figma 将应用层的”逻辑分片”概念与 Postgres 层的”物理分片”分离开来。
逻辑分片涉及为每个表创建多个视图,每个视图对应给定分片中数据的子集。所有对表的读写都通过这些视图发送,使表看起来是水平分片的,即使数据物理上位于单个数据库主机上。
这种分离使 Figma 能够解耦迁移的两个部分,并独立实施它们。他们可以在执行风险更高的分布式物理分片之前,执行更安全、风险更低的逻辑分片推广。
回滚逻辑分片只是一个简单的配置更改,而回滚物理分片操作则需要更复杂的协调以确保数据一致性。
DBProxy 查询引擎用于路由和查询执行
为了支持水平分片,Figma 工程团队构建了一个名为 DBProxy 的新服务,它位于应用和连接池层(如 PGBouncer)之间。
DBProxy 包括一个轻量级查询引擎,能够解析和执行水平分片查询。它由三个主要组件组成:
- 解析器(Parser):解析传入的 SQL 查询
- 规划器(Planner):确定如何执行查询(哪个分片、什么操作)
- 执行器(Executor):将查询路由到适当的分片并返回结果
下图展示了这三个组件在查询处理工作流中的实际使用:

在水平分片的世界中,查询总是有权衡的。针对单个分片键的查询相对容易实现。查询引擎只需提取分片键并将查询路由到适当的物理数据库。
然而,如果查询不包含分片键,查询引擎必须执行更复杂的”scatter-gather”(分散 - 收集)操作。这个操作类似于捉迷藏游戏:你将查询发送到每个分片(分散),然后从每个分片拼凑出答案(收集)。
下图展示了单分片查询与 scatter-gather 查询的比较:

正如你所见,这增加了数据库的负载,过多的 scatter-gather 查询会损害水平可扩展性。
为了更好地管理事情,DBProxy 处理负载调度、事务支持、数据库拓扑管理和改进的可观察性。
Shadow Application 准备度框架
Figma 添加了一个”shadow application readiness”(影子应用准备度)框架,能够预测实时生产流量在不同潜在分片键下的表现。
这个框架帮助他们保持 DBProxy 简单,同时减少了应用开发者重写不支持的查询所需的工作。
所有查询和相关计划都记录到 Snowflake 数据库中,他们可以在那里进行离线分析。根据收集的数据,他们能够选择一种支持最常见 90% 查询的查询语言,同时避免查询引擎中的最坏情况复杂性。
结论
Figma 的基础设施团队于 2023 年 9 月交付了他们的第一个水平分片表,这标志着他们数据库扩展之旅中的一个重要里程碑。
这是一个成功的实施,对可用性的影响最小。此外,团队在分片操作后没有观察到延迟或可用性的回退。
Figma 的最终目标是水平分片其数据库中的每个表,实现近乎无限的可扩展性。他们已经确定了几个需要解决的挑战,如:
- 简化跨分片查询
- 改进工具和操作实践
- 降低开发者使用分片数据库的复杂性
最后,在获得足够的时间后,他们还计划重新评估他们当前的方法,即使用内部 RDS 水平分片与切换到开源或托管替代方案。
参考:
- Figma 工程博客
- Postgres 逻辑复制文档
- 水平分片最佳实践
本文为学习目的的个人翻译,译文仅供参考。
原文链接:100X Scaling: How Figma Scaled its Databases。
版权归原作者或原刊登方所有。本文为非官方译本;如有不妥,请联系删除。