SELECTSELECT

SELECT

Clonare database in Snowflake: 60 volte più veloce

By Niall WoodwardOct 22, 20223 min read

Questa pagina è disponibile anche in English, Deutsch, Español, Français, 日本語 e Português.

Introduzione

La scorsa settimana ho avuto il piacere di partecipare alla conferenza Coalesce di dbt a Londra, dove ho seguito un intervento davvero interessante di Felipe Leite e Stephen Pastan di Miro. Hanno spiegato come fossero riusciti a ottenere un netto miglioramento delle prestazioni sostituendo il clone dell'intero database con cloni multipli a livello di tabella. Non potevo non provarlo di persona.

Gli esperimenti

I risultati sono stati raccolti con la seguente query, che misura la durata di ogni processo passando alla funzione DATEDIFF il primo start time e l'ultimo end time delle query:

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;

Ottimizza l'utilizzo di Snowflake

SELECT ottimizza in automatico e ti aiuta a gestire l'utilizzo di Snowflake in tutta semplicità.

Setup

Creiamo un database con 10 schemi, ognuno con 100 tabelle:

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);")

Riferimento - Clone del database

1create database test_1 clone test;

L'operazione è stata completata in 22m 34s.

Risultati:

Query Count 1
Durata 22m 34s
Crediti cloud services 0.179

Esperimento 1 - Cloni a livello di schema

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',

Espandi codice

Risultati:

Query Count 12
Durata 1m 47s
Crediti cloud services 0.148

Con execute_async ogni istruzione SQL viene eseguita senza attendere il completamento della precedente: il risultato è che tutti e 10 gli schemi vengono clonati in parallelo. Stiamo parlando di un'esecuzione 10 volte più rapida, dall'inizio alla fine, rispetto al clone classico del database.

Esperimento 2 - Cloni a livello di tabella

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:

Espandi codice

L'esecuzione ha richiesto 1 minuto e 48 secondi: il vero collo di bottiglia è la velocità con cui il client riesce a inviare le query (probabilmente per via dei tempi di attesa sulla rete). Per attenuare il problema, ho distribuito i comandi su 10 thread:

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):

Espandi codice

Risultati:

Query Count 1012
Durata 22s
Crediti cloud services 0.165

Con 10 thread, l'intervallo fra l'avvio del comando create database e il completamento dell'ultimo create table ... clone è stato di appena 22 secondi: 60 volte più veloce del comando create database ... clone. Il collo di bottiglia rimane la velocità con cui le query vengono inviate.

In sintesi

Il quadro completo dei risultati:

Strategia di clonazione Riferimento - Clone del database Esperimento 1 - Cloni a livello di schema Esperimento 2 - Cloni a livello di tabella
Query count 1 12 1012
Durata 22m 34s 1m 47s 22s
Crediti cloud services 0.179 0.148 0.165

Tutte le query eseguite hanno utilizzato esclusivamente cloud services, senza bisogno di un warehouse attivo né di riattivarne uno sospeso.

Mi auguro che Snowflake migliori le funzionalità di clonazione di schemi e database; nel frattempo, clonare le tabelle resta la strada migliore.

Ancora grazie a Felipe Leite e Stephen Pastan di Miro per aver condiviso questo approccio!

Niall Woodward·Co-founder & CTO di SELECT

Niall è Co-Founder e CTO di SELECT, la piattaforma SaaS per la gestione e l'ottimizzazione dei costi di Snowflake. Prima di fondare SELECT, ha lavorato come data engineer presso Brooklyn Data Company e in diverse startup. Appassionato di open-source, è anche maintainer di SQLFluff e autore di tre pacchetti dbt: dbt_artifacts, dbt_snowflake_monitoring e dbt_query_tags.