Cuộc Gọi Lúc 2 Giờ Sáng
Lúc 2:17 sáng, tôi nhận được tin nhắn Slack: “MongoDB đang báo lỗi, không thể kết nối.” Sau nửa tiếng tìm hiểu, vấn đề thực sự không phải là lỗi kết nối — instance MongoDB tự host của chúng tôi đã vượt giới hạn bộ nhớ trên một VPS nhỏ, và kỹ sư trực đêm đã vô tình dừng service khi cố khởi động lại nó. Điều khiến tôi trăn trở không phải là sự cố đó. Mà là cuộc trò chuyện vào lúc 9 giờ sáng hôm sau: “Sao chúng ta lại dùng MongoDB? Chúng ta đã có PostgreSQL rồi mà.”
Một tuần tìm hiểu sau đó, tôi có được một cái tên: FerretDB.
Tôi đã dùng MySQL, PostgreSQL và MongoDB qua nhiều dự án với quy mô khác nhau. Mỗi cái đều có chỗ đứng riêng. MySQL cho các workload giao dịch.
PostgreSQL cho các truy vấn phức tạp và stack cần nhiều extension. MongoDB cho schema linh hoạt khi cần phát triển nhanh ở giai đoạn đầu. Nhưng MongoDB đi kèm với chi phí thực sự — không chỉ là mối lo về giấy phép từ khi chuyển sang SSPL năm 2018, mà còn là gánh nặng vận hành khi phải duy trì một hệ thống database riêng biệt với team nhỏ. FerretDB giải quyết bài toán này: giữ nguyên MongoDB drivers và tooling hiện có, chuyển dữ liệu vào PostgreSQL.
FerretDB Thực Sự Là Gì
FerretDB không phải là MongoDB fork. Đây là một proxy — một lớp dịch thuật nói MongoDB wire protocol ở một đầu và giao tiếp với PostgreSQL ở đầu kia. Ứng dụng của bạn kết nối với FerretDB hoàn toàn giống như kết nối với MongoDB, dùng cùng drivers, cùng connection strings, cùng queries. Phía sau hậu trường, FerretDB chuyển đổi các MongoDB operations thành SQL và thực thi trên một PostgreSQL database tiêu chuẩn.
Hai lợi ích cụ thể đến từ thiết kế này:
- Bạn có giấy phép 100% mã nguồn mở (Apache 2.0). Không SSPL, không lo bị vendor lock-in.
- Dữ liệu của bạn nằm trong PostgreSQL — ACID transactions, point-in-time recovery, và toàn bộ hệ sinh thái extension của PostgreSQL đều có sẵn miễn phí.
Cơ Chế Dịch Thuật Hoạt Động Như Thế Nào
Khi bạn chạy db.users.find({age: {$gt: 25}}), FerretDB nhận MongoDB wire protocol message, phân tích BSON query, và tạo ra PostgreSQL query trên một cột JSONB. Documents được lưu dưới dạng các hàng JSONB. Collections ánh xạ thành tables. Quá trình dịch thuật hoàn toàn vô hình với application code — ứng dụng của bạn không hề biết mình đang giao tiếp với PostgreSQL.
FerretDB vẫn đang trong quá trình trưởng thành — chưa đạt được sự tương đương tính năng đầy đủ với MongoDB. Hỗ trợ aggregation đang được cải thiện, nhưng các operator như $facet và $graphLookup chưa được triển khai đầy đủ. Hiệu năng cũng khác so với storage engine gốc của MongoDB. Tuy nhiên, với các workload đơn giản, nó hoạt động rất ổn định.
Chạy FerretDB với Docker
Bạn cần hai container: PostgreSQL (lưu trữ thực tế) và FerretDB (bộ dịch wire protocol). Đây là file docker-compose.yml tôi đã kiểm thử và đang sử dụng:
version: '3.8'
services:
postgres:
image: postgres:16
container_name: ferretdb-postgres
environment:
POSTGRES_USER: ferretdb
POSTGRES_PASSWORD: secret123
POSTGRES_DB: ferretdb
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- ferretdb-net
restart: unless-stopped
ferretdb:
image: ghcr.io/ferretdb/ferretdb:latest
container_name: ferretdb
environment:
FERRETDB_POSTGRESQL_URL: postgres://ferretdb:secret123@postgres:5432/ferretdb
ports:
- "27017:27017"
networks:
- ferretdb-net
depends_on:
- postgres
restart: unless-stopped
volumes:
postgres_data:
networks:
ferretdb-net:
driver: bridge
Khởi động stack:
docker compose up -d
Chờ PostgreSQL khoảng 15 giây để khởi tạo, sau đó kiểm tra output của FerretDB:
docker compose logs ferretdb
Tìm dòng Listening on 0.0.0.0:27017. Đó là tín hiệu cho thấy nó đang chấp nhận kết nối.
Kết Nối Với mongosh
Kết nối hoàn toàn như với một MongoDB instance thực sự:
mongosh mongodb://localhost:27017
Chưa có mongosh local? Chạy thẳng từ Docker mà không cần cài đặt gì:
docker run --rm -it --network ferretdb-net mongo:6 mongosh mongodb://ferretdb:27017
Sau khi kết nối, chạy vài thao tác để xác nhận mọi thứ hoạt động:
use testdb
db.servers.insertMany([
{ hostname: 'web-01', role: 'frontend', region: 'us-east' },
{ hostname: 'db-01', role: 'database', region: 'us-east' },
{ hostname: 'cache-01', role: 'cache', region: 'eu-west' }
])
db.servers.find({ region: 'us-east' })
db.servers.updateOne(
{ hostname: 'web-01' },
{ $set: { status: 'active' } }
)
db.servers.countDocuments()
Tất cả đều chạy được. Bây giờ mở PostgreSQL shell và xem dữ liệu đó thực sự được lưu ở đâu:
docker exec -it ferretdb-postgres psql -U ferretdb -d ferretdb
-- Xem các bảng được tạo ra
\dt
-- Truy vấn documents được lưu dưới dạng JSONB
SELECT * FROM testdb.servers_9c4bc50e LIMIT 5;
Các MongoDB documents của bạn là các hàng JSONB trong một PostgreSQL table. Nhìn thấy điều này có gì đó rất thú vị.
Kết Nối Từ Python
Code PyMongo hiện tại kết nối với FerretDB mà không cần thay đổi gì:
from pymongo import MongoClient
client = MongoClient('mongodb://localhost:27017')
db = client['myapp']
collection = db['events']
collection.insert_one({
'event': 'deployment',
'service': 'api',
'status': 'success',
'timestamp': '2024-01-15T02:17:00Z'
})
for doc in collection.find({'status': 'success'}):
print(doc)
Cùng driver. Cùng connection string. Cùng query API. FerretDB vô hình với application code.
Những Điều Có Thể Gây Rắc Rối Cho Bạn
Xác thực: Cấu hình trên không bật xác thực ở cấp MongoDB. Với bất kỳ môi trường nào ngoài local dev, hãy giữ FerretDB khỏi các port công khai — bind nó vào một private Docker network. Tài liệu của FerretDB hướng dẫn cấu hình username/password qua biến môi trường nếu bạn cần xác thực ở application layer.
Thiếu sót trong aggregation pipeline: Các stage phức tạp như $lookup qua nhiều collection hoặc các thao tác $group lồng sâu có thể không hoạt động như mong đợi. Hãy kiểm tra các aggregation pipeline cụ thể của bạn với FerretDB trước khi quyết định migrate — đừng giả định rằng mọi thứ đều được hỗ trợ.
Index quan trọng hơn ở đây: Không có index rõ ràng, các truy vấn JSONB có thể rất chậm. Tạo chúng theo cách tương tự như trong MongoDB:
db.servers.createIndex({ region: 1 })
db.servers.createIndex({ hostname: 1 }, { unique: true })
Chúng được chuyển đổi thành PostgreSQL B-tree indexes trên các JSONB paths. Đừng bỏ qua bước này — hiệu năng sẽ giảm đáng kể trên các collection lớn nếu thiếu index.
Tên bảng: FerretDB tạo tên bảng PostgreSQL kèm hậu tố hash (ví dụ: servers_9c4bc50e). Khi truy vấn PostgreSQL trực tiếp để monitoring hoặc debugging, chạy \dt trong psql để tìm tên thực tế trước khi viết SQL trực tiếp.
Khi Nào FerretDB Là Lựa Chọn Đúng Đắn
Chín tháng chạy FerretDB trong production với hai internal service. Đây là lúc tôi sẽ chọn dùng nó:
- Team đang vận hành PostgreSQL và muốn hợp nhất hạ tầng thay vì duy trì thêm một hệ thống database riêng biệt
- Dự án mà giấy phép SSPL của MongoDB là rào cản về compliance hoặc quan điểm mã nguồn mở
- Các công cụ nội bộ và microservice với khối lượng dữ liệu vừa phải và pattern truy vấn đơn giản
- Dự án mới muốn dùng document model nhưng vẫn tận dụng được công cụ backup, recovery và replication đã được kiểm chứng của PostgreSQL
Hãy bỏ qua nó nếu bạn phụ thuộc vào các tính năng đặc thù của MongoDB Atlas, time-series collections, tìm kiếm full-text nâng cao, hoặc workload high-throughput được tinh chỉnh riêng cho storage engine của MongoDB. Khoảng cách tính năng là có thật với các trường hợp sử dụng phức tạp.
Hai service chúng tôi đã migrate sau sự cố lúc 2 giờ sáng

