Cấu hình chuyển đổi Master-Slave MongoDB trên Windows Server

Bài viết này tập trung riêng vào quy trình chuyển đổi vai trò Secondary lên Primary cho MongoDB (Replica Set), chia rõ 2 tình huống thực tế: (1) chủ động chuyển đổi khi Primary vẫn còn sống — ưu tiên an toàn dữ liệu tuyệt đối; và (2) Primary gặp sự cố mất kết nối/hỏng đột ngột (disaster) — ưu tiên khôi phục dịch vụ nhanh nhất có thể. Đi kèm checklist thao tác nhanh để tra cứu tại chỗ khi cần.

I. Switchover chủ động (Primary còn sống)

Áp dụng khi: cần bảo trì/patch Primary, chuyển tải sang site khác theo kế hoạch, hoặc diễn tập định kỳ. Khác với MariaDB, MongoDB chỉ cần MỘT lệnh duy nhất để hoàn tất switchover — không cần các bước thủ công như STOP SLAVE/CHANGE MASTER TO.

▶ Thực hiện trên: Node đang PRIMARY

  1. Kiểm tra nhanh trạng thái cụm trước khi chuyển — đảm bảo Secondary đang health: 1, không bị lag:
rs.status()

Kiểm tra trạng thái cụm trước khi chuyển đổi — cả 2 node health: 1

Kiểm tra trạng thái cụm trước khi chuyển đổi — cả 2 node health: 1

  1. Yêu cầu Primary hiện tại tự nhường quyền cho Secondary một cách an toàn (built-in, không cần force):
rs.stepDown(60)
// 60 = số giây node này không được ứng cử lại làm Primary,
// đủ thời gian để Secondary được bầu làm Primary mới

Kết quả rs.stepDown(60) trên 103.48.195.25

Kết quả rs.stepDown(60) trên 103.48.195.25 — thành công, node tự hạ xuống Secondary

  1. MongoDB driver/replica set tự động bầu Secondary còn lại lên PRIMARY — không cần thao tác thủ công thêm.
  2. Xác nhận trên node mới:
rs.status()
// stateStr: "PRIMARY" tại node vừa được bầu

Xác nhận Primary mới (45.119.85.253) và Secondary (103.48.195.25) sau switchover

Xác nhận Primary mới (45.119.85.253) và Secondary (103.48.195.25) sau switchover

  1. Ghi thử dữ liệu để xác nhận Primary mới đã sẵn sàng nhận ghi:
use emr_db
db.ho_so_benh_an.insertOne({ so_hoso: "HS-0003", chan_doan: "...", ngay_kham: new Date() })

Ghi thử dữ liệu thành công trên Primary mới

Ghi thử dữ liệu thành công trên Primary mới — acknowledged: true

Với đúng connection string dạng mongodb://host1:27017,host2:27017/?replicaSet=rs0, driver ứng dụng tự phát hiện Primary mới ngay lập tức, KHÔNG cần sửa cấu hình ứng dụng. Nên chuẩn hoá connection string kiểu này ngay từ đầu.

Muốn ưu tiên một node cụ thể luôn được bầu làm Primary (ví dụ node chính sau khi chuyển đổi xong): đặt priority cao hơn cho node đó trước khi stepDown — lệnh rs.reconfig() này chỉ chạy được khi đang kết nối đúng vào node PRIMARY hiện tại:

cfg = rs.conf()
cfg.members.forEach(m => {
  if (m.host === "<host ưu tiên>:27017") { m.priority = 2 }
})
rs.reconfig(cfg)

✓ rs.stepDown() là cơ chế switchover chuẩn, an toàn, không mất dữ liệu (Primary cũ chỉ nhường quyền sau khi đã đảm bảo Secondary đủ điều kiện bầu chọn).

II. Failover khẩn cấp (Primary down / disaster)

⚠ Chỉ thực hiện các bước dưới đây SAU KHI đã xác nhận chắc chắn Primary không còn truy cập được (không chỉ là mạng chậm/gián đoạn tạm thời) — promote nhầm trong khi Primary cũ vẫn đang chạy sẽ gây SPLIT-BRAIN (2 node cùng nhận ghi, dữ liệu phân kỳ, rất khó gộp lại).

1. Bước 0 — Xác nhận Primary thực sự down

  • Ping IP Primary, kiểm tra port (Test-NetConnection -ComputerName <IP Primary> -Port 27017).
  • Thử RDP/console trực tiếp vào server nếu có thể — xác định do OS/server chết hẳn hay chỉ do mạng.
  • Kiểm tra từ phía Secondary: rs.status() để xem lỗi kết nối cụ thể và thời điểm Primary ngưng gửi dữ liệu (optimeDate của member not reachable).
  • Nếu có thể, thử liên hệ team hạ tầng/ISP xác nhận sự cố mạng hay sự cố server thật sự trước khi quyết định promote.

Với đúng 2 node có quyền bầu (votingMembersCount = 2), khi 1 node chết thì node còn lại KHÔNG đủ đa số phiếu nên MongoDB KHÔNG tự động bầu Primary mới — đây là hành vi đúng thiết kế, không phải lỗi. Bắt buộc can thiệp thủ công ở các bước dưới.

Bằng chứng Primary mất kết nối hoàn toàn

Bằng chứng Primary (45.119.85.253) mất kết nối hoàn toàn — health: 0, not reachable/healthy; Secondary vẫn giữ nguyên SECONDARY, không tự bầu

2. Promote Secondary lên Primary

▶ Thực hiện trên: Secondary còn sống

  1. Promote thủ công bằng forced reconfig — lấy cấu hình hiện tại, loại bỏ node đã chết, ép reconfig:
cfg = rs.conf()
cfg.members = cfg.members.filter(m => m.host !== "<host node đã chết>:27017")
rs.reconfig(cfg, {force: true})

Lấy cấu hình hiện tại

Lấy cấu hình hiện tại — cfg = rs.conf()

Loại node đã chết khỏi danh sách members

Loại node đã chết khỏi danh sách members

Ép reconfig với force: true

Ép reconfig với force: true — thành công

  1. Xác nhận node này đã chính thức PRIMARY (stateStr: “PRIMARY”, self: true), sau đó chuyển hướng connection string ứng dụng nếu chưa dùng chuỗi đa host:
rs.status()

Xác nhận PRIMARY sau forced reconfig

Xác nhận 103.48.195.25 đã chính thức PRIMARY sau forced reconfig

  1. Ghi thử dữ liệu để xác nhận hệ thống đã hoạt động trở lại:
use emr_db
db.ho_so_benh_an.insertOne({ so_hoso: "HS-0004", chan_doan: "...", ngay_kham: new Date() })

Ghi thử dữ liệu thành công trên Primary vừa được promote

Ghi thử dữ liệu thành công trên Primary vừa được promote — acknowledged: true

3. Xử lý node cũ khi phục hồi trở lại

▶ Thực hiện trên: Node cũ

⚠ Bắt buộc: KHÔNG để ứng dụng kết nối/ghi thẳng vào node cũ ngay khi nó vừa bật lại — node này đã bị loại khỏi replica set, cần add lại đúng quy trình trước khi cho tham gia phục vụ.

  1. Bật server, để service MongoDB tự khởi động (Get-Service xác nhận Running).

▶ Thực hiện trên: PRIMARY hiện tại — thực hiện add lại

  1. Kết nối vào đúng node đang PRIMARY, add lại node cũ vào replica set — MongoDB tự nhận diện và đồng bộ lại toàn bộ dữ liệu (initial sync), không cần chỉ định cấu hình phức tạp:
rs.add("<host node cũ>:27017")

Add lại node cũ vào replica set

Add lại node cũ (45.119.85.253) vào replica set — thành công

  1. Node cũ tự động đồng bộ lại toàn bộ dữ liệu từ Primary (ghi đè sạch dữ liệu cục bộ cũ). Xác nhận rs.status() cho thấy node cũ đã ở SECONDARY, health: 1, syncSourceHost trỏ đúng về Primary hiện tại:
rs.status()

Xác nhận node cũ đã rejoin thành công

Xác nhận node cũ (45.119.85.253) đã rejoin thành công, ở trạng thái SECONDARY, đồng bộ sạch