确定分布策略

选择分布键

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

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

  1. 确定应用程序类型

  2. 选择分布列

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

确定表的类型

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

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

  1. 准备分发。 这些表已经包含 distribution key,并准备好分发。

  2. 需要回填。 这些表可以按所选 key 进行逻辑分布,但不包含直接引用它的列。稍后将修改这些表以添加该列。

  3. 引用表。 这些表通常很小,不包含 distribution key,通常由分布式表连接,和/或在租户之间共享。这些表中的每一个的副本将在所有节点上维护。常见示例包括国家代码查找、产品类别等。

  4. 本地表。 这些通常不连接到其他表,并且不包含 distribution key。 它们仅在 coordinator 节点上维护。常见示例包括管理员用户查找和其他实用程序表。

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

Schema before migration

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

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

为迁移准备源表

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

添加分布键

在我们的店面示例中,stores 和 products 表有一个 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);