Articles précédents sur multi-régions
CockroachDB Abstractions multi-régions pour les développeurs MongoDB avec FerretDB
Motivation
Je travaillais avec un prospect sur un cas d’utilisation où mes connaissances des abstractions multi-régions de CockroachDB ont été mises à l’épreuve. Après avoir essayé et échoué à obtenir un comportement d’écriture à faible latence à partir d’une table distribuée géographiquement, j’ai contacté l’équipe d’ingénierie qui a corrigé ma compréhension. C’est ma façon de documenter ma compréhension et de la mémoriser.
Étapes de haut niveau
- Provisionner un cluster CockroachDB multirégional
- Démontrer le problème
- Démonstration de la solution
- Solution de contournement pour les clés primaires non UUID
Instructions étape par étape
Provisionner un cluster CockroachDB multirégional
Vous aurez besoin d’accéder à un cluster CockroachDB multirégional. Le moyen le plus simple et le plus économique est de s’inscrire à CockroachDB Serverless. C’est ce que j’utilise dans ma démonstration. Mon cluster sans serveur s’étend sur trois régions dans GCP.
Nous pouvons nous connecter aux terminaux régionaux en utilisant le Connect
modal. La chaîne de connexion par défaut donnée sera localisée car CockroachDB Serverless détecte automatiquement la localité de l’utilisateur final. Pour accéder au cluster à partir des régions distantes, vous devez ajouter les régions à la chaîne de connexion. Essentiellement, l’hôte artem-serverless-mr-26.h4f.cockroachlabs.cloud
devient artem-serverless-mr-26.h4f.gcp-us-east1.cockroachlabs.cloud
, artem-serverless-mr-26.h4f.gcp-us-west2.cockroachlabs.cloud
et artem-serverless-mr-26.h4f.gcp-europe-west1.cockroachlabs.cloud
.
Nous pouvons vérifier la région de la passerelle à partir de laquelle nous nous connectons en interrogeant la fonction d’assistance select gateway_region();
de chacune des connexions.
gateway_region ------------------ gcp-us-east1 gateway_region ------------------ gcp-us-west2 gateway_region -------------------- gcp-europe-west1
Démontrer le problème
Si l’on considère un système d’inventaire passant à CockroachDB et tirant parti des abstractions multirégionales, le défi consiste généralement à obtenir la clé primaire correcte pour optimiser les écritures. Les exemples suivants devraient montrer où se situent les défis et comment les contourner.
Pour commencer, nous allons créer une base de données appelée demo et y activer les abstractions multi-régions.
CREATE DATABASE demo; ALTER DATABASE demo SET PRIMARY REGION "gcp-us-east1"; ALTER DATABASE demo ADD region "gcp-us-west2"; ALTER DATABASE demo ADD region "gcp-europe-west1"; SET override_multi_region_zone_config = true;
N’hésitez pas à consulter la documentation pour plus d’informations, mais en résumé, nous inscrivons notre base de données dans des abstractions et des paramètres multirégionaux gcp-us-east1
comme région principale car c’est la région la plus proche de mon emplacement.
Ensuite, je passe une propriété pour remplacer certaines configurations de zone. Cette étape est facultative, sauf si vous revenez pour apporter d’autres modifications par la suite.
Enfin, nous souhaitons épingler les locataires à la région des États-Unis afin que les modifications de schéma dans un contexte multirégional soient effectuées plus rapidement. Vous pouvez en apprendre davantage ici.
ALTER DATABASE system CONFIGURE ZONE USING constraints="{"+region=gcp-us-east1": 1}", lease_preferences="[[+region=gcp-us-east1]]";
Créons un tableau pour illustrer le problème.
CREATE TABLE demo_int_pk (productID PRIMARY KEY, count) AS SELECT unordered_unique_rowid() AS productID, generate_series(1, 10) AS count;
Faire le tableau REGIONAL BY ROW
.
ALTER TABLE demo_int_pk SET LOCALITY REGIONAL BY ROW;
La clé primaire de la table est un type entier. Ce problème n’est pas spécifique aux nombres entiers. Vous aurez également un comportement similaire avec d’autres types, à l’exception de l’UUID – mais j’avance un peu.
Regardons le schéma de la table.
CREATE TABLE public.demo_int_pk ( productid INT8 NOT NULL, count INT8 NULL, crdb_region demo.public.crdb_internal_region NOT VISIBLE NOT NULL DEFAULT default_to_database_primary_region(gateway_region())::demo.public.crdb_internal_region, CONSTRAINT demo_int_pk_pkey PRIMARY KEY (productid ASC) ) LOCALITY REGIONAL BY ROW
Nous pouvons confirmer que le PK est bien un entier. Nous pouvons également voir une nouvelle colonne appelée crdb_region
qui a été ajouté lorsque nous avons activé REGIONAL BY ROW
.
Regardons les données.
SELECT crdb_region, * FROM demo_int_pk;
crdb_region | productid | count ---------------+---------------------+-------- gcp-us-east1 | 1021351265574322179 | 5 gcp-us-east1 | 2174272770181169155 | 9 gcp-us-east1 | 2750733522484592643 | 1 gcp-us-east1 | 3327194274788016131 | 7 gcp-us-east1 | 3903655027091439619 | 3 gcp-us-east1 | 5633037284001710083 | 6 gcp-us-east1 | 6209498036305133571 | 2 gcp-us-east1 | 6785958788608557059 | 10 gcp-us-east1 | 7938880293215404035 | 8 gcp-us-east1 | 8515341045518827523 | 4
Remarquez le crdb_region
colonne indique la région USA Est qui est la région où j’ai exécuté l’instruction d’insertion et où les lignes sont hébergées. Essayons de faire une mise à jour sur le décompte d’inventaire pour l’un des enregistrements.
WITH update_demo_int_pk AS ( UPDATE demo_int_pk SET count = 100 WHERE productID = 2750733522484592643 RETURNING crdb_region, count ) SELECT gateway_region(), crdb_region, count FROM update_demo_int_pk;
gateway_region | crdb_region | count -----------------+--------------+-------- gcp-us-east1 | gcp-us-east1 | 100 Time: 40ms total (execution 11ms / network 29ms)
Il faut 11 ms pour mettre à jour le décompte à l’aide de la connexion USA Est. Remarquez que j’utilise le gateway_region()
fonction, qui confirmera que j’utilise la connexion USA Est. Mettons maintenant à jour l’enregistrement d’une autre région. je vais utiliser gcp-us-west2
région.
WITH update_demo_int_pk AS ( UPDATE demo_int_pk SET count = 100 WHERE productID = 2750733522484592643 RETURNING crdb_region, count ) SELECT gateway_region(), crdb_region, count FROM update_demo_int_pk;
gateway_region | crdb_region | count -----------------+--------------+-------- gcp-us-west2 | gcp-us-east1 | 100 Time: 1.280s total (execution 1.204s / network 0.076s)
La requête a pris 1,2 secondes pour s’exécuter. Ce n’est pas surprenant étant donné que le dossier est physiquement stocké dans la région Est des États-Unis. Regardons le plan de la requête :
distribution: local vectorized: true • root │ ├── • render │ │ │ └── • scan buffer │ estimated row count: 1 │ label: buffer 2 (update_demo_int_pk) │ └── • subquery │ id: @S1 │ original sql: UPDATE demo_int_pk SET count = 100 WHERE productid = 2750733522484592643 RETURNING crdb_region, count │ exec mode: all rows │ └── • buffer │ label: buffer 2 (update_demo_int_pk) │ └── • update │ estimated row count: 1 │ table: demo_int_pk │ set: count │ └── • render │ └── • union all │ estimated row count: 1 │ limit: 1 │ ├── • scan │ estimated row count: 1 (10% of the table; stats collected 19 seconds ago; using stats forecast for 12 minutes in the future) │ table: demo_int_pk@demo_int_pk_pkey │ spans: [/'gcp-us-west2'/2750733522484592643 - /'gcp-us-west2'/2750733522484592643] │ └── • scan estimated row count: 1 (10% of the table; stats collected 19 seconds ago; using stats forecast for 12 minutes in the future) table: demo_int_pk@demo_int_pk_pkey spans: [/'gcp-europe-west1'/2750733522484592643 - /'gcp-europe-west1'/2750733522484592643] [/'gcp-us-east1'/2750733522484592643 - /'gcp-us-east1'/2750733522484592643]
Si vous lisez la partie inférieure, nous réunissons tous les résultats des scans dans le gcp-us-west2
et gcp-europe-west1
régions pour valider l’enregistrement est unique dans toutes les régions.
Ajoutons un enregistrement dans la région Ouest des États-Unis. Nous nous attendons à ce que l’écriture soit rapide.
INSERT INTO demo_int_pk (productID, count) VALUES (unordered_unique_rowid(), 1) RETURNING gateway_region(), crdb_region, productID, count;
gateway_region | crdb_region | productid | count -----------------+--------------+---------------------+-------- gcp-us-west2 | gcp-us-west2 | 5746135747274211350 | 1 Time: 296ms total (execution 217ms / network 78ms)
L’insertion prend 217 ms, même si nous écrivons depuis la région ouest des États-Unis.
L’histoire est la même si nous devions insérer depuis l’UE :
gateway_region | crdb_region | productid | count -------------------+------------------+---------------------+-------- gcp-europe-west1 | gcp-europe-west1 | 1222035725603831819 | 1 Time: 338ms total (execution 250ms / network 88ms)
La mise à jour d’un enregistrement depuis la région extérieure sera également coûteuse :
WITH update_demo_int_pk AS ( UPDATE demo_int_pk SET count = 100 WHERE productID = 2750733522484592643 RETURNING crdb_region, count ) SELECT gateway_region(), crdb_region, count FROM update_demo_int_pk;
gateway_region | crdb_region | count -----------------+--------------+-------- gcp-us-west2 | gcp-us-east1 | 100 Time: 354ms total (execution 277ms / network 76ms)
Juste pour expliquer la déclaration – j’ai mis à jour le dossier stocké physiquement dans l’est des États-Unis (crdb_region
) passant par le point de terminaison USA Ouest (gateway_region
).
Nous avons maintenant des écritures très rapides de la région locale mais des écritures très lentes de partout ailleurs.
Concentrons-nous maintenant sur la façon dont nous pouvons améliorer le scénario. La dure leçon pour moi a été de réaliser que les docs appellent une optimisation que je n’avais pas vue à l’origine.
Remarque : Lors de l’utilisation
DEFAULT gen_random_uuid()
sur les colonnes dansREGIONAL BY ROW
tables, les vérifications d’unicité sur ces colonnes sont désactivées par défaut à des fins de performances. CockroachDB assume l’unicité en fonction de la façon dont cette colonne…