🖥 Máquina local con mongod🔧 MongoDB ≥ 6.0⏱ ~2 horas📊 1 primario · 2 secundarios
0
¿Qué es un Replica Set?
Un Replica Set es un grupo de instancias de mongod que mantienen el mismo conjunto de datos. Proporciona redundancia (los datos existen en varias copias) y alta disponibilidad (si el nodo primario falla, uno de los secundarios es elegido automáticamente como nuevo primario).
Oplog
El operation log es una colección especial (local.oplog.rs) donde el primario registra cada operación de escritura. Los secundarios leen este log y aplican las operaciones en orden.
Heartbeat
Cada nodo envía un ping a los demás cada 2 segundos. Si el primario no responde durante 10 s (configurable), los secundarios inician una elección para elegir uno nuevo.
Quórum
Se necesita la mayoría de votos (⌈n/2⌉ + 1) para elegir un primario. Con 3 nodos el quórum es 2, por lo que el clúster sobrevive a la caída de 1 nodo.
0b
Paso previo: copiar la base de datos video al futuro primario
Antes de configurar el Replica Set necesitamos que el futuro primario (puerto 27018) tenga los datos de la base de datos video, que actualmente vive en el mongod standalone del puerto 27017. El orden de pasos es: crear el directorio → arrancar un standalone temporal en 27018 → copiar los datos → detenerlo → relanzarlo ya con la configuración de replica set.
⚠
Requisito previoEl mongod standalone original (puerto 27017) debe estar corriendo. Aún no necesitas los nodos secundarios (27019, 27020).
Paso 0b.1 — Crear el directorio de datos y arrancar el standalone temporal en 27018
bash — crear directorio y arrancar mongod temporal en 27018
# 1. Crear el directorio de datos para el futuro primario
mkdir -p /data/rs0-0
# 2. Arrancar mongod como standalone temporal (SIN replSetName)# Usamos --fork para que libere la terminal
mongod \
--dbpath /data/rs0-0 \
--port 27018 \
--bind_ip 127.0.0.1 \
--logpath /tmp/mongod-27018-tmp.log \
--logappend \
--fork
# Verificar que está corriendo
mongosh --port 27018 --eval "db.adminCommand({ ping: 1 })"
✓
Respuesta esperada de ping{ ok: 1 } — el standalone en 27018 está listo para recibir datos.
ℹ
¿Por qué arrancar sin replSetName?Un mongod con replSetName configurado no acepta operaciones normales hasta que el replica set esté inicializado. Al arrancarlo primero como standalone podemos restaurar datos con mongorestore sin complicaciones; después lo relanzamos con la configuración de replica set.
Paso 0b.2 — Exportar la BD video desde el nodo 27017
mongodump crea un volcado BSON de la base de datos en un directorio local. No detiene el servidor.
bash — exportar BD video desde el standalone 27017
# Crear un directorio para el volcado
mkdir -p /tmp/dump-video
# Exportar SOLO la base de datos "video" del nodo standalone
mongodump \
--host 127.0.0.1 \
--port 27017 \
--db video \
--out /tmp/dump-video# Verificar que se crearon los archivos BSON
ls /tmp/dump-video/video/
Salida esperada
2026-10-08T12:30:00 writing video.movies to dump
2026-10-08T12:30:00 done dumping video.movies (9525 documents)
...# En /tmp/dump-video/video/ deberías ver:
movies.bson movies.metadata.json
comments.bson comments.metadata.json
... (una .bson por colección)
Paso 0b.2 — Importar la BD video en el futuro primario 27018
ℹ
¿El nodo 27018 ya tiene datos?Si el directorio /data/rs0-0 es nuevo y vacío, mongorestore simplemente crea la base de datos. Si ya existieran datos, usa la opción --drop para limpiar antes de importar y evitar duplicados.
bash — importar BD video en el futuro primario 27018
# Restaurar la BD video en el nodo 27018
mongorestore \
--host 127.0.0.1 \
--port 27018 \
--db video \
/tmp/dump-video/video# Opción --drop si quieres limpiar antes de restaurar:# mongorestore --host 127.0.0.1 --port 27018 --db video --drop /tmp/dump-video/video
Salida esperada
2026-10-08T12:30:05 restoring video.movies from /tmp/dump-video/video/movies.bson
2026-10-08T12:30:059525 document(s) restored successfully2026-10-08T12:30:05 restoring video.comments from ...
2026-10-08T12:30:05X document(s) restored successfully
Paso 0b.3 — Verificar la copia
mongosh :27018 — confirmar que la BD video está disponible
mongosh --port 27018
use video
# Listar colecciones
db.getCollectionNames()
# Comparar número de documentos con el origen
db.movies.countDocuments() ← debe coincidir con el 27017
✓
¿Los conteos coinciden con el nodo 27017?Ejecuta mongosh --port 27017 --eval "use video; db.movies.countDocuments()" en otra terminal y compara. Si los números son iguales, la copia fue exitosa y puedes continuar con la configuración del Replica Set.
ℹ
¿Por qué no simplemente copiar los archivos de datos?Copiar el directorio dbPath directamente funciona solo si el mongod origen está detenido (o con db.fsyncLock()). Con mongodump/mongorestore puedes hacerlo con el servidor en producción sin riesgo de corrupción.
✅ Una vez confirmada la copia, detén el mongod del 27018 si ya está corriendo sin replicación, porque en el siguiente paso lo relanzarás con la configuración de replica set.
1
Preparar directorios y archivos de configuración
Cada instancia de mongod necesita su propio directorio de datos y su propio puerto. Crearemos tres nodos en la misma máquina.
Nodo
Rol inicial
Puerto
Directorio de datos
rs0-0
Primario
27018
/data/rs0-0
rs0-1
Secundario
27019
/data/rs0-1
rs0-2
Secundario
27020
/data/rs0-2
⚠
Linux / macOSLos comandos de creación de directorios asumen una ruta /data/. En macOS puedes usar ~/data/ si /data requiere permisos de root. En Windows sustituye por C:\data\rs0-0 etc. y abre una terminal como Administrador.
bash — crear directorios
# El directorio /data/rs0-0 ya fue creado en el Paso 0b.1# Crear directorios de datos para los nodos restantes
mkdir -p /data/rs0-1 /data/rs0-2
# Crear directorio para los logs
mkdir -p /data/logs
Crea tres archivos de configuración YAML (uno por nodo):
/data/mongod-rs0-0.conf (nodo primario, puerto 27018)
Abre tres terminales separadas (o usa & / screen / tmux) y lanza cada nodo:
Terminal 1 — nodo rs0-0 (27018)
mongod --config /data/mongod-rs0-0.conf
Terminal 2 — nodo rs0-1 (27019)
mongod --config /data/mongod-rs0-1.conf
Terminal 3 — nodo rs0-2 (27020)
mongod --config /data/mongod-rs0-2.conf
ℹ
Verificar que los tres procesos están corriendoEn una cuarta terminal ejecuta pgrep -a mongod (Linux/macOS) o tasklist | findstr mongod (Windows). Debes ver tres PIDs distintos.
3
Inicializar el Replica Set
Con los tres procesos corriendo, conéctate al primario candidato (puerto 27018) e inicializa el replica set:
¿Qué significa priority: 2 en el nodo 0?La prioridad determina qué nodo tiene más probabilidad de ser elegido primario en una votación. Con priority: 2, el nodo 27018 siempre intentará ser primario cuando esté disponible. Los demás tienen priority: 1.
4
Usar una base de datos existente y verificar la replicación
La base de datos video ya está en el nodo primario (copiada en el Paso 0b). Al ejecutar rs.initiate(), MongoDB hizo la initial sync y replicó automáticamente todos los datos a los secundarios. Vamos a comprobarlo consultando la colección movieDetails y luego añadiremos un nuevo documento para observar su replicación en tiempo real.
✓
La BD video ya está replicadaCuando el replica set se inicializó, los secundarios copiaron automáticamente todos los datos del primario (incluyendo video.movieDetails). No necesitas hacer nada adicional para los datos históricos.
4.1 — Consultar movieDetails en el primario y contar documentos
mongosh :27018 — consultas sobre movieDetails
# Conectar al primario
mongosh --port 27018
use video
# Total de películas en la colección
db.movieDetails.countDocuments()
# Ver un documento completo de ejemplo
db.movieDetails.findOne({ title: "Once Upon a Time in the West" })
# Películas con rating IMDb ≥ 8.5, ordenadas por rating descendente
db.movieDetails.find(
{ "imdb.rating": { $gte: 8.5 } },
{ title: 1, year: 1, "imdb.rating": 1, _id: 0 }
).sort({ "imdb.rating": -1 }).limit(5)
Salida esperada (películas top IMDb)
[
{ title: 'Once Upon a Time in the West', year: 1968, imdb: { rating: 8.6 } },
{ title: 'The Godfather', year: 1972, imdb: { rating: 9.2 } },
...
]
4.2 — Insertar una nueva película en el primario y verificar replicación en Secundario 1 (27019)
mongosh :27018 — insertar una nueva película
# En el primario — insertar una película nuevause video
db.movieDetails.insertOne({
title: "El laberinto del fauno",
year: 2006,
rated: "R",
runtime: 118,
countries: ["España", "México"],
genres: ["Drama", "Fantasy"],
director: "Guillermo del Toro",
imdb: { id: "tt0457430", rating: 8.2, votes: 680000 },
tomato: { meter: 95, userMeter: 91 },
awards: { wins: 65, nominations: 105, text: "65 wins & 105 nominations." },
type: "movie"
})
Abre una nueva terminal, conéctate al Secundario 1 y verifica que el documento apareció:
mongosh :27019 — leer en el Secundario 1
# Conectar al secundario en 27019
mongosh --port 27019
# Habilitar lectura en el secundario
db.getMongo().setReadPref("secondaryPreferred")
use video
db.movieDetails.findOne({ title: "El laberinto del fauno" })
✓
Resultado esperadoEl documento recién insertado en el primario aparece en el secundario. La replicación en redes locales ocurre en milisegundos.
4.3 — Comprobar el oplog
El oplog registra el insert anterior. Inspecciónalo desde el primario:
¿Cómo funciona el oplog internamente?El oplog es una capped collection (de tamaño fijo) en la base de datos local. Cada secundario mantiene un cursor abierto sobre el oplog del primario y aplica las operaciones en orden idempotente. Si un secundario se desconecta y se reconecta, retoma desde su último timestamp registrado.
5
Verificar que las modificaciones se replican
Realiza una actualización en el primario y comprueba que se propaga a ambos secundarios.
mongosh :27018 — actualizar un documento en movieDetails
# En el primario (27018)use video
# Actualizar el Tomatometer y añadir un campo de reseña editorial
db.movieDetails.updateOne(
{ title: "Once Upon a Time in the West" },
{ $set: { "tomato.meter": 99, editorial: "Obra maestra del spaghetti western" },
$inc: { "imdb.votes": 5000 } }
)
mongosh :27020 — confirmar en el Secundario 2
# En el secundario 27020 (nueva terminal)
mongosh --port 27020
db.getMongo().setReadPref("secondaryPreferred")
use video
db.movieDetails.findOne(
{ title: "Once Upon a Time in the West" },
{ title: 1, "tomato.meter": 1, "imdb.votes": 1, editorial: 1, _id: 0 }
)
Resultado esperado en el secundario
{
title: 'Once Upon a Time in the West',
editorial: 'Obra maestra del spaghetti western', ← campo nuevo replicadoimdb: { votes: 206283 }, ← 201283 + 5000, replicadotomato: { meter: 99 } ← actualizado desde 98
}
# Desde el primario
rs.add("127.0.0.1:27021")
# Verificar que ya aparece como SECONDARY
rs.status()
ℹ
Initial syncEl nuevo nodo aparecerá en estado STARTUP2 mientras copia los datos. Dependiendo del tamaño de la base de datos tardará segundos o minutos. Monitorea el progreso con rs.status() repetidamente hasta ver SECONDARY.
⚠
Número impar de votantesAhora el set tiene 4 miembros. Para mantener un quórum claro se recomienda un número impar. Puedes agregar unárbitro(no almacena datos, solo vota) con rs.addArb("127.0.0.1:27021"), o configurar el nodo 3 como votes: 0 para que no vote.
7
Eliminar el primario y observar la elección automática
Simularemos la caída del nodo primario (27018) para observar cómo el replica set elige automáticamente un nuevo primario.
🔴
AtenciónMatar el proceso primario genera ~10–30 segundos de indisponibilidad de escritura mientras dura la elección. Es exactamente lo que queremos observar.
7.1 — Apagar el primario
Desde el primario, usa el comando stepDown (forma controlada) o simplemente mata el proceso:
Opción A — stepDown controlado (desde mongosh :27018)
# El primario cede su rol voluntariamente durante 60 s
rs.stepDown(60)
Opción B — matar el proceso (bash)
# Encontrar el PID del mongod en 27018
lsof -i :27018 | grep LISTEN
# Matar el proceso (reemplaza 12345 con el PID real)
kill -9 12345
7.2 — Observar la elección desde un secundario
mongosh :27019 — monitorear el estado cada 2 s
# Conectar a un secundario sobreviviente
mongosh --port 27019
# Monitorear repetidamente (Ctrl+C para detener)while(true) {
let status = rs.status()
status.members.forEach(m => {
print(`${m.name.padEnd(22)} ${m.stateStr}`)
})
print("─────────────────────────────")
sleep(2000)
}
Evolución del estado durante la elección
── t=0: caída del primario ──────────────
127.0.0.1:27018 DOWN
127.0.0.1:27019 SECONDARY
127.0.0.1:27020 SECONDARY
127.0.0.1:27021 SECONDARY── t=2s: detección de falla ─────────────
127.0.0.1:27018 UNKNOWN
127.0.0.1:27019 RECOVERING
127.0.0.1:27020 SECONDARY
127.0.0.1:27021 SECONDARY── t=12s: nueva elección completada ─────
127.0.0.1:27018 DOWN
127.0.0.1:27019 PRIMARY← nuevo primario elegido
127.0.0.1:27020 SECONDARY
127.0.0.1:27021 SECONDARY
ℹ
¿Por qué 27019 fue elegido y no 27020?Ambos tienen priority: 1. La elección la gana el nodo con eloplog más actualizado(mayor timestamp). En condiciones normales, ambos están sincronizados y cualquiera puede ganar; la votación es aleatoria con ese desempate.
7.3 — Reiniciar el nodo original y observar su regreso como secundario
bash — reiniciar el nodo 27018
mongod --config /data/mongod-rs0-0.conf
✓
¿Qué ocurre al reconectarse?El nodo 27018 detecta que existe un primario (27019) y entra en modo SECONDARY. Como tiene priority: 2, después de un breve periodo MongoDB ejecutará una nueva elección y 27018 recuperará automáticamente el rol de primario si así lo configuramos.
8
Controlar lectura y escritura en nodos secundarios
Por defecto, los secundarios no aceptan lecturas ni escrituras directas. Las escrituras siempre van al primario. Para leer de un secundario hay que indicarlo explícitamente. En esta sección se explica cómo controlar este comportamiento a nivel de conexión y a nivel del replica set.
8.1 — Comportamiento por defecto: intentar leer un secundario sin configuración
mongosh :27019 — sin setReadPref (falla intencionalmente)
mongosh --port 27019
use universidad
db.estudiantes.find() ← ejecutar sin setReadPref
Error esperado
MongoServerError: not primary and secondaryOk=false - consider using db.getMongo().setReadPref() or connecting to a secondary
✓
Este error es correcto y esperado.MongoDB protege la coherencia de lectura por defecto. Para leer de un secundario hay que ser explícito.
8.2 — Preferencias de lectura (Read Preferences)
MongoDB ofrece cinco modos. Elige según tus necesidades de coherencia y latencia:
Modo
Descripción
Cuándo usarlo
primary
Solo el primario (defecto)
Lecturas con coherencia fuerte
primaryPreferred
Primario si disponible, si no secundario
Alta disponibilidad con coherencia preferida
secondary
Solo secundarios (round-robin)
Reportes, analítica, sin impacto al primario
secondaryPreferred
Secundario si disponible, si no primario
Distribución de carga de lectura
nearest
Nodo con menor latencia de red
Aplicaciones geográficamente distribuidas
mongosh — configurar preferencias de lectura
── Leer SOLO del primario (coherencia fuerte) ──
db.getMongo().setReadPref("primary")
── Permitir leer de cualquier secundario ──────
db.getMongo().setReadPref("secondary")
── Secundario preferido, primario si no hay ──
db.getMongo().setReadPref("secondaryPreferred")
── Con tag para elegir un secundario específico
db.getMongo().setReadPref("secondary", [{ dc: "mexico" }])
8.3 — Bloquear totalmente la lectura en un secundario específico
Puedes deshabilitar que un nodo vote y que sea elegible como primario, convirtiéndolo en un nodo oculto (hidden member). Un nodo oculto no aparece en la topología de clientes y sólo puede ser leído con la preferencia secondary y tag explícito. Es útil para backups o reportes sin afectar el clúster principal.
mongosh :27018 — convertir el nodo 27021 en nodo oculto
# Obtener la configuración actuallet cfg = rs.conf()
# El nodo 27021 está en el índice 3 del array members
cfg.members[3].hidden = true
cfg.members[3].priority = 0← obligatorio cuando hidden: true
rs.reconfig(cfg)
Verificar: el nodo oculto no aparece en isMaster / hello
# Desde cualquier nodo
db.hello()
# El campo "hosts" NO incluirá 127.0.0.1:27021# pero rs.status() sí lo mostrará como SECONDARY
8.4 — Impedir escrituras accidentales en un secundario
Incluso si alguien se conecta directamente a un secundario, MongoDB impide la escritura por defecto. Pero si se usa --directConnection en la cadena de conexión, es posible intentarlo. Esto siempre falla con un error claro:
mongosh — intento de escritura directa en secundario
mongosh "mongodb://127.0.0.1:27019/?directConnection=true"
use video
db.movieDetails.insertOne({ title: "Test desde secundario", year: 2026 })
Error esperado
MongoServerError: not primaryWrites to secondaries are not supported.
✓
MongoDB garantiza esto a nivel del servidor.No se puede escribir en un secundario sin antes convertirlo en primario. Esta restricción no se puede desactivar; es parte del protocolo del replica set.
8.5 — Write Concern: controlar cuántos nodos confirman una escritura
Puedes hacer que MongoDB espere a que un número determinado de nodos confirme la escritura antes de responder al cliente:
mongosh :27018 — escritura con confirmación de la mayoría
Write Concern vs Read Preference Write Concerncontrola cuántos nodos deben confirmar una escritura (durabilidad).Read Preferencecontrola desde qué nodo se lee (disponibilidad/latencia). Son conceptos ortogonales y se combinan para ajustar la consistencia de tu aplicación.
✓
Resumen y puntos clave
Inicialización
rs.initiate() une a los nodos en un conjunto. El nodo con mayor priority se convierte en primario.
Replicación automática
Todo lo que se escribe en el primario queda en el oplog y se aplica en los secundarios en milisegundos en redes locales.
Elección automática
Si el primario falla, los nodos restantes votan y eligen uno nuevo en ~10–30 s, sin intervención manual.
Nodos ocultos
hidden: true + priority: 0 crea un nodo invisible para los clientes: ideal para backups o analítica sin afectar al clúster de producción.
Escritura solo en primario
MongoDB rechaza escrituras en secundarios con error not primary. Esta restricción es del servidor y no puede desactivarse.
Write Concern
w: "majority" garantiza que la escritura sobrevive a la caída del primario porque ya se replicó en la mayoría de nodos.