Qué significa “modelar orientado a consultas”
El modelado orientado a consultas consiste en ajustar el modelo lógico (tablas, claves, restricciones y estructuras de acceso) para que las consultas críticas y las cargas de trabajo esperadas funcionen con buen rendimiento y escalen, sin romper reglas de negocio ni degradar la consistencia. No se trata de “optimizar una consulta”, sino de diseñar para patrones repetidos: lecturas frecuentes, filtros típicos, ordenamientos, agregaciones, y también para el costo de mantener esos datos cuando hay escrituras.
La idea central: cada decisión de rendimiento tiene un costo (más almacenamiento, más complejidad, más costo de escritura, más riesgo de inconsistencias). Por eso, el enfoque es controlado y documentado.
Anticipar consultas críticas y cargas de trabajo (guía práctica)
Paso 1: Inventario de consultas y SLAs
Antes de tocar el modelo, define un inventario de consultas críticas (las que más se ejecutan o las que deben responder rápido). Para cada consulta, captura:
- Propósito (pantalla, API, reporte, proceso batch).
- Frecuencia (por minuto/hora/día).
- Volumen (filas esperadas hoy y en 12–24 meses).
- Latencia objetivo (p95/p99 si aplica).
- Patrón: filtros (WHERE), joins, ORDER BY, GROUP BY, paginación.
Ejemplo de ficha de consulta (formato sugerido):
Q-01: Listado de pedidos por cliente (últimos 90 días), ordenado por fecha desc, paginado 50/50. Frecuencia: alta (API). SLA: p95 < 200ms. Crecimiento: 10M pedidos/año.Paso 2: Clasificar el tipo de carga
Clasifica el sistema (o cada módulo) según su mezcla de operaciones:
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
- OLTP: muchas escrituras pequeñas, lecturas por clave, consistencia fuerte.
- Lectura intensiva: muchas consultas repetidas, necesidad de cachés/vistas/índices.
- Analítica/Reporting: agregaciones grandes, escaneos, ventanas temporales.
Esta clasificación ayuda a decidir cuánto “peso” puedes agregar al modelo (índices, materializaciones, tablas resumen) sin colapsar el rendimiento de escritura.
Paso 3: Identificar “rutas de acceso”
Para cada consulta crítica, define su ruta de acceso ideal: qué columna filtra primero, qué columnas ordenan, qué columnas se devuelven. Esto se traduce en decisiones de índices y, a veces, en denormalizaciones controladas.
Índices: relación con claves, selectividad y costo de escritura
Índices y claves: cómo se conectan
Conceptualmente, un índice es una estructura auxiliar que acelera búsquedas/ordenamientos. En el modelo lógico, se relaciona con:
- Clave primaria (PK): normalmente crea (o se apoya en) un índice único. Excelente para búsquedas por identificador.
- Claves únicas (UK): suelen requerir índices únicos para garantizar unicidad y acelerar búsquedas por “clave natural” o alternativa (por ejemplo, email).
- Claves foráneas (FK): muchas veces conviene indexarlas para acelerar joins y validaciones de integridad referencial (especialmente en borrados/actualizaciones del padre).
Regla práctica: si una columna participa frecuentemente en joins o filtros, evalúa indexarla; si además debe ser única, el índice único aporta integridad y rendimiento a la vez.
Selectividad: cuándo un índice realmente ayuda
La selectividad mide qué tan bien un filtro reduce el conjunto de filas. Un índice suele ser más útil cuando el filtro devuelve un porcentaje pequeño del total (alta selectividad). Si filtras por un valor que aparece en muchísimas filas (baja selectividad), el motor puede preferir un escaneo.
Ejemplos típicos:
- Alta selectividad:
pedido_id,email,nro_documento. - Baja selectividad:
estado = 'ACTIVO'si casi todos están activos.
Esto no significa que nunca indexes columnas de baja selectividad: pueden ser útiles en índices compuestos (por ejemplo, (cliente_id, estado, fecha)) donde la primera columna sí es selectiva.
Índices compuestos: orden, filtros y ORDER BY
Un índice compuesto sirve cuando la consulta filtra/ordena por varias columnas. El orden de columnas importa: en general, coloca primero las columnas más usadas en filtros y con buena selectividad, y luego las de ordenamiento.
Ejemplo conceptual (no dependiente de un motor específico):
Consulta: pedidos de un cliente en rango de fechas, ordenados por fecha desc, paginados. WHERE cliente_id = ? AND fecha >= ? ORDER BY fecha DESCUn índice compuesto como (cliente_id, fecha) suele ayudar porque permite localizar rápidamente los pedidos del cliente y recorrerlos ya ordenados por fecha.
Costos de escritura: el “precio” de cada índice
Cada índice adicional implica trabajo extra en INSERT/UPDATE/DELETE:
- Más escrituras físicas (actualizar estructura del índice).
- Mayor uso de almacenamiento.
- Posibles bloqueos/contención en cargas altas.
Criterio práctico: crea índices para consultas críticas y elimina/evita índices “por si acaso”. Mantén un registro de por qué existe cada índice (consulta que lo justifica y métrica esperada).
Checklist lógico para decidir un índice
- ¿La columna aparece en WHERE/JOIN/ORDER BY de consultas críticas?
- ¿La selectividad es razonable o forma parte de un índice compuesto útil?
- ¿El índice ayuda a evitar un sort o un escaneo grande?
- ¿El costo de escritura es aceptable para la carga OLTP?
- ¿Puede reemplazarse por un índice existente (evitar duplicados)?
Vistas y “materialización conceptual”: qué resuelven y qué exigen
Vistas: encapsular consultas y estabilizar contratos
Una vista es una definición guardada de una consulta. A nivel de modelado, sirve para:
- Reutilización: centralizar joins y reglas de filtrado (por ejemplo, “solo activos”).
- Contrato estable: exponer un esquema lógico a consumidores sin revelar toda la complejidad.
- Seguridad: limitar columnas/filas expuestas (según el motor y la estrategia de permisos).
Importante: una vista “normal” no almacena datos; su rendimiento depende de la consulta subyacente y de los índices de las tablas base.
Materialización conceptual: cuando “precomputar” tiene sentido
La materialización (vista materializada o estructura equivalente) consiste en almacenar el resultado precomputado de una consulta para acelerar lecturas repetidas, típicamente agregaciones o joins costosos.
Problemas que resuelve:
- Dashboards con agregaciones frecuentes (ventas por día, por categoría).
- Consultas con joins pesados sobre tablas grandes.
- Reducción de carga en horas pico (sirviendo resultados ya preparados).
Exigencias en consistencia:
- Frescura: ¿los datos pueden estar desfasados (segundos/minutos) o deben ser inmediatos?
- Modo de actualización: refresco incremental vs refresco completo.
- Gestión de fallos: qué pasa si el refresco falla; cómo se reintenta; cómo se monitorea.
En términos de reglas de negocio, la materialización no debe “inventar” estados: debe derivar de fuentes confiables y tener definido su nivel de consistencia aceptable.
Denormalizaciones típicas controladas
La denormalización orientada a consultas agrega redundancia deliberada para acelerar lecturas. Debe ser controlada: con criterios de aceptación, mecanismo de mantenimiento y documentación clara.
1) Campos agregados (contadores y totales)
Ejemplos:
cliente.total_pedidospedido.total_itemspedido.total_importe
Cuándo conviene: cuando el total se consulta muy a menudo y calcularlo en tiempo real (SUM/COUNT) sobre muchas filas es costoso.
Riesgo: desincronización si no se actualiza en cada cambio relevante.
2) Tablas resumen (hechos agregados por período/dimensión)
Ejemplos:
ventas_diarias(fecha, tienda_id, total_ventas, total_pedidos)inventario_resumen(producto_id, stock_disponible, stock_reservado)
Cuándo conviene: reporting y paneles donde se consulta por fecha/dimensión repetidamente.
Decisión clave: granularidad (diaria, horaria) y dimensiones (por tienda, por categoría). A mayor granularidad, mayor costo de mantenimiento.
3) Copias de atributos para evitar joins (duplicación selectiva)
Ejemplo: guardar cliente_nombre en pedido para listados rápidos.
Cuándo conviene: cuando el join es muy frecuente y el atributo cambia poco, o cuando se necesita “congelar” el valor histórico (por ejemplo, nombre al momento de la compra).
Regla de negocio a definir: ¿es un dato “actual” o “histórico”? Si es histórico, no debe actualizarse cuando cambie el cliente.
Mecanismos de mantenimiento: triggers, jobs y lógica de aplicación
Opción A: Triggers (sincronización inmediata)
Un trigger actualiza el dato denormalizado en la misma transacción (o en el mismo evento) que modifica la tabla base.
Ventajas:
- Consistencia fuerte (menos desfase).
- Centraliza la regla en la base de datos.
Costos/precauciones:
- Mayor latencia de escritura.
- Complejidad de depuración.
- Riesgo de cascadas de triggers y contención.
Ejemplo conceptual: mantener pedido.total_items al insertar/eliminar renglones en pedido_item.
Opción B: Jobs/Procesos batch (consistencia eventual controlada)
Un job recalcula o ajusta resúmenes cada cierto intervalo (cada minuto/hora/noche).
Ventajas:
- Reduce costo en transacciones OLTP.
- Permite recomputar masivamente y corregir desvíos.
Costos/precauciones:
- Datos pueden estar desactualizados entre ejecuciones.
- Necesita monitoreo, reintentos y control de ventanas de tiempo.
Patrón útil: tabla resumen diaria recalculada incrementalmente por partición temporal (por ejemplo, “solo hoy” y “ayer”).
Opción C: Lógica de aplicación (servicio como fuente de verdad operativa)
La aplicación actualiza tanto la tabla base como la denormalizada.
Ventajas:
- Control explícito en el flujo de negocio.
- Más fácil de versionar junto con el código.
Costos/precauciones:
- Riesgo de inconsistencias si hay múltiples servicios escribiendo.
- Requiere transacciones bien definidas y pruebas robustas.
Recomendación: si eliges esta opción, define claramente el “dueño” del dato denormalizado (un único servicio) y cómo se evita la doble escritura no coordinada.
Criterios de aceptación para optimizaciones orientadas a consultas
Antes de introducir un índice, vista materializada o denormalización, define criterios verificables:
- Consulta objetivo: identificador (Q-01, Q-02) y SQL/pseudoconsulta asociada.
- Métrica esperada: latencia p95/p99, CPU, lecturas lógicas, filas examinadas.
- Impacto en escritura: incremento máximo aceptable de latencia en INSERT/UPDATE/DELETE.
- Consistencia: inmediata vs eventual; tolerancia de desfase (por ejemplo, 60s).
- Plan de rollback: cómo desactivar el índice/materialización si empeora la carga.
Documentación mínima que debe acompañar el modelo
| Elemento | Qué documentar | Ejemplo |
|---|---|---|
| Índice | Consulta que lo justifica, columnas y orden, selectividad esperada, costo de escritura | IDX_pedido_cliente_fecha para Q-01 (cliente_id, fecha) |
| Vista | Propósito, tablas base, filtros “oficiales”, consumidores | vw_pedidos_activos: excluye cancelados |
| Materialización/Resumen | Fuente, periodicidad de refresco, desfase tolerado, monitoreo | ventas_diarias: refresco cada 5 min, desfase 10 min |
| Denormalización | Campo/tabla redundante, regla de verdad (actual vs histórico), mecanismo de mantenimiento | pedido.total_importe mantenido por trigger en items |
Ejercicio guiado: diseñar para 3 consultas críticas sin romper integridad
Escenario
Supón un módulo de pedidos con estas consultas:
- Q-01: pedidos por cliente (últimos 90 días), ordenados por fecha desc, paginados.
- Q-02: detalle de un pedido con sus ítems (por
pedido_id). - Q-03: dashboard: total vendido por día (últimos 30 días).
Paso a paso
Mapea filtros y ordenamientos:
- Q-01 filtra por
cliente_idy rango defecha, ordena porfecha. - Q-02 accede por PK
pedido_idy join por FK en ítems. - Q-03 agrega por
fecha.
- Q-01 filtra por
Decide índices mínimos:
- Índice compuesto para Q-01:
(cliente_id, fecha). - Índice en FK de ítems para Q-02:
pedido_item(pedido_id). - Para Q-03, si se calcula en vivo: índice por
fechaen pedidos (o en tabla de pagos/ventas según el modelo).
- Índice compuesto para Q-01:
Evalúa materialización para Q-03:
- Si Q-03 se consulta constantemente y la tabla crece rápido, crea una tabla resumen
ventas_diarias(o vista materializada equivalente). - Define frescura: por ejemplo, desfase aceptable 5 minutos.
- Si Q-03 se consulta constantemente y la tabla crece rápido, crea una tabla resumen
Define mecanismo de mantenimiento:
- Para
ventas_diarias: job cada 5 minutos que recalcula solo el día actual y ajusta el anterior (por correcciones tardías). - Monitoreo: registrar última ejecución exitosa y filas afectadas.
- Para
Valida integridad y reglas:
- Los índices no cambian reglas, solo acceso.
- La tabla resumen debe derivar de fuentes con integridad garantizada (por ejemplo, ventas confirmadas).
- Documenta si el dashboard es eventual (y cuánto).
Documenta y prueba:
- Asocia cada índice/materialización a Q-01/Q-02/Q-03.
- Prueba carga: mide latencia de lectura y el impacto en escrituras.