F7·b · Approach — Trino como motor principal del SQL Editor (lectura Y escritura)
Decisión del owner 2026-08-08: Carbon SQL es Trino SQL; el SQL Editor debe leer y
escribir el Warehouse por Trino, y DuckDB sale del mapa.Este documento dice qué falta entre hoy y eso, con las mejores prácticas del
ecosistema contrastadas contra nuestro stack. Ordenado por lo que BLOQUEA, no por
lo que apetece.Cruza con: federated-catalog-wedge.md ·
platform-thesis.md §D-bis ·
trino-absorcion.md
0 · Dónde estamos hoy, medido
SQL Editor → runQuery (Junction) → getSqlEngine('duckdb') → DuckDB → Iceberg/R2
↑ lib/compute/junction.ts:1534, CABLEADO A FUEGO
Lectura y DML, los dos. Trino no aparece en el path, y no podría: su adaptador
declara write: false y su credencial acuñada es de sólo lectura (gate ⑨ de F2: un
INSERT devuelve 403 desde R2).
1 · ⛔ LOS DOS BLOQUEANTES DUROS
① Vercel no alcanza a Trino. Punto.
El TrinoCluster es listenerClass: cluster-internal y no publica ningún puerto.
El SQL Editor corre en Vercel. No hay configuración que arregle eso: hay que
exponer el motor.
La práctica del ecosistema, y coincide con lo que ya teníamos apalabrado en F6:
«Best practice is to instead use an external load balancer for managing TLS
connections rather than securing the coordinator directly.» — doc de Trino, TLS
⇒ LB externo termina TLS, con certificado de verdad, y detrás el coordinador. Las
piezas ya están casi todas: la IP 34.78.181.113 reservada, trino.paladio.io
resolviendo, y JWT ya configurado (F3·a). Falta el balanceador y el certificado.
⚠️ Y JWT exige TLS + shared secret, que ya tenemos (internalSecretClass). Nada que
rehacer ahí.
② 🔴 Trino 470 tiene un CVE que se dispara EXACTAMENTE al dar escritura
| Qué | las credenciales de storage se serializan en el JSON de la query, accesible por UI/API |
| Privilegio necesario | ⭐ escritura a nivel SQL — literalmente lo que este documento quiere habilitar |
| Qué se expone | credenciales estáticas y vended |
| Versiones afectadas | 439 → 479 |
| Corregido en | 480 |
| Nosotros | 470 ⚠️ |
⇒ Habilitar escritura sobre nuestro Trino actual entrega las credenciales de R2 a cualquiera con permiso de escritura. No es teórico y no se mitiga configurando.
La salida, verificada: el operador Stackable 25.3 sólo tiene 451/455/470, pero
SDP 26.7 trae Trino 481 — por encima de 479, o sea parcheado, y por encima de
463, o sea que conserva los namespaces anidados que nuestro main.default exige.
⇒ Actualizar el operador 25.3 → 26.7 es prerrequisito de la escritura, no una mejora opcional. Y es una fase propia: cambian CRDs y puede moverse la superficie de configuración (F1 ya nos enseñó que los campos se mueven entre versiones).
2 · Lo que hay que hacer después, y ninguno es trivial
③ La credencial acuñada tiene que poder escribir
Hoy Lakekeeper le acuña a Trino una credencial de sólo lectura — medido, no supuesto (gate ⑨). Escribir exige cambiar el grant y el vending. Ojo al orden con el ②: dar escritura ANTES de parchear el CVE es la peor combinación posible.
④ iceberg.unique-table-location = 'true' — obligatorio para nosotros
Lakekeeper lo documenta como necesario cuando el soft-delete está activo, para que
una tabla recreada no choque con la que está esperando su expiración. Nuestro
soft-delete está activo (7 días). Sin esta línea, un DROP + CREATE de la misma
tabla rompe — y el fallo aparece días después, cuando expira la vieja.
⑤ Junction: dejar de cablear el motor a fuego
// lib/compute/junction.ts:1534
const engine = getSqlEngine('duckdb');
Es una línea, pero cambiarla por getSqlEngine('trino') sería repetir el error. Ese
punto es la POLÍTICA de la puerta: debe seleccionar según capacidad declarada
(supportsFeatures) y disponibilidad (available), no según una constante. Si no,
el día que Trino no esté, el editor cae entero en vez de degradar.
⑥ El contrato del control-plane — lo que de verdad protege la invariante
El DML de DuckDB pasa por withDmlControlPlane, que estampa dataset_transactions e
iceberg_sync_log. La invariante real del Warehouse no es «un solo escritor»: es
«nadie escribe esquivando la puerta». Ya hay dos escritores legítimos (ml-runner en
la ingesta, DuckDB en el DML) y los dos pasan por ahí.
⇒ El DML de Trino tiene que entrar por runQuery con writeTarget, igual que
DuckDB. Si commitea directo contra el catálogo, el oráculo de frescura se queda ciego y
la transacción no existe para nadie.
(Y hay un detalle a favor: DuckDB no puede estampar snapshot_properties en su
commit Iceberg — limitación medida. Trino sí escribe propiedades de snapshot, así que
la trazabilidad mejora con el cambio.)
⑦ Las 8 divergencias, ahora al revés
Con Carbon SQL = Trino SQL, dejan de ser «a Trino le faltan 8» y pasan a ser
«nuestras superficies emiten dialecto DuckDB y tienen que emitir Trino». Las 5 de
traducir pasan a «la superficie deja de emitirlo»; qualify y distinct-on hay que
reescribirlas; y timestamp-arithmetic es la que no avisa.
Ver lib/compute/dialect-divergence.ts.
⑧ ⚠️ El protocolo de Trino NO encaja con una función serverless
El protocolo de cliente de Trino es un bucle de sondeo: POST /v1/statement →
seguir nextUri hasta que deje de venir. Una función de Vercel tiene tope de duración;
una query larga sobrevive a la petición HTTP que la lanzó, y el cliente muere a
mitad del bucle.
Nuestro trino-client.ts hace hoy el bucle en proceso — correcto para queries
cortas y equivocado como diseño general.
⇒ La forma correcta: no sostener la petición abierta. Enviar la query, devolver el
queryId, y sondear desde el cliente (o desde un worker durable). Es, además, lo que
permite cancelar y lo que hace que un editor SQL se sienta como un editor SQL.
⑨ La latencia manda más aquí que en ningún otro sitio
Medido: la ida y vuelta al catálogo por nuestra cara cuesta ~3,4 s por query, y no
es el dato — es la topología (GKE europe-west1 → Vercel → Railway → R2 WEUR). Para
batch da igual; para un editor interactivo es la diferencia entre usable e inusable.
Co-locar la cara y el catálogo con el motor deja de ser optimización y pasa a ser
requisito de esta fase.
3 · La secuencia, y por qué en este orden
| Fase | Por qué va aquí | |
|---|---|---|
| 1 | Actualizar Stackable 25.3 → 26.7 (Trino 470 → 481) | cierra el CVE ANTES de que exista un solo permiso de escritura. Y hacerlo con el motor todavía en sólo-lectura es la ventana barata |
| 2 | Exponer Trino — LB + certificado + trino.paladio.io (F6/F3·b) | sin esto Vercel no habla con el motor y nada de lo demás se puede probar e2e |
| 3 | Lectura por Trino en el SQL Editor — selección de motor real en Junction (⑤) + las divergencias de dialecto (⑦) | lectura primero: si algo diverge, se ve sin haber tocado un byte |
| 4 | Escritura: vending con permiso (③) + unique-table-location (④) + DML por runQuery con writeTarget (⑥) | sólo cuando 1-3 estén verdes |
| 5 | Retirar DuckDB | lo último, y sólo cuando 4 lleve tiempo verde |
⭐ La regla que ordena todo: el CVE antes que el permiso, la exposición antes que
la prueba, la lectura antes que la escritura, y la retirada la última. Cualquier otro
orden crea una ventana en la que algo está mal y no se nota.
4 · Lo que este approach NO resuelve
- No decide si el editor debe cancelar queries — que es lo que hace útil el ⑧, y es producto, no infraestructura.
- No dimensiona la concurrencia. El SQL Editor es interactivo y multiusuario; el coordinador es uno. Eso es Gateway/flota (F6) y hay que medirlo antes de prometerlo.
- No aborda la identidad del usuario final en el catálogo. trino#27917 sigue abierto: Trino habla con Lakekeeper como Trino, no como el usuario. OPA aplica la política del usuario en el motor; el catálogo sigue viendo un único principal técnico. Es una capa, no dos, y con escritura habilitada conviene tenerlo escrito.