分布式 PostgreSQL 集群(Citus)官方教程 - 迁移现有应用程序

简介: 分布式 PostgreSQL 集群(Citus)官方教程 - 迁移现有应用程序

将现有应用程序迁移到 Citus 有时需要调整 schema 和查询以获得最佳性能。 Citus 扩展了 PostgreSQL 的分布式功能,但它不是扩展所有工作负载的直接替代品。高性能 Citus 集群需要考虑数据模型、工具和所使用的 SQL 功能的选择。


第一步是优化现有的数据库模式,以便它可以在多台计算机上高效工作。

  • 确定分布策略
  • 选择分布键(distribution key)
  • 识别表的类型
  • 为迁移准备源表
  • 添加分布键
  • 回填新创建的列

接下来,更新应用程序代码和查询以处理 schema 更改。

  • 准备申请 Citus
  • 建立开发 Citus 集群
  • 向查询添加分布键
  • 启用安全连接
  • 检查跨节点流量

在开发环境中测试更改后,最后一步是将生产数据迁移到 Citus 集群并切换生产应用程序。我们有技术可以最大限度地减少此步骤的停机时间。

  • 迁移生产数据
  • 小型数据库迁移
  • 大数据库迁移


确定分布策略



选择分布键


迁移到 Citus 的第一步是确定合适的distribution key 并相应地规划表分布。在多租户应用程序中,这通常是租户的内部标识符。我们通常将其称为“租户 ID(tenant ID)”。用例可能会有所不同,因此我们建议您在此步骤中进行彻底检查。


如需指导,请阅读以下部分:

  1. 确定应用程序类型
  1. 选择分布列


我们很乐意帮助您检查您的环境,以确保选择了理想的 distribution key。为此,我们通常会检查 schema 布局、更大的表、长时间运行和/或有问题的查询、标准用例等。


确定表的类型


一旦确定了 distribution key,请查看 schema 以确定如何处理每个表以及是否需要对表布局进行任何修改。我们通常建议使用电子表格进行跟踪,并创建了您可以使用的模板。


表格通常属于以下类别之一:


  1. 准备分发。 这些表已经包含 distribution key,并准备好分发。
  2. 需要回填。 这些表可以按所选 key 进行逻辑分布,但不包含直接引用它的列。稍后将修改这些表以添加该列。
  3. 参考表。 这些表通常很小,不包含 distribution key,通常由分布式表连接,和/或在租户之间共享。这些表中的每一个的副本将在所有节点上维护。常见示例包括国家代码查找、产品类别等。
  4. 本地表。 这些通常不连接到其他表,并且不包含 distribution key。它们仅在 coordinator 节点上维护。常见示例包括管理员用户查找和其他实用程序表。

考虑一个类似于 EtsyShopify 的示例多租户应用程序,其中每个租户都是商店。这是简化 schema 的一部分:


image.png


(带下划线的项目是主键,斜体项目是外键。)

在此示例中,商店是自然租户。在这种情况下,租户 IDstore_id。在集群中分布表之后,我们希望与同一存储相关的行一起驻留在同一节点上。


为迁移准备源表



一旦确定了所需数据库更改的范围,下一个主要步骤就是修改应用程序现有数据库的数据结构。首先,修改需要回填的表,为 distribution key 添加一列。


添加分布键


在我们的店面示例中,storesproducts 表有一个 store_id 并准备好分布。规范化后,line_items 表缺少商店 ID。如果我们想通过 store_id 分布,表需要这个列。

-- denormalize line_items by including store_id
ALTER TABLE line_items ADD COLUMN store_id uuid;


请务必检查所有表中的分布列是否具有相同的类型,例如:不要混合 intbigint。列类型必须匹配以确保正确的数据托管。


回填新创建的列


更新 schema 后,在添加该列的表中回填 tenant_id 列的缺失值。在我们的示例中,line_items 需要 store_id 的值。


我们通过从带有订单的 join 查询中获取缺失值来回填表:


UPDATE line_items
   SET store_id = orders.store_id
  FROM line_items
 INNER JOIN orders
 WHERE line_items.order_id = orders.order_id;


一次执行整个表可能会导致数据库负载过大并中断其他查询。相反,回填可以更慢地完成。一种方法是创建一个一次回填小批量的函数,然后使用 pg_cron 重复调用该函数。


-- the function to backfill up to one
-- thousand rows from line_items
CREATE FUNCTION backfill_batch()
RETURNS void LANGUAGE sql AS $$
  WITH batch AS (
    SELECT line_items_id, order_id
      FROM line_items
     WHERE store_id IS NULL
     LIMIT 10000
       FOR UPDATE
      SKIP LOCKED
  )
  UPDATE line_items AS li
     SET store_id = orders.store_id
    FROM batch, orders
   WHERE batch.line_item_id = li.line_item_id
     AND batch.order_id = orders.order_id;
$$;
-- run the function every quarter hour
SELECT cron.schedule('*/15 * * * *', 'SELECT backfill_batch()');
-- ^^ note the return value of cron.schedule


回填完成后,可以禁用 cron job


-- assuming 42 is the job id returned
-- from cron.schedule
SELECT cron.unschedule(42);


准备申请 Citus



建立开发 Citus 集群


在修改应用程序以使用 Citus 时,您需要一个数据库来进行测试。按照说明设置您选择的单节点 Citus。

接下来从应用程序的原始数据库中转储 schema 的副本,并在新的开发数据库中恢复 schema


# get schema from source db
pg_dump \
   --format=plain \
   --no-owner \
   --schema-only \
   --file=schema.sql \
   --schema=target_schema \
   postgres://user:pass@host:5432/db
# load schema into test db
psql postgres://user:pass@testhost:5432/db -f schema.sql


schema 应在您希望分发的所有表中包含一个分发键(tenant id)。在 pg_dumpingschema 之前,请确保您已完成上一节中的准备源表以进行迁移的步骤。


在键中包含分布列


Citus 不能强制唯一性约束,除非唯一索引或主键包含分布列。因此,我们必须在示例中修改主键和外键以包含 store_id

下一节中列出的一些库能够帮助迁移数据库 schema 以将分布列包含在键中。然而,下面是一个底层 SQL 命令示例,用于在开发数据库中组合简单键:


BEGIN;
-- drop simple primary keys (cascades to foreign keys)
ALTER TABLE products   DROP CONSTRAINT products_pkey CASCADE;
ALTER TABLE orders     DROP CONSTRAINT orders_pkey CASCADE;
ALTER TABLE line_items DROP CONSTRAINT line_items_pkey CASCADE;
-- recreate primary keys to include would-be distribution column
ALTER TABLE products   ADD PRIMARY KEY (store_id, product_id);
ALTER TABLE orders     ADD PRIMARY KEY (store_id, order_id);
ALTER TABLE line_items ADD PRIMARY KEY (store_id, line_item_id);
-- recreate foreign keys to include would-be distribution column
ALTER TABLE line_items ADD CONSTRAINT line_items_store_fkey
  FOREIGN KEY (store_id) REFERENCES stores (store_id);
ALTER TABLE line_items ADD CONSTRAINT line_items_product_fkey
  FOREIGN KEY (store_id, product_id) REFERENCES products (store_id, product_id);
ALTER TABLE line_items ADD CONSTRAINT line_items_order_fkey
  FOREIGN KEY (store_id, order_id) REFERENCES orders (store_id, order_id);
COMMIT;


至此完成,上一节中的 schema 将如下所示:


image.png(带下划线的项目是主键,斜体项目是外键。)

请务必修改数据流以向传入数据添加键。


向查询添加分布键


一旦 distribution key 出现在所有适当的表上,应用程序就需要将它包含在查询中。以下步骤应使用在开发环境中运行的应用程序副本完成,并针对 Citus 后端进行测试。在应用程序与 Citus 一起工作后,我们将了解如何将生产数据从源数据库迁移到真正的 Citus 集群中。


  • 应更新写入表的应用程序代码和任何其他摄取进程以包含新列。
  • Citus 上针对修改后的 schema 运行应用程序测试套件是确定哪些代码区域需要修改的好方法。
  • 启用数据库日志记录是个好主意。这些日志可以帮助发现多租户应用程序中的杂散跨分片查询,这些查询应转换为每租户查询。

支持跨分片查询,但在多租户应用程序中,大多数查询应针对单个节点。对于简单的 selectupdatedelete 查询,这意味着 where 子句应按 tenant id 进行过滤。Citus 然后可以在单个节点上有效地运行这些查询。


许多流行的应用程序框架都有一些帮助程序库,可以很容易地在查询中包含租户 ID

  • Ruby on Rails
  • Django
  • ASP.NET
  • Java Hibernate

可以先将库用于数据库写入(包括数据摄取),然后再用于读取查询。例如,activerecord-multi-tenant gem 有一个只修改写查询的只写模式(write-only mode)。

  • activerecord-multi-tenant
  • write-only mode


其他(SQL原则)


如果您使用与上述不同的 ORM,或者更直接地在 SQL 中执行多租户查询,请遵循这些一般原则。我们将使用我们之前的电子商务应用程序示例。

假设我们想要获取订单的详细信息。过滤租户 ID 的分布式查询在多租户应用程序中运行效率最高,因此下面的更改使查询更快(而两个查询返回相同的结果):


-- before
SELECT *
  FROM orders
 WHERE order_id = 123;
-- after
SELECT *
  FROM orders
 WHERE order_id = 123
   AND store_id = 42; -- <== added


租户 id 列不仅对插入语句有益,而且至关重要。插入必须包含租户 id 列的值,否则 Citus 将无法将数据路由到正确的分片并引发错误。

最后,在 join 表时,请确保也按租户 ID 进行过滤。例如,这里是如何检查给定商店已售出多少“很棒的羊毛裤”:


-- One way is to include store_id in the join and also
-- filter by it in one of the queries
SELECT sum(l.quantity)
  FROM line_items l
 INNER JOIN products p
    ON l.product_id = p.product_id
   AND l.store_id = p.store_id
 WHERE p.name='Awesome Wool Pants'
   AND l.store_id='8c69aa0d-3f13-4440-86ca-443566c1fc75'
-- Equivalently you omit store_id from the join condition
-- but filter both tables by it. This may be useful if
-- building the query in an ORM
SELECT sum(l.quantity)
  FROM line_items l
 INNER JOIN products p ON l.product_id = p.product_id
 WHERE p.name='Awesome Wool Pants'
   AND l.store_id='8c69aa0d-3f13-4440-86ca-443566c1fc75'
   AND p.store_id='8c69aa0d-3f13-4440-86ca-443566c1fc75'


启用安全连接


客户端应使用 SSL 连接到 Citus 以保护信息并防止中间人攻击。事实上,Citus Cloud 拒绝未加密的连接。要了解如何建立 SSL 连接,请参阅使用 SSL 连接。

  • SSL 连接


检查跨节点流量


对于庞大而复杂的应用程序代码库,应用程序生成的某些查询通常会被忽略,因此不会对它们使用 tenant_id 过滤器。Citus 的并行执行器仍然会成功执行这些查询,因此,在测试期间,这些查询仍然隐藏,因为应用程序仍然可以正常工作。但是,如果查询不包含 tenant_id 过滤器,Citus 的执行程序将并行访问每个分片,但只有一个会返回数据。这会不必要地消耗资源,并且只有在迁移到更高吞吐量的生产环境时才会出现问题。


为了防止在生产中启动后才遇到此类问题,可以设置一个配置值来记录命中多个分片的查询。在正确配置和迁移的多租户应用程序中,每个查询一次只能命中一个分片。

在测试期间,可以配置以下内容:


-- adjust for your own database's name of course
ALTER DATABASE citus SET citus.multi_task_query_log_level = 'error';


如果 Citus 遇到将命中多个分片的查询,它将出错。测试期间出错允许应用程序开发人员查找和迁移此类查询。

在生产启动期间,可以配置相同的设置来记录,而不是错误输出:


ALTER DATABASE citus SET citus.multi_task_query_log_level = 'log';


配置参数部分包含有关此设置支持的值的更多信息。

  • 配置参数部分


迁移生产数据


此时,已更新数据库 schema 和应用程序查询以与 Citus 一起使用,您已准备好进行最后一步。是时候将数据迁移到 Citus 集群并将应用程序切换到其新数据库了。

数据迁移路径取决于停机时间要求数据大小,但通常属于以下两类之一。


  • 小型数据库迁移
  • 大数据库迁移


小型数据库迁移


对于可以容忍一点停机时间的较小环境,请使用简单的 pg_dump/pg_restore 进程。以下是步骤。

  1. 从您的开发数据库中保存数据库结构:


pg_dump \
   --format=plain \
   --no-owner \
   --schema-only \
   --file=schema.sql \
   --schema=target_schema \
   postgres://user:pass@host:5432/db


  1. 使用 psql 连接到 Citus 集群并创建 schema

\i schema.sql


  1. 运行您的 create_distributed_tablecreate_reference_table 语句。如果您收到有关外键的错误,通常是由于操作顺序所致。在分发表之前删除外键,然后重新添加它们。
  2. 将应用程序置于维护模式,并禁用对旧数据库的任何其他写入。
  3. 使用 pg_dump 将原始生产数据库中的数据保存到磁盘:


pg_dump \
   --format=custom \
   --no-owner \
   --data-only \
   --file=data.dump \
   --schema=target_schema \
   postgres://user:pass@host:5432/db


  1. 使用 pg_restore 导入 Citus


# remember to use connection details for Citus,
# not the source database
pg_restore  \
   --host=host \
   --dbname=dbname \
   --username=username \
   data.dump
# it'll prompt you for the connection password


  1. 测试应用。
  2. 运行。


大数据库迁移(Citus Cloud)


较大的环境可以使用 Citus Warp 进行在线复制。Citus Warp 允许您在更改发生时将更改从 PostgreSQL 源数据库流式传输到 Citus Cloud 集群。就好像应用程序自动写入两个数据库而不是一个,除非具有完美的事务逻辑。Citus Warp 可与启用了 logical_decoding 插件的 Postgres 9.4 及更高版本一起使用(只要您使用的是 9.4 或更高版本,Amazon RDS 就支持此功能)。


对于此过程,我们强烈建议您通过开 ticket、联系我们在 Slack 上的解决方案工程师之一或任何适合您的方法来联系我们。为了进行 warp,我们通过 VPC 对等或 IP 白名单Citus 集群的 coordinator 节点连接到现有数据库,并开始复制。

以下是开始 Citus Warp 流程之前需要执行的步骤:


  1. 在目标 Citus 集群上复制 schema 结构
  2. 在源数据库中启用逻辑复制
  3. 允许从 Citus coordinator 节点到源的网络连接
  4. 联系我们开始复制


重复 schema


将数据迁移到 Citus 的第一步是确保 schema 完全匹配,至少对于您选择迁移的表而言。一种方法是针对您的开发数据库(用于本地测试应用程序的 Citus 数据库)运行 pg_dump --schema-only。在 coordinator Citus 节点上重放输出。另一种方法是针对目标数据库运行应用程序迁移脚本。


您希望迁移的所有表都必须具有主键。相应的目标表也必须具有主键,唯一的区别是这些键也允许组合以包含分布列,如识别分布策略中所述。

还要确保在开始复制之前在集群中分布表,这样数据就不必单独放在 coordinator 节点上。


启用逻辑复制


某些托管数据库(例如 Amazon RDS)需要通过更改服务器配置参数来启用复制。在 RDS 上,您需要创建一个新参数组,在其中设置 rds.logical_replication = 1,然后将参数组设为活动参数组。应用更改需要重新启动数据库服务器,这可以安排在下一个维护时段。


如果您正在管理自己的 PostgreSQL 安装,请将这些设置添加到 postgresql.conf


wal_level = logical
max_replication_slots = 5 # has to be > 0
max_wal_senders = 5       # has to be > 0


需要重新启动数据库才能使更改生效。


开放访问网络连接


在 Cloud 控制台中,确定主机名(以 db.citusdata.com 结尾)。Dig 主机名以找到其 IP 地址:


dig +short <hostname> A


如果您使用的是 RDS,请编辑入站数据库安全组以添加自定义 TCP 规则:


Protocol

  • TCP

Port Range

  • 5432

Source

  • /32

这会将 Citus coordinator 节点的 IP 地址列入白名单以进行入站连接。连接两者的另一种方法是在它们的 VPC 之间建立对等互连。如果需要,我们可以帮助进行设置。


开始复制


通过在 Citus Cloud 控制台中打开 support ticket 与我们联系。云工程师将使用 Citus Warp 连接到您的数据库,以执行初始数据库转储、打开复制槽并开始复制。我们可以在迁移中包含/排除您选择的表。


在复制的第一阶段,如果数据库处于写入负载下,Postgres 预写日志 (WAL) 可能会大幅增长。在开始此过程之前,请确保源数据库上有足够的磁盘空间。我们建议 100GB 可用空间或总磁盘空间的 20%,以较大者为准。一旦初始 dump/restore 完成并开始复制,那么数据库将能够再次归档未使用的 WAL 文件。


随着 Warp 的进行,请注意源数据库上的磁盘使用情况。如果源和目标之间存在数据类型不匹配,或其他意外的 schema 更改,则复制可能会停止。在长时间停顿期间,复制槽可以在源上无限增长,从而导致潜在的崩溃。


由于复制停滞的可能性,我们强烈建议在进行 Citus warp 时尽量减少 schema 更改。如果需要进行侵入式 schema 更改,您将需要停止 warp 并重试。


进行侵入式 schema 更改的步骤:

  1. 请求 Citus Cloud 工程师停止 warp
  2. 更改源数据库上的 schema
  3. 更改目标数据库上的 schema
  4. 再次开始 warp


切换到 Citus 并停止与旧数据库的所有连接


当复制赶上源数据库的当前状态时,还有一件事要做。由于复制过程的性质,序列值不会在目标数据库上正确更新。为了获得正确的序列值,例如 id 列,您需要在打开对目标数据库的写入之前手动调整序列值。


一旦这一切完成,应用程序就可以连接到新数据库了。我们不建议同时写入源数据库和目标数据库。


当应用程序切换到新数据库并且源数据库上没有发生进一步的更改时,请再次联系我们以删除复制槽。迁移完成。

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍如何基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
11月前
|
存储 负载均衡 NoSQL
【赵渝强老师】Redis Cluster分布式集群
Redis Cluster是Redis的分布式存储解决方案,通过哈希槽(slot)实现数据分片,支持水平扩展,具备高可用性和负载均衡能力,适用于大规模数据场景。
757 2
|
负载均衡 算法 关系型数据库
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
本文聚焦 MySQL 集群架构中的负载均衡算法,阐述其重要性。详细介绍轮询、加权轮询、最少连接、加权最少连接、随机、源地址哈希等常用算法,分析各自优缺点及适用场景。并提供 Java 语言代码实现示例,助力直观理解。文章结构清晰,语言通俗易懂,对理解和应用负载均衡算法具有实用价值和参考价值。
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
|
Cloud Native 关系型数据库 分布式数据库
登顶TPC-C|云原生数据库PolarDB技术揭秘:Limitless集群和分布式扩展篇
阿里云PolarDB云原生数据库在TPC-C基准测试中以20.55亿tpmC的成绩刷新世界纪录,展现卓越性能与性价比。其轻量版满足国产化需求,兼具高性能与低成本,适用于多种场景,推动数据库技术革新与发展。
|
9月前
|
NoSQL 算法 Redis
【Docker】(3)学习Docker中 镜像与容器数据卷、映射关系!手把手带你安装 MySql主从同步 和 Redis三主三从集群!并且进行主从切换与扩容操作,还有分析 哈希分区 等知识点!
Union文件系统(UnionFS)是一种**分层、轻量级并且高性能的文件系统**,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem) Union 文件系统是 Docker 镜像的基础。 镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
919 6
|
Cloud Native 关系型数据库 分布式数据库
登顶TPC-C|云原生数据库PolarDB技术揭秘:Limitless集群和分布式扩展篇
云原生数据库PolarDB技术揭秘:Limitless集群和分布式扩展篇
|
负载均衡 算法 关系型数据库
大数据新视界--大数据大厂之MySQL数据库课程设计:MySQL集群架构负载均衡故障排除与解决方案
本文深入探讨 MySQL 集群架构负载均衡的常见故障及排除方法。涵盖请求分配不均、节点无法响应、负载均衡器故障等现象,介绍多种负载均衡算法及故障排除步骤,包括检查负载均衡器状态、调整算法、诊断修复节点故障等。还阐述了预防措施与确保系统稳定性的方法,如定期监控维护、备份恢复策略、团队协作与知识管理等。为确保 MySQL 数据库系统高可用性提供全面指导。
|
关系型数据库 Java 数据库连接
【YashanDB知识库】Kettle迁移PostgreSQL到YashanDB
本文介绍了在Windows环境下使用开源工具Kettle将PostgreSQL数据迁移到YashanDB的方法,适用于YMP不支持PostgreSQL的场景。环境配置包括Kettle 8.3、JAVA 1.8、PostgreSQL 12和YashanDB 23.2.1.100。通过设置JAVA环境变量、解压作业包、启动Kettle图形界面,配置数据库连接(PostgreSQLInput与YashanOutput)以及修改表清单文件等步骤,最终执行总任务完成数据迁移。若迁移失败,可通过日志定位问题并重试,目标表会在每次同步前被truncate以避免数据冲突。
【YashanDB知识库】Kettle迁移PostgreSQL到YashanDB
|
并行计算 PyTorch 算法框架/工具
融合AMD与NVIDIA GPU集群的MLOps:异构计算环境中的分布式训练架构实践
本文探讨了如何通过技术手段混合使用AMD与NVIDIA GPU集群以支持PyTorch分布式训练。面对CUDA与ROCm框架互操作性不足的问题,文章提出利用UCC和UCX等统一通信框架实现高效数据传输,并在异构Kubernetes集群中部署任务。通过解决轻度与强度异构环境下的挑战,如计算能力不平衡、内存容量差异及通信性能优化,文章展示了如何无需重构代码即可充分利用异构硬件资源。尽管存在RDMA验证不足、通信性能次优等局限性,但该方案为最大化GPU资源利用率、降低供应商锁定提供了可行路径。源代码已公开,供读者参考实践。
1476 3
融合AMD与NVIDIA GPU集群的MLOps:异构计算环境中的分布式训练架构实践
|
监控 关系型数据库 MySQL
云数据库:从零到一,构建高可用MySQL集群
在互联网时代,数据成为企业核心资产,传统单机数据库难以满足高并发、高可用需求。云数据库通过弹性扩展、分布式架构等优势解决了这些问题,但也面临数据安全和性能优化挑战。本文介绍了如何从零开始构建高可用MySQL集群,涵盖选择云服务提供商、创建实例、配置高可用架构、数据备份恢复及性能优化等内容,并通过电商平台案例展示了具体应用。
|
存储 SpringCloudAlibaba Java
【SpringCloud Alibaba系列】一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论
一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论。
【SpringCloud Alibaba系列】一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论

推荐镜像

更多