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.