Types de tables avec Citus

Il y a 4 sortes de tables avec Citus

  • Distributed

  • Reference

  • Local

  • Managed Local

La différence entre ces tables se situe dans leur localisation dans un environnement distribué de Citus.

Pour rappel, un environnement Citus est constitué de noeuds:

  • 1 master

  • n workers

  • 1 manager

Tables distribuées

Les tables distribuées sont réparties en shards qui sont distribuées sur les workers. Toutes les lignes qui ont la même valeur dans la colonne de distribution sont localisées dans le même shard.

La colonne de distribution doit faire partie de la clé primaire. Elle doit être de type entier, UUID ou text ; le type varchar pose des problèmes depuis la version 11.1.

CREATE TABLE sw_dist01 (
    id uuid,
    tenant_key uuid,
    value text,
    PRIMARY KEY (tenant_key, id)
);

SELECT create_distributed_table('sw_dist01', 'tenant_key');

On peut regrouper les tables pour qu’elles aient la même distribution.

CREATE TABLE sw_dist02 (
    id uuid,
    tenant_key uuid,
    dist01_id uuid,
    value text,
    PRIMARY KEY (tenant_key, id)
);
ALTER TABLE sw_dist02 ADD CONSTRAINT fk_dist02_01 FOREIGN KEY (tenant_key, dist01_id) REFERENCES sw_dist01;

SELECT create_distributed_table('sw_dist02', 'tenant_key', colocate_with => 'sw_dist01');

La façon dont les données sont réparties est stockée dans les métadonnées.

SELECT * FROM pg_dist_shard WHERE logicalrelid='sw_dist01'::regclass;

+--------------+---------+--------------+---------------+---------------+
| logicalrelid | shardid | shardstorage | shardminvalue | shardmaxvalue |
+--------------+---------+--------------+---------------+---------------+
| sw_dist01    | 102104  | t            | -2147483648   | -2013265921   |
| sw_dist01    | 102105  | t            | -2013265920   | -1879048193   |
| sw_dist01    | 102106  | t            | -1879048192   | -1744830465   |
| sw_dist01    | 102107  | t            | -1744830464   | -1610612737   |
| ...                                                                   |

Tables de référence

Pour les tables de référence, toutes les données sont présentes sur tous les workers. Ça permet à n’importe quelle table distribuée d’avoir une foreign key vers cette table.

CREATE TABLE sw_ref01 (
    id uuid not null,
    value text,
    PRIMARY KEY (id)
);

SELECT create_reference_table('sw_ref01');

Ce type est adapté aux tables peu volumineuses et/ou avec peu de mise à jour.

Ces tables se retrouvent aussi dans les métadonnées.

SELECT * FROM pg_dist_shard WHERE logicalrelid='sw_ref01'::regclass;

+--------------+---------+--------------+---------------+---------------+
| logicalrelid | shardid | shardstorage | shardminvalue | shardmaxvalue |
+--------------+---------+--------------+---------------+---------------+
| sw_ref01     | 102168  | t            |               |               |

Tables locales

Les tables locales ne sont présentes que sur le master.

Une table est locale tant qu’elle n’a été déclarée ni distributed ni reference.

Une table distributed ou reference peut être déclassée en locale. C’est la même requête, que la table soit distribuée ou référence.

SELECT undistribute_table('sw_local01');

Les tables locales gérées ont été ajoutées dans les métadonnées de Citus. On les retrouve dans la vue citus_shards, contrairement aux tables locales simples.

Un table locale devient automatiquement gérée lorsqu’on établit une clé étrangère avec une table de référence. On peut aussi la déclarer ainsi.

SELECT citus_add_local_table_to_metadata('sw_local01');

La gestion de la table peut être annulée avec la même fonction undistribute_table(…​) déjà vue ci-dessus.

Enfin, on peut forcer toutes les tables locales à être gérées dès leur création.

ALTER SYSTEM SET citus.use_citus_managed_tables TO true;
SELECT pg_reload_conf();

Relations entre tables

Les contraintes pour établir une foreign key entre deux tables sont les suivantes.

Table source Table cible Autorisé

Local

Local

Oui

Local

Distribuée

Non

Local

Référence

Oui

Référence

Local

Oui, si le coordinateur est un noeud

Référence

Distribuée

Non

Référence

Référence

Oui

Distribuée

Local

Non

Distribuée

Distribuée

Oui, si collocalisée

Distribuée

Référence

Oui

Pour déclarer le coordinateur comme un noeud:

SELECT citus_add_node('localhost', 5432, groupId => 0);

D’autres contraintes peuvent apparaître en plus de celles-ci dans certaines requêtes complexes. Par exemple, dans une requête avec sous-requête ci-dessous, le tableau est légèrement différent.

SELECT t1.id
FROM sw_dist01 t1
WHERE (
  EXISTS(
    SELECT 1
    FROM sw_dist03 t3
    WHERE t3.t01_id = t1.id
  )
);
Table requête table sous-requête Autorisé

Local

Local

Oui

Local

Distribuée

Non

Local

Référence

Oui, si le coordinateur est un noeud

Référence

Local

Oui, si le coordinateur est un noeud

Référence

Distribuée

Non

Référence

Référence

Oui

Distribuée

Local

Non

Distribuée

Distribuée

Oui, si collocalisée

Distribuée

Référence

Oui

Ces contraintes sont principalement dues à la jointure t3.t01_id = t1.id.

Pour cet exemple, on peut obtenir le même résultat sans ces contraintes avec une jointure simple ou avec une sous-requête suivante:

SELECT t3.id
FROM sw_dist03 t3
INNER JOIN sw_dist01 t1 ON t1.id = t3.t01_id;

-- ou

SELECT t3.id
FROM sw_dist03 t3
WHERE (
  t3.t01_id IN (
    SELECT t1.id
    FROM sw_dist01 t1
  )
);

Mises à jour distribuées

Certaines tables ne peuvent pas être mises à jour dans la même transaction.

Par exemple, la transaction suivante ne fonctionne que dans certaines conditions. Elle concerne une table distribuée (sw_dist01) et une table locale (sw_local01).

BEGIN;
DELETE FROM sw_dist01;
INSERT INTO sw_local01 (id, value) VALUES (uuid_generate_v4(), 'Local 01');
COMMIT;

Telle quelle, la transaction se passe sans problème. En revanche s’il existe une clé étrangère entre une table de référence et sw_local01, elle échoue.

CREATE TABLE sw_local01 ()
    id uuid,
    value text,
    PRIMARY KEY (id)
);

CREATE TABLE sw_ref01 (
    id uuid,
    value text,
    local01_id uuid,
    PRIMARY KEY (id)
);
SELECT create_reference_table('sw_ref01');
ALTER TABLE sw_ref01 ADD CONSTRAINT fk_ref01_local01 FOREIGN KEY (local01_id) REFERENCES sw_local01;

Dans ces conditions, la transaction donne l’erreur suivante:

ERROR: cannot modify table "sw_local01" because there was a parallel operation on a distributed table
Detail: When there is a foreign key to a reference table or to a local table,
        Citus needs to perform all operations over a single connection per node to ensure consistency.
Hint: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';"

Pour résoudre le problème, on peut passer la propagation de la transaction en séquentiel.

BEGIN;
SET LOCAL citus.multi_shard_modify_mode TO 'sequential';
DELETE FROM sw_dist01;
INSERT INTO sw_local01 (id, value) VALUES (uuid_generate_v4(), 'Local 01');
COMMIT;

La transaction peut fonctionner sans ça si la première requête n’est pas réellement distribuée, mais se localise sur un seul shard. Pour ça, on doit limiter la requête sur la colonne de distribution.

BEGIN;
DELETE FROM sw_dist01 WHERE tenant_key = '942b411f-e414-4468-b98c-8d491563c7d8';
INSERT INTO sw_local01 (id, value) VALUES (uuid_generate_v4(), 'Local 01');
COMMIT;