Los seis ejes de decisión, y cómo medir cada uno
Forma de los datos, volumen, complejidad de consulta, reactividad, tamaño del binario y sincronización son los ejes que deciden por ti, y cada uno admite una medición concreta en lugar de una opinión.
Un catálogo de familias no es todavía un criterio de elección: es un mapa sin coordenadas. Lo que convierte el mapa en una decisión es un conjunto pequeño de magnitudes que describen tu problema y no las herramientas, porque son esas magnitudes las que eliminan candidatas antes de que empiece cualquier discusión sobre preferencias. Son seis, se pueden medir todas en una tarde con instrumentos que ya tienes, y tienen la propiedad incómoda de que casi nadie las mide: la mayoría de las decisiones de almacenamiento local se toman con estimaciones mentales de volumen que se equivocan por dos órdenes de magnitud y con suposiciones sobre la complejidad de las consultas futuras que el propio producto desmiente en el tercer mes. Este es el nivel donde ese hábito se corrige.
- Enunciar los seis ejes que determinan la elección y traducir cada uno a una medición concreta.
- Detectar las interacciones entre ejes que invalidan combinaciones que sobre el papel parecían viables.
- Reconocer por qué la sincronización es un eje de orden superior que debe evaluarse antes que los demás.
- Componer con las seis mediciones un perfil escrito que sobreviva a los cambios de opinión del equipo.
Los seis ejes
Son seis y no diez porque el criterio para admitir un eje es exigente: tiene que ser una magnitud del problema, no de la solución, y tiene que eliminar candidatas por sí solo. La calidad de la documentación, la vitalidad de la comunidad o la familiaridad del equipo son consideraciones legítimas para desempatar, pero no son ejes, porque no descartan a nadie: un proyecto mal documentado que resuelve tu problema sigue siendo viable, mientras que uno excelente que no cabe en la memoria del dispositivo no lo es. Mantener esa distinción entre criterios eliminatorios y criterios de desempate evita la discusión circular en la que casi siempre acaban estas decisiones.
Forma de los datos
¿Son documentos autocontenidos con estructura anidada, o entidades relacionadas por referencias que se recorren en ambos sentidos? La medida no es una impresión: cuenta cuántas de tus lecturas necesitan datos de más de una colección para pintar una sola vista. Si son casi ninguna, tienes documentos; si son la mayoría, tienes un modelo relacional aunque lo hayas guardado en objetos anidados.
Volumen
No el total en disco, sino el conjunto de trabajo: los datos que una sesión típica necesita tocar. Mide el número de registros, su tamaño medio serializado y el producto de ambos, y hazlo con datos reales de tus usuarios más pesados, no con la semilla de desarrollo. El percentil alto es el que decide, porque es quien encuentra los problemas primero.
Complejidad de consulta
Cuenta cuántas de tus consultas se expresan como un rango sobre un único índice y cuántas necesitan combinar condiciones, unir colecciones, agregar o ordenar por algo distinto de lo que filtran. La cifra que importa no es la de hoy sino la tendencia: si en seis meses ha crecido, seguirá creciendo, y estás mirando la variable que más probablemente rompa tu elección.
Reactividad
¿Cuántas vistas deben actualizarse solas cuando algo cambia, y cuántos contextos escriben en los mismos datos? Una aplicación con una pestaña y tres vistas puede propagar cambios a mano sin drama; una con varias pestañas, un worker que escribe en segundo plano y decenas de vistas dependientes necesita seguimiento de dependencias de verdad, y construirlo bien es un proyecto en sí mismo.
Tamaño del binario
Cuánto añade la elección al primer arranque, medido en bytes transferidos y en tiempo hasta que la base está lista para responder. Los dos números importan por separado: un módulo de WebAssembly grande se descarga una vez y se cachea, pero compilarlo e inicializarlo se paga en cada arranque frío, y ese coste recae íntegro sobre la primera impresión del producto.
Sincronización
¿Habrá una segunda copia de estos datos en algún sitio, ahora o dentro de un año? La respuesta honesta casi nunca es no, y este eje es distinto de los otros cinco porque no gradúa una magnitud: cambia la pregunta entera. Si vas a sincronizar, el motor de réplica va a imponer su modelo de identidad, de versionado y de aplicación de cambios sobre todo lo demás.
Los tres primeros ejes admiten instrumentación directa y barata, y hacerlo cambia decisiones con una frecuencia sorprendente. El volumen se estima casi siempre por debajo porque se piensa en el usuario medio y el que rompe la aplicación es el del percentil noventa y nueve. La complejidad de consulta se estima por debajo porque se cuentan las consultas que hay hoy y no la pendiente con la que aparecen. Y la forma de los datos se estima mal porque el modelo mental del equipo suele ser el del diagrama de entidades y no el de las lecturas que la interfaz ejecuta realmente.
Cómo medir sin adivinar
La instrumentación no requiere nada sofisticado. El conjunto de trabajo se mide contando y pesando lo que una sesión real toca; la complejidad de consulta se mide clasificando las consultas que ya existen; y el tamaño del binario se mide con las herramientas de red que ya usas. Lo único que hace falta es escribirlo.
// Eje 2: el conjunto de trabajo, medido y no imaginado.
async function perfilarVolumen(coleccion) {
const registros = await coleccion.toArray();
const bytes = registros.reduce(
(acc, r) => acc + new Blob([JSON.stringify(r)]).size,
0,
);
return {
n: registros.length,
mediaBytes: Math.round(bytes / Math.max(registros.length, 1)),
totalMB: +(bytes / 1_048_576).toFixed(1),
};
}
// Eje 3: clasificar las consultas que ya escribiste.
const consultas = [
{ nombre: "docsPorAutor", tipo: "rango" },
{ nombre: "docsRecientesConComentarios", tipo: "union" },
{ nombre: "resumenPorEtiqueta", tipo: "agregacion" },
];
const complejas = consultas.filter((c) => c.tipo !== "rango").length;
El quinto eje se mide de una forma que casi nadie aplica y que separa dos cantidades habitualmente confundidas: lo que cuesta traer el código y lo que cuesta ponerlo en marcha. La primera se cachea y se paga una vez; la segunda se paga en cada arranque frío, y es la que el usuario percibe.
// Eje 5: separar transferencia de puesta en marcha.
const marcaInicio = performance.now();
const modulo = await import("./motor.js");
const traido = performance.now() - marcaInicio;
const marcaListo = performance.now();
const base = await modulo.abrir("app");
await base.query(`SELECT 1`);
const listo = performance.now() - marcaListo;
// Y los bytes reales, tomados de la propia plataforma.
const recursos = performance
.getEntriesByType("resource")
.filter((r) => r.name.includes("motor"))
.reduce((acc, r) => acc + r.encodedBodySize, 0);
registrar({ traido, listo, kb: Math.round(recursos / 1024) });
Sobre el peso hay una precisión que evita malentendidos: lo que se compara no son cifras publicadas sino bytes que tu compilador emite para tu caso de uso, con la parte de la librería que realmente importas. La documentación de PGlite, por ejemplo, anuncia su distribución por debajo de tres megabytes comprimida, y esa cifra es un dato útil para situar una familia entera; pero lo que va a viajar por la red en tu aplicación depende de qué extensiones cargues, de cómo esté configurado tu empaquetador y de si el módulo se descarga en el arranque o bajo demanda. La única medición que decide es la tuya.
Los ejes no son independientes
Aquí es donde una lista se convierte en un criterio. Los seis ejes interactúan, y algunas combinaciones que sobre el papel parecen razonables son directamente inviables.
flowchart TB
V[Volumen alto] --> R{Cabe en memoria}
R -->|no| NR[Descarta almacenes en memoria]
R -->|si| SR[Almacen reactivo viable]
C[Consultas complejas] --> M{Existe planificador}
M -->|no| MM[Uniones a mano en tu codigo]
M -->|si| MP[Motor SQL en el cliente]
S[Vas a sincronizar] --> D[Decide primero el motor de replica]
D --> E[El almacen queda condicionado]
style D fill:#cba6f7,color:#11111b
style NR fill:#f38ba8,color:#11111b
style MM fill:#f38ba8,color:#11111bTres interacciones concretas concentran casi todos los errores que se cometen en este nivel. La primera es volumen alto con almacén en memoria: la reactividad inmediata que compras deja de importar el día que el conjunto de trabajo no cabe en el dispositivo más modesto de tu base de usuarios, y ese día no se anuncia. La segunda es consultas complejas sin planificador: la ausencia no produce un error, produce código tuyo que hace uniones a mano, y ese código crece en silencio hasta que un día alguien se da cuenta de que la mitad del repositorio es un motor de bases de datos mal escrito. La tercera es la más cara y la más común: elegir el almacén antes de saber si habrá sincronización, y descubrir seis meses después que el motor de réplica que necesitas trae su propio almacén y su propio modelo, con lo que la elección anterior no se migra, se tira.
Al empezar un proyecto, las consultas vivas parecen un lujo prescindible: hay tres vistas, una pestaña, y propagar cambios a mano es trivial. Al cabo de un año hay cuarenta vistas, dos pestañas abiertas a la vez y un proceso de fondo que escribe, y la propagación manual se ha convertido en la principal fuente de errores de interfaz, del tipo peor: los que no fallan, solo muestran datos viejos. Este eje tiene una asimetría notable respecto a los demás, y es que añadir reactividad después es mucho más caro que añadir volumen o consultas después, porque exige que absolutamente todas las escrituras del sistema pasen por el mismo sitio, y para entonces no lo hacen.
Del perfil a la decisión
Hay una cuarta interacción menos comentada y que en la práctica decide muchos proyectos: la que se da entre el tamaño del binario y la reactividad. Los motores más capaces suelen exigir vivir en un contexto separado, lo que convierte cada consulta en un intercambio de mensajes y hace que la observación de cambios deje de ser un detalle de la librería para pasar a ser un protocolo que tienes que diseñar tú. Es decir: al subir de tramo por el eje de complejidad de consulta puedes perder, sin haberlo pedido, la reactividad que dabas por resuelta. Quien no anticipa esa pérdida acaba reconstruyendo a mano, sobre un canal de mensajes, la propagación de cambios que la familia anterior le regalaba, y ese trabajo suele costar más que la migración misma.
El producto final de este nivel no es una elección: es un documento de seis líneas, una por eje, con una cifra o una frase en cada una.
// El perfil, versionado junto al codigo que describe.
export const perfilDeDatos = {
forma: "relacional: 6 de cada 10 vistas cruzan dos colecciones",
volumenConjuntoTrabajo: { registros: 42_000, mb: 61, percentil: 95 },
complejidad: { rango: 7, combinada: 5, union: 4, agregacion: 2 },
reactividad: { vistas: 38, contextosQueEscriben: 3 },
binario: { kbTransferidos: 190, msHastaPrimeraConsulta: 240 },
sincronizacion: "prevista para el proximo ano, aun sin motor elegido",
revisadoEl: "2026-08-05",
};
Escribirlo como código y no como prosa tiene una ventaja concreta: se versiona, se revisa en las mismas revisiones que el resto, y las cifras obsoletas destacan. Ese documento tiene tres virtudes que no tiene ninguna discusión de equipo. Sobrevive a los cambios de opinión, porque las cifras no cambian cuando cambia el humor. Permite reevaluar sin repetir el trabajo, porque cuando el volumen se multiplique por diez basta con actualizar una línea y ver qué candidatas caen. Y, sobre todo, obliga a nombrar en voz alta las suposiciones que de otro modo se quedan implícitas, que son justamente las que después resultan falsas.
Hay una inversión de perspectiva en este nivel que vale más que cualquier tabla comparativa, y consiste en darse cuenta de que los seis ejes no son atributos de las librerías sino de tu aplicación. Esto suena obvio enunciado así y sin embargo casi todo el material que circula sobre el tema hace exactamente lo contrario: parte de las herramientas, enumera sus funciones y deja al lector la tarea imposible de deducir cuál le conviene sin haber caracterizado nunca su propio problema. El resultado previsible es que la elección acaba determinándola la familiaridad del equipo, la popularidad aparente o la calidad de la página de inicio, tres variables que no tienen ninguna relación causal con si la cosa va a funcionar dentro de dieciocho meses. Cuando la caracterización se hace primero ocurre algo que sorprende la primera vez: el espacio de candidatas se colapsa solo, y casi siempre a una o dos, sin que haya hecho falta comparar ninguna funcionalidad. Un conjunto de trabajo de doscientos megabytes elimina de un plumazo la familia entera de los almacenes en memoria, con independencia de lo elegante que sea su API. Un dominio con cinco entidades que se recorren en ambos sentidos y consultas que el usuario compone elimina la familia de los envoltorios, por mucho que su peso sea seductor. Una decisión ya tomada de sincronizar entre dispositivos subordina las otras cinco a la elección del motor de réplica, porque ese motor no negocia el formato en el que guarda. Y hay una razón más profunda para trabajar así, que trasciende esta decisión concreta: las mediciones son transferibles y las opiniones no. Si mides tu conjunto de trabajo, esa cifra te sirve para elegir almacén, para dimensionar la sincronización, para decidir la estrategia de paginación, para estimar cuota y para saber si el desalojo te va a doler; si en su lugar acumulas la impresión de que son bastantes datos, no te sirve para nada más que para la conversación en la que la dijiste. Caracterizar el problema con números es, en este oficio, la única forma de trabajo cuyo resultado no caduca cuando cambia la moda.
- Mide tu conjunto de trabajo real con datos de tus usuarios más pesados, no con la semilla de desarrollo, y anota número de registros y megabytes.
- Clasifica todas tus consultas en rango simple, condición combinada, unión y agregación, y calcula la proporción de cada clase.
- Cuenta cuántas vistas deben reaccionar a un cambio y cuántos contextos distintos escriben en los mismos datos.
- Mide los bytes transferidos y el tiempo hasta primera consulta de dos candidatas de familias distintas, en tu propio empaquetado.
- Responde por escrito y con fecha si va a haber una segunda copia de estos datos, y qué pasa si la respuesta cambia dentro de un año.
- Junta las seis líneas en un documento, guárdalo en el repositorio y ponle una revisión en el calendario a seis meses vista.