Mở đầu
Tập này đưa ứng dụng quản lý chi tiêu từ SQLite sang PostgreSQL, bật kết nối bền vững bằng CONN_MAX_AGE, dùng PgBouncer giảm tải database và thiết lập backup tự động. Kết quả: app chạy ổn định với PostgreSQL, không mở kết nối mới mỗi request, và có bản backup khôi phục được. Nối tiếp tập 3 về Gunicorn và Nginx, tập này lo phần dữ liệu phía sau — nơi một cấu hình sai có thể làm sập hệ thống dù web server vẫn chạy tốt.
1. Chuyển từ SQLite sang PostgreSQL
SQLite hợp để phát triển, nhưng trên production nó không chịu được nhiều ghi đồng thời và thiếu backup từ xa. Cài driver rồi khai báo database qua biến môi trường, không commit secret vào code như tập 2 đã dặn.
pip install "psycopg[binary]"
Cấu hình trong settings production:
import environenv = environ.Env()DATABASES = { "default": env.db("DATABASE_URL"),}
Với DATABASE_URL=postgres://chi_tieu_app:mat-khau-manh@db:5432/chi_tieu_prod, Django tự hiểu engine là PostgreSQL. Sau đó chạy python manage.py migrate. Nếu app có dữ liệu thật trên SQLite, đừng copy file: xuất bằng dumpdata rồi nạp lại bằng loaddata, kiểm tra kỹ khóa ngoại trước khi đổi server.
2. CONN_MAX_AGE: giữ kết nối thay vì mở mới mỗi request
Mặc định Django đóng kết nối database khi request kết thúc, nên với Gunicorn nhiều worker, mỗi request lại tốn một lần bắt tay TCP và xác thực:
DATABASES = { "default": { **env.db("DATABASE_URL"), "CONN_MAX_AGE": 60, "CONN_HEALTH_CHECKS": True, }}
CONN_MAX_AGE = 60 giữ mỗi kết nối tối đa 60 giây, còn CONN_HEALTH_CHECKS (từ Django 4.1) giúp Django loại bỏ kết nối đã chết trước khi dùng lại. Đừng đặt quá lớn: kết nối giữ lâu chiếm slot trên PostgreSQL.
3. PgBouncer: khi nhiều worker hơn số kết nối cho phép
PostgreSQL mặc định chỉ cho khoảng 100 kết nối đồng thời, trong khi vài worker Gunicorn đã có thể vượt ngưỡng. PgBouncer gom nhiều kết nối của app thành ít kết nối thật:
[databases]chi_tieu_prod = host=db port=5432 dbname=chi_tieu_prod[pgbouncer]listen_port = 6432pool_mode = transactiondefault_pool_size = 20max_client_conn = 200
Chế độ transaction phổ biến nhất: kết nối trả về pool ngay khi transaction kết thúc, nên hàng trăm client chạy tốt trên vài chục kết nối thật. Lưu ý: ở chế độ này, Django phải thêm "DISABLE_SERVER_SIDE_CURSORS": True vào OPTIONS của DATABASES, vì cursor phía server không sống qua transaction.
4. Backup cơ bản: chưa từng restore thì chưa có backup
Backup đơn giản nhất là pg_dump chạy theo lịch, nén lại và đẩy sang nơi khác server database — ví dụ:
pg_dump -h db -U chi_tieu_app chi_tieu_prod | gzip > /backup/chi_tieu_prod_$(date +%F).sql.gz
Chạy lệnh này bằng cron mỗi đêm, giữ 7 bản gần nhất và đồng bộ lên object storage. Quan trọng hơn: mỗi tháng diễn tập restore một lần lên database trống để xác nhận dữ liệu đọc được.
Áp dụng vào dự án của bạn
Với ứng dụng quản lý chi tiêu: chuyển sang PostgreSQL trên staging trước, bật CONN_MAX_AGE = 60, theo dõi số kết nối bằng SELECT count(*) FROM pg_stat_activity;, rồi mới đưa PgBouncer vào khi chạm ngưỡng. Với vài chục người dùng đồng thời, kết nối bền vững cộng backup mỗi đêm đã đủ.
Bài viết liên quan
- Django vận hành production #1: Giới thiệu series + checklist production-readiness
- Django vận hành production #2: Tách settings dev/staging/prod + biến môi trường và secrets
- Django vận hành production #3: Gunicorn + Nginx — chạy Django sau reverse proxy
Câu hỏi thường gặp
Có nên dùng SQLite trên production nếu app còn nhỏ không?
Không nên, trừ khi app chỉ đọc và gần như không có ghi đồng thời. SQLite khóa toàn bộ file khi ghi, nên hai request ghi cùng lúc khiến một request phải chờ.
CONN_MAX_AGE đặt càng lớn càng tốt không?
Không. Giá trị lớn giữ kết nối lâu, dễ chạm trần max_connections. Bắt đầu ở 60 giây, quan sát pg_stat_activity rồi mới tăng nếu mở kết nối thực sự là nút thắt.
Khi nào bắt buộc phải dùng PgBouncer?
Khi tổng số worker và process vượt quá số kết nối PostgreSQL cho phép, hoặc nhiều service cùng trỏ vào một database. Dưới ngưỡng đó, CONN_MAX_AGE là đủ.
Nguồn tham khảo
Django: Databases, Persistent connections (docs.djangoproject.com); PgBouncer: Configuration, pool modes (pgbouncer.github.io); PostgreSQL: pg_dump, max_connections (postgresql.org).
