扩展我们的分析处理服务(Smartly.io):使用 Citus 对 PostgreSQL 数据库进行分片
原文:Scaling Our Analytical Processing Service: Sharding a PostgreSQL Database with Citus
在线广告商正在根据绩效数据做出越来越多的决策。 无论是选择要投资的受众或创意,还是启用广告活动预算的算法优化,决策越来越依赖于随时可用的数据。 我们的开发团队构建了强大的工具来帮助我们的客户分析性能数据并做出更好的决策。
我们的解决方案由高度可定制的报告组成,包括由我们自己的极其灵活的查询语言提供支持的下钻表和图表。支持查询语言的数据服务处理数 TB
的数据。除了作为我们面向用户的分析工具的后端之外,它还为我们所有的自动优化功能和我们的一些内部 BI
系统提供支持。在这篇博文中,我将向您介绍我们如何通过对后端系统使用的数据库进行分片来解决扩展问题。
海量数据库等于扩展麻烦
我们的分析数据处理服务,称为 Distillery
,使用 PostgreSQL
数据库。该服务将 JSON
格式的查询安全地转换为最终在数据库级别运行的 SQL
查询。大多数数据处理都发生在数据库中,因此 Distillery
后端主要将我们自己的查询语言转换为 SQL
查询。原始的 API
查询很复杂,这使得一些生成的 SQL
查询变得复杂,并使得它们对数据库级别的要求很高。因此,当我们在报告系统的开发过程中遇到扩展问题时,我们并不感到惊讶。
过去,我们垂直扩展了我们的主副本数据库架构,但后来很明显我们已经达到了这种方法的极限。我们的数据库在运行三年中积累了近 5TB
的数据,并且变得无法管理。大尺寸使得更新繁重的应用程序写入速度变慢,维护任务难以执行。最后,最大的问题是我们的数据中心无法提供更大的服务器。
解决方案:使用 Citus 分片 PostgreSQL 数据库
当垂直扩展失败时,我们不得不开始水平扩展我们的报告数据库。这意味着我们需要在多个数据库服务器之间拆分数据和处理。我们还必须缩小包含每个单独数据库实例中统计数据的庞大数据库表。
这种将数据库数据切片成更小单元的方法称为数据库分片。我们的团队决定使用 PostgreSQL
Citus
插件来处理分片。这不是唯一的选择 — 我们考虑使用自定义应用程序级分片,但决定使用 Citus
插件,因为:
- 我们有大量复杂的查询,需要同时使用多个不同的分片。
Citus
插件自动处理这些复杂的查询并在分片之间分配处理。 - 它还广泛支持我们运行复杂报告查询所需的
PostgreSQL
功能。 - 该扩展使分片管理相对容易,因此我们不必花费太多精力来管理单独数据库实例中的分片表。
Citus
基于 coordinator(协调器)
和 worker(工作器)
PostgreSQL 数据库实例。worker
持有数据库表分片,coordinator
计划 SQL
查询,以便它们可以跨 worker
之间的多个分片表运行。 这允许将大型表分布在多个服务器上,并分布到更小、更易于管理的数据库表中。写入较小的表更有效,因为数据库索引维护成本降低。此外,写入负载是并行化的,并在数据库实例之间共享。Citus
解决了我们最大的两个痛点:写入效率低下
和垂直扩展即将结束
。
Citus
的数据库分片带来了额外的好处,因为新架构加速了我们的报告查询。我们的一些查询命中了多个 worker
实例和分片,Citus
扩展可以对其进行优化以在不同的数据库实例中并行运行它们。 由于较小的表索引和更多资源可用于在单独的 worker
中进行查询处理,因此仅针对单个 worker
分片的查询也会加快速度。
将大型数据库和复杂的报告查询迁移到这种类型的分片数据库架构中绝非易事。它涉及仔细的准备和计划,我们将在接下来进行研究。
迁移到新数据库
过去,我们通过旧的 PHP
单体运行报告查询。早在数据库扩展问题出现之前,我们就开始使用 Ruby on Rails
构建更新的报告后端。在决定只在新后端处理 SQL
查询迁移后,我们开始逐步淘汰旧后端。这使我们能够专门针对 Citus
优化新的报告查询。它使从应用程序级别的迁移更容易,因为我们只需迁移此服务即可与 Citus
分片 PostgreSQL
一起使用。
分片数据库对数据库模式有一定的要求。模式必须具有一个作为分片条件的值。分片逻辑使用此值来区分数据位于哪个分片上。 在 Citus-PostgreSQL
中,分片是使用表主键控制的。此复合主键包含一个或多个列,其中第一个定义的列用作分片值:
ALTER TABLE ad_stats ADD PRIMARY KEY (account_id, ad_id, date);
SELECT create_distributed_table('ad_stats', 'account_id'); -- Defines sharding for Citus cluster
这里 account ID
列用作分片键,这意味着我们正在根据我们的客户帐户分配数据(单个客户也可以有多个帐户)。这意味着单个帐户的数据位于单个表分片中。我们必须确保所有主键都采用这种格式,并且表中包含帐户 ID
信息。我们还必须更改一些外键和唯一性约束,因为它们还必须包含分片列。幸运的是,所有这些更改都可以安全地应用于正在运行的生产数据库,而没有任何性能或数据完整性问题,尽管我们不得不进行一些更广泛的数据库索引重建。
第二步是让我们的报表后端生成的 SQL
查询与分片数据库兼容。首先,查询必须包含 SQL WHERE
子句中的分片值。这意味着,例如,过滤器必须采用以下形式
SELECT * FROM campaigns WHERE account_id = 'xxx' AND name = 'yyy'
如果我们没有 account_id
条件,Citus
分布式查询计划器将没有信息需要从哪个分片中找到相关行。从所有可能的分片中读取不会像从单个分片中读取那样有效。
此外,Citus
对您可以在分片表之间执行的 JOIN
类型有一定的限制。通常 JOIN
要求分片列出现在 JOIN
条件中。例如,这将不起作用:
SELECT *
FROM
campaigns
LEFT JOIN ads ON campaigns.id = ads.campaign_id
WHERE
campaigns.account_id = 'xxx'
这将导致错误:
ERROR: cannot run outer join query if join is not on the partition column&
这意味着 SQL
外连接需要 Citus
无法从查询中确定的表分片之间的一对一匹配。因此,查询需要在 JOIN
条件中包含分片列,Citus
能够从中检测到 ads
表连接的范围在一个分片内:
SELECT *
FROM
campaigns
LEFT JOIN ads ON campaigns.account_id = ads.account_id -- Use sharding column
AND campaigns.id = ads.campaign_id
WHERE
campaigns.account_id = 'xxx'
我们进行了各种其他 SQL
查询优化,使 Citus
查询规划器能够有效地运行我们复杂的统计报告查询。 例如,我们使用通用表表达式 (CTE
) 组织查询,这允许 Citus
查询计划器为涉及同时读取多个分片的繁重查询选择最佳计划。 这些针对多个帐户的查询也在 Citus
worker
集群中高度并行化,从而提高数据处理效率。 此外,我们还为 Citus
扩展做出了贡献,增加了对 PostgreSQL JSON(B)
聚合的支持,我们的报告查询将其用于某些数据预聚合步骤。您可以在 Github
中查看PR。
PR
运行中的新数据库系统
我们的数据库系统完全从单一主副本配置迁移到 coordinator
+ 4 个 worker
服务器,每个服务器都复制以实现高可用性。这意味着我们包含 5TB
数据的旧数据库被分割成一个集群,其中每个数据库服务器保存大约 1TB
数据。Citus
允许我们相当容易地添加更多的 worker
服务器,以便在公司继续发展时将其进一步分割。我们还可以将拥有大量统计数据的最苛刻的客户隔离到他们自己的数据库服务器上。
迁移前的数据库架构。
迁移后的数据库架构。
上图描绘了迁移前后的数据库架构。与之前拥有 2
台大型数据库服务器的状态相比,我们现在总共拥有 10
台数据库服务器。这些较小的数据库实例更易于管理,因为大多数数据存在于单独的数据库工作服务器中。协调器持有较少量的数据,例如一些元数据和对分片不敏感的数据。第二张图还显示了我们用来确保在一个数据库实例出现故障时快速恢复的数据库副本。这种从 primary master
服务器到副本服务器的故障转移由 pgpool
组件处理。副本还共享来自主服务器的一些读取负载。
最后,我们在数据处理方面要求最高的数据透视表报告查询从新数据库系统中获得了 2-10
倍的性能提升。 此功能生成的数据库查询非常复杂,因为我们允许用户自由定义数据的分组
、过滤
和聚合
方式。它还允许查询跨分片自由运行,因为用户可以定义任何帐户组合。Citus
分片数据库的好处真正体现在这些特定的查询中。数据库迁移非常必要,因为我们的旧数据库基础架构几乎被它生成的复杂查询所淹没。
该图显示了在数据库迁移项目期间,某些类型的查询获得性能提升的 90
个百分点的持续时间。
更多
扩展我们的分析处理服务(Smartly.io):使用 Citus 对 PostgreSQL 数据库进行分片的更多相关文章
- zookeeper源码分析之五服务端(集群leader)处理请求流程
leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...
- zookeeper源码分析之四服务端(单机)处理请求流程
上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...
- SharePoint 2013中的默认爬网文件扩展名和分析文件类型
摘要:了解默认情况下 SharePoint 2013 爬网的文件扩展名及其解析的文件类型,可以借此了解搜索可以爬的文件和支持的功能. 如果“管理文件类型”页上的列表包含文件扩展名,爬网组件将仅爬网文件 ...
- 9.2 Binder系统_驱动情景分析_服务注册过程
1. 几个重要结构体的引入给test_server添加一个goodbye服务, 由此引入以下概念: 进程间通信其实质也是需要三要素:源.目的.数据,源是自己,目的用handle表示:通讯的过程是源向实 ...
- 对Oracle 、SQL Server、MySQL、PostgreSQL数据库优缺点分析
对Oracle .SQL Server.MySQL.PostgreSQL数据库优缺点分析 Oracle Database Oracle Database,又名Oracle RDBMS,或简称Oracl ...
- 从运维的角度分析使用阿里云数据库RDS的必要性--你不应该在阿里云上使用自建的MySQL/SQL Server/Oracle/PostgreSQL数据库
开宗明义,你不应该在阿里云上使用自建的MySQL or SQL Server数据库,对了,还有Oracle or PostgreSQL数据库. 云数据库 RDS(Relational Database ...
- Linux通过端口转发来访问内网服务(端口转发访问阿里云Redis数据库等服务)
# 安装rinetd wget http://www.boutell.com/rinetd/http/rinetd.tar.gz&&tar -xvf rinetd.tar.gz& ...
- Windows10安装多个版本的PostgreSQL数据库,但是均没有自动注册Windows服务的解决方法
1.确保正确安装了PostgreSQL数据库,注意端口号不能相同 我的安装目录如图: 其中9.6版本的端口号为5432,10版本的端口号为5433,11版本的端口号为5434.若不知道端口号,可在Po ...
- vacuumdb - 收集垃圾并且分析一个PostgreSQL 数据库
SYNOPSIS vacuumdb [ connection-option...] [ --full | -f ] [ --verbose | -v ] [ --analyze | -z ] [ -- ...
随机推荐
- 《Spring Boot 实战纪实》之过滤器
导航 什么是过滤器 Spring的过滤器 Filter定义 过滤的对象 典型应用 过滤器的使用 Filter生命周期 过滤器链 自定义敏感词过滤器 新增自定义过滤器 添加 @WebFilter注解 添 ...
- Visual Studio 2022 下载链接及激活密钥
Visual Studio 2022 下载链接:https://visualstudio.microsoft.com/zh-hans/vs/ 激活码: 专业版: TD244-P4NB7-YQ6XK-Y ...
- 计算机网络-5-9-TCP拥塞控制
TCP拥塞控制 拥塞控制的一般原理 在计算机网络中的链路容量(带宽),交换节点中的缓存和处理机等,都是网络的资源,在某段时间,若对网络中某一资源的需求超过该资源所能提供的可用部分,网络性能就会变坏,这 ...
- react 没有嵌套关系的组件通讯
前提准备四个文件,两个子组件:List.List2和一个events.js文件以及一个App.js父组件; 在src目录下创建events.js,里面的内容如下: // events.js(以常用的发 ...
- [ACM]Link-Cut Tree实现动态树初探
动态树问题是指的一类问题,而不是具体指的某一种数据结构.它主要维护一个包含若干有根树的森林,实现对森林的修改和查询等. 实现动态树的数据结构据说主要有4种,Link-Cut Tree是其中的一种.Li ...
- 一文详解Kafka API
摘要:Kafka的API有Producer API,Consumer API还有自定义Interceptor (自定义拦截器),以及处理的流使用的Streams API和构建连接器的Kafka Con ...
- python篇第5天【变量】
第4天加班 多个变量赋值 Python允许你同时为多个变量赋值.例如: a = b = c = 1 以上实例,创建一个整型对象,值为1,三个变量被分配到相同的内存空间上. 您也可以为多个对象指定多个变 ...
- 读写分离&分库分表学习笔记
读写分离 何为读写分离? 见名思意,根据读写分离的名字,我们就可以知道:读写分离主要是为了将对数据库的读写操作分散到不同的数据库节点上. 这样的话,就能够小幅提升写性能,大幅提升读性能. 我简单画了一 ...
- DelayQueue延迟队列-实现缓存
延迟阻塞队列DelayQueue DelayQueue 是一个支持延时获取元素的阻塞队列, 内部采用优先队列 PriorityQueue 存储元素, 同时元素必须实现 Delayed 接口:在创建元素 ...
- Note -「矩阵树定理」学习笔记
大概--会很简洁吧 qwq. 矩阵树定理 对于无自环无向图 \(G=(V,E)\),令其度数矩阵 \(D\),邻接矩阵 \(A\),令该图的 \(\text{Kirchhoff}\) 矩阵 \ ...