准备申请 Citus

建立 Development 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_dumping schema 之前,请确保您已完成上一节中的 为迁移准备源表

在键中包含分布列

Citus cannot enforce 唯一性约束,除非唯一索引或主键包含分布列。 因此,我们必须在示例中修改主键和外键以包含 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 将如下所示:

Schema after migration

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

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

向查询添加分布键

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

  • 应更新写入表的应用程序代码和任何其他摄取进程以包含新列。

  • 在 Citus 上针对修改后的 schema 运行应用程序测试套件是确定哪些代码区域需要修改的好方法。

  • 启用数据库日志记录是个好主意。这些日志可以帮助发现多租户应用程序中的杂散跨分片查询,这些查询应转换为每租户查询。

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

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

可以先将库用于数据库写入(包括数据摄取),然后再用于读取查询。 例如, activerecord-multi-tenant gem 有一个只修改写查询的 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 连接。

检查跨节点流量

对于庞大而复杂的应用程序代码库,应用程序生成的某些查询通常会被忽略,因此不会对它们使用 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';

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