⭐⭐⭐ N·4 · EL RÉGIMEN DE NAMESPACE — un inquilino, un entorno, un nivel
Qué cierra: seis formas de nombrar conviviendo sobre las mismas tablas.
Estado: 🏁 CERRADO — datos consolidados · código desplegado y sirviendo · trinquete verde.Regla de la casa: cada hecho lleva el comando que lo demuestra.
1 · El régimen
warehouse w_<tenant> › namespace {env} › tabla ds_<hex>
el inquilino, una vez el entorno, una vez la identidad física
⭐⭐⭐ El inquilino ya era el warehouse, y nadie lo había dicho. Medido: 2 de 2
workspaces tienen warehouse propio upstream (w_<hex>), que es el prefix de Iceberg
REST — y ése es el mecanismo de multi-tenencia del estándar: Polaris pone un catálogo
por inquilino, Unity Catalog un metastore/catálogo, Lakekeeper project→warehouse.
⇒ Escribir w_<tenant> también dentro del namespace decía el inquilino dos veces.
Eso hacían prod.w_<hex> y prod_w_<hex>. La consolidación no promueve al inquilino:
le quita la repetición, porque ya estaba arriba.
⚠️ Por qué el entorno se queda en el namespace
Lo suyo sería un warehouse por entorno. No se puede: la API de gestión de Gravitino
responde 404 (medido en /api/metalakes y /api/metalakes/carbon/catalogs), así que
no hay forma de crear uno. test y prod comparten warehouse y el namespace es lo único
que los separa — quitarlo de ahí los haría colisionar.
⛔⛔ Y sigue siendo UN SOLO NIVEL
El punto lo hacía multinivel, que es lo que impedía NACER: Gravitino lo aplana y
devuelve 500 con la tabla ya creada. Con un nivel, createTable responde 200 en 0,8 s.
2 · De dónde se venía
| régimen | tablas |
|---|---|
main.default | 83 |
prod.w_<hex> | 12 |
main.test | 9 |
prod_w_<hex> | 2 |
karma.test | 2 |
| total | 108 vivas + 2 fantasmas |
⭐ Ninguno estaba mal a propósito: cada uno fue el bueno en su momento y se quedó porque el namespace se PINEA por tabla. Nadie los eligió — se acumularon. Ésa es la deriva que un documento no para: no hay un commit que la introduzca, hay ciento ocho tablas nacidas en fechas distintas.
3 · Cómo se migró — y por qué así
⭐ renameTable en lote. Es lo único que da el estándar: Iceberg REST tiene
renameTable/renameView pero no renameNamespace (apache/iceberg#13023, abierta),
y Unity Catalog tampoco renombra un catálogo — obliga a crear el destino y mover los
activos. No mueve bytes: sólo el puntero de metadata.
# ensayo (no toca nada)
N4_DESTINO=prod npx dotenv -e .env.local -- npx tsx scripts/warehouse/n4-1-consolidar-regimenes.ts
# de verdad
N4_DESTINO=prod npx dotenv -e .env.local -- npx tsx scripts/warehouse/n4-1-consolidar-regimenes.ts --aplicar --reconciliar-fantasmas
# el trinquete
npm run check:regimen-namespace
Las tres decisiones que sostienen la migración
① El destino es un PARÁMETRO, nunca LAKEHOUSE_ENV. La máquina que corre el script
tiene LAKEHOUSE_ENV=test; derivarlo habría metido 108 tablas de producción en el
namespace de desarrollo, y el pin es permanente. Un destino que se adivina es un destino
que algún día se adivina mal.
② Compensación POR TABLA. Cada tabla son dos escrituras en dos sistemas —el rename en el catálogo y el pin en Index—. Si la segunda falla, la tabla existe donde Index no la busca: invisible, y no por estar rota. ⇒ se deshace el rename y se para. Y si la vuelta atrás también falla, se para gritando con la coordenada exacta.
③ ⛔⛔ TRES estados en el censo, no dos. Dos corridas seguidas dieron 107/3 y 108/2: un timeout de red se estaba contando como fantasma. Un censo que no distingue «no está» de «no pude preguntar» deja tablas atrás en silencio. ⇒ un fallo de transporte es indeterminado, se reintenta, y si sigue sin saberse se aborta la migración entera: mover un conjunto que no se ha podido censar es moverlo a ciegas.
⭐ Y el censo posterior vuelve a preguntarle al catálogo, no al bucle. «El bucle no dio error» no es un censo.
4 · 🪤 Las trampas que costaron una corrida
| ⛔⛔ | El primer censo dijo «0 vivas de 131» — cero errores, número absurdo. Preguntaba por el warehouse compartido cuando el inquilino tiene el suyo. El prefijo se resuelve por inquilino; que el número fuera imposible fue lo único que lo delató |
| ⛔ | El separador de namespace en la URL es %1F (unit separator), no un punto |
| ⚠️ | Crear un namespace que ya existe da 409, y eso no es un fallo: hace falta que exista, no haberlo creado nosotros |
5 · 🏁 DESPLEGADO — y el trinquete demostró para qué sirve
Antes de desplegar, el trinquete cazó lo que ningún test en frío podía: una tabla creada
por la puerta justo después de consolidar nació en prod_w_7b500f4d…, el régimen
viejo. La cara que acuña el namespace es la desplegada, no la del árbol local — y
seguía sirviendo el código anterior. A los diez minutos de existir el trinquete.
⇒ Desplegado desde worktree limpio (3dfdace), y verificado por el RUNTIME:
npx vercel --prod --yes --cwd <worktree limpio>
npx dotenv -e .env.local -- npx tsx scripts/warehouse/s5e-verificar-nacimiento.ts
npm run check:regimen-namespace # → 110 datasets, régimen «prod», 1 namespace
| tabla nueva por la puerta | nace en prod ✅ (antes: prod_w_<hex>) |
| E·3 · lectura | VERDE tras mover 110 tablas |
| trinquete | VERDE · 1 régimen |
⚠️ vercel --prod embarca el ÁRBOL DE TRABAJO, no el commit — con ~55 ficheros del
owner sin commitear, desplegar desde el directorio principal los habría embarcado. De ahí
el worktree. Ver vercel-deploy-desde-worktree.
⭐ Y la regla que esto vuelve a pagar: «desplegado» no es «sirviendo lo desplegado».
El Ready de Vercel no lo contesta; lo contesta crear una tabla y mirar dónde nace.
6 · Lo que el trinquete vigila
npm run check:regimen-namespace falla si un dataset materializado tiene un namespace
que ① es multinivel, ② lleva el inquilino (w_<hex>), o ③ no es un token de
entorno.
⚠️ Un namespace nulo no falla: son datasets que nunca se materializaron en Iceberg. Confundir «no tiene» con «lo tiene mal» obligaría a desactivar el trinquete, y un trinquete desactivado no vigila nada.