Ir al contenido

Patrones de Bases de Datos que Debes Conocer Antes de Elegir tu Próxima Base de Datos

La elección entre Postgres y MongoDB no se trata de cuál es 'mejor'. Se trata de comprender los patrones de acceso, los requisitos de consistencia y las restricciones operativas de tu sistema.

Durval Pereira
Durval Pereira
5 min

La pregunta equivocada

"¿Debería usar Postgres o MongoDB?" es la primera pregunta equivocada. La primera pregunta correcta es: "¿Cuáles son mis patrones de acceso?"

Cada base de datos está optimizada para patrones de acceso específicos. Elegir una base de datos sin comprender tus patrones de acceso es como elegir una estructura de datos sin comprender tu algoritmo; podrías tener suerte, pero probablemente te estés complicando la vida.

Análisis de patrones de acceso

Antes de evaluar cualquier base de datos, documenta tus patrones de acceso explícitamente:

interface AccessPattern {
  name: string
  type: 'read' | 'write' | 'read-write'
  frequency: 'hot' | 'warm' | 'cold'
  latencyRequirement: 'realtime' | 'fast' | 'batch'
  consistency: 'strong' | 'eventual' | 'causal'
  description: string
}

const patterns: AccessPattern[] = [
  {
    name: 'user-profile-read',
    type: 'read',
    frequency: 'hot',
    latencyRequirement: 'realtime',
    consistency: 'eventual',
    description: 'Fetch user profile by ID on every page load',
  },
  {
    name: 'order-creation',
    type: 'write',
    frequency: 'warm',
    latencyRequirement: 'fast',
    consistency: 'strong',
    description: 'Create an order with line items, update inventory',
  },
  {
    name: 'analytics-aggregation',
    type: 'read',
    frequency: 'cold',
    latencyRequirement: 'batch',
    consistency: 'eventual',
    description: 'Aggregate daily metrics across all users',
  },
]

Este ejercicio por sí solo aclarará el 80% de tus decisiones sobre bases de datos. Un sistema dominado por lecturas frecuentes (hot reads) con consistencia eventual es un problema completamente diferente de un sistema dominado por escrituras transaccionales con consistencia fuerte.

Patrón: Tabla única, múltiples patrones de acceso

El patrón más subestimado en las bases de datos relacionales. En lugar de normalizar todo en una docena de tablas con JOINs complejos, diseña tu tabla principal en torno a tu patrón de acceso más frecuente (hottest access pattern) y utiliza estructuras secundarias para el resto.

-- Primary table: optimized for the hot path (fetch by ID)
CREATE TABLE documents (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  type        TEXT NOT NULL,
  owner_id    UUID NOT NULL,
  data        JSONB NOT NULL,
  metadata    JSONB DEFAULT '{}',
  created_at  TIMESTAMPTZ DEFAULT now(),
  updated_at  TIMESTAMPTZ DEFAULT now()
);

-- Indexes for secondary access patterns
CREATE INDEX idx_documents_owner ON documents(owner_id);
CREATE INDEX idx_documents_type ON documents(type);
CREATE INDEX idx_documents_created ON documents(created_at DESC);

-- GIN index for JSONB queries when needed
CREATE INDEX idx_documents_data ON documents USING GIN (data);

Este enfoque te brinda la flexibilidad de un almacén de documentos con las garantías transaccionales de Postgres. La columna JSONB maneja la variación del esquema sin requerir migraciones para cada cambio de campo.

Patrón: Escrituras basadas en eventos, lecturas materializadas

Cuando tus patrones de escritura y patrones de lectura son fundamentalmente diferentes, intentar servir ambos desde la misma estructura de datos genera problemas. El event sourcing los separa limpiamente.

interface DomainEvent {
  id: string
  aggregateId: string
  type: string
  data: Record<string, unknown>
  timestamp: Date
  version: number
}

// Write side: append events
async function processCommand(command: CreateOrder): Promise<void> {
  const events: DomainEvent[] = [
    {
      id: generateId(),
      aggregateId: command.orderId,
      type: 'OrderCreated',
      data: { customerId: command.customerId, items: command.items },
      timestamp: new Date(),
      version: 1,
    },
    {
      id: generateId(),
      aggregateId: command.orderId,
      type: 'InventoryReserved',
      data: { items: command.items },
      timestamp: new Date(),
      version: 2,
    },
  ]

  await eventStore.appendEvents(command.orderId, events)
}

// Read side: maintain materialized views
async function handleEvent(event: DomainEvent): Promise<void> {
  switch (event.type) {
    case 'OrderCreated':
      await orderReadModel.upsert({
        id: event.aggregateId,
        status: 'created',
        ...event.data,
      })
      break
    case 'InventoryReserved':
      await inventoryReadModel.decrementStock(event.data.items)
      break
  }
}

Este patrón añade complejidad, pero brilla cuando tienes requisitos de auditoría estrictos, necesitas reconstruir modelos de lectura desde cero o tienes necesidades de escalado de lectura y escritura drásticamente diferentes.

El valor predeterminado de Postgres

Para la mayoría de las aplicaciones, la respuesta pragmática es: empieza con Postgres.

No porque Postgres sea el mejor en todo. No lo es. Sino porque:

  1. Es lo suficientemente bueno en la mayoría de las cosas. Consultas relacionales, documentos JSON, búsqueda de texto completo, datos geoespaciales, series temporales con extensiones — Postgres maneja todo esto de manera competente.
  2. Madurez operativa. Las herramientas de copia de seguridad, replicación, monitoreo y recuperación para Postgres están probadas en batalla.
  3. Conocimiento del equipo. La mayoría de los ingenieros conocen SQL. Menos conocen los lenguajes de consulta de bases de datos especializadas.
  4. Flexibilidad. Siempre puedes extraer una base de datos especializada para un patrón de acceso específico más tarde. Empezar con una especializada es mucho más difícil de revertir.

El momento adecuado para considerar una base de datos especializada es cuando puedes probar — con datos, no con intuición — que Postgres es el cuello de botella para un patrón de acceso específico. E incluso entonces, la respuesta a menudo es "añadir un almacén de datos especializado junto a Postgres" en lugar de "reemplazar Postgres".

Cuándo mirar más allá de Postgres

Hay casos legítimos para bases de datos especializadas:

Datos de series temporales a escala. Si estás ingiriendo millones de puntos de datos por segundo con consultas de agregación basadas en el tiempo, TimescaleDB (extensión de Postgres) o una base de datos de series temporales dedicada como ClickHouse superarán significativamente a un Postgres estándar.

Recorridos de grafos. Si tus consultas principales implican recorrer relaciones con muchos niveles de profundidad — redes sociales, motores de recomendación, grafos de dependencia — una base de datos de grafos como Neo4j expresará estas consultas de forma más natural y las ejecutará de manera más eficiente.

Búsqueda en tiempo real. Si la búsqueda de texto completo es una característica principal con una puntuación de relevancia compleja, Elasticsearch o Typesense proporcionarán una mejor calidad de búsqueda que el tsvector de Postgres.

Alto rendimiento de escritura. Si necesitas escribir millones de filas por segundo con una indexación mínima, una base de datos estructurada en log como ScyllaDB o Apache Cassandra está diseñada precisamente para esto.

La lista de verificación del modelado de datos

Antes de finalizar tu esquema, responde estas preguntas:

  1. ¿Cuál es tu consulta de lectura más frecuente (hottest read query)? Optimiza tus índices primarios para esto.
  2. ¿Cuáles son tus requisitos de consistencia? La consistencia fuerte tiene un costo en el rendimiento. Sé explícito sobre dónde la necesitas.
  3. ¿Cómo crecen tus datos? ¿Series temporales, solo de añadir (append-only) o mutables? Esto afecta la estrategia de almacenamiento.
  4. ¿Cuáles son tus patrones de JOIN? Si nunca haces JOIN entre dos tablas, es posible que no necesiten estar en la misma base de datos.
  5. ¿Cuáles son tus requisitos de copia de seguridad y recuperación? Algunas bases de datos hacen que la recuperación a un punto en el tiempo sea trivial. Otras la hacen dolorosa.

La mejor decisión sobre una base de datos es aquella que es aburrida, predecible y bien comprendida por tu equipo. Guarda las opciones tecnológicas interesantes para los problemas que realmente las necesiten.


Este artículo forma parte de una serie sobre los fundamentos de la infraestructura de datos.

Etiquetasdatabasespostgresdata-modelingsystem-design