Mnemo

PostgreSQL y Prisma — Entrevista

Fuente: Generado con prompts/generar-mazo.md

34 tarjetas34 vencidas ahora0 ya repasadas

postgresprismasqlbackend

Tarjetas

¿Qué hace un índice B-tree y cuándo NO ayuda?

Estructura ordenada que convierte búsquedas O(n) en O(log n) sobre la columna indexada. No ayuda en `LIKE '%x'` (prefijo comodín), ni en columnas de baja selectividad (booleanos).

¿Qué hace ON DELETE CASCADE en una FK?

Borrar el padre borra en cascada sus hijos referenciados, en la BD, sin código de por medio.

¿Por qué existe el connection pool y qué rompe sin él?

Un stock fijo de conexiones reutilizadas entre requests. Sin pool: una conexión TCP nueva por request (cara) y Postgres se satura (max_connections).

¿INNER JOIN vs LEFT JOIN?

INNER: solo filas con match en ambas tablas. LEFT: todas las de la izquierda + match o NULL si no hay.

¿Cómo se resuelve el N+1 en Prisma?

Una sola query con `include`/`select` anidado: `prisma.user.findMany({ include: { posts: true } })` trae usuarios y sus posts en dos queries (o una con join), no 1+N.

¿WHERE vs HAVING?

WHERE filtra filas ANTES de agrupar; HAVING filtra grupos DESPUÉS del GROUP BY.

¿Por qué migrar con archivos versionados y no con SQL a mano?

La migración es el historial auditable y reproducible del schema: mismo comando aplica lo pendiente en cualquier entorno (dev, CI, prod).

¿Qué garantiza una transacción (ACID aplicado)?

Atomicidad (todo o nada), consistencia (constraints respetados), aislamiento (transacciones concurrentes no se pisan) y durabilidad (commit = sobrevive al crash).

¿Cuándo desnormalizar (y cuándo es el clásico error)?

Denormalizar = guardar datos derivados (contador, snapshot) para evitar joins/agregaciones en caliente. Se justifica con lectura intensiva y tolerancia a inconsistencia transitoria.

¿upsert en Prisma — cuándo y con qué cuidado?

`upsert` = update si existe, create si no, atómico. Ideal para seeds e idempotencia.

¿Índice compuesto — por qué importa el orden de columnas?

El B-tree ordena por la primer columna, luego la segunda, etc. El índice sirve a queries que filtran por el prefijo (izquierda) del orden.

¿Para qué sirve EXPLAIN (y EXPLAIN ANALYZE)?

Muestra el plan de ejecución: qué usa el planner (seq scan, index scan, join strategy). `ANALYZE` además lo ejecuta y reporta tiempos reales.

¿Transacción batch vs interactiva en Prisma?

Batch: array de operaciones, todas juntas. Interactiva: función async que recibe `tx` y decide en el medio (leer, calcular, escribir).

¿select vs include en Prisma?

`include` AGREGA relaciones al fetch completo; `select` ELIGE exactamente qué columnas y relaciones traer (y permite escoger campos de las relaciones).

¿Por qué un SELECT no bloquea un INSERT concurrente en Postgres?

Por MVCC: cada transacción lee un snapshot de las filas visibles, y el INSERT/UPDATE crea nuevas versiones en vez de pisar las existentes. Readers y writers nunca se pelean por la misma fila.

¿Qué anomalía permite cada nivel de aislamiento?

Read committed (default): non-repeatable read — dos lecturas de la misma fila dentro de una transacción pueden diferir. Repeatable read: lectura estable por snapshot, pero permite write skew. Serializable: ninguna — aborta con serialization failure.

¿Por qué el dinero nunca va en float?

float guarda binario: 0.1 no es representable y el error de redondeo se acumula en cada suma. numeric es decimal exacto de precisión arbitraria — el único tipo para montos.

¿VACUUM vs ANALYZE — para qué sirve cada uno?

VACUUM reclama el espacio de las filas muertas que dejó el MVCC; ANALYZE actualiza las estadísticas de distribución que usa el planner para elegir planes. Uno limpia storage, el otro alimenta al planificador.

¿timestamp vs timestamptz — por qué siempre timestamptz?

timestamptz NO guarda la zona: guarda el instante absoluto normalizado a UTC y lo muestra en la zona de quien lee. timestamp sin zona guarda un valor ambiguo, sin referencia.

¿FK nullable vs NOT NULL — qué cambia?

NOT NULL: la relación es obligatoria, toda fila referencia un padre. Nullable: relación opcional — NULL significa "sin padre", no cero.

¿count(*), count(col) o estimado?

`count(*)` cuenta todas las filas; `count(col)` solo las que tienen col NOT NULL — semántica distinta, performance igual. Para UIs sobre tablas grandes: estimado (reltuples) o contador desnormalizado.

¿Soft delete vs DELETE físico — qué elige qué?

Soft delete: marcar `deletedAt` y filtrarlo en cada query — recuperable y auditable, a costa de contaminar todas las consultas. Físico: simple y rápido, sin retorno.

¿Qué resuelven las window functions si ya existe GROUP BY?

GROUP BY colapsa N filas en una por grupo; la ventana calcula sobre el conjunto SIN colapsar: ROW_NUMBER por grupo, rankings, running totals, LAG contra la fila anterior.

¿Unique constraint vs unique index — son lo mismo?

El constraint es la regla lógica de integridad declarada en la tabla; el unique index es el mecanismo físico que la implementa. Crear el constraint crea su índice; el índice suelto NO es un constraint.

¿Vista vs vista materializada — cuándo cada una?

La vista guarda la query y la corre en cada acceso (siempre fresca, paga en cada lectura). La materializada guarda el resultado físico y solo cambia cuando ejecutás REFRESH.

¿OFFSET vs keyset (cursor) — por qué OFFSET no escala?

OFFSET n lee y DESCARTA n filas antes de servir la página: la página 500 paga el precio completo en cada request. Keyset: `WHERE (createdAt, id) < (último_visto)` + ORDER BY + LIMIT, directo por el índice.

¿Enum nativo, tabla de referencia o string para un status?

Enum de Postgres: validación en la BD y orden declarado, pero cada valor nuevo es un ALTER TYPE. Tabla de referencia: FK + metadatos por estado (color, orden, descripción). String: cero validación.

¿CTE vs subquery — y qué agrega WITH RECURSIVE?

Para filtros simples son equivalentes: el CTE nombra el paso intermedio y lo reusa. WITH RECURSIVE itera sobre su propio resultado: recorre árboles y grafos que una join plana no expresa.

¿LIKE, ILIKE, full-text o trigram — qué busca qué?

`LIKE 'pref%'` aprovecha un B-tree; `'%sub%'` no usa nada estándar. Full-text (tsvector + GIN) busca lenguaje natural con ranking y stemming; pg_trgm + GIN resuelve substrings y fuzzy en tablas grandes.

¿Cuándo bajar a $queryRaw en Prisma?

Cuando la query no es expresable en la API: WITH RECURSIVE, window functions, FILTER en agregados, hints del planner, operadores jsonb exóticos. Para todo lo demás, la API tipada.

¿Qué rompe pgBouncer en modo transaction?

Que cada sentencia puede caer en una conexión distinta: todo estado de SESIÓN se pierde entre sentencias — prepared statements, SET, advisory locks, LISTEN/NOTIFY. Prisma lo declara con `pgbouncer=true` en la URL.

¿Relación declarada en el schema vs JOIN manual — qué gana?

La relación genera la API tipada: include, create anidado y filtros sobre la relación (`where: { posts: { some: ... } }`) que Prisma traduce al JOIN/EXISTS. El JOIN manual es SQL crudo sin tipos inferidos.

¿Qué es el drift entre schema.prisma y la base real?

Que el modelo y la BD divergen: alguien editó el schema sin generar migración, tocó la BD a mano, o corrió `db push` en un entorno compartido.

¿JSONB vs columnas normales — cuándo cada uno?

Columnas: schema estable, tipos, constraints, FK y estadísticas del planner. JSONB: payload variable por fila (respuesta de API externa, atributos por tenant), consultable con `@>` e indexable con GIN.