SELECTSELECT

SELECT

Clones de bases Snowflake 60x plus rapides

By Niall WoodwardOct 22, 20223 min read

Cette page est également disponible en English, Deutsch, Español, Italiano, 日本語 et Português.

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.