SELECTSELECT

SELECT

Clones de bases de datos 60x más rápidos en Snowflake

By Niall WoodwardOct 22, 20223 min read

Esta página también está disponible en English, Deutsch, Français, Italiano, 日本語 y Português.

Introducción

La semana pasada tuve el gusto de asistir a Coalesce, la conferencia de dbt en Londres, y entré a una charla buenísima de Felipe Leite y Stephen Pastan, de Miro. Contaron cómo habían conseguido una mejora considerable de velocidad al reemplazar los clones de bases de datos por varios clones de tablas. Tenía que probarlo.

Experimentos

Los resultados se obtuvieron con la siguiente consulta, que mide la duración de cada proceso pasándole la hora de inicio de la primera query y la hora de finalización de la última a la función 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;

Optimiza tu uso de Snowflake

SELECT optimiza automáticamente y te ayuda a gestionar tu uso de Snowflake sin complicaciones.

Preparación

Crea una base de datos con 10 schemas y 100 tablas en cada uno:

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

Control - Clon de base de datos

1create database test_1 clone test;

Esta operación tardó 22m 34s en ejecutarse.

Resultados:

Cantidad de queries 1
Duración 22m 34s
Créditos de cloud services 0.179

Experimento 1 - Clones a nivel de 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',

Expandir código

Resultados:

Cantidad de queries 12
Duración 1m 47s
Créditos de cloud services 0.148

Con execute_async cada sentencia SQL se ejecuta sin esperar a que termine la anterior, por lo que los 10 schemas se clonan en paralelo. Nada menos que 10 veces más rápido de principio a fin que el clon de base de datos tradicional.

Experimento 2 - Clones a nivel de tabla

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:

Expandir código

Esto tardó 1 minuto y 48s en completarse; el factor limitante era la velocidad a la que el cliente lograba despachar las queries (seguramente por los tiempos de espera de red). Para mitigarlo, distribuí los comandos en 10 hilos:

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

Expandir código

Resultados:

Cantidad de queries 1012
Duración 22s
Créditos de cloud services 0.165

Con 10 hilos, el tiempo entre el inicio del comando create database y la finalización del último create table ... clone fue de apenas 22 segundos. Esto es 60 veces más rápido que el comando create database ... clone. El cuello de botella sigue siendo la velocidad a la que se despachan las queries.

En resumen

Los resultados completos:

Estrategia de clonación Control - Clon de base de datos Experimento 1 - Clones a nivel de schema Experimento 2 - Clones a nivel de tabla
Cantidad de queries 1 12 1012
Duración 22m 34s 1m 47s 22s
Créditos de cloud services 0.179 0.148 0.165

Todas las queries ejecutadas fueron exclusivamente de cloud services, sin necesidad de un warehouse activo ni de reanudar uno suspendido.

Ojalá Snowflake mejore la funcionalidad de clonación de schemas y bases de datos, pero por ahora clonar tablas parece ser la mejor opción.

¡Gracias de nuevo a Felipe Leite y Stephen Pastan, de Miro, por compartirlo!

Niall Woodward·Co-founder & CTO of SELECT

Niall es Co-Founder y CTO de SELECT, una plataforma SaaS de gestión y optimización de costos para Snowflake. Antes de fundar SELECT, fue data engineer en Brooklyn Data Company y en varias startups. Como entusiasta del open-source, también es maintainer de SQLFluff y creador de tres paquetes de dbt: dbt_artifacts, dbt_snowflake_monitoring y dbt_query_tags.