Introduction
J'ai eu le plaisir d'assister à la conférence Coalesce de dbt à Londres la semaine dernière, et j'y ai suivi une excellente présentation de Felipe Leite et Stephen Pastan, de Miro. Ils y expliquaient comment ils avaient considérablement accéléré leurs clonages en remplaçant les clones de bases de données par plusieurs clones de tables. Il fallait que je teste par moi-même.
Expériences
Les résultats ont été obtenus avec la requête suivante, qui mesure la durée de chaque processus en passant l'heure de début de la première requête et l'heure de fin de la dernière à la fonction DATEDIFF :
select
count(*) as query_count,
datediff(seconds, min(start_time), max(end_time)) as duration,
sum(credits_used_cloud_services) as credits_used_cloud_services
from snowflake.account_usage.query_history where query_tag = X;
Optimisez votre consommation Snowflake
SELECT optimise automatiquement votre consommation Snowflake et vous aide à la piloter en toute simplicité.
Mise en place
Créons une base de données avec 10 schémas et 100 tables dans chacun :
import snowflake.connector
con = snowflake.connector.connect(
...
)
for i in range(1, 11):
con.cursor().execute(f"create schema test.schema_{i};")
for j in range(1, 101):
con.cursor().execute(f"create table test.schema_{i}.table_{j} (i number) as (select 1);")
Référence – Clone de base de données
1create database test_1 clone test;
Cette opération a pris 22 min 34 s.
Résultats :
| Nombre de requêtes | 1 |
|---|---|
| Durée | 22 min 34 s |
| Crédits cloud services | 0,179 |
Expérience 1 – Clones au niveau du schéma
import snowflake.connector
from snowflake.connector import DictCursor
def clone_database_by_schema(con, source_database, target_database):
con.cursor().execute(f"create database {target_database};")
cursor = con.cursor(DictCursor)
cursor.execute(f"show schemas in database {source_database};")
for i in cursor.fetchall():
if i["name"] not in ("INFORMATION_SCHEMA", "PUBLIC"):
con.cursor().execute_async(f"create schema {target_database}.{i['name']} clone {source_database}.{i['name']};")
con = snowflake.connector.connect(
...
session_parameters={
'QUERY_TAG': 'test 2',
Afficher le code
Résultats :
| Nombre de requêtes | 12 |
|---|---|
| Durée | 1 min 47 s |
| Crédits cloud services | 0,148 |
Avec execute_async, chaque instruction SQL est lancée sans attendre la fin de la précédente : les 10 schémas sont donc clonés en parallèle. Un gain impressionnant de 10x du début à la fin par rapport au clone de base de données classique.
Expérience 2 – Clones au niveau de la table
import snowflake.connector
from snowflake.connector import DictCursor
def clone_database_by_table(con, source_database, target_database):
con.cursor().execute(f"create database {target_database};")
cursor = con.cursor(DictCursor)
cursor.execute(f"show tables in database {source_database};")
results = cursor.fetchall()
schemas_to_create = {r['schema_name'] for r in results}
tables_to_clone = [f"{r['schema_name']}.{r['name']}" for r in results]
for schema in schemas_to_create:
con.cursor().execute(f"create schema {target_database}.{schema};")
for table in tables_to_clone:
Afficher le code
L'opération a pris 1 min 48 s, le facteur limitant étant la cadence à laquelle le client pouvait envoyer les requêtes (sans doute à cause de la latence réseau). Pour y remédier, j'ai réparti les commandes sur 10 threads :
1import snowflake.connector
2from snowflake.connector import DictCursor
3import threading
4
5class ThreadedRunCommands():
6 """Helper class for running queries across a configurable number of threads"""
7 def __init__(self, con, threads):
8 self.threads = threads
9 self.register_command_thread = 0
10 self.thread_commands = [\
\
11 [] for _ in range(self.threads)\
\
12 ]
13 self.con = con
14
15 def register_command(self, command):
Afficher le code
Résultats :
| Nombre de requêtes | 1012 |
|---|---|
| Durée | 22 s |
| Crédits cloud services | 0,165 |
Avec 10 threads, seulement 22 secondes s'écoulent entre le lancement de la commande create database et la fin de la dernière commande create table ... clone. Soit 60x plus rapide que la commande create database ... clone. Le goulot d'étranglement reste la cadence d'envoi des requêtes.
En résumé
Voici l'ensemble des résultats :
| Stratégie de clonage | Référence – Clone de base de données | Expérience 1 – Clones au niveau du schéma | Expérience 2 – Clones au niveau de la table |
|---|---|---|---|
| Nombre de requêtes | 1 | 12 | 1012 |
| Durée | 22 min 34 s | 1 min 47 s | 22 s |
| Crédits cloud services | 0,179 | 0,148 | 0,165 |
Toutes les requêtes exécutées relèvent uniquement des cloud services : elles n'ont nécessité ni warehouse actif ni reprise d'un warehouse suspendu.
On peut espérer que Snowflake fasse évoluer ses fonctionnalités de clonage de schémas et de bases de données ; en attendant, le clonage par table est clairement la voie à suivre.
Merci encore à Felipe Leite et Stephen Pastan de Miro pour ce partage !
Niall Woodward·Co-fondateur & CTO de SELECT
Niall est co-fondateur et CTO de SELECT, une plateforme SaaS de gestion et d'optimisation des coûts Snowflake. Avant de lancer SELECT, il était data engineer chez Brooklyn Data Company et dans plusieurs startups. Passionné d'open source, il est aussi mainteneur de SQLFluff et créateur de trois packages dbt : dbt_artifacts, dbt_snowflake_monitoring et dbt_query_tags.