Cinco años conectando data warehouses: lo que he aprendido
7 min de lectura

Cinco años conectando data warehouses: lo que he aprendido

1431 palabras

En abril de 2021 hice mi primer commit en el monorepo de la plataforma cloud-native de CARTO. Era el PR número 9, un docker-compose. Cinco años después he mirado hacia atrás con calma y me he encontrado con unos 450 commits, unos 447 pull requests y presencia en prácticamente todos los servicios del repositorio.

Lo interesante no son las cifras. Lo interesante es que, revisando ese historial, aparece un hilo conductor que yo mismo no había identificado del todo: he pasado media década conectando la plataforma a data warehouses ajenos. Seis proveedores distintos —BigQuery, Snowflake, Redshift, Databricks, Oracle y PostgreSQL—, cada uno con su propio modelo de credenciales, su pooling, sus timeouts y sus mensajes de error.

Este artículo es lo que he sacado en claro de ese trabajo. No es una guía, es más bien el poso de repetir el mismo problema seis veces y descubrir que solo cambia la superficie.

Conectar a una base de datos ajena no es “conectar a una base de datos”

Cuando escribes una conexión a PostgreSQL en tu propio proyecto, hay un montón de decisiones que ni te planteas: sabes la versión, controlas la red, tienes las credenciales, y si algo falla lo ves en tus logs.

Nada de eso aplica cuando la conexión la configura un cliente contra su propia infraestructura. El warehouse no es tuyo. La credencial es del cliente y puede caducar, rotarse o revocarse sin avisar. La latencia depende de una red que no controlas. El esquema puede tener diez tablas o diez mil. Y cuando algo falla, el error que recibes viene de un sistema que no puedes inspeccionar.

Esto convierte lo que parece una tarea de fontanería en un problema de diseño bastante más profundo. La superficie es siempre la misma —“guarda estas credenciales y ejecuta esta query”—, pero debajo cada proveedor tiene su propia personalidad.

El modelo de credenciales es donde está la verdadera diferencia

Si tuviera que quedarme con un solo aprendizaje, sería este: lo que distingue a un data warehouse de otro no es el dialecto SQL, es cómo demuestra quién eres.

El SQL se parece bastante entre proveedores. Las diferencias existen, pero son manejables. La autenticación, en cambio, es donde cada uno ha tomado decisiones radicalmente distintas, y donde te encuentras los problemas que de verdad cuestan.

Un recorrido rápido por lo que me he encontrado:

Service accounts con clave. El modelo clásico: el cliente te da un JSON con una clave privada y tú la guardas. Funciona, es sencillo de implementar, y es exactamente lo que a cualquier responsable de seguridad le quita el sueño. Estás almacenando una credencial de larga duración que da acceso a los datos de otra empresa.

Workload Identity Federation. La respuesta a lo anterior, y un cambio de mentalidad importante. En lugar de guardar una clave, tu servicio presenta su propia identidad y el proveedor decide si confía en ella. No hay secreto que almacenar, ni que rotar, ni que filtrar. Implementarlo cuesta más —hay que configurar la relación de confianza en ambos lados— pero elimina una categoría entera de riesgo.

OAuth en dos sabores. Aquí hay una distinción que tardé en interiorizar y que conviene tener clara desde el principio: no es lo mismo user-to-machine que machine-to-machine. En el primero, la conexión actúa en nombre de una persona concreta, hereda sus permisos y muere cuando esa persona pierde el acceso. En el segundo, la conexión tiene identidad propia. Elegir mal significa que un mapa publicado deja de funcionar el día que alguien cambia de departamento.

Key-pair authentication. Un punto intermedio: criptografía asimétrica en vez de contraseña, sin la complejidad de federar identidades. Buen equilibrio cuando el proveedor no ofrece federación.

La conclusión práctica es que el modelo de autenticación hay que elegirlo antes de escribir la primera línea, porque condiciona todo lo demás: dónde vive el secreto, qué pasa cuando caduca, quién puede usar la conexión y qué ocurre cuando ese alguien deja la empresa.

Los tokens caducan en el peor momento posible

De todos los bugs que he tocado en estos años, los relacionados con tokens en caché son los que más me han enseñado.

El patrón es siempre el mismo. Cacheas un token para no pedir uno nuevo en cada petición —razonable, es una llamada de red cara—. El token caduca. Tu caché no se entera. A partir de ahí sirves un token muerto a cada petición que llega, y el error que devuelve el proveedor rara vez dice “tu token ha caducado”: dice algo genérico sobre permisos o sobre la conexión.

Lo que aprendí de esto no es “invalida la caché”, que es evidente. Es algo más sutil: el evento que invalida un token no siempre ocurre en tu sistema. Cuando un usuario vuelve a autenticarse en el proveedor, tu caché no recibe ninguna notificación. Sigue tan contenta con su copia obsoleta. Cualquier diseño que asuma que verá pasar todos los cambios de estado relevantes está construido sobre una premisa falsa.

La lección general, que va mucho más allá de los tokens: cuando cacheas algo cuyo ciclo de vida controla un tercero, la invalidación por tiempo no basta. Necesitas un camino para detectar que lo cacheado ya no sirve, y recuperarte sin que el usuario tenga que hacer nada.

Los errores no controlados son el enemigo real

Hay un patrón que he repetido tantas veces en estos cinco años que se ha convertido casi en un reflejo: convertir un 500 no controlado en una respuesta correcta.

Parece un detalle menor. No lo es. Un 500 significa que tu servicio no supo qué hacer con una situación, y en una plataforma multi-tenant eso tiene consecuencias que van más allá de la petición que falló.

Cuando una excepción del driver de un proveedor sube sin capturar, se lleva por delante cosas que no tienen nada que ver: puede tumbar el proceso que atiende a otros clientes, o dejar conexiones colgadas en el pool. El fallo de un cliente concreto, con su warehouse concreto mal configurado, acaba degradando el servicio de todos los demás.

Por eso el manejo de errores específico por proveedor no es cosmética. Cada driver tiene su propia taxonomía de fallos —timeouts, permisos, esquemas que no existen, cuotas superadas— y traducirlos a respuestas HTTP correctas es lo que mantiene el aislamiento entre inquilinos.

Además tiene un efecto secundario que no esperaba: cuando los errores están bien clasificados, el soporte deja de escalarte tickets. Un mensaje que dice “el usuario no tiene permisos sobre este esquema” lo resuelve el propio cliente. Un 500 genérico acaba siempre en tu bandeja de entrada.

Listar esquemas es más difícil de lo que parece

Un ejemplo pequeño que ilustra bien lo que decía sobre no controlar el otro lado.

Para que alguien elija una tabla, primero hay que enseñarle qué tablas hay. La forma obvia de hacerlo es recorrer la jerarquía y preguntar por cada objeto. Funciona perfectamente cuando el cliente tiene veinte tablas.

Con un cliente que tiene decenas de miles, ese mismo código convierte una pantalla de selección en una operación de varios minutos, o directamente en un timeout. Y no puedes hacer nada desde tu lado: el coste está en el warehouse ajeno.

La solución pasa por dejar de preguntar objeto a objeto y usar las vistas de metadatos del proveedor, que devuelven el catálogo completo en una sola query. Es más trabajo, porque cada proveedor expone esos metadatos de forma distinta.

Lo que me llevo de aquí es una regla que aplico ya casi sin pensar: cualquier operación cuyo coste crezca con datos que no controlas es un incidente esperando su momento. Funcionará en desarrollo, funcionará en la demo, y fallará con el cliente más grande, que suele ser el que menos ganas tienes de que le falle algo.

Cinco años, una conclusión

Si tuviera que resumir todo esto en una idea, sería que la integración con sistemas ajenos es fundamentalmente un ejercicio de humildad. Tu código es la parte pequeña. La parte grande es todo lo que ocurre al otro lado del cable: credenciales que caducan, esquemas que crecen, redes que van lentas, proveedores que cambian su API.

El trabajo bien hecho aquí no se nota. Nadie escribe para agradecerte que su conexión a Snowflake lleve dos años funcionando sin intervención. Se nota justo lo contrario, y por eso la mayor parte del esfuerzo se va en los caminos que nunca deberían ejecutarse.

Cuando empecé pensaba que conectar a seis data warehouses distintos sería seis veces el mismo trabajo. Resultó ser una vez el mismo trabajo y seis veces un problema distinto de autenticación.

Últimas Entradas

7 min

1393 palabras

In April 2021 I made my first commit to the monorepo behind CARTO’s cloud-native platform. It was PR number 9, a docker-compose. Five years later I’ve looked back with some calm and found around 450 commits, around 447 pull requests, and a presence in virtually every service in the repository.

The numbers aren’t the interesting part. What’s interesting is that, going through that history, a thread shows up that I hadn’t fully identified myself: I’ve spent half a decade connecting the platform to other people’s data warehouses. Six different providers — BigQuery, Snowflake, Redshift, Databricks, Oracle and PostgreSQL — each with its own credential model, its pooling, its timeouts and its error messages.

5 min

984 palabras

El 26 de agosto, Mark Raasveldt y Hannes Mühleisen publicaron una nota muy breve en el blog de DuckDB: DuckLabs, la empresa detrás de DuckDB, pasa a ser una filial de Amazon Web Services. La operación se cierra a principios de septiembre.

Lo he leído con más atención de la habitual porque en CARTO llevamos relativamente poco tiempo usando DuckDB, y yo mismo he escrito aquí sobre formatos de archivo y rendimiento y sobre el lío de httpfs detrás de un proxy. Cuando adoptas una pieza en producción y meses después la compra un hiperescalador, lo mínimo es sentarse a mirar la letra pequeña.

5 min

947 palabras

On August 26th, Mark Raasveldt and Hannes Mühleisen published a very short note on the DuckDB blog: DuckLabs, the company behind DuckDB, is becoming a subsidiary of Amazon Web Services. The deal closes in early September.

I read it more carefully than usual because at CARTO we’ve been using DuckDB for a relatively short time, and I’ve written here about file formats and performance and about the httpfs proxy mess. When you adopt a piece of software in production and months later a hyperscaler buys it, the least you can do is sit down and read the fine print.

4 min

796 palabras

El problema: httpfs ignora tus variables de entorno

Si trabajas con DuckDB y la extensión httpfs para leer Parquet remotos, CSVs desde S3 o cualquier recurso HTTP, probablemente asumes que las variables de entorno HTTP_PROXY y HTTPS_PROXY funcionan igual que en cualquier otra herramienta. Curl las respeta. wget las respeta. Python requests las respeta. Node.js las respeta.

DuckDB no.

Me he encontrado con esto trabajando en un entorno corporativo con proxy obligatorio. Tenía un script que leía ficheros Parquet desde Google Cloud Storage usando httpfs, y simplemente no funcionaba. Sin error claro, sin timeout descriptivo, solo silencio. Mientras tanto, un curl al mismo recurso con las mismas variables de entorno devolvía los datos sin problema.

11 min

2298 palabras

Toda consulta empieza con un plan. Toda consulta lenta probablemente empieza con uno malo. Y más a menudo de lo que crees, las estadísticas son las culpables.

Pero ¿cómo funciona realmente? PostgreSQL no ejecuta la consulta para averiguarlo — estima el coste. Lee datos precalculados de pg_class y pg_statistic y hace los cálculos para encontrar la ruta más barata hacia tus datos.

En el escenario ideal, los números que lee son precisos y obtienes el plan que esperas. Pero cuando están desactualizados, la situación se descontrola. El planificador estima 500 filas, planifica un nested loop, y se encuentra con 25,000. Lo que parecía un plan óptimo se convierte en una falla en cascada.

3 min

583 palabras

Amazon ha dado un paso importante en el mundo de la inteligencia artificial con el lanzamiento de S3 Vectors, el primer servicio de almacenamiento en la nube con soporte nativo para vectores a gran escala. Esta novedad promete reducir hasta un 90% los costes de subida, almacenamiento y consulta de datos vectoriales.

¿Qué son los vectores y por qué nos importan?

Los vectores son representaciones numéricas de datos no estructurados (texto, imágenes, audio, video) generados por modelos de embedding. Son la base de las aplicaciones de IA generativa que necesitan encontrar similitudes entre datos usando métricas de distancia.