“DuckDB en dev. Snowflake en prod. Lo mejor de ambos mundos.” Es una premisa atractiva. dbt anunció soporte beta para DuckDB en la CLI de Fusion en April 2026. Al July 15, 2026, seguía siendo una opción beta para ejecutar un proyecto de dbt contra una base de datos embebida sin obtener primero credenciales del warehouse.
La versión útil de la idea es más precisa:
Usa DuckDB como un ciclo rápido de desarrollo local. Conserva un ciclo de validación más acotado en Snowflake como control de compatibilidad con producción.
DuckDB puede validar una gran parte de la lógica de transformación de forma rápida y económica. No puede demostrar que el SQL específico de Snowflake, el comportamiento incremental, las políticas de seguridad, los identificadores, las zonas horarias o el rendimiento físico funcionarán correctamente. Fusion mejora el análisis del código; junto con la extensión de dbt para VS Code, también agrega feedback en el editor y linaje a nivel de columna. No es una capa de traducción entre dialectos SQL.
Este artículo desarrolla un ejemplo representativo de una aplicación Idea existente en la que los usuarios envían ideas, votan, comentan y generan eventos de producto. Su proyecto de analítica tiene muchos modelos de dbt organizados en capas bronze, silver y gold. El ejemplo es intencionalmente concreto, pero el flujo de trabajo se aplica a otros proyectos maduros de dbt.
Audiencia: ingenieros y arquitectos responsables del desarrollo de analítica, CI, seguridad de datos y costos del warehouse.
En resumen
- Al July 15, 2026, el soporte de DuckDB en la CLI de dbt Fusion está en beta. Una misma base de código de dbt puede apuntar a DuckDB local y a Snowflake en producción, pero eso no implica que el SQL compilado ni el comportamiento en tiempo de ejecución sean idénticos.
- No apuntes los modelos bronze directamente a
read_parquet()oread_csv()si quieres un análisis estático confiable de Fusion. Primero carga fixtures sanitizados en tablas de DuckDB con la estructura de las fuentes y luego deja que dbt lea esas tablas mediante llamadas normales asource(). - Para un grafo medallion grande, usa un archivo
.duckdbpersistente, construye una vez el grafo portable completo y después usa selección por estado para los modelos modificados. Un manifest de Snowflake de producción puede ayudar a identificar cambios de código, pero--deferentre motores no hace que DuckDB pueda consultar las relaciones de Snowflake. - Aísla las expresiones específicas de cada base de datos detrás de macros con dispatch y etiqueta los modelos que sean realmente exclusivos de Snowflake para excluirlos de forma deliberada en las ejecuciones locales.
- Trata el soporte de modelos incrementales como dependiente de la versión: prueba la lógica portable en DuckDB y luego prueba el comportamiento de materialización de producción en Snowflake.
- Mantén una pirámide de pruebas: pruebas unitarias de dbt y un build portable en DuckDB para obtener feedback rápido; después, Snowflake Slim CI o un clon gobernado para verificar la paridad con producción.
- El cómputo local no es automáticamente gratuito. Exportar fixtures consume cómputo de Snowflake y puede generar cargos por transferencia entre regiones o nubes, según la ruta de descarga.
Qué aprenderás aquí
- Qué cambia con el soporte de DuckDB en dbt Fusion y qué no cambia
- Cómo adaptar un proyecto existente de dbt con capas bronze/silver/gold a DuckDB local
- Cómo crear datos locales sanitizados con la estructura de las fuentes sin reescribir las referencias de los modelos
- Cómo organizar perfiles, macros con dispatch por adapter, etiquetas de modelos y selección por estado
- Qué verificaciones corresponden a DuckDB y cuáles deben seguir ejecutándose en Snowflake
- Las fallas más probables de portabilidad, datos, seguridad, concurrencia y rendimiento
- Una guía de solución de problemas que relaciona síntomas con diagnósticos
Qué cambió en April 2026 y cuál era la situación en julio
El patrón de DuckDB local no es nuevo. El adapter comunitario dbt-duckdb ya permitía ejecutar proyectos de dbt Core contra DuckDB. La novedad es que DuckDB ahora está integrado en la CLI de Fusion. La CLI proporciona el flujo de análisis y ejecución del motor de Rust; el linaje a nivel de columna y la experiencia más completa en el editor requieren la extensión de dbt para VS Code.
La página oficial de configuración de DuckDB destaca cuatro límites importantes:
- El adapter de DuckDB para Fusion está en beta.
- Todavía no tiene paridad completa de funcionalidades con el adapter existente
dbt-duckdb. - El driver de DuckDB incluido con Fusion no puede cargar extensiones como
httpfs,parquetospatial; para usarlas se necesita un driver de DuckDB instalado en el sistema mediante la CLIdbc. - Es posible que el análisis estático no infiera los esquemas de archivos locales CSV, Parquet o JSON a los que se accede mediante
read_csv(),read_parquet()oread_json(). Por lo tanto, una consulta puede funcionar en tiempo de ejecución mientras Fusion informa una advertencia de resolución de tipos o un error de compilación.
dbc es una CLI para instalar drivers ADBC; no es otro adapter de dbt:
dbc install duckdb
Instala la versión preliminar actual con el comando de la guía de inicio rápido oficial y luego fija la versión exacta que funciona en la imagen de desarrollo y en CI del equipo:
python -m pip install --pre dbt
dbt --version
Las versiones preliminares cambian rápido. No copies un marcador de posición variable como 2.0.0-preview.x en un build reproducible.
Elige deliberadamente la herramienta de ejecución local:
| Situación | Herramienta local recomendada | Motivo |
|---|---|---|
| Proyecto nuevo que acepta el riesgo de una versión preliminar | CLI de Fusion con versión fijada + DuckDB | Un solo motor para análisis y ejecución; agrega la extensión de dbt para VS Code para integrar el editor |
Flujo estable existente con dbt-duckdb que usa funciones de las que Fusion carece | Conserva dbt Core + dbt-duckdb; prueba Fusion por separado | Evita convertir una evaluación del adapter en una migración |
| Se necesitan extensiones de DuckDB | Fusion con el driver del sistema instalado por dbc | El driver incluido no puede cargar extensiones |
| Se necesitan linaje de columnas y feedback detallado en el editor | Fusion más la extensión de dbt para VS Code | La CLI por sí sola no expone esas funciones del editor |
| Validación de paridad con producción | Target de Snowflake en CI | DuckDB no puede demostrar el comportamiento de Snowflake en tiempo de ejecución |
Si se necesitan ambas herramientas locales durante la migración, fija sus versiones en entornos virtuales o contenedores separados. No permitas que dos instalaciones compitan por el mismo comando dbt.
El adapter reduce la fricción de conexión; no elimina el segundo motor de ejecución:
dbt project + Jinja + DAG
│
┌───────┴────────┐
│ │
▼ ▼
Fusion + DuckDB Fusion + Snowflake
local relations warehouse relations
DuckDB SQL Snowflake SQL
local resources Snowflake policies and compute
│ │
fast logic signal production-parity signal
El dispatch de adapters de dbt existe precisamente porque la sintaxis SQL, los tipos de datos y el comportamiento admitido difieren entre plataformas de datos. Fusion puede entender ambos targets sin hacerlos equivalentes.
El caso de la aplicación Idea existente
Supongamos que la aplicación ya tiene:
- tablas transaccionales PostgreSQL para
users,ideas,votesycomments - eventos de producto entregados como JSON
- una fuente de facturación con planes de workspace
- jobs de ingesta que depositan datos de origen en Snowflake
- aproximadamente 140 modelos de dbt acumulados con el tiempo
La cantidad exacta es ilustrativa, no un benchmark. Lo importante es la estructura del grafo:
| Capa | Modelos de ejemplo | Responsabilidad | Expectativa local |
|---|---|---|---|
| Bronze | stg_app__ideas, stg_app__votes, stg_events__idea_viewed | Renombrar, convertir tipos y conservar metadatos de origen | Portable, salvo los metadatos específicos de la ingesta |
| Silver | int_idea_vote_rollup, int_idea_lifecycle, dim_users, fct_idea_events | Deduplicar, unir, validar la granularidad y derivar entidades reutilizables | Mayormente portable |
| Gold | mart_idea_engagement, mart_workspace_adoption, mart_trending_ideas | Métricas de negocio y agregados para el producto | Lógica portable; la optimización física puede ser exclusiva de Snowflake |
| Específica de la plataforma | vistas seguras, hooks de políticas de enmascaramiento, Snowpark o funciones externas | Gobernanza y capacidades nativas del warehouse | Solo Snowflake |
Los nombres medallion describen una calidad creciente: bronze es datos sin procesar, silver está validada e integrada, y gold está lista para el negocio. No requieren una base de datos particular. Esto hace portables las capas lógicas, aunque su ingesta e implementación física pueden seguir siendo específicas de cada plataforma.
Comienza la prueba piloto con un flujo vertical completo en lugar de los 140 modelos:
dbt build --target duckdb --select +mart_idea_engagement
El + inicial selecciona los ancestros del mart y obliga a la prueba piloto a ejercitar conjuntamente sus fuentes raw, la preparación bronze, la agregación de votos silver y el resultado gold.
Arquitectura objetivo
EXISTING PRODUCTION PATH
Postgres ─┐
Events ──┼─> ingestion ─> Snowflake RAW ─> bronze ─> silver ─> gold
Billing ──┘ dbt on Snowflake
│
dashboards / ML
LOCAL DEVELOPMENT PATH
sanitized, versioned source snapshot
│
▼
bootstrap raw DuckDB tables
│
▼
bronze ─> silver ─> gold
dbt Fusion on DuckDB
│
unit + data-test feedback
Pull request ─> DuckDB check ─> Snowflake parity check ─> merge
La ruta local no reemplaza la ingesta ni copia el warehouse. Proporciona un contrato de datos pequeño, con la estructura de producción, al mismo grafo de dbt.
Paso 1: Mantén un único contrato lógico de fuentes
Los modelos de dbt deben seguir usando fuentes lógicas estables:
# models/sources/app.yml
version: 2
sources:
- name: app
database: "{{ env_var('DBT_RAW_DATABASE', 'idea_local') }}"
schema: raw
tables:
- name: users
- name: ideas
- name: votes
- name: comments
- name: product_events
En desarrollo local, idea_local es el nombre base de idea_local.duckdb. En Snowflake CI y producción, DBT_RAW_DATABASE apunta a la base de datos raw gobernada. El SQL del modelo permanece sin cambios:
select
idea_id,
workspace_id,
title,
status,
created_at,
updated_at,
_ingested_at
from {{ source('app', 'ideas') }}
No dividas el grafo en copias models/duckdb/ y models/snowflake/. Eso crea dos productos que divergen con el tiempo. Mantén un solo grafo y aísla únicamente las expresiones o materializaciones que realmente difieren.
Paso 2: Inicializa tablas locales con la estructura de las fuentes
Para un proyecto maduro, el límite local más claro es un conjunto de tablas físicas de DuckDB que coincidan con el contrato de las fuentes raw de Snowflake.
-- scripts/bootstrap_local.sql
create schema if not exists raw;
create or replace table raw.ideas as
select
idea_id::varchar as idea_id,
workspace_id::varchar as workspace_id,
title::varchar as title,
status::varchar as status,
created_at::timestamp as created_at,
updated_at::timestamp as updated_at,
_ingested_at::timestamp as _ingested_at
from read_parquet('fixtures/raw/ideas.parquet');
create or replace table raw.votes as
select *
from read_parquet('fixtures/raw/votes.parquet');
Ejecuta esta inicialización con un cliente de DuckDB que pueda leer el formato de archivo elegido:
export DUCKDB_PATH="$(pwd)/local/idea_local.duckdb"
export DUCKDB_TEMP_DIRECTORY="$(pwd)/local/tmp"
mkdir -p local/tmp
duckdb "${DUCKDB_PATH}" < scripts/bootstrap_local.sql
Esta capa intermedia importa. Evita la limitación documentada del análisis de lectores de archivos planos de Fusion al presentar a dbt fuentes de tablas comunes. También impide que las rutas de archivos y la mecánica de los fixtures se filtren a los modelos de producción.
Usa tres tamaños de datos para distintas tareas:
| Dataset | Contenido | Ubicación | Propósito |
|---|---|---|---|
| Fixtures deterministas pequeños | Valores nulos, duplicados, actualizaciones tardías, estados inválidos y casos límite de zonas horarias seleccionados a mano | Bajo control de versiones cuando la política lo permita | Pruebas unitarias y de regresión de lógica |
| Snapshot sanitizado de las fuentes | Muestra pequeña pero consistente en sus relaciones entre todas las tablas raw | Almacén de artefactos con control de acceso o generación local | DAG portable completo |
| Datos gobernados del warehouse | Esquema de desarrollo enmascarado, relaciones padre de producción mediante defer o clon autorizado | Solo Snowflake | Validación de dialecto, materialización, políticas y escala |
Un error común es tomar muestras aleatorias de cada tabla de forma independiente. Esto destruye las relaciones de claves foráneas: los votos muestreados ya no tienen ideas correspondientes en la muestra, y las ideas muestreadas ya no tienen usuarios correspondientes. Toma la muestra a partir de entidades raíz del negocio como workspace_id y luego incluye las filas relacionadas de cada fuente.
Para datos sensibles, el pipeline de inicialización debe eliminar las columnas que no se usan, tokenizar las claves de unión estables, generalizar las marcas de tiempo cuando sea necesario y publicar un manifest con verificaciones de esquema y conteo de filas. Una laptop no debe convertirse en una réplica de producción sin gobernanza.
Genera fixtures a partir de entidades raíz del negocio
El generador de fixtures debe conservar las relaciones entre tablas. El siguiente pseudocódigo con sintaxis de Snowflake selecciona un conjunto determinista de workspaces y luego exporta cada entidad relacionada. secure_token_v1 representa una función de tokenización con clave, propiedad de la organización, no un hash público simple:
create or replace transient table fixture_roots as
select workspace_id
from idea_raw.raw.workspaces
where mod(abs(hash(workspace_id)), 1000) < 5
qualify row_number() over (order by workspace_id) <= 100;
create or replace transient table fixture_ideas as
select
secure_token_v1(i.idea_id) as idea_id,
secure_token_v1(i.workspace_id) as workspace_id,
concat('Idea ', row_number() over (order by i.idea_id)) as title,
i.status,
date_trunc('day', i.created_at) as created_at,
date_trunc('day', i.updated_at) as updated_at,
i._ingested_at
from idea_raw.raw.ideas i
join fixture_roots r using (workspace_id);
create or replace transient table fixture_votes as
select
secure_token_v1(v.vote_id) as vote_id,
secure_token_v1(v.idea_id) as idea_id,
secure_token_v1(v.user_id) as user_id,
v.voted_at,
v._ingested_at
from idea_raw.raw.votes v
join idea_raw.raw.ideas i using (idea_id)
join fixture_roots r using (workspace_id);
copy into @dev_fixture_stage/idea-app/2026-07-15.1/ideas/
from fixture_ideas
file_format = (type = parquet compression = snappy)
header = true
overwrite = true;
Debe usarse la misma versión de tokenización para una clave en todos los lugares donde aparezca. Repite la descarga para cada tabla de fixtures aprobada. Mantén el stage con control de acceso y versiona la ruta para que los desarrolladores y CI puedan reproducir la misma entrada.
Publica un manifest pequeño con cada conjunto de fixtures:
fixture_version: "2026-07-15.1"
source_snapshot_at: "2026-07-15T00:00:00Z"
sampling_root: workspace_id
sanitizer_version: secure_token_v1
tables:
raw.ideas: {rows: 842, schema_md5: "..."}
raw.votes: {rows: 3217, schema_md5: "..."}
Después de la inicialización local, compara los conteos de filas observados y los hashes de esquema con este manifest y detén el proceso antes de ejecutar dbt si no coinciden. El publicador de fixtures puede generar un script de validación con los valores esperados:
select case
when count(*) = 842 then true
else error('raw.ideas row count does not match fixture manifest')
end as ideas_row_count_ok
from raw.ideas;
with observed as (
select md5(
string_agg(
column_name || ':' || data_type,
',' order by column_index
)
) as schema_md5
from duckdb_columns()
where database_name = current_database()
and schema_name = 'raw'
and table_name = 'ideas'
)
select case
when schema_md5 = '<manifest schema_md5>' then true
else error('raw.ideas schema does not match fixture manifest')
end as ideas_schema_ok
from observed;
Ejecuta estas aserciones dentro de bootstrap_local.sql para que una descarga parcial o un esquema de fixtures desactualizado detenga el flujo antes de que dbt comience.
El responsable de plataforma o de gobernanza de datos debe encargarse de la extracción y la sanitización. Los ingenieros de analítica deben encargarse de las filas diseñadas deliberadamente para casos límite. Actualiza el snapshot cuando cambien los contratos de las fuentes o cuando ya no represente distribuciones importantes, no en cada ejecución local.
La fidelidad necesaria de los fixtures también varía según la capa: bronze necesita casos límite malformados y específicos de las fuentes; silver necesita integridad de las relaciones y de la granularidad; gold necesita suficiente cardinalidad y sesgo en los grupos para ejercitar las métricas de negocio.
Paso 3: Define los targets de DuckDB y Snowflake
Mantén las credenciales fuera del repositorio, normalmente en ~/.dbt/profiles.yml o en un secreto administrado:
idea_analytics:
target: "{{ env_var('DBT_TARGET', 'duckdb') }}"
outputs:
duckdb:
type: duckdb
path: "{{ env_var('DUCKDB_PATH', './local/idea_local.duckdb') }}"
schema: analytics
threads: 4
settings:
memory_limit: "4GB"
temp_directory: "{{ env_var('DUCKDB_TEMP_DIRECTORY', './local/tmp') }}"
snowflake:
type: snowflake
account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}"
user: "{{ env_var('SNOWFLAKE_USER') }}"
private_key_path: "{{ env_var('SNOWFLAKE_PRIVATE_KEY_PATH') }}"
role: TRANSFORMER
database: IDEA_ANALYTICS
warehouse: DBT_CI
schema: "DBT_{{ env_var('CI_RUN_ID', 'DEV') }}"
threads: 8
Luego verifica los targets por separado:
export DUCKDB_PATH="$(pwd)/local/idea_local.duckdb"
export DUCKDB_TEMP_DIRECTORY="$(pwd)/local/tmp"
DBT_TARGET=duckdb dbt debug --target duckdb
DBT_TARGET=snowflake dbt debug --target snowflake
DuckDB resuelve las rutas relativas de bases de datos a partir de la ubicación de profiles.yml, que puede ser ~/.dbt en lugar del proyecto. Un DUCKDB_PATH absoluto garantiza que el comando de inicialización y dbt apunten al mismo archivo.
Usa en Snowflake CI una opción sin contraseña compatible con Fusion, como la autenticación con par de claves. Si quieres usar federación de identidad de cargas de trabajo, confirma primero que el driver de Fusion con versión fijada y el método de autenticación la admitan. El perfil anterior solo muestra dónde colocar los campos respaldados por secretos.
Para un grafo grande, es preferible un archivo .duckdb persistente a :memory:. Una base de datos en memoria empieza vacía en cada invocación, por lo que comandos separados de dbt run y dbt test no pueden depender de relaciones padre materializadas por un proceso anterior.
Paso 4: Define un límite de portabilidad
La mayor parte de la lógica de selección, unión, filtrado y ventanas se puede compartir. La sintaxis específica de cada base de datos debe ser explícita, no aparecer accidentalmente.
Por ejemplo, la extracción de JSON difiere entre Snowflake y DuckDB. Aísla esa diferencia detrás del dispatch por adapter:
El ejemplo supone que el name del proyecto en dbt_project.yml es idea_analytics; reemplaza ese namespace de dispatch por el nombre real del proyecto o paquete.
-- macros/json_string.sql
{% macro json_string(column_name, json_path) %}
{{ return(adapter.dispatch('json_string', 'idea_analytics')(column_name, json_path)) }}
{% endmacro %}
{% macro snowflake__json_string(column_name, json_path) %}
get_path(
parse_json({{ column_name }}),
{{ dbt.string_literal(json_path) }}
)::varchar
{% endmacro %}
{% macro duckdb__json_string(column_name, json_path) %}
json_extract_string(
{{ column_name }},
{{ dbt.string_literal('$.' ~ json_path) }}
)
{% endmacro %}
Este ejemplo define payload como texto JSON en el límite compartido. El modelo conserva la intención de negocio:
-- models/silver/int_event_idea_ids.sql
select
event_id,
{{ json_string('payload', 'idea_id') }} as idea_id,
occurred_at
from {{ source('app', 'product_events') }}
Prefiere las macros multibase de datos existentes de dbt para operaciones comunes como aritmética de fechas, conversiones seguras de tipos, funciones de cadenas y nombres de tipos. Agrega dispatch en el proyecto solo cuando la abstracción integrada no cubra el requisito.
Este patrón mejora la mantenibilidad, pero genera dos expresiones compiladas. Las pruebas compartidas deben verificar que ambas implementaciones produzcan el mismo resultado.
unit_tests:
- name: extracts_idea_id_from_json_text
model: int_event_idea_ids
given:
- input: source('app', 'product_events')
rows:
- event_id: e1
payload: '{"idea_id":"i1"}'
occurred_at: '2026-07-15 10:00:00'
expect:
rows:
- event_id: e1
idea_id: i1
occurred_at: '2026-07-15 10:00:00'
Ejecuta la misma prueba en ambos targets. Que pase en DuckDB no dice nada sobre la implementación seleccionada para Snowflake mediante dispatch:
dbt test --target duckdb --select extracts_idea_id_from_json_text
dbt test --target snowflake --select extracts_idea_id_from_json_text
Esta prueba cubre una ruta que devuelve un valor escalar. Agrega casos explícitos para una clave ausente, JSON null, SQL NULL, arreglos, números y objetos anidados antes de tratar la macro como una capa general de compatibilidad JSON.
Etiqueta los modelos cuyo comportamiento no se pueda reproducir localmente:
{{ config(tags=['snowflake_only']) }}
select
idea_id,
ai_complete('model-name', moderation_prompt) as moderation_result
from {{ ref('ideas_requiring_moderation') }}
Ejecuta el grafo portable de forma explícita:
dbt build --target duckdb --exclude tag:snowflake_only+
No reemplaces un modelo exclusivo de Snowflake con SQL local ficticio que siempre pase. Proporciona un stub de contrato significativo en un límite claramente definido o excluye el modelo y valídalo en Snowflake.
El límite definido por la etiqueta también debe ser válido en el DAG. Excluir una relación padre exclusiva de Snowflake mientras se seleccionan sus descendientes portables deja una referencia a una relación que la ejecución local no construyó. Excluye esa rama completa o proporciona una relación de límite probada con el mismo contrato lógico.
Inspecciona y excluye la rama completa exclusiva de Snowflake, incluidos sus descendientes:
dbt ls --select tag:snowflake_only+
dbt build --target duckdb --exclude tag:snowflake_only+
Si un mart portable realmente necesita uno de esos descendientes, el límite requiere una implementación local real o una relación de contrato respaldada por fixtures. Una etiqueta por sí sola no puede hacer portable la dependencia.
Paso 5: Haz que un grafo grande sea rápido
En una copia recién obtenida del repositorio:
export DUCKDB_PATH="$(pwd)/local/idea_local.duckdb"
export DUCKDB_TEMP_DIRECTORY="$(pwd)/local/tmp"
mkdir -p local/tmp
duckdb "${DUCKDB_PATH}" < scripts/bootstrap_local.sql
dbt build --target duckdb --exclude tag:snowflake_only+
mkdir -p .dbt-state/local
cp target/manifest.json .dbt-state/local/manifest.json
Durante la iteración normal:
dbt build \
--target duckdb \
--select state:modified+ \
--state .dbt-state/local \
--exclude tag:snowflake_only+
Esto funciona cuando las relaciones ascendentes que no cambiaron todavía están presentes en el archivo persistente de DuckDB. Si el archivo es nuevo o falta una relación ascendente, incluye los ancestros:
dbt build \
--target duckdb \
--select +state:modified+ \
--state .dbt-state/local \
--exclude tag:snowflake_only+
No agregues --defer de producción al comando de DuckDB sin evaluar sus consecuencias. El defer de dbt resuelve las llamadas a ref() no construidas hacia relaciones de otro manifest. Una relación de Snowflake nombrada en un manifest de producción no se vuelve legible para un proceso local de DuckDB. Para la ejecución local, usa un manifest de DuckDB construido desde la rama de comparación y relaciones locales.
Un manifest de Snowflake todavía puede ser útil para inspeccionar cambios lógicos, pero la configuración de base de datos, esquema, alias, env_var y var que depende del entorno puede generar resultados inesperados de state:modified entre targets. dbt documenta estas salvedades de la comparación de estado. Inspecciona el conjunto seleccionado antes de tratar un manifest de otro motor como fuente definitiva.
Paso 6: Prueba la lógica y el estado incremental por separado
Las pruebas unitarias de dbt son adecuadas para reglas de transformación acotadas porque usan entradas estáticas. Un modelo de ciclo de vida puede probar transiciones inválidas sin construir el grafo completo:
unit_tests:
- name: closed_ideas_cannot_return_to_review
model: int_idea_lifecycle
given:
- input: ref('stg_app__ideas')
rows:
- {idea_id: i1, status: closed, updated_at: '2026-07-10 09:00:00'}
- input: ref('stg_app__idea_status_events')
rows:
- {idea_id: i1, status: in_review, occurred_at: '2026-07-10 10:00:00'}
expect:
rows:
- {idea_id: i1, effective_status: closed}
Ejecuta la capa rápida de pruebas unitarias:
dbt test --target duckdb --select test_type:unit
El conocimiento de columnas integrado en dbt v2 elimina el requisito de dbt Core v1 de materializar cada relación padre directa antes de las pruebas unitarias. Si la versión preliminar exacta todavía solicita una relación, especialmente para un modelo incremental, crea primero relaciones padre que contengan solo el esquema con dbt run --empty.
Luego construye y ejecuta pruebas de datos contra las relaciones locales:
dbt build --target duckdb --exclude tag:snowflake_only+
Los modelos incrementales necesitan una prueba adicional con estado. Los adapters maduros dbt-duckdb y dbt-snowflake anuncian soporte para merge y delete+insert, y dbt documenta que la mecánica incremental varía según la base de datos. El adapter de DuckDB para Fusion está en beta y todavía no promete paridad completa con dbt-duckdb, así que verifica cada estrategia requerida contra la versión preliminar exacta fijada antes de adoptarla.
{{ config(
materialized='incremental',
unique_key='event_id',
incremental_strategy='merge'
) }}
select
event_id,
idea_id,
event_type,
occurred_at,
_ingested_at,
_source_sequence
from {{ ref('stg_events__idea_events') }}
{% if is_incremental() %}
where _ingested_at >= (
select coalesce(
{{ dbt.dateadd(
datepart="day",
interval=-3,
from_date_or_timestamp="max(_ingested_at)"
) }},
timestamp '1970-01-01'
)
from {{ this }}
)
{% endif %}
qualify row_number() over (
partition by event_id
order by _ingested_at desc, _source_sequence desc
) = 1
La superposición de tres días es ilustrativa. Define el período de revisión retrospectiva según el nivel de servicio de llegada tardía de la fuente, o usa un offset persistente de la fuente. Una marca de agua estricta de > max(_ingested_at) puede omitir filas que comparten la marca de tiempo máxima o llegan tarde. El ejemplo supone que la ingesta proporciona un _source_sequence estable; si no existe un criterio de desempate determinista, rechaza los duplicados ambiguos en lugar de dejar que row_number() elija uno arbitrariamente.
Ejercita al menos estos casos en cada motor:
- Target vacío y carga inicial
- Nueva ejecución del mismo lote
- Un evento que llega tarde
- Una fila actualizada con el mismo
unique_key - Filas de origen duplicadas para un mismo
unique_key - Un cambio de esquema
Si la versión preliminar actual de DuckDB para Fusion no admite la estrategia requerida, usa DuckDB para probar el SQL de refresco completo del modelo y conserva las pruebas de materialización incremental en Snowflake. No presentes una reconstrucción de tabla local como evidencia de que un merge de producción funciona.
Con ERROR_ON_NONDETERMINISTIC_MERGE=TRUE —el valor predeterminado de Snowflake—, Snowflake genera un error cuando varias filas de origen coinciden con una fila de destino y hacen que el resultado de una actualización o de una combinación de actualización y eliminación sea no determinista. Las filas de origen duplicadas sin coincidencia todavía pueden insertarse por separado. Cambiar a delete+insert puede modificar el comportamiento, pero no debe ocultar una granularidad incorrecta. Agrega una prueba de unicidad y corrige primero la lógica de origen.
El diseño de CI: primero rapidez, luego fidelidad
El flujo de trabajo práctico es una pirámide de pruebas, no un reemplazo de motor:
| Verificación | Control de DuckDB | Control de Snowflake |
|---|---|---|
| Lógica portable de selección, unión y ventanas | Señal rápida principal | Confirmar en modelos modificados |
| Paridad del SQL con dispatch | Ejecutar la implementación de DuckDB | Ejecutar la implementación de Snowflake |
Mecánica de merge incremental | Dependiente de la versión, señal parcial | Verificación definitiva |
| RBAC, enmascaramiento y políticas de acceso a filas | No cubierto | Verificación definitiva |
| Poda de microparticiones, clustering y dimensionamiento del warehouse | No cubierto | Verificación definitiva |
Every save / local command
└─ Fusion parsing + selected DuckDB model
(+ editor and column lineage with the dbt VS Code extension)
Every commit
└─ dbt unit tests + portable DuckDB graph on deterministic fixtures
Every pull request
└─ Snowflake Slim CI for state:modified+ and downstream tests
Before production deployment
└─ Snowflake-native models, policies, hooks, incremental paths,
query plans, and required scale checks
Un pipeline de PR ilustrativo:
# Gate 1: fast and warehouse-independent
export DUCKDB_BASENAME="ci_${CI_RUN_ID}"
export DUCKDB_PATH="$(pwd)/local/${DUCKDB_BASENAME}.duckdb"
export DUCKDB_TEMP_DIRECTORY="$(pwd)/local/tmp"
export DBT_RAW_DATABASE="${DUCKDB_BASENAME}"
mkdir -p local/tmp
duckdb "${DUCKDB_PATH}" < scripts/bootstrap_local.sql
DBT_TARGET=duckdb dbt build \
--target duckdb \
--exclude tag:snowflake_only+
# Gate 2: real target semantics
DBT_TARGET=snowflake dbt build \
--target snowflake \
--select state:modified+ \
--defer \
--state path/to/production-artifacts
El segundo control sigue el patrón Slim CI de dbt: construye los modelos modificados y sus dependencias descendentes en un esquema aislado de Snowflake, mientras resuelve las relaciones padre no seleccionadas a partir de un manifest anterior de producción.
Para pruebas de alta fidelidad, Snowflake también documenta la clonación sin copia de bases de datos o esquemas. Un clon puede exponer datos con la estructura de producción sin duplicar todo el almacenamiento, pero CI sigue consumiendo cómputo del warehouse y almacenando las microparticiones modificadas. El acceso a los datos clonados también sigue siendo una decisión de gobernanza.
El objetivo de costos debe ser menos trabajo en el warehouse, no “nada de Snowflake en ningún lugar”. Un control de Snowflake pequeño y dirigido conserva la semántica de producción mientras reduce el trabajo del warehouse frente a ejecutar el grafo completo de Snowflake en cada edición.
Dónde suele fallar la portabilidad
| Riesgo | Por qué puede pasar en DuckDB | Qué lo detecta |
|---|---|---|
| Funciones y sintaxis SQL | El dialecto de DuckDB se basa en PostgreSQL; Snowflake tiene funciones y sintaxis semiestructurada diferentes | Macros multibase de datos o con dispatch, más CI en Snowflake |
| Identificadores y uso de comillas | Snowflake convierte a mayúsculas los identificadores sin comillas y conserva las mayúsculas y minúsculas de los que llevan comillas; las relaciones locales pueden usar otra capitalización | Política explícita de nombres y compilación o build en Snowflake |
| Marcas de tiempo | Ambos motores tienen comportamiento de zona horaria dependiente de la sesión, pero sus tipos y precisión de marcas de tiempo difieren | Configurar UTC explícitamente, convertir tipos de forma deliberada y probar fixtures de horario de verano y límites de fecha |
| Precisión numérica | Los fixtures pequeños pueden no revelar desbordamientos decimales, redondeos o conversiones implícitas | Conversiones explícitas a decimal(p,s) y valores límite en ambos motores |
| JSON y datos anidados | Las funciones JSON/VARIANT de DuckDB y VARIANT/FLATTEN de Snowflake no son intercambiables | Macros con dispatch y fixtures anidados representativos |
| Materializaciones incrementales | El merge generado por el adapter, las relaciones temporales y el comportamiento ante cambios de esquema difieren | Pruebas de dos ejecuciones y de datos tardíos en Snowflake |
| Hooks y paquetes | Un paquete puede asumir un adapter, comportamiento de Python, catálogo o formato de logs específico | Verificación de preparación para Fusion y CI específica por target |
| Políticas de seguridad | DuckDB local no aplica RBAC, enmascaramiento, políticas de acceso a filas ni comportamiento de vistas seguras de Snowflake | Pruebas de políticas y rol de mínimo privilegio en Snowflake |
| Rendimiento físico | La velocidad de una laptop no predice la poda de microparticiones, el clustering, el dimensionamiento del warehouse ni el uso de disco por desbordamiento en Snowflake | Historial o perfil de consultas y prueba de escala en Snowflake |
| Actualidad de las fuentes | Un snapshot local estático no ejercita el retraso de ingesta ni los metadatos actuales de las fuentes | Verificaciones de actualidad en producción |
| Distribución de datos | Las muestras pequeñas ocultan sesgos, patrones poco frecuentes de valores nulos y joins de alta cardinalidad | Fixtures estratificados más pruebas a escala de warehouse |
| Concurrencia | Un proceso local puede parecer saludable mientras los jobs de CI compiten por un mismo archivo de DuckDB | Un archivo de base de datos por job y workspaces aislados |
Guía de solución de problemas
Comienza con el diagnóstico más acotado:
| Síntoma | Primera verificación |
|---|---|
| Error de resolución de tipos de archivo plano | Confirma que el modelo lea una tabla inicializada, no read_parquet() |
| Relación no encontrada | Compara el nombre base de DuckDB, la base de datos de la fuente, el esquema y el resultado de la inicialización |
| Pasa localmente, pero falla la sintaxis en Snowflake | Compara el SQL compilado para cada target y las macros con dispatch |
| Error de bloqueo de archivo | Cierra los otros procesos de escritura y usa un archivo por job de CI |
| El conteo no coincide | Revisa la versión de los fixtures, los valores nulos, la zona horaria, la precisión y el estado incremental |
| Error de merge en Snowflake | Prueba la unicidad en la granularidad declarada de unique_key |
“Fusion no puede resolver el tipo de read_parquet()”
Causa probable: el análisis estático actual de Fusion no puede inferir el esquema del archivo plano, aunque DuckDB pueda ejecutar la consulta.
Solución:
- Carga el archivo en una tabla física
rawdurante la inicialización. - Referencia esa tabla mediante
source(). - Si se necesita una extensión, instala el driver de DuckDB del sistema con
dbc install duckdb; el driver incluido con Fusion no puede cargar extensiones. - Si el análisis estricto bloquea SQL de lectura de archivos que por lo demás es válido, cambia solo la rama afectada a
static_analysis: baseline. El modo baseline todavía puede emitir advertencias y no proporciona comprobación estricta de tipos ni linaje detallado de columnas.
“El modelo pasa localmente, pero Snowflake indica una función desconocida o un error de sintaxis”
Causa probable: SQL específico del warehouse se filtró a un modelo supuestamente portable.
Solución:
- Inspecciona el SQL compilado para ambos targets.
- Reemplaza la expresión por una macro multibase de datos de dbt cuando esté disponible.
- De lo contrario, agrega implementaciones
snowflake__...yduckdb__...con dispatch. - Agrega un fixture que verifique resultados iguales y conserva el control de Snowflake para los PR.
“No se encuentra la tabla de origen en DuckDB”
Causa probable: el database de la fuente no coincide con el nombre base del archivo de DuckDB, la inicialización usó otro esquema o una ruta relativa se resolvió desde un directorio de perfiles diferente.
Solución:
dbt debug --target duckdb
duckdb "${DUCKDB_PATH}" \
-c "select database_name, schema_name, table_name from duckdb_tables();"
Alinea DBT_RAW_DATABASE, el nombre base de .duckdb, el esquema de la fuente y el resultado de la inicialización. Prefiere una ruta absoluta de DuckDB en CI.
“No se pudo bloquear el archivo de DuckDB”
Causa probable: otro proceso tiene el archivo abierto para escritura. El archivo nativo de DuckDB admite un proceso de lectura y escritura a la vez, aunque ese proceso puede usar varios hilos.
Solución:
- Cierra los clientes interactivos de DuckDB antes de ejecutar dbt.
- Asigna a cada job de CI su propia ruta de base de datos.
- No compartas un archivo
.duckdbcon permiso de escritura entre contenedores ni en un sistema de archivos de red. - Mantén la concurrencia interna de modelos de dbt dentro de un solo proceso.
“DuckDB es terminado por el sistema o llena el disco”
Causa probable: un join amplio, una ordenación o una cantidad alta de hilos agotó la memoria o el almacenamiento temporal.
Solución:
- Reduce la cantidad de hilos de dbt y DuckDB.
- Configura
memory_limit,temp_directoryymax_temp_directory_size. - Asegúrate de que la ruta temporal tenga espacio.
- Reduce el tamaño de los fixtures mientras conservas los casos límite y la integridad de las relaciones.
- Usa
preserve_insertion_order=falsepara las cargas masivas correspondientes después de confirmar que el orden no forma parte del contrato.
La guía de DuckDB sobre falta de memoria señala que reducir el límite de memoria a aproximadamente 50–60% de la RAM de la máquina puede evitar que el sistema operativo termine el proceso, ya que algunas operaciones asignan memoria fuera del administrador de buffers.
“Snowflake informa un merge no determinista”
Causa probable: las filas de entrada del modelo incremental no son únicas según unique_key.
Solución:
- Ejecuta una consulta agrupada de duplicados sobre la entrada incremental.
- Agrega una prueba de unicidad de dbt en la granularidad declarada del modelo.
- Deduplica de forma determinista con una regla de orden explícita.
- Usa
delete+insertsolo cuando esa sea realmente la semántica deseada.
Un fixture local pequeño suele pasar por alto esto porque contiene solo una fila por clave.
“Los conteos difieren entre DuckDB y Snowflake”
Revisa lo siguiente en orden:
- Versión de los fixtures y conteos de filas de origen
- Manejo de valores nulos en joins y predicados
- Mayúsculas y minúsculas en identificadores y cadenas
- Tipo de marca de tiempo, zona horaria de la sesión y conversiones en los límites de fecha
- Precisión decimal y división entera
- JSON null frente a SQL
NULL row_number()no determinista olimitsin unorder bycompleto- Estado del target incremental
Convierte el conjunto más pequeño de filas que difieran en un fixture determinista de prueba unitaria antes de cambiar el SQL de producción.
“El grafo local está desactualizado o falta una relación”
Causa probable: la selección por estado supuso que existía una relación de un build local anterior, pero el archivo de base de datos o el manifest provenía de otra rama.
Solución:
export DUCKDB_PATH="$(pwd)/local/idea_local.duckdb"
rm -f "${DUCKDB_PATH}"
duckdb "${DUCKDB_PATH}" < scripts/bootstrap_local.sql
dbt build --target duckdb --exclude tag:snowflake_only+
mkdir -p .dbt-state/local
cp target/manifest.json .dbt-state/local/manifest.json
Trata el archivo de base de datos y su manifest guardado como una sola generación de caché. Reconstruye ambos después de cambiar de rama, actualizar dbt o modificar de manera sustancial los fixtures de origen.
“Un paquete o hook funcionaba en dbt Core, pero falla en Fusion”
Causa probable: compatibilidad incompleta con Fusion, eliminación de comportamiento obsoleto, introspección compleja o comportamiento no compatible de Jinja o del adapter.
Solución:
- Actualiza los paquetes y verifica que su rango compatible de dbt incluya
2.0.0. - Ejecuta las verificaciones oficiales de preparación para Fusion.
- Inicia los recursos afectados en modo baseline de análisis estático.
- Fija la versión preliminar de Fusion que se sabe que funciona.
- Conserva la ruta de producción existente de dbt Core/Snowflake hasta que la funcionalidad bloqueante tenga una migración verificada.
La presencia de un target de DuckDB no hace que un proyecto existente esté listo para Fusion.
“La factura de Snowflake cambió de lugar en vez de reducirse”
Causa probable: los desarrolladores exportan repetidamente snapshots grandes, leen almacenamiento de objetos en otra región o siguen ejecutando un grafo completo de Snowflake en CI.
Solución:
- Publica fixtures pequeños, versionados y consistentes en sus relaciones con una frecuencia controlada.
- Mantén los artefactos en la región adecuada.
- Usa selección por estado tanto en los controles locales como en los de Snowflake.
- Mide en conjunto el cómputo de exportación, los cargos aplicables por transferencia de datos y los créditos del warehouse consumidos por CI.
- No actualices los datos locales cuando baste un fixture para un cambio solo de código.
La seguridad y la gobernanza son arquitectura, no limpieza posterior
DuckDB local cambia el límite de confianza. Snowflake centraliza la autenticación, RBAC, el enmascaramiento, las políticas de acceso a filas y el historial de acceso. En Enterprise Edition o superior, Access History puede registrar referencias a políticas de enmascaramiento y de acceso a filas cuando se consultan objetos protegidos. Un archivo .duckdb o Parquet en una laptop no hereda esos controles.
Un pipeline seguro de fixtures debe responder:
- ¿Quién tiene permiso para generar y descargar un fixture?
- ¿Qué columnas se eliminan, enmascaran, tokenizan o generalizan?
- ¿Es posible volver a unir tokens estables con otro dataset filtrado?
- ¿Cuánto tiempo permanecen los archivos locales y cómo se eliminan?
- ¿Se pueden auditar las versiones de los fixtures y el código que los genera?
- ¿Puede CI usar datos sintéticos mientras solo un control restringido de Snowflake accede a datos gobernados?
Si la organización no puede responder esas preguntas, los datos locales con la estructura de producción no están listos para un uso amplio por parte de los desarrolladores. Comienza con fixtures sintéticos o seleccionados a mano.
Lecturas relacionadas: EDA con NATS: proyecciones CQRS sitúa el warehouse en la ruta de lectura de analítica, mientras que Preparación de bases de datos para agentes de IA seguros profundiza en el mínimo privilegio y el acceso gobernado a los datos.
Cuándo encaja bien este patrón
Usa DuckDB como ciclo interno predeterminado cuando:
- la mayoría de los modelos usan SQL relacional en lugar de funciones nativas de Snowflake
- los desarrolladores pasan una cantidad considerable de tiempo esperando iteraciones pequeñas de modelos
- el proyecto puede crear fixtures de origen representativos y sanitizados
- el equipo mantendrá el dispatch por adapter y las pruebas de paridad
- se conserva un control real de Snowflake antes del merge o del despliegue
Mantén Snowflake como target principal de desarrollo cuando:
- el grafo depende en gran medida de Snowpark, Cortex, funciones externas, streams/tasks, objetos seguros o políticas de plataforma
- la corrección depende de la distribución de datos a escala de producción y de los planes de consulta
- los datos no pueden salir de la plataforma gobernada de forma legal o segura
- replicar las fuentes costaría más que las consultas de desarrollo que reemplazaría
- mantener dos implementaciones de adapter superaría el tiempo ahorrado para obtener feedback
Esto no tiene que ser una decisión de todo o nada. Un proyecto puede descubrir que una gran parte de su lógica silver y gold es portable, mientras que la ingesta, la gobernanza y los modelos especializados siguen siendo exclusivos de Snowflake.
Un plan de adopción de bajo riesgo
- Haz un inventario de portabilidad. Clasifica los modelos y paquetes como portables, que necesitan dispatch o exclusivos de Snowflake.
- Elige un flujo vertical. Para la aplicación Idea, usa
ideas + votes → idea engagement, que abarca bronze, silver y gold. - Construye fixtures con la estructura de las fuentes. Incluye valores nulos, votos duplicados, ideas eliminadas, eventos tardíos, decimales grandes y límites de zonas horarias.
- Agrega el target de DuckDB. Usa un archivo persistente y fija la versión preliminar de Fusion.
- Elimina los lectores directos de archivos del SQL de los modelos. Carga los archivos en tablas raw durante la inicialización.
- Agrega pruebas entre motores. Comienza con funciones con dispatch y modelos incrementales.
- Agrega el control rápido de CI. Ejecuta las pruebas unitarias y el grafo portable en un archivo de DuckDB aislado.
- Conserva Snowflake Slim CI. Ejecuta los modelos modificados y sus descendientes con la semántica real del target.
- Mide todo el sistema. Compara la latencia para obtener feedback, los créditos de Snowflake, el costo de generación de fixtures, los cargos aplicables por transferencia de datos y las fallas de paridad.
- Amplía solo mientras las fallas de paridad sigan siendo visibles. Una falla detectada por Snowflake CI es evidencia útil sobre el límite, no una razón para ocultar el control.
La conclusión práctica
“DuckDB en dev, Snowflake en prod” aporta valor cuando se trata como una estrategia de verificación por capas:
- DuckDB responde: ¿Funciona la lógica de transformación portable con un dataset pequeño y representativo?
- Snowflake responde: ¿El target de producción la compilará, materializará, protegerá y ejecutará correctamente?
Fusion permite responder la primera pregunta de forma más rápida y completa mediante la ejecución local y la comprensión de SQL. Con la extensión de dbt para VS Code, el mismo flujo de trabajo también ofrece feedback en el editor y linaje a nivel de columna. No convierte las dos preguntas en una sola.
Para un proyecto medallion grande, la mejor implementación no consiste en replicar el warehouse en cada laptop. Consiste en conservar un solo grafo de dbt, establecer un límite de portabilidad deliberado, proporcionar fixtures pequeños con la estructura de las fuentes y gastar cómputo de Snowflake solo donde lo que se está probando es Snowflake.
Fuentes
Consultadas el July 15, 2026. El estado de los productos depende de la fecha.
- dbt Developer Hub: configuración de DuckDB — estado beta de DuckDB en Fusion, adapter integrado, requisitos de driver y extensiones, opciones de perfil y limitaciones del análisis estático para archivos planos
- Documentación de dbc: instalación de drivers — instalación del driver ADBC de DuckDB del sistema con
dbc - dbt Developer Hub: notas de la versión de April 2026 — anuncio en April 2026 del soporte beta de DuckDB en la CLI
- dbt Developer Hub: guía de inicio rápido del motor dbt Fusion — instalación de la versión preliminar actual y flujo de trabajo con la CLI local
- dbt Developer Hub: sobre las conexiones a plataformas de datos — adapters integrados de Fusion y arquitectura de conexión ADBC
- dbt Developer Hub: análisis estático — modos baseline/strict y limitaciones conocidas del análisis
- dbt Developer Hub: funcionalidades compatibles con Fusion — distinción entre las capacidades de la CLI independiente y las funciones de la extensión para VS Code
- dbt Developer Hub: actualización a v2.0 — orden de las pruebas unitarias en v2 y conocimiento integrado de columnas
- dbt Developer Hub: lista de preparación para Fusion — verificaciones para migrar paquetes y proyectos
- dbt Developer Hub: dispatch de adapters y macros multibase de datos — implementaciones de macros específicas por base de datos y operaciones portables
- dbt Developer Hub: pruebas unitarias — entradas estáticas de modelos y uso recomendado en desarrollo y CI
- dbt Developer Hub: estrategia incremental y configuraciones de Snowflake — estrategias compatibles y comportamiento de merge en Snowflake
- dbt Developer Hub: mejores prácticas para flujos de trabajo, Defer y salvedades de la comparación de estado — selección por estado, entornos aislados, Slim CI y riesgos de comparación dependiente del entorno
- Documentación de DuckDB: dialecto SQL — dialecto basado en PostgreSQL y diferencias semánticas documentadas
- Documentación de DuckDB: concurrencia — un solo proceso de lectura y escritura y bloqueo de archivos
- Documentación de DuckDB: solución de problemas de falta de memoria y configuración — controles de hilos, memoria y almacenamiento temporal
- Documentación de DuckDB: tipos de marcas de tiempo — representación de zonas horarias y comportamiento de la zona configurada
- Documentación de DuckDB: funciones JSON — comportamiento de
json_extract_stringutilizado por la macro con dispatch - Documentación de DuckDB: funciones de utilidad —
error()y funciones hash utilizadas para validar el manifest de fixtures - Documentación de Snowflake: requisitos de identificadores — mayúsculas y minúsculas en identificadores con y sin comillas
- Documentación de Snowflake: tipos de fecha y hora —
TIMESTAMP_NTZ,TIMESTAMP_LTZ,TIMESTAMP_TZ, precisión y parámetros de sesión - Documentación de Snowflake: FLATTEN — comportamiento nativo de Snowflake para expandir datos semiestructurados
- Documentación de Snowflake: GET_PATH — extracción de rutas JSON en Snowflake utilizada por la macro con dispatch
- Documentación de Snowflake: MERGE — comportamiento determinista y no determinista de merge
- Documentación de Snowflake: AI_COMPLETE — función actual de completado de IA utilizada por el ejemplo exclusivo de Snowflake
- Documentación de Snowflake: costo de transferencia de datos — cuándo las descargas generan cargos por transferencia entre regiones o nubes
- Documentación de Snowflake: costo de cómputo — cómputo del warehouse consumido por consultas de exportación y CI
- Documentación de Snowflake: consideraciones sobre clonación — comportamiento de almacenamiento y gobernanza de los clones sin copia
- Documentación de Snowflake: Access History — auditoría del acceso a objetos y de las políticas de enmascaramiento o acceso a filas aplicadas
- dbt Developer Hub: configuración de Snowflake — métodos de autenticación compatibles con Fusion
- Documentación de Snowflake: tutorial de CI/CD con dbt — targets de desarrollo aislados y opciones de clonación sin copia
- Documentación de Databricks: arquitectura medallion — fuente de las definiciones de las capas bronze, silver y gold utilizadas aquí
Inspiración, no evidencia: la publicación de Joachim Hodana en LinkedIn aportó la premisa “DuckDB en dev, Snowflake en prod”.
Criterio editorial: la pirámide de pruebas con dos motores, el límite de fixtures con la estructura de las fuentes, la clasificación de modelos, la implementación de la aplicación Idea y la secuencia de adopción son patrones de arquitectura recomendados, sintetizados a partir de las capacidades y limitaciones documentadas. No afirman que DuckDB y Snowflake produzcan resultados idénticos ni constituyen un benchmark de costos medido. Valida el patrón con tu propio grafo, política de datos y factura del warehouse.