SELECTSELECT

SELECT

Datenbank-Klone in Snowflake: 60x schneller

By Niall WoodwardOct 22, 20223 min read

Diese Seite ist auch in English, Español, Français, Italiano, 日本語 und Português verfügbar.

Einleitung

Letzte Woche durfte ich die Coalesce-Konferenz von dbt in London besuchen und bin dort in einen wirklich starken Vortrag von Felipe Leite und Stephen Pastan von Miro hineingeraten. Die beiden berichteten, wie sie einen deutlichen Geschwindigkeitsgewinn erzielt haben, indem sie Datenbank-Klone durch mehrere Tabellen-Klone ersetzt haben. Das musste ich selbst ausprobieren.

Experimente

Die Ergebnisse wurden mit folgender Query erfasst. Sie misst die Dauer jedes Vorgangs, indem die früheste Query-Startzeit und die letzte Query-Endzeit an die DATEDIFF-Funktion übergeben werden:

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;

Snowflake-Nutzung optimieren

SELECT optimiert Ihre Snowflake-Nutzung automatisch und macht das Management spürbar einfacher.

Setup

Legen Sie eine Datenbank mit 10 Schemas an, in jedem 100 Tabellen:

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

Referenzwert – Datenbank-Klon

1create database test_1 clone test;

Dieser Vorgang dauerte 22 Min. 34 Sek.

Ergebnisse:

Query Count 1
Dauer 22 Min. 34 Sek.
Cloud Services Credits 0,179

Experiment 1 – Klone auf Schema-Ebene

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

Code ausklappen

Ergebnisse:

Query Count 12
Dauer 1 Min. 47 Sek.
Cloud Services Credits 0,148

Mit execute_async wird jede SQL-Anweisung abgesetzt, ohne auf den Abschluss der vorherigen zu warten – so werden alle 10 Schemas parallel geklont. Das Ergebnis: stolze 10x schneller von Anfang bis Ende als der klassische Datenbank-Klon.

Experiment 2 – Klone auf Tabellen-Ebene

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:

Code ausklappen

Das dauerte 1 Minute 48 Sekunden. Der begrenzende Faktor war die Rate, mit der der Client die Queries absetzen konnte (vermutlich wegen Netzwerk-Latenz). Um das abzufedern, habe ich die Befehle auf 10 Threads verteilt:

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

Code ausklappen

Ergebnisse:

Query Count 1012
Dauer 22 Sek.
Cloud Services Credits 0,165

Mit 10 Threads vergingen zwischen dem Start des create database-Befehls und dem Abschluss des letzten create table ... clone-Befehls gerade einmal 22 Sekunden – also 60x schneller als der Befehl create database ... clone. Der Engpass bleibt weiterhin die Rate, mit der sich Queries absetzen lassen.

Fazit

Die vollständigen Ergebnisse:

Klon-Strategie Referenzwert – Datenbank-Klon Experiment 1 – Klone auf Schema-Ebene Experiment 2 – Klone auf Tabellen-Ebene
Query Count 1 12 1012
Dauer 22 Min. 34 Sek. 1 Min. 47 Sek. 22 Sek.
Cloud Services Credits 0,179 0,148 0,165

Alle ausgeführten Queries liefen ausschließlich über Cloud Services – ein laufendes Warehouse oder das Aufwecken eines pausierten Warehouses war nicht nötig.

Ich hoffe, dass Snowflake die Schema- und Datenbank-Klon-Funktionalität weiter verbessert. Bis dahin ist das Klonen einzelner Tabellen klar der bessere Weg.

Nochmals vielen Dank an Felipe Leite und Stephen Pastan von Miro, dass sie diese Erkenntnisse geteilt haben!

Niall Woodward·Co-Founder & CTO von SELECT

Niall ist Co-Founder & CTO von SELECT, einer SaaS-Plattform für Kostenmanagement und Optimierung in Snowflake. Vor SELECT war Niall als Data Engineer bei Brooklyn Data Company und in mehreren Startups tätig. Als Open-Source-Enthusiast ist er außerdem Maintainer von SQLFluff sowie Schöpfer von drei dbt-Packages: dbt_artifacts, dbt_snowflake_monitoring und dbt_query_tags.