Skip to content
SP StackPractices
intermediate Por StackPractices

Réplicas de Lectura — Escala Lecturas Sin Cambiar la Lógica de Aplicación

Guía práctica sobre réplicas de lectura: configurar replicación, enrutar consultas de lectura, manejar lag de replicación, y escalar cargas de trabajo intensivas en lectura con PostgreSQL, MySQL y réplicas gestionadas en la nube.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Descripción General

Las réplicas de lectura son copias de tu base de datos primaria que manejan consultas de solo lectura. Son la forma más simple y costo-efectiva de escalar cargas de trabajo intensivas en lectura de base de datos. Al descargar las consultas SELECT a las réplicas, reduces la carga en el primario, mejoras los tiempos de respuesta y aumentas la disponibilidad.

Esta guía cubre configuración de replicación, enrutamiento de consultas, manejo de lag de replicación y mejores prácticas operativas.

Cuándo Usar

  • Las consultas de lectura exceden el 80% de la carga de trabajo de tu base de datos
  • Consultas analíticas (reportes, agregaciones) ralentizan las escrituras transaccionales
  • Necesitas escalado de lectura más allá de lo que una sola instancia puede proporcionar
  • Quieres distribución geográfica de lectura (réplicas en múltiples regiones)
  • Necesitas un hot standby para failover sin hardware de standby dedicado
  • Tu working set cabe en memoria pero el volumen de consultas excede la capacidad de CPU

Cuándo NO Usar

  • Tu carga de trabajo es intensiva en escritura (>50% escrituras) — las réplicas no ayudan al escalado de escritura
  • Requieres lecturas fuertemente consistentes inmediatamente después de escrituras — el lag de replicación puede violar esto
  • Tus consultas ya están limitadas por CPU en la réplica — agregar más réplicas es mejor que hacerlas más grandes
  • No has optimizado consultas e índices en el primario — arregla esos primero

Conceptos Clave

ConceptoDescripción
Primario (Master)La instancia de base de datos que acepta escrituras
Réplica (Slave)Una instancia de base de datos que replica datos desde el primario
Lag de ReplicaciónRetraso entre una escritura en el primario y su aparición en la réplica
Replicación StreamingTransferencia continua de WAL (PostgreSQL) o binlog (MySQL)
Replicación LógicaReplicación a nivel de fila con filtrado (PostgreSQL 10+)
PromociónConvertir una réplica en el nuevo primario durante failover

Arquitectura de Réplicas de Lectura

         Escrituras + Lecturas Críticas

         ┌────▼────┐
         │ Primario │
         │  (R/W)  │
         └────┬────┘
              │ WAL / Binlog
      ┌───────┼───────┐
      │       │       │
   ┌──▼──┐ ┌─▼───┐ ┌─▼───┐
   │Repl │ │Repl │ │Repl │
   │  1  │ │  2  │ │  3  │
   └──┬──┘ └──┬──┘ └──┬──┘
      │       │       │
      └───────┼───────┘

         Consultas de Lectura

Implementación de Réplicas de Lectura Paso a Paso

1. Configura Replicación Streaming

Configura replicación física de streaming para copias casi en tiempo real:

# postgresql.conf (Primario)
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
archive_mode = on
archive_command = 'cp %p /var/lib/postgresql/archive/%f'
hot_standby = on
# postgresql.conf (Réplica)
hot_standby = on
hot_standby_feedback = on
max_standby_streaming_delay = 30s
# pg_hba.conf (Primario): Permitir conexiones de replicación
host replication replicator 192.168.1.0/24 scram-sha-256
# Inicializar réplica con backup base
pg_basebackup -h primary-host -D /var/lib/postgresql/data -U replicator -P -v -R
# my.cnf (MySQL Primario)
[mysqld]
server-id = 1
log_bin = /var/log/mysql/mysql-bin
binlog_format = ROW
expire_logs_days = 7
max_binlog_size = 500M
# my.cnf (MySQL Réplica)
[mysqld]
server-id = 2
relay_log = /var/log/mysql/mysql-relay-bin
log_bin = /var/log/mysql/mysql-bin
read_only = 1
-- MySQL: Configurar replicación en réplica
CHANGE REPLICATION SOURCE TO
    SOURCE_HOST='primary-host',
    SOURCE_USER='replicator',
    SOURCE_PASSWORD='password',
    SOURCE_LOG_FILE='mysql-bin.000001',
    SOURCE_LOG_POS=0;

START REPLICA;

2. Enruta Lecturas a Réplicas

Dirige consultas de lectura a réplicas mientras mantienes escrituras en el primario:

# Ejemplo: Python con división de lectura/escritura
import psycopg2
from contextlib import contextmanager

# Pools de conexiones
primary_pool = psycopg2.pool.ThreadedConnectionPool(1, 10, dsn="dbname=app primary")
replica_pool = psycopg2.pool.ThreadedConnectionPool(1, 10, dsn="dbname=app replica")

@contextmanager
def get_db_connection(read_only=False):
    """Obtener conexión: primario para escrituras, réplica para lecturas."""
    pool = replica_pool if read_only else primary_pool
    conn = pool.getconn()
    try:
        yield conn
    finally:
        pool.putconn(conn)

# Uso
def get_user(user_id):
    with get_db_connection(read_only=True) as conn:
        cursor = conn.cursor()
        cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
        return cursor.fetchone()

def update_user(user_id, data):
    with get_db_connection(read_only=False) as conn:
        cursor = conn.cursor()
        cursor.execute("UPDATE users SET name = %s WHERE id = %s", (data['name'], user_id))
        conn.commit()
// Ejemplo: Spring Boot con enrutamiento de lectura/escritura
@Configuration
public class DataSourceConfig {
    
    @Bean
    public DataSource routingDataSource(
            @Qualifier("primaryDataSource") DataSource primary,
            @Qualifier("replicaDataSource") DataSource replica) {
        
        ReplicationRoutingDataSource routing = new ReplicationRoutingDataSource();
        Map<Object, Object> targets = new HashMap<>();
        targets.put("primary", primary);
        targets.put("replica", replica);
        routing.setTargetDataSources(targets);
        routing.setDefaultTargetDataSource(primary);
        return routing;
    }
}

// La anotación Transactional determina el enrutamiento
@Service
public class UserService {
    
    @Transactional(readOnly = true)
    public User getUser(String id) {
        return userRepository.findById(id).orElseThrow();
    }
    
    @Transactional
    public User updateUser(User user) {
        return userRepository.save(user);
    }
}

Estrategias de enrutamiento:

EstrategiaImplementaciónMejor Para
División de pool de conexionesPools separados para primario y réplicaAplicaciones simples
Basado en proxyPgBouncer, ProxySQL, HAProxyCero cambios en aplicación
Integración ORMRouters de base de datos Django, Spring AbstractRoutingDataSourceApps basadas en frameworks
Basado en DNSEndpoints separados (primary.db, replica.db)Microservicios

3. Maneja el Lag de Replicación

El lag de replicación es el principal desafío con réplicas de lectura:

# Ejemplo: Enrutamiento consciente de lag
import time

class LagAwareRouter:
    def __init__(self, primary, replica, max_lag_seconds=5):
        self.primary = primary
        self.replica = replica
        self.max_lag = max_lag_seconds
    
    def get_replica_lag(self):
        """Verificar lag de replicación actual en segundos."""
        cursor = self.replica.cursor()
        cursor.execute("""
            SELECT 
                EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))
            AS lag_seconds
        """)
        return cursor.fetchone()[0] or 0
    
    def route_query(self, query, requires_freshness=False):
        """Enrutar a réplica si el lag es aceptable y no se requiere frescura."""
        if requires_freshness:
            return self.primary
        
        lag = self.get_replica_lag()
        if lag > self.max_lag:
            # Fallback a primario si la réplica está muy atrasada
            return self.primary
        
        return self.replica

# Uso: Forzar primario para datos modificados por usuario
user = router.route_query("SELECT * FROM users WHERE id = %s", 
                          requires_freshness=True)
-- PostgreSQL: Monitorear lag de replicación
SELECT 
    client_addr,
    state,
    sent_lsn,
    write_lsn,
    flush_lsn,
    replay_lsn,
    pg_size_pretty(pg_wal_lsn_diff(sent_lsn, replay_lsn)) as lag
FROM pg_stat_replication;

-- MySQL: Monitorear lag de replicación
SHOW REPLICA STATUS\G
-- Buscar: Seconds_Behind_Source

Estrategias para manejar lag:

EnfoqueCómo FuncionaTrade-off
Stickiness de sesiónLeer del primario después de una escritura en la misma sesiónLigeramente más carga en primario
Umbral de lagEnrutar al primario si el lag excede X segundosSimple, pero puede generar picos de carga en primario
Consistencia eventualAceptar lecturas obsoletas, documentarloMejor rendimiento, pero inconsistencia visible a usuarios
Redirección post-escrituraRastrear claves recientemente modificadas, enrutar esas al primarioComplejo, requiere lógica de aplicación
Consistencia causalRastrear LSN por sesión, esperar que la réplica se ponga al díaPostgreSQL 14+ replicación lógica

4. Configura Monitoreo y Alertas

Rastrea la salud de la réplica y el lag:

# Ejemplo: Reglas de Prometheus para monitoreo de replicación
groups:
  - name: replication_alerts
    rules:
      - alert: HighReplicationLag
        expr: |
          pg_stat_replication_pg_wal_lsn_diff / 1024 / 1024 > 100
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Lag de replicación excede 100MB"

      - alert: ReplicationStopped
        expr: |
          pg_stat_replication_state == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "La replicación se ha detenido"

      - alert: ReplicaLagSeconds
        expr: |
          mysql_slave_lag_seconds > 10
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Lag de réplica MySQL > 10 segundos"

Métricas clave para monitorear:

  • Lag en bytes/segundos: ¿Qué tan atrasada está la réplica?
  • Estado de replicación: ¿Está corriendo, pausada o detenida?
  • Tendencia de lag: ¿El lag está aumentando (indica que la réplica no puede mantenerse)?
  • Latencia de consulta en réplica: ¿Las lecturas siguen siendo rápidas?
  • Conteo de conexiones: ¿La réplica está en su límite de conexiones?

5. Planifica para Failover

Cuando el primario falla, promueve una réplica:

# PostgreSQL: Promoción manual
pg_ctl promote -D /var/lib/postgresql/data

# O usando repmgr (herramienta de failover automatizado)
repmgr standby promote
# MySQL: Promover réplica a primario
STOP REPLICA;
RESET REPLICA ALL;
SET GLOBAL read_only = OFF;

Enfoques de failover:

EnfoqueRTOComplejidadMejor Para
Promoción manual5-30 minBajaEquipos pequeños, sistemas no críticos
Herramientas automatizadas30-120sMediarepmgr, Patroni, orchestrator
Servicio gestionado0-60sNingunaRDS Multi-AZ, Cloud SQL HA
Replicación síncrona0s (sin pérdida de datos)AltaSistemas financieros (latencia por seguridad)

Mejores Prácticas

  • Empieza con una réplica. Una réplica bien configurada resuelve el 80% de las necesidades de escalado de lectura.
  • Usa réplicas para reportes y analítica. Aísla consultas costosas del primario.
  • Monitorea el lag sin descanso. Lag que crece sin control indica que la réplica no puede mantenerse.
  • Prueba failover antes de necesitarlo. Promueve una réplica en staging trimestralmente.
  • Mantén hardware de réplica igual al primario. Una réplica más lenta crea lag durante carga pico.
  • Usa pooling de conexiones en réplicas también. Las réplicas tienen los mismos límites de conexión que los primarios.

Errores Comunes

  • Enrutar todas las lecturas a réplicas. Estado de sesión, datos recientemente modificados, y lecturas críticas deberían quedarse en el primario.
  • Ignorar lag de replicación. Los usuarios ven datos obsoletos y reportan “bugs” que en realidad son lag.
  • Una sola réplica sin plan de failover. Si la réplica falla, tu capacidad de lectura cae a cero.
  • Ejecutar escrituras en réplicas. Las escrituras accidentales rompen la replicación y requieren reinicialización.
  • Sin monitoreo de lag. Solo descubres problemas de lag cuando los usuarios se quejan.
  • Usar réplicas para escalado de escritura. Las réplicas solo escalan lecturas. Para escalado de escritura, considera sharding o particionamiento.

Variantes

  • Replicación en cascada: Réplica → Réplica → Réplica para distribución geográfica
  • Multi-primario (master-master): Escrituras aceptadas en múltiples nodos — complejo, usar con cautela
  • Replicación lógica: Replicación selectiva de tabla/columna (PostgreSQL 10+, filtrado de binlog MySQL)
  • Réplicas en diferentes regiones: Réplicas gestionadas por proveedores cloud para reducción de latencia global
  • Réplica retrasada: Réplica intencionalmente atrasada por horas para recuperación punto-en-tiempo

FAQ

P: ¿Cuánto lag es aceptable? Para la mayoría de aplicaciones, <1 segundo es ideal, <5 segundos es aceptable. Las cargas de trabajo analíticas pueden tolerar minutos. Los sistemas financieros pueden requerir replicación síncrona (lag cero).

P: ¿Puedo escribir en una réplica de lectura? No — las réplicas son de solo lectura por diseño. Algunos sistemas (MySQL Group Replication, extensiones multi-master de PostgreSQL) permiten escrituras multi-master, pero añaden complejidad significativa.

P: ¿Cuántas réplicas puedo tener? PostgreSQL soporta hasta ~10 réplicas de streaming antes de que el overhead de WAL sender se vuelva significativo. Para más, usa réplicas en cascada (réplica de una réplica) o replicación lógica.

P: ¿Necesito réplicas si uso caché? Sí — la caché y las réplicas se complementan. La caché maneja datos calientes; las réplicas manejan misses de caché y consultas analíticas.

Conclusión

Las réplicas de lectura son la forma más simple de escalar lecturas de base de datos. Al configurar replicación streaming, enrutar consultas inteligentemente y monitorear el lag, puedes manejar 10x crecimiento de lectura sin cambiar significativamente tu modelo de datos o arquitectura de aplicación.

Recursos Relacionados