Apache CouchDB: Cài đặt, replication đa chiều và đồng bộ offline-first thực chiến

Database tutorial - IT technology blog
Database tutorial - IT technology blog

Chạy CouchDB trong 5 phút — làm trước, hỏi sau

Mình sẽ không kể lý thuyết ngay. Cài lên, chạy thử, rồi mới hiểu tại sao nó khác.

Cài đặt trên Ubuntu/Debian

# Thêm GPG key và repository chính thức của Apache CouchDB
curl -L https://couchdb.apache.org/repo/keys.asc | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/couchdb.gpg
echo "deb https://apache.jfrog.io/artifactory/couchdb-deb/ $(lsb_release -cs) main" | \
  sudo tee /etc/apt/sources.list.d/couchdb.list

sudo apt update && sudo apt install -y couchdb

Wizard cài đặt hỏi chế độ: chọn standalone nếu chạy 1 node, clustered nếu muốn nhiều máy. Đặt mật khẩu admin ngay ở bước này — không có lần thứ hai.

Xác nhận service đã lên:

sudo systemctl status couchdb
curl http://localhost:5984/
# Kết quả mong đợi:
# {"couchdb":"Welcome","version":"3.x.x", ...}

Tạo database và document đầu tiên

# Tạo database tên "myapp"
curl -X PUT http://admin:yourpassword@localhost:5984/myapp

# Thêm document JSON đầu tiên
curl -X POST http://admin:yourpassword@localhost:5984/myapp \
  -H "Content-Type: application/json" \
  -d '{"name": "Nguyen Van A", "role": "developer", "city": "Hanoi"}'

# Xem toàn bộ document trong database
curl http://admin:yourpassword@localhost:5984/myapp/_all_docs?include_docs=true

Truy cập http://localhost:5984/_utils để vào Fauxton — Web UI tích hợp sẵn, không cài thêm gì. Tạo, xem, sửa document bằng tay ở đây rất nhanh khi đang test.


Mình đã dùng MySQL, PostgreSQL, MongoDB rồi — CouchDB giải quyết bài toán gì khác?

Làm việc với cả ba loại trên ở nhiều dự án, mình thấy mỗi cái có vị trí riêng. MySQL/PostgreSQL tốt nhất khi dữ liệu có cấu trúc cố định, quan hệ rõ ràng. MongoDB linh hoạt schema hơn, nhưng replication thiên về primary-secondary — node secondary chỉ đọc được, không ghi.

CouchDB giải quyết bài toán khác hẳn: ứng dụng phải hoạt động khi không có mạng, rồi đồng bộ lại khi mạng về. App cho nhân viên hiện trường, POS ở vùng sóng yếu, hệ thống logistics ngoài đồng — đây là địa hạt của CouchDB.

Ba điểm mà mình thấy CouchDB thực sự khác

  • HTTP API thuần túy: Mọi thao tác đều qua REST. Không cần driver đặc biệt — curl là đủ để bắt đầu.
  • MVCC (Multi-Version Concurrency Control): Không dùng lock khi ghi. Mỗi document có trường _rev tracking revision. Đọc không bị block bởi ghi, throughput đọc rất tốt.
  • Replication multi-master: Mọi node đều ghi được. Khi sync, CouchDB tự phát hiện conflict — bạn quyết định xử lý thế nào, không phải database.

Document model của CouchDB

Mỗi document là JSON với _id (tự sinh hoặc tự đặt) và _rev (revision hash):

{
  "_id": "order_20260710_001",
  "_rev": "1-abc123def456",
  "customer": "Tran Thi B",
  "items": [
    {"sku": "LAP001", "qty": 2, "price": 15000000},
    {"sku": "MOU002", "qty": 1, "price": 350000}
  ],
  "total": 30350000,
  "status": "pending",
  "created_at": "2026-07-10T09:00:00Z"
}

Mỗi lần update, _rev thay đổi. Nếu hai node cùng sửa một document khi offline, CouchDB giữ cả hai phiên bản và đánh dấu conflict — bạn quyết định merge sau.


Cấu hình Replication — phần mà CouchDB làm tốt nhất

Replication một chiều (one-way)

# Sao chép database "myapp" từ server A sang server B
curl -X POST http://admin:pass@localhost:5984/_replicate \
  -H "Content-Type: application/json" \
  -d '{
    "source": "http://admin:pass@server-a:5984/myapp",
    "target": "http://admin:pass@server-b:5984/myapp",
    "create_target": true
  }'

Continuous Replication (đồng bộ liên tục)

# "continuous": true để replication chạy mãi, không dừng sau 1 lần
curl -X POST http://admin:pass@localhost:5984/_replicate \
  -H "Content-Type: application/json" \
  -d '{
    "source": "http://admin:pass@server-a:5984/myapp",
    "target": "http://admin:pass@server-b:5984/myapp",
    "continuous": true,
    "create_target": true
  }'

Bidirectional Replication (đồng bộ 2 chiều)

Chạy 2 job song song — một chiều A→B, một chiều B→A:

# Chiều A → B
curl -X POST http://admin:pass@server-a:5984/_replicate \
  -H "Content-Type: application/json" \
  -d '{"source": "myapp", "target": "http://admin:pass@server-b:5984/myapp", "continuous": true}'

# Chiều B → A
curl -X POST http://admin:pass@server-b:5984/_replicate \
  -H "Content-Type: application/json" \
  -d '{"source": "myapp", "target": "http://admin:pass@server-a:5984/myapp", "continuous": true}'

Sau khi xong, hai server tự đồng bộ mỗi khi có thay đổi — kể cả khi một bên vừa quay lại online sau thời gian offline. Không cần làm gì thêm.


Xử lý Conflict sau khi đồng bộ

Conflict xảy ra thường xuyên hơn bạn nghĩ — nhất là khi nhân viên dùng app ở vùng 3G chập chờn, offline 2 tiếng rồi sync lại. CouchDB không tự merge nội dung — nó để bạn chọn revision nào thắng.

import requests

base = "http://admin:yourpassword@localhost:5984"

# Tìm document đang có conflict
def find_conflicts(db):
    url = f"{base}/{db}/_all_docs?include_docs=true&conflicts=true"
    docs = requests.get(url).json()["rows"]
    return [row["doc"] for row in docs if "_conflicts" in row.get("doc", {})]

# Lấy các revision đang conflict
def get_conflict_revs(db, doc_id, conflict_revs):
    results = []
    for rev in conflict_revs:
        url = f"{base}/{db}/{doc_id}?rev={rev}"
        results.append(requests.get(url).json())
    return results

# Xóa revision thua (giữ revision bạn muốn)
def resolve_conflict(db, doc_id, losing_rev):
    url = f"{base}/{db}/{doc_id}?rev={losing_rev}"
    resp = requests.delete(url)
    print(f"Deleted losing rev: {resp.status_code}")

# Ví dụ sử dụng
conflicts = find_conflicts("myapp")
for doc in conflicts:
    print(f"Conflict on: {doc['_id']}")
    revs = get_conflict_revs("myapp", doc["_id"], doc["_conflicts"])
    # Giữ revision hiện tại (winning), xóa các revision conflict
    for rev in doc["_conflicts"]:
        resolve_conflict("myapp", doc["_id"], rev)

Logic merge phụ thuộc nghiệp vụ: đơn hàng thì lấy cái mới nhất theo created_at, log hoạt động thì cần giữ cả hai. CouchDB cung cấp cơ chế — bạn viết rule.


4 điều nên biết trước khi dùng CouchDB thực chiến

1. Kết hợp PouchDB ở client — bộ đôi offline-first hoàn chỉnh

PouchDB là thư viện JavaScript (~46KB gzipped) chạy ngay trong browser hoặc React Native. API tương tự CouchDB, tự đồng bộ hai chiều khi mạng về:

// Trong ứng dụng web/React Native
const db = new PouchDB('local_myapp');
const remoteDB = new PouchDB('http://admin:pass@yourserver:5984/myapp');

// Bật sync 2 chiều, tự động reconnect khi mạng về
db.sync(remoteDB, { live: true, retry: true })
  .on('change', info => console.log('Synced:', info))
  .on('error', err => console.error('Sync error:', err));

Người dùng nhập liệu offline bình thường. Khi mạng về, PouchDB tự đẩy lên và kéo về — không cần code thêm gì.

2. Tạo index để query nhanh hơn

Mặc định CouchDB dùng views (MapReduce) — cồng kềnh để viết. Từ CouchDB 2.x có Mango Query, dễ hơn nhiều:

# Tạo index trên field "status" + "created_at"
curl -X POST http://admin:pass@localhost:5984/myapp/_index \
  -H "Content-Type: application/json" \
  -d '{"index": {"fields": ["status", "created_at"]}, "name": "status-index"}'

# Query dùng Mango syntax
curl -X POST http://admin:pass@localhost:5984/myapp/_find \
  -H "Content-Type: application/json" \
  -d '{
    "selector": {"status": "pending"},
    "sort": [{"created_at": "desc"}],
    "limit": 20
  }'

3. Không dùng CouchDB khi nào?

  • Cần JOIN phức tạp giữa nhiều bảng → dùng PostgreSQL.
  • Dữ liệu time-series, metric → dùng TimescaleDB hoặc InfluxDB.
  • Cache tốc độ cao → dùng Redis.
  • CouchDB phù hợp khi: dữ liệu dạng document, cần offline-first, cần multi-master mà không muốn xử lý distributed lock.

4. Bật CORS nếu dùng PouchDB từ browser

curl -X PUT http://admin:pass@localhost:5984/_node/nonode@nohost/_config/httpd/enable_cors \
  -d '"true"'
curl -X PUT http://admin:pass@localhost:5984/_node/nonode@nohost/_config/cors/origins \
  -d '"*"'
curl -X PUT http://admin:pass@localhost:5984/_node/nonode@nohost/_config/cors/methods \
  -d '"GET,PUT,POST,HEAD,DELETE"'

Đổi "*" thành domain cụ thể khi lên production. Để "*" lúc dev cho tiện, nhưng đừng để vậy khi deploy thật.

CouchDB không phải database cho mọi bài toán. Nhưng khi app cần chạy khi mất mạng rồi sync lại, hoặc nhiều node phải ghi đồng thời mà không muốn đau đầu với distributed lock — nó xử lý đúng việc đó, không cần workaround.

Share: