数据库架构是否该随着公司估值一起变化?

原文|The growing pains of database architecture

作者|Tim Liang, Software Engineer at Figma


2020 年,因为 Figma 不断加入新功能,筹备第二条产品线和用户不断增长导致数据库流量每年以 3x 速度增长,我们的基础设施遇到了增长瓶颈。很清楚的是,原本的基础设施无法扩展以满足新需求,我们用了单个大型 Amazon RDS 数据库来存储元数据,比如权限、文件信息和评论等,虽然可以丝滑处理大多核心协作功能,但只有一个数据库的话限制很大。尤其在高峰期,流量达到 65% 以上时,单个数据库查询量过大导致 CPU 利用率上升。随着使用接近极限,数据库延迟变得越来越不可预测,严重影响用户体验。

如果数据库完全饱和,Figma 就会停止工作。

我们离宕机还挺遥远,但作为基础设施团队,我们得主动识别并解决可扩展性问题。需要一种解决方案以减少潜在的不稳定因素,并为未来的规模铺平道路。在实施该解决方案时,性能和可靠性是首要考虑因素;我们的团队旨在构建一个可持续发展的平台,使工程师能够快速迭代 Figma 而不影响用户体验。如果说 Figma 的基础设施是道路,那我们不能关闭高速公路来进行工作。

我们从一些修复开始,先延长一下道路的生命,并为一个更完整解决方案打下基础:

  • 将数据库升级到最大实例(从 r5.12xlarge 到 r5.24xlarge),以最大化 CPU 利用率。
  • 创建多个读取副本以扩展读取流量。
  • 为新用例建立新数据库以限制原始数据库增长。
  • 添加 PgBouncer 作为连接池器来限制新增连接(已经达到上千)所产生影响。

file

我们加入了 PgBouncer 作为连接池

虽然以上措施改善了些许,但也有局限性。分析了数据库流量后,我们发现写入操作,比如收集、更新或删除数据对消耗了大量数据库利用率。此外,并非所有数据读取都可以移动到副本中,因为应用程序对复制延迟滞后的敏感度不同。因此,从读和写两个方面来看,我们仍需要给原始数据库减压。是时候摆脱渐进式变化并寻找长期解决方案了。

探索之路

首先,我们探索了水平扩展数据库的可能。Figma 使用的数据库管理系统是 Postgres,很多流行的托管解决方案并不兼容。如果我们决定使用可水平扩展的数据库,那要么找到一个兼容 Postgres 的托管解决方案,要么自托管。

迁移到 NoSQL 数据库或 Vitess (MySQL) 要复杂的双重读写迁移,特别是对于 NoSQL 来说还要进行工程浩大的应用程序端更改。如果用支持 Postgres 的 NewSQL 数据库,我们将会是云上分布式 Postgres 中的最大单集群,我们不想冒险成为第一个遇到缩放问题的客户。对于托管方案,我们能控制的比较少,因此在没有经过针对我们规模级别的压力测试就依赖它们会带来更多风险。如果不用托管方案,那就得自托管。但由于迄今为止我们一直依赖托管方案,在团队能够支持自托管所需大量培训和投入,这意味着成本,也会分散我们主要关注的可扩展性 - 这才是个生死攸关的问题。

在决定不采取水平分区的两种前进路径之后,我们决定垂直分区。这同时具有短期和长期效益:垂直分区现在可以缓解原始数据库的压力,并为以后水平划分子集提供了一条路。

我们的分区方法

在开始前,我们首先需要确定要将哪些表分区到自己的数据库中。有两个重要因素:

  • 影响:移动表应该能够解决大部分工作负载
  • 隔离性:这些表不应与其他表紧密相连

为了衡量影响,我们参考了查询的平均活跃会话(AAS),它描述了在某一时刻给定查询的活跃线程数量的平均值。我们通过以 10 毫秒间隔查询 pg_stat_activity 来计算 AAS,以识别与查询相关联的 CPU 等待,并按表名聚合信息。

每个表「隔离」的程度对于是否容易进行分区至关重要。当我们将表移到另一个数据库同时,我们也失去了重要功能,例如原子事务、FK 验证和连接表。因此,移动表可能会需要开发人员重新编写 Figma 中很多代码,成本较高。我们最好通过识别易于分区的查询模式和表来制定策略。

但是,从后端角度看这很困难。Ruby 作为我们应用程序后端,服务了大部分 Web 请求,它们生成了大部分数据库查询语句,开发人员使用 Active Record 编写这些查询语句。由于 Ruby 和 Active Record 的动态性,仅通过静态代码分析很难确定哪些物理表受到 Active Record 查询的影响。首先,我们创建了运行时验证器,这些验证器连接到 Active Record。这些验证器将生产查询和事务信息(例如调用者位置和涉及的表)发送到 Snowflake(我们的云上数仓)中进行处理。我们使用此信息查找经常引用相同组表格的查询和事务。哪儿工作负载成本高,那这些表就作为垂直分区的主要候选项。

管理迁移

一旦确定了要分区的表,就要制定一个计划来在迁移。虽然离线操作很简单,但对于我们来说不是一个选项 - Figma 需要始终保持在线和高效以支持用户的实时协作。我们要协调跨数千个应用程序后端实例的数据移动,以便它们可以在正确的时刻将查询路由到新数据库。这样就可以在没有维护窗口或停机时间(这会对用户造成干扰,并且还需要工程师进行非工作时间内的工作)情况下分区数据库。我们的解决方案要满足以下目标:

  • 将潜在可用性影响限制在 1 分钟内
  • 自动化该过程,使其易于重复执行
  • 近期的分区操作能撤回

没能找到符合我们要求的预构建解决方案,而且我们也希望灵活地适应未来情况。只有一个选择:自己构建。

定制的解决方案

高层次上,我们进行了以下操作(第 3-6 步在几秒钟内完成,以最小化停机时间):

  • 准备客户端应用程序以从多个数据库分区查询
  • 将表从原始数据库复制到新数据库,直到复制延迟接近 0
  • 暂停原始数据库的活动
  • 等待数据库同步
  • 将查询流量重新定向到新的数据库
  • 恢复活动

正确准备客户端应用程序是一个重要问题,Figma 应用程序后端的复杂性让我们很焦虑。如果在分区之后错过了某些边缘情况会怎么样呢?为降低风险,我们靠 PgBouncer 层来获取运行时可见性,并确保应用程序正确配置。与产品团队合作,以确保应用程序兼容分区后的数据库后,我们创建了单独的 PgBouncer 服务虚拟分流流量。安全组确保只有 PgBouncer 可以直接访问数据库,这意味着客户端应用程序始终通过 PgBouncer 连接。首先分区 PgBouncer 层将给客户留出余地。尽管我们能检测到路由不匹配,但由于两个 PgBouncer 具有相同的目标数据库,客户端仍将成功查询数据。

file

初始状态

file

将 PgBouncer 分区后的数据库状态

一旦验证了应用程序已准备好为每个 PgBouncer 建立单独的连接(并正确发送流量),我们继续进行下一步。

file

数据库分区后的状态

合乎逻辑的选择

在 Postgres 中,有两种复制数据的方式:流复制或逻辑复制。我们选择了逻辑复制,因为它允许我们:

  • 传输一部分表格,这样开始时,我们可以在目标数据库使用更小的存储占用(减少存储硬件占用可以增加可靠性)。
  • 复制到运行着不同 Postgres 版本的数据库中,这意味着我们可以进行最小停机时间主版本升级。AWS 针对主版本升级支持蓝绿部署,但该功能尚未在 RDS Postgres 上提供。
  • 设置反向复制,使我们能够回滚操作。

使用逻辑复制的主要问题是我们有几个 TB 的生产数据,因此初始数据副本可能要数天甚至数周才能完成。我们希望避免这种情况,不仅为了将窗口期限制在最小范围内,也为了减少重新启动的成本。我们考虑过在正确的时间进行快照恢复并开始复制,但一个快照恢复就淘汰了能有较小存储占用空间。于是,我们开始调查为什么逻辑副本性能如此低下。原来拷贝降速是由于 Postgres 在目标数据库中维护索引的方式导致的。逻辑复制批量复制行,但它更新索引的效率低下:一次更新一行。删除目标数据库中的索引并在初始数据副本之后重建索引后,制作副本时间缩短到了几小时。

通过逻辑复制,我们能够从新分区数据库构建反向复制流,并返回原始状态。在原始数据库停止接收流量后,立即启动此复制流。对新数据库进行修改将被回传到旧数据库,在回滚事件中旧数据库会有这些更新。

关键步骤

解决了复制问题后,我们到了协调查询重定向的关键步骤。每天每时都有数千个客户端服务查询数据库。跨越这么多客户端节点进行协调很容易出问题。通过分两个阶段(先对 PgBouncers 分区,然后再是数据)执行切片操作,关键的数据分区操作只需要在为分区表提供服务的少量 PgBouncer 节点之间进行协调。

以下是正在进行中的操作概述:我们短暂地跨节点暂停所有相关数据库流量,方便同步新数据库以实现逻辑复制(PgBouncer 可以方便地支持暂停新连接和重定向)。当 PgBouncer 暂停新连接时,在原始数据库上撤销客户端对已分区表的查询权限。经过短暂的宽限期后,我们会取消任何剩余未完成的查询。由于我们的应用程序大多只会发出短时查询,因此通常会取消不到 10 个查询请求。此时,在流量暂停状态下,我们需要验证两个数据库是否相同。

在重定向客户端之前确保两个数据库相同是防止数据丢失的基本要求。我们使用日志序列号(LSN)来确定两个数据库是否同步。一旦确认没有新写入,就从原始数据库中取样一个 LSN,然后等待副本回放到此 LSN。此时,原始数据库和副本中的数据是相同的。

file

同步机制

在确认副本已经同步之后,我们停止复制并将副本提升为新数据库,并如前所述设置反向复制。然后恢复 PgBouncer 中的流量,不过现在查询被转到了新数据库。

file

过程总结

为未来规划

我们已经在生产环境中成功执行了多次分区操作,每一次都达到了最初的目标:在不影响可靠性的情况下解决可扩展性问题。我们第一次操作涉及移动两个高流量表,而 2022 年 10 月的最后一波操作涉及 50 个表。在每个操作期间,我们观察到大约 30 秒的部分可用性影响(请求丢失率约为 2%)。现在,每个数据库分区都具还有大量空间。我们最大的分区 CPU 利用率约为 10%,并且我们已经减少了一些低流量区所分配的资源。

现在还有很多工作要做。数据库很多时,客户端应用程序必须维护对每一个数据库的了解,并且随着添加更多数据库和客户端路由复杂度呈乘法级别增长。因此,我们引入了新的查询路由服务来集中和简化路由逻辑以便于扩展到更多分区。一些表有高写入流量或数十亿行和数千兆字节磁盘占用空间,这些表将分别遇到磁盘利用率,CPU 和 I/O 瓶颈问题。我们清楚,如果仅依赖垂直分区,最终还是会碰到扩展限制。回到最大化杠杆的目标上来说,我们为垂直分区打造的工具将让我们更好地处理高写入流量表的水平切片。它为我们提供了足够的「跑道」来维护当前项目并保持 Figma「高速公路」的畅通,同时也可以看到未来发展方向。


💡 你可以访问官网,免费注册云账号,立即体验 Bytebase。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/31357.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

云原生之深入解析Kubernetes中Kubectl Top如何进行资源监控

一、Kubectl top 的使用 kubectl top 是基础命令,但是需要部署配套的组件才能获取到监控值: 1.8 以下:部署 heapter; 1.8 以上:部署 metric-server; kubectl top node:查看 node 的使用情况&a…

【C++】构造函数调用规则

欢迎来到博主 Apeiron 的博客,祝您旅程愉快 !时止则止,时行则行。动静不失其时,其道光明。 1、缘起 (1)默认情况下,C 编译器至少给一个类添加 3 个函数 ① 默认构造函数(无参&#…

开源软件介绍——国内和国际主要开源社区

我是荔园微风,作为一名在IT界整整25年的老兵,今天我们来看一看国内和国际上有哪些主要开源社区。 开源社区的定义 开源社区又称为开放源代码社区,一般由拥有共同兴趣爱好的人组成。根据相应的开源软件许可证协议公布软件源代码的网络平台&a…

ChatGPT从入门到精通,深入认识ChatGPT

ChatGPT从入门到精通,一站式掌握办公自动化/爬虫/数据分析和可视化图表制作 全面AI时代就在转角 道路已经铺好了 “局外人”or“先行者” 就在此刻 等你决定1、ChatGPT从入门到精通,一站式掌握办公自动化/爬虫/数据分析和可视( 点击观看完整版本 )https…

Clickhouse之物化视图分享

前言 ClickHouse广泛用于用户和系统日志查询场景中,主要针对于OLAP场景,为业务方提供稳定高效的查询服务。在业务场景下,数据以不同的格式、途径写入到clickhouse。用传统JOIN方式查询海量数据,通常有如下痛点: 每个查询的代码冗…

CTFshow-pwn入门-前置基础pwn23-pwn25

pwn23-25的题目会涉及到ret2shellcode、ret2libc等内容,本篇文章只会侧重研究这几道题目的wp,不会过多涉及到ret2shellcode、ret2libc的基本原理,等有时间再来写关于ret2libc、ret2shellcode…的相关内容。大家可以参考CTFwiki的文章去慢慢学…

【机器学习】十大算法之一 “SVM”

作者主页:爱笑的男孩。的博客_CSDN博客-深度学习,活动,python领域博主爱笑的男孩。擅长深度学习,活动,python,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域.https://blog.csdn.net/Code_and516?typeblog个…

Kubernetes(k8s)部署模式发展

目录 1 简介2 物理单机(~2000)2.1 主要代表 3 虚拟化:初期(2001~2009)3.1 VMware3.2 laaS 4 虚拟化:成熟期(2010~至今)4.1 OpenStack4.2 虚拟化四巨头 5 容器化:(2013-至今)5.1 Dock…

【备战秋招】每日一题:2023.04.26-华为OD机式-第三题-MC方块

在线评测链接:P1231 题目内容 MC最新版本更新了一种特殊的方块,幽匿催发体。这种方块能够吸收生物死亡掉落的经验并感染周围方块,使其变成幽匿块。Steve想要以此为基础尝试搭建一个经验仓库,他来到了创造超平坦模式,在只有草方块…

被测系统架构与数据流分析

开源项目litemall系统架构(https://github.com/linlinjava/litemall) 角色与数据用户产品前端技术栈后端技术栈数据存储 开源项目Mall的系统架构(https://github.com/macrozheng/mall) 角色与数据用户产品前端技术栈后端技术栈服务治理技术栈监控技术栈大数据处理技术栈数据存…

自动化测试工具 AirTest 的使用方法与简介

目录 前言: Airtest简介 1.基于图像识别的Airtest框架 2.基于UI识别的Poco框架 Airtest环境搭建 Airtest布局 Airtest使用步骤 第一步:连接移动设备 第二步:创建一个.air文件(也就是我们的测试脚本) 第三步&#xff1a…

【MySQL数据库 | 第二十篇】explain执行计划

目录 前言: explain: 语法: 总结: 前言: 上一篇我们介绍了从时间角度分析MySQL语句执行效率的三大工具:SQL执行频率,慢日志查询,profile。但是这三个方法也只是在时间角度粗略的…

如何在 XMind 中绘制流程图

XMind 是专业强大的思维导图软件,由于其结构没有任何限制,很多朋友特别喜欢用它来绘制流程图。禁不住大家的多次询问,今天 XMind 酱就将这简单的流程图绘图方法分享给大家。 在 XMind 中,绘制流程图的主角是「自由主题」和「联系」。它们可以打破思维导图的限制,让你自由…

Type-C PD显示器方案简介

方案概述 LDR6020 Type-C PD显示器方案可以给显示器提供一个全功能C口,支持手机,电脑,游戏主机等一线投屏功能,同时支持PD快充输出。LDR6020内置了 USB Power Delivery 控制器和 PD BMC PHY 收发器,支持PD2.0/3.0等快…

Java多线程与并发

1、JDK版本的选择 选择JDK8、JDK11进行讲解的原因:Oracle长期支持 2、进程和线程的区别 进程和线程的由来 3、进程与线程的区别 进程是资源分配的最小单位,线程是cpu调度的最小单位. 所有与进程相关的资源,都被记录在PCB(进程控制块)中。进程是抢占…

数学建模竞赛国赛入场券之攻略

数学建模竞赛国赛入场券之攻略 1.团队契合度 在3天的准备时间中,如果是临时组建的草台班子光处理分歧可能就已经耗掉一半时间,最好在赛前就完成磨合,像一起做模拟题练练手之类,甲准备图论、乙准备优化方法,然后再一块…

存储笔记8 ipsan

Module Objectives IP SAN的组件 IP SAN的好处 描述SAN中的IP融合及其影响 描述的基本架构 –iSCSI –FCIP –FCoE 讨论IP SAN技术的市场驱动因素 列出IP SAN技术 列出iSCSI的组件和连接选项 描述iSCSI体系结构和拓扑结构 解释iSNS操作 描述FCIP的体系结构 IP SAN互联…

Redis持久化机制与Redis事务

一、Redis 持久化机制 Redis 是个基于内存的数据库。那服务一旦宕机,内存中数据必将全部丢失。所以丢失数据的恢复对于 Redis 是十分重要的,我们首先想到是可以从数据库中恢复,但是在由 Redis 宕机时(说明相关工作正在运行&#…

UDS系列-31服务(Routine Control)

诊断协议那些事儿 诊断协议那些事儿专栏系列文章,本文介绍例程控制服务RoutineControl,该服务的目的是Client端使用Routine Control服务来执行定义的步骤序列并获取特定序列的相关结果。这个服务经常在EOL、Bootloader中使用,比如,检查刷写条件是否满足、擦除内存、覆盖正…

Maven如何创建Maven web项目

1、创建一个新的模块: 1.1 使用骨架点一下,这里 1.2 找到maven-archetype-webapp项目,选中点击,一路next就行。 1.3 删除不必要的maven配置:(这里我不需要,针对自己情况而定) 可以从name这里开…
最新文章