Fix MongoServerError: not primary Khi Ghi Dữ Liệu vào MongoDB Replica Set

intermediate🍃 MongoDB2026-03-18| MongoDB 4.x / 5.x / 6.x, triển khai Replica Set, mọi hệ điều hành (Linux/macOS/Windows), drivers: Node.js (mongoose/mongodb), Python (pymongo), Java

Error Message

MongoServerError: not primary
#mongodb#replicaset#primary#write-concern

Mô Tả Lỗi

Bạn thực hiện insert, update hoặc delete trên MongoDB cluster và nhận được lỗi này:

MongoServerError: not primary

Các thao tác ghi đột ngột bị từ chối. Chín trong mười trường hợp, ứng dụng của bạn đang kết nối tới secondary node thay vì primary. Trường hợp còn lại: primary vừa từ chức và quá trình bầu chọn chưa hoàn tất.

Nguyên Nhân

Trong một replica set, chỉ có primary mới nhận thao tác ghi. Các secondary là read-only — không có ngoại lệ. Lỗi not primary xuất hiện trong một số tình huống cụ thể:

  • Connection string trỏ thẳng vào IP/port của secondary thay vì dùng URI của replica set.
  • Vừa xảy ra failover — primary cũ đã từ chức và chưa có primary mới được bầu chọn.
  • Read preference được đặt là secondary hoặc secondaryPreferred, nhưng thao tác ghi lại đi qua cùng một connection.
  • Một hidden hoặc delayed secondary được tạm thời thăng cấp trong quá trình bảo trì.
  • DNS hoặc load balancer đang định tuyến traffic đến sai node.

Bước 1 — Kiểm Tra Trạng Thái Replica Set

Kết nối vào bất kỳ node nào và chạy lệnh:

rs.status()

Kiểm tra mảng members và xem trường stateStr của từng node:

{
  "members" : [
    { "name" : "mongo1:27017", "stateStr" : "PRIMARY" },
    { "name" : "mongo2:27017", "stateStr" : "SECONDARY" },
    { "name" : "mongo3:27017", "stateStr" : "SECONDARY" }
  ]
}

Tất cả node đều hiển thị SECONDARY? Hoặc có node bị kẹt ở trạng thái RECOVERING? Replica set hiện không có primary. Đây là trạng thái bình thường trong quá trình bầu chọn — hãy đợi 10–30 giây rồi kiểm tra lại.

Để xác nhận bạn đang kết nối vào node nào:

db.isMaster()
// hoặc lệnh tương đương mới hơn:
db.hello()

Nếu ismaster (hoặc isWritablePrimary) trả về false, bạn đang ở trên secondary. Đó chính là vấn đề.

Cách Sửa 1 — Dùng Connection String Của Replica Set

Đây là nguyên nhân phổ biến nhất. Kết nối tới một node duy nhất khiến driver không biết gì về topology của replica set:

# Sai — một node duy nhất, không nhận biết replica set
mongodb://mongo1:27017/mydb

Chuyển sang URI đầy đủ liệt kê tất cả các thành viên kèm tham số replicaSet:

# Đúng — driver tự động tìm primary
mongodb://mongo1:27017,mongo2:27017,mongo3:27017/mydb?replicaSet=rs0

Lúc này driver tự xử lý failover. Primary thay đổi? Driver sẽ tự tìm primary mới. Bạn không cần làm gì.

Trên Atlas, dùng định dạng SRV — thông tin replica set đã được tích hợp sẵn:

mongodb+srv://user:pass@cluster0.xxxxx.mongodb.net/mydb

Cách Sửa 2 — Cập Nhật Kết Nối Node.js / Mongoose

// Trước (lỗi)
const uri = 'mongodb://192.168.1.10:27017/mydb';

// Sau (nhận biết replica set)
const uri = 'mongodb://192.168.1.10:27017,192.168.1.11:27017,192.168.1.12:27017/mydb?replicaSet=rs0';

await mongoose.connect(uri, {
  serverSelectionTimeoutMS: 5000,
  heartbeatFrequencyMS: 10000,
});

Cách Sửa 3 — Cập Nhật Kết Nối Python / PyMongo

from pymongo import MongoClient

# Trước (lỗi)
client = MongoClient('mongodb://192.168.1.10:27017/')

# Sau (nhận biết replica set)
client = MongoClient(
    'mongodb://192.168.1.10:27017,192.168.1.11:27017,192.168.1.12:27017/',
    replicaSet='rs0',
    serverSelectionTimeoutMS=5000
)

# Xác nhận bạn đang kết nối vào primary
print(client.primary)  # Kết quả cần là ('192.168.1.10', 27017)

Cách Sửa 4 — Trong Quá Trình Failover (Trạng Thái Tạm Thời)

Đôi khi cluster vừa mất primary — do crash, network partition hoặc bảo trì có kế hoạch. Quá trình bầu chọn cần thời gian. Theo dõi trực tiếp bằng lệnh:

# Chạy vòng lặp này trong mongosh
while true; do
  mongosh --quiet --eval "rs.status().members.forEach(m => print(m.name, m.stateStr))"
  sleep 3
done

Trong điều kiện bình thường, quá trình bầu chọn hoàn tất trong vòng 10 giây. Nếu kéo dài hơn, hãy kiểm tra ba điều sau:

  • Bạn có ít nhất 3 node không? Cần đa số phiếu để bầu chọn primary.
  • Kết nối mạng giữa các node có ổn định không?
  • Có node nào bị kẹt ở trạng thái RECOVERING không?

Cần kích hoạt bầu chọn mới thủ công? Kết nối vào primary hiện tại và chạy:

rs.stepDown()  // Cho primary hiện tại từ chức, kích hoạt bầu chọn mới

Cách Sửa 5 — Kiểm Tra Cài Đặt writeConcern

Dễ bỏ sót: đặt readPreferencesecondary cho thao tác ghi là vô nghĩa — và có thể gây ra lỗi này. Kiểm tra lại các lệnh ghi của bạn:

// Sai — read preference secondary áp dụng cho thao tác ghi
db.collection('orders').insertOne(
  { item: 'widget' },
  { readPreference: 'secondary' }  // Cái này không áp dụng cho ghi
);

// Đúng — write concern tách biệt với read preference
db.collection('orders').insertOne(
  { item: 'widget' },
  { writeConcern: { w: 'majority', wtimeout: 5000 } }
);

Kiểm Tra Sau Khi Sửa

Sau khi cập nhật connection string, chạy thử nghiệm nhanh từ mongosh:

use mydb
db.test.insertOne({ ping: new Date() })
// Kết quả cần là: { acknowledged: true, insertedId: ... }

Lỗi MongoServerError: not primary sẽ không còn xuất hiện nữa. Để xác nhận driver đã kết nối vào primary:

# Node.js — ghi log topology
mongoose.connection.on('connected', () => {
  console.log('Đã kết nối tới:', mongoose.connection.host);
});

Phòng Tránh

  • Luôn dùng connection string của replica set trong môi trường production. Hardcode IP của một node duy nhất là quả bom hẹn giờ — failover sẽ xảy ra, thường vào lúc 3 giờ sáng.
  • Đặt timeout tích cực. Cấu hình serverSelectionTimeoutMSconnectTimeoutMS để ứng dụng thất bại nhanh và thử lại thay vì treo trong quá trình bầu chọn.
  • Bật retryable writes. Thêm retryWrites=true vào connection string (mặc định trong hầu hết driver hiện đại). Các thay đổi primary tạm thời sẽ trở nên vô hình với ứng dụng.
  • Theo dõi sức khỏe replica set chủ động. Cảnh báo khi có node chuyển sang trạng thái RECOVERING hoặc UNKNOWN — đừng chờ người dùng báo lỗi ghi.
  • Tách client đọc và ghi. Đọc từ secondary để phân tải? Dùng client riêng với readPreference: 'secondaryPreferred'. Giữ client ghi chỉ kết nối tới primary.
# Connection string đầy đủ sẵn sàng cho production
mongodb://mongo1:27017,mongo2:27017,mongo3:27017/mydb
  ?replicaSet=rs0
  &retryWrites=true
  &w=majority
  &readPreference=primaryPreferred
  &serverSelectionTimeoutMS=5000

Related Error Notes