Réplicas de Lectura: Escala Lecturas Sin Tocar tu App
Guía de producción sobre réplicas de lectura: configuración de replicación streaming, enrutamiento de lectura/escritura (app, ORM y proxy), manejo del lag, monitoreo, failover y ajuste fino en PostgreSQL, MySQL y réplicas gestionadas en la nube.
Descripción General
Las réplicas de lectura son copias de tu base de datos primaria que responden consultas de solo lectura. El primario sigue aceptando escrituras y transmite cada cambio a las réplicas — registros WAL en PostgreSQL, eventos de binlog en MySQL — que los replican en tiempo casi real. Como la replicación es asíncrona, una réplica siempre va un poco por detrás, y esa diferencia (el lag de replicación) es el único trade-off que tu aplicación tiene que asumir.
Es el primer paso más común para escalar cargas intensivas en lectura, y con razón: los dashboards de analítica, las búsquedas y las lecturas de API se mueven a las réplicas mientras el primario respira. Esta guía recorre el ciclo completo — configurar la replicación streaming, separar lecturas de escrituras a nivel de aplicación, ORM y proxy, manejar el lag, monitorear, failover y los ajustes que importan en producción. Para topologías de replicación más allá de las réplicas de lectura (multi-master, commit síncrono), consulta Replicación de Bases de Datos. Versiones ejecutables de las configuraciones y enrutadores de esta guía están en el repositorio complementario.
Cuándo Usar
Las réplicas compensan cuando las lecturas dominan y unos cientos de milisegundos de desfase son aceptables:
- Las consultas de lectura superan el 80 % de la carga de tu base de datos
- Las consultas analíticas (reportes, agregaciones) ralentizan las escrituras transaccionales
- Necesitas escalado de lectura más allá de lo que una sola instancia puede dar
- Quieres distribución geográfica de lectura — réplicas en 2-3 regiones cerca de los usuarios
- Necesitas un hot standby para failover sin hardware dedicado
- Tu conjunto de trabajo cabe en memoria pero el volumen de consultas excede la CPU
Antes de añadir una réplica, asegúrate de que las lecturas sean eficientes — una réplica solo ejecuta la misma consulta lenta en otro sitio. Optimización de Consultas PostgreSQL cubre primero el ajuste de índices y planes.
Cuándo NO Usar
- Cargas intensivas en escritura. Si las escrituras superan ~50 % del tráfico, las réplicas añaden sobrecarga sin descargar nada — cada escritura sigue llegando al primario. Mejor mira sharding o particionamiento.
- Consistencia fuerte lectura-tras-escritura. Si cada lectura debe ver la última escritura, el lag de replicación lo violará. Quédate en el primario o usa un motor SQL distribuido (CockroachDB, Yugabyte) diseñado para consistencia fuerte.
- Conjuntos de datos pequeños y rápidos. Si la base cabe en RAM y las consultas ya son rápidas, escalar verticalmente es más simple y barato que operar un segundo nodo.
- Presupuestos ajustados. Cada réplica duplica aproximadamente tu factura de base de datos — en RDS una réplica cuesta lo mismo que el primario. Si el problema es el coste, ajusta consultas e índices primero.
- Equipos muy pequeños. Las réplicas añaden procedimientos de failover, monitoreo y lógica de enrutamiento. Para una herramienta interna no crítica, ese peso operativo quizá no compense.
Conceptos Clave
| Concepto | Descripción |
|---|---|
| Primario (Master) | La instancia de base de datos que acepta escrituras |
| Réplica | Instancia de solo lectura que replica los cambios del primario |
| Lag de Replicación | Retraso entre una escritura en el primario y su aparición en la réplica |
| Replicación Streaming | Transferencia continua de WAL (PostgreSQL) o binlog (MySQL) |
| Replicación Lógica | Replicación a nivel de fila con filtrado por tabla (PostgreSQL 10+) |
| Promoción | Convertir una réplica en el nuevo primario durante un failover |
La replicación física streaming copia bloques de WAL según llegan — rápida, pero replica el clúster entero. La replicación lógica envía cambios a nivel de fila de tablas seleccionadas, más flexible (esquemas distintos, columnas filtradas) a costa de más sobrecarga. Las réplicas de lectura casi siempre usan la física; para profundidad específica de PostgreSQL en ambas, consulta la Guía Completa de Replicación en PostgreSQL.
Arquitectura de Réplicas de Lectura
Las escrituras y las lecturas críticas de consistencia se quedan en el primario; el resto se reparte entre las réplicas detrás de una capa de enrutamiento:
El enrutador puede vivir en tu código (pools, hooks del ORM) o fuera de él (PgBouncer, ProxySQL, RDS Proxy). Ambos enfoques se cubren abajo.
Implementación Paso a Paso
1. Configura la Replicación Streaming
Monta replicación física streaming para copias casi en tiempo real. En el primario, permite senders de WAL y conserva suficiente WAL en disco para réplicas que se atrasen:
# postgresql.conf (Primario)
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
wal_keep_size = 1024
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
hot_standby_feedback merece una nota: evita que el primario haga vacuum de filas que una consulta larga en la réplica todavía necesita, a costa de algo de bloat en el primario. Déjalo activado salvo que las consultas de la réplica duren muchos minutos de forma habitual.
# pg_hba.conf (Primario): Permitir conexiones de replicación
host replication replicator 192.168.1.0/24 scram-sha-256
# Inicializar la réplica con un backup base
pg_basebackup -h primary-host -D /var/lib/postgresql/data -U replicator -P -v -R
En MySQL la misma idea usa coordenadas de binlog. binlog_format = ROW es el default seguro — los binlogs por sentencia pueden divergir con consultas no deterministas:
# 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 la replicación en la 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 las Lecturas a las Réplicas
Dirige las consultas de lectura a las réplicas mientras mantienes las escrituras en el primario. La versión más simple son dos pools de conexiones y un helper:
# Ejemplo: Python con división lectura/escritura
import psycopg2
from contextlib import contextmanager
primary_pool = psycopg2.pool.ThreadedConnectionPool(1, 10, dsn="dbname=app host=primary")
replica_pool = psycopg2.pool.ThreadedConnectionPool(1, 10, dsn="dbname=app host=replica1")
@contextmanager
def get_db_connection(read_only=False):
"""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)
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()
Si ya usas un ORM, aprovecha sus hooks de enrutamiento en vez de pools a mano. SQLAlchemy cambia de engine según el tipo de operación:
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
import random
primary_engine = create_engine("postgresql://user:pass@primary:5432/app")
replica_engines = [
create_engine("postgresql://user:pass@replica1:5432/app"),
create_engine("postgresql://user:pass@replica2:5432/app"),
]
class RoutingSession:
def __init__(self):
self._write_session = sessionmaker(bind=primary_engine)()
self._read_session = sessionmaker(bind=random.choice(replica_engines))()
def execute_write(self, query, params=None):
return self._write_session.execute(text(query), params or {})
def execute_read(self, query, params=None):
return self._read_session.execute(text(query), params or {})
Django trae enrutadores de base de datos de serie — lecturas y escrituras se separan declarándolo:
# routers.py
class ReadReplicaRouter:
def db_for_read(self, model, **hints):
return 'replica'
def db_for_write(self, model, **hints):
return 'default'
def allow_migrate(self, db, app_label, model_name=None, **hints):
return db == 'default'
# settings.py
DATABASE_ROUTERS = ['myapp.routers.ReadReplicaRouter']
Spring enruta por la anotación de transacción — @Transactional(readOnly = true) va a la réplica automáticamente:
@Configuration
public class DataSourceConfig {
@Bean
public DataSource routingDataSource(
@Qualifier("primaryDataSource") DataSource primary,
@Qualifier("replicaDataSource") DataSource replica) {
AbstractRoutingDataSource routing = new AbstractRoutingDataSource() {
@Override
protected Object determineCurrentLookupKey() {
return TransactionSynchronizationManager.isCurrentTransactionReadOnly()
? "replica" : "primary";
}
};
Map<Object, Object> targets = new HashMap<>();
targets.put("primary", primary);
targets.put("replica", replica);
routing.setTargetDataSources(targets);
routing.setDefaultTargetDataSource(primary);
return routing;
}
}
Estrategias de Enrutamiento
| Estrategia | Implementación | Mejor Para |
|---|---|---|
| División de pools | Pools separados para primario y réplica | Aplicaciones simples |
| Integración ORM | Routers de Django, AbstractRoutingDataSource de Spring, SQLAlchemy | Apps basadas en frameworks |
| Basada en proxy | PgBouncer, ProxySQL, HAProxy, RDS Proxy | Cero cambios en la aplicación |
| Multi-host del driver | libpq host=a,b, loadBalance de Connector/J | Failover básico, no balanceo real |
| Basada en DNS | Endpoints separados (primary.db, replica.db) | Microservicios |
3. Delega el Enrutamiento a un Proxy
Cuando no quieres lógica de enrutamiento en la app — varios servicios, lenguajes mezclados — pon un proxy delante. PgBouncer expone cada backend como alias de base de datos y la app elige uno por consulta:
# pgbouncer.ini
[databases]
master = host=master.db.internal port=5432 dbname=app
replica1 = host=replica1.db.internal port=5432 dbname=app
replica2 = host=replica2.db.internal port=5432 dbname=app
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
import psycopg2, random
def get_connection(is_write=False):
if is_write:
return psycopg2.connect("postgresql://user:pass@pgbouncer:6432/master")
replica = random.choice(['replica1', 'replica2'])
return psycopg2.connect(f"postgresql://user:pass@pgbouncer:6432/{replica}")
ProxySQL va más lejos en MySQL — inspecciona cada consulta y enruta por regla, así que SELECT ... FOR UPDATE puede quedarse en el primario mientras las lecturas normales se reparten:
-- Configurar ProxySQL con los servidores backend
INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES
(0, 'master.db.internal', 3306),
(1, 'replica1.db.internal', 3306),
(1, 'replica2.db.internal', 3306);
-- Reglas: SELECT a réplicas, lecturas con bloqueo al primario
INSERT INTO mysql_query_rules(rule_id, active, match_digest, destination_hostgroup, apply)
VALUES
(1, 1, '^SELECT.*FOR UPDATE', 0, 1),
(2, 1, '^SELECT', 1, 1);
LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
En AWS, RDS Proxy hace el pooling como servicio gestionado — se pone delante de tu primario y réplicas y absorbe tormentas de conexiones sin tocar la app:
# Fragmento de CloudFormation para RDS Proxy
Resources:
ReadWriteProxy:
Type: AWS::RDS::DBProxy
Properties:
DBProxyName: app-proxy
EngineFamily: POSTGRESQL
RoleArn: !GetAtt ProxyRole.Arn
Auth:
- AuthScheme: SECRETS
SecretArn: !Ref DBSecretArn
TargetGroupName: default
ConnectionPoolConfiguration:
MaxConnectionsPercent: 80
IdleClientTimeout: 1800
4. Maneja el Lag de Replicación
El lag de replicación es lo que rompe los montajes ingenuos con réplicas. Un usuario actualiza su perfil, recarga la página al instante y la lectura cae en una réplica que aún no ha replicado la escritura — ve el valor viejo y reporta un bug. Para enrutar alrededor de esto, o mides el lag antes de cada lectura o fijas las lecturas post-escritura al primario:
# Ejemplo: Enrutamiento consciente del lag
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):
"""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 hace falta frescura."""
if requires_freshness:
return self.primary
lag = self.get_replica_lag()
if lag > self.max_lag:
return self.primary
return self.replica
Para el caso común — releer lo que acabas de escribir — basta un wrapper con fallback:
def read_after_write(conn_master, conn_replica, query, params, max_wait=2.0):
"""Leer de la réplica con fallback al primario si el lag es alto."""
with conn_master.cursor() as cur:
cur.execute("""
SELECT EXTRACT(EPOCH FROM (now() - replay_lag))::float
FROM pg_stat_replication LIMIT 1
""")
lag = cur.fetchone()[0] or 0
if lag > max_wait:
with conn_master.cursor() as cur:
cur.execute(query, params)
return cur.fetchall()
try:
with conn_replica.cursor() as cur:
cur.execute(query, params)
return cur.fetchall()
except Exception:
with conn_master.cursor() as cur:
cur.execute(query, params)
return cur.fetchall()
Estrategias para Manejar el Lag
| Enfoque | Cómo Funciona | Trade-off |
|---|---|---|
| Afinidad de sesión | Leer del primario tras una escritura en la misma sesión | Algo más de carga en el primario |
| Umbral de lag | Enrutar al primario si el lag supera X segundos | Simple, pero puede picar la carga del primario |
| Consistencia eventual | Aceptar lecturas desfasadas y documentarlo | Mejor rendimiento, pero inconsistencia visible |
| Redirección post-escritura | Rastrear claves recién modificadas y enrutarlas al primario | Complejo, requiere lógica en la aplicación |
| Consistencia causal | Rastrear el LSN por sesión y esperar a que la réplica alcance | PostgreSQL 14+ replicación lógica |
En la práctica, afinidad de sesión más un umbral de lag cubre la mayoría de las apps. La consistencia causal es la respuesta elegante, pero la pagas en infraestructura de rastreo.
5. Monitorea el Lag y la Salud
Rastrea la salud de las réplicas y el lag desde el día uno — sin eso vas a ciegas:
-- PostgreSQL: lag por réplica en el primario
SELECT
client_addr,
state,
sent_lsn,
replay_lsn,
pg_size_pretty(pg_wal_lsn_diff(sent_lsn, replay_lsn)) AS lag
FROM pg_stat_replication;
-- Estado del receptor WAL en la propia réplica
SELECT status, receive_start_lsn, written_lsn, flushed_lsn
FROM pg_stat_wal_receiver;
-- Lag de slots (WAL sin consumir se acumula si un slot se atasca)
SELECT slot_name, restart_lsn,
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS lag_bytes
FROM pg_replication_slots;
-- MySQL: lag de la réplica
SHOW REPLICA STATUS\G
-- Campos clave: Seconds_Behind_Source (< 5),
-- Replica_IO_Running y Replica_SQL_Running (ambos Yes)
-- Salud de la conexión vía performance schema
SELECT
channel_name,
service_state,
last_error_number,
last_error_message
FROM performance_schema.replication_connection_status;
Alerta sobre los dos modos de fallo que de verdad duelen — el lag superando tu umbral y la replicación deteniéndose en silencio:
# 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: "El lag de replicación supera 100MB"
- alert: ReplicationStopped
expr: |
pg_stat_replication_state == 0
for: 1m
labels:
severity: critical
annotations:
summary: "La replicación se ha detenido"
Métricas Clave
- Lag en bytes/segundos: ¿cuánto va atrasada la réplica?
- Estado de replicación: ¿corriendo, pausada o detenida?
- Tendencia del lag: ¿crece de forma sostenida? Entonces la réplica no da abasto
- Latencia de consultas en la réplica: ¿las lecturas siguen siendo rápidas bajo carga?
- Número de conexiones: ¿la réplica está cerca de
max_connections?
6. Planifica el Failover
Cuando el primario muere, una de esas réplicas se convierte en el nuevo primario — y conviene haber ensayado ese movimiento antes de las 3 de la mañana:
# PostgreSQL: Promoción manual
pg_ctl promote -D /var/lib/postgresql/data
# O con repmgr (herramienta de failover automático)
repmgr standby promote
# MySQL: Promover réplica a primario
STOP REPLICA;
RESET REPLICA ALL;
SET GLOBAL read_only = OFF;
Enfoques de Failover
| Enfoque | RTO | Complejidad | Mejor Para |
|---|---|---|---|
| Promoción manual | 5-30 min | Baja | Equipos pequeños, sistemas no críticos |
| Herramientas automáticas | 30-120s | Media | repmgr, Patroni, orchestrator |
| Servicio gestionado | 0-60s | Ninguna | RDS Multi-AZ, Cloud SQL HA |
| Replicación síncrona | 0s (sin pérdida de datos) | Alta | Sistemas financieros (latencia por seguridad) |
Recuerda que una réplica promovida mueve tu endpoint de escritura — las apps que apuntan al primario viejo necesitan un cambio de DNS, un proxy o un rollout de configuración. Los servicios gestionados ocultan casi todo esto; en montajes propios hay que probar el camino completo, no solo el comando de promoción.
Ajuste en Producción
Una réplica con lag o sin conexiones es peor que no tener réplica. Estos son los ajustes que conviene revisar una vez en producción:
Dimensiona el número de réplicas con datos, no por intuición. Si el lag supera tu umbral de forma sostenida, o las réplicas están infra-dimensionadas o tienes más de las que el primario puede alimentar:
SELECT application_name, client_addr,
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes,
EXTRACT(EPOCH FROM replay_lag) AS lag_seconds
FROM pg_stat_replication;
Reparte las réplicas entre zonas de disponibilidad. Una caída de zona no debería tirar toda tu capacidad de lectura — y las réplicas multi-AZ además sirven de DR:
# AWS RDS: réplicas de lectura en distintas AZ
ReadReplica1:
Type: AWS::RDS::DBInstance
Properties:
SourceDBInstanceIdentifier: !Ref MasterInstance
AvailabilityZone: us-east-1b
DBInstanceClass: db.r6g.large
Encuentra las consultas que pertenecen a una réplica. pg_stat_statements muestra qué lecturas calientes generan más carga — esas son las primeras candidatas a enrutar a réplicas:
SELECT query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
WHERE query LIKE 'SELECT%'
ORDER BY calls DESC
LIMIT 20;
Pool de conexiones por réplica. Las réplicas tienen el mismo techo de max_connections que el primario; PgBouncer o RDS Proxy delante de cada réplica evita que una tormenta de conexiones la tumbe. Consulta Connection Pooling en profundidad para dimensionar pools.
Lo que Funciona
- Empieza con una réplica. Una réplica bien configurada resuelve el 80 % de las necesidades de escalado de lectura. Rara vez he necesitado más de 2-3 incluso en apps de mucho tráfico — una ratio 1:3 a 1:5 de primario a réplicas es un techo razonable.
- Umbrales de lag por caso de uso. Sobre 1-5 segundos para lecturas de cara al usuario, hasta un minuto para analítica. Alerta sobre el más exigente.
- Relee desde el primario tras una escritura. El bug clásico de producción es un usuario que actualiza datos y ve el valor viejo porque la lectura cayó en una réplica atrasada.
- Usa réplicas para reportes y analítica. Aísla las consultas costosas del primario — muchas veces ese es todo el punto.
- Prueba el failover antes de necesitarlo. Promueve una réplica en staging cada trimestre. Cuando el primario falla, quieres memoria muscular, no un wiki que nunca ejecutaste.
- Hardware de réplica igual al primario. Una réplica más débil genera lag en los picos, justo cuando más la necesitas.
- Monitorea el lag desde el día uno.
pg_stat_replicationen PostgreSQL,SHOW REPLICA STATUSen MySQL — sin monitoreo solo te enteras del lag por las quejas de los usuarios.
Errores Comunes
- Enrutar todas las lecturas a réplicas. El estado de sesión, los datos recién modificados y las lecturas críticas pertenecen al primario.
- Ignorar el lag de replicación. Los usuarios reportan “bugs” que en realidad son lecturas desfasadas.
- Una sola réplica sin plan de failover. Si muere, la capacidad de lectura cae a cero — y si además era tu standby, te quedas sin DR.
- Ejecutar escrituras en réplicas. Las escrituras accidentales rompen la replicación y obligan a reinicializar. Una vez vi una migración enrutada a una réplica que dejaba el esquema misteriosamente sin cambios.
- Sobre-replicar. Cada réplica añade carga de WAL sender en el primario. Diez réplicas sobre un primario modesto solo convierten al primario en cuello de botella otra vez.
- Usar réplicas para escalar escrituras. Las réplicas solo escalan lecturas — para escrituras mira sharding.
Variantes
- Replicación en cascada: Réplica → Réplica → Réplica para distribución geográfica o fan-out más allá de la capacidad de WAL senders del primario
- Multi-primario (master-master): Escrituras aceptadas en varios nodos — potente pero la resolución de conflictos es difícil; usar con cautela
- Replicación lógica: Replicación selectiva por tabla/columna (PostgreSQL 10+, filtrado de binlog en MySQL) para copias parciales y upgrades sin downtime
- Réplicas entre regiones: Réplicas gestionadas en otras regiones para latencia global de lectura y DR
- Réplica retardada: Una réplica intencionadamente horas por detrás — tu seguro contra un
DROP TABLEaccidental
Cada motor implementa la replicación a su manera; este es el panorama:
| Base de datos | Tipo de replicación | Monitoreo de lag | Enrutamiento de lecturas |
|---|---|---|---|
| PostgreSQL | Streaming / Lógica | pg_stat_replication | PgBouncer, proxy propio |
| MySQL | Binlog (async/semi-sync) | SHOW REPLICA STATUS | ProxySQL, MaxScale |
| Cloud RDS | Streaming gestionado | CloudWatch / Cloud Monitoring | RDS Proxy, propio |
| CockroachDB | Multi-activo (Raft) | Integrado | Automático |
Ver También
- Replicación de bases de datos — primaria y multi-primaria
- Guía Completa de Replicación en PostgreSQL
- Implementación de Sharding — cuando las réplicas no bastan y las escrituras también necesitan escalar
- Connection Pooling en Profundidad
- Optimización de Consultas PostgreSQL — arregla las lecturas lentas antes de escalarlas
Lecturas Recomendadas
- Documentación de replicación streaming de PostgreSQL — guía oficial de hot standbys y streaming
- Documentación de replicación de MySQL — binlog, modos async y semi-sync
- Réplicas de lectura en AWS RDS — réplicas gestionadas, multi-región, promoción
- Documentación de PgBouncer — modos de pool y configuración multi-backend
- Documentación de ProxySQL — reglas de consultas para separar lecturas/escrituras en MySQL
Conclusión
Las réplicas de lectura son la forma más simple y duradera de escalar lecturas de base de datos — pero no son gratis. Cambian un poco de consistencia (el lag) por mucha capacidad de lectura, y a los equipos que se queman son los que las desplegaron sin monitoreo de lag ni simulacros de failover. Configura la replicación streaming, elige una capa de enrutamiento acorde a tu stack, alerta sobre el lag antes de que lo noten los usuarios y ensaya la promoción. Con eso, un par de réplicas aguantarán 10 veces más lecturas sin tocar tu modelo de datos.
Preguntas frecuentes
¿Cuánto lag de replicación es aceptable?
Para lecturas de cara al usuario, menos de 1 segundo es lo ideal y menos de 5 funciona. La analítica tolera minutos. Para invalidación de caché manténlo bajo un segundo, y fija el umbral de alerta en el requisito más exigente. Los sistemas financieros pueden necesitar replicación síncrona — lag cero a costa de latencia de escritura.
¿Puedo escribir en una réplica de lectura?
No — las réplicas estándar son de solo lectura y rechazan escrituras directamente. Los sistemas multi-master (MySQL Group Replication, Galera, CockroachDB) aceptan escrituras en varios nodos pero traen una complejidad real de resolución de conflictos.
¿Cuántas réplicas debería tener?
Empieza con una o dos. PostgreSQL aguanta unas 10 réplicas streaming antes de que el overhead de WAL sender se note; para más, réplicas en cascada o replicación lógica. La mayoría de cargas ven rendimientos decrecientes más allá de una ratio 1:5 primario-réplica.
¿Necesito un proxy para separar lecturas y escrituras?
No siempre. Muchos ORMs (Django, Spring, SQLAlchemy) y drivers (libpq, Connector/J) pueden enrutar lecturas por sí mismos. Añade ProxySQL, PgBouncer o RDS Proxy cuando quieras el enrutamiento fuera del código o necesites reglas por consulta, como fijar FOR UPDATE al primario.
¿Por qué mi lectura tras escribir devolvió el valor viejo?
Lag de replicación — la réplica no había replicado la escritura cuando llegó la lectura. Envía la re-lectura al primario tras las escrituras, o espera a que el lag baje de tu umbral. Es el bug de réplicas más común en producción.
¿Necesito réplicas si ya uso caché?
Sí — resuelven problemas distintos. La caché sirve datos calientes rápido; las réplicas manejan los misses de caché y las consultas analíticas que no deberían tocar ni la caché ni el primario. Consulta Estrategias de Caché para ver cómo encajan las dos capas.
Recursos Relacionados
Replicación de bases de datos — primaria y multi-primaria
Guía práctica de estrategias de replicación de bases de datos: primaria-réplica, multi-primaria, síncrona vs asíncrona, y cómo manejar failover y resolución de conflictos.
GuideGuía Completa de Replicación en PostgreSQL
Configura y opera la replicación de PostgreSQL en producción: streaming y replicación lógica, commit síncrono, failover con Patroni y monitorización del lag.
GuideGuía Completa de Sharding de Bases de Datos
Domina el sharding: shard keys, estrategias de partición, consistent hashing, consultas cross-shard, resharding, Vitess y Citus.
GuidePooling de Conexiones a Base de Datos: Guía Práctica
Guía práctica sobre pooling de conexiones de base de datos: dimensionar pools, manejar timeouts de inactividad, detectar fugas, y configurar HikariCP, PgBouncer y pools nativos en la nube para máximo throughput.
RecipeEstrategias de Caché: Navegador, CDN, Redis y Base de Datos
Implementa caché efectiva en cada capa del stack: caché de navegador, edge de CDN, caché de aplicación con Redis y caché de consultas de base de datos — con estrategias de invalidación que mantienen los datos frescos.
RecipeOptimizacion de Queries e Indexing en PostgreSQL
Analiza y optimiza queries lentas en PostgreSQL usando EXPLAIN, indexing apropiado, partial indexes y reescritura de queries para reducir tiempo de ejecucion