Cấu hình Prometheus và Grafana để giám sát PostgreSQL
Triển khai Prometheus và Exporter cho PostgreSQL
Để giám sát PostgreSQL, bạn cần cài đặt Prometheus để thu thập metrics và PostgreSQL Exporter để chuyển đổi dữ liệu từ DB sang định dạng Prometheus.
Trước tiên, cập nhật hệ thống và cài đặt Prometheus cùng PostgreSQL Exporter thông qua gói APT hoặc Docker. Ở đây mình dùng Docker để dễ quản lý và tách biệt môi trường.
Việc này tạo ra container Prometheus chạy trên cổng 9090 và Exporter chạy trên cổng 9187 để PostgreSQL kết nối.
sudo apt update && sudo apt install -y docker.io docker-compose
Kết quả: Docker và Docker Compose được cài đặt sẵn sàng trên Ubuntu 24.04.
Tạo thư mục cấu hình Prometheus và file `prometheus.yml` để định nghĩa target là PostgreSQL Exporter.
sudo mkdir -p /opt/prometheus && sudo chown $USER:$USER /opt/prometheus
Kết quả: Thư mục `/opt/prometheus` được tạo với quyền sở hữu của user hiện tại.
Viết file cấu hình `prometheus.yml` để chỉ định scrape config cho exporter. Lưu ý: `postgresql-exporter` cần biến môi trường `DATA_SOURCE_URI` để biết kết nối vào DB nào.
sudo tee /opt/prometheus/prometheus.yml
Kết quả: File `/opt/prometheus/prometheus.yml` được tạo với cấu hình scrape 2 target.
Tạo file `docker-compose.yml` để khởi động cả Prometheus, Grafana và Exporter cùng lúc. Cấu hình này bao gồm volume để lưu dữ liệu và mapping cổng ra host.
sudo tee /opt/prometheus/docker-compose.yml
Kết quả: File `docker-compose.yml` hoàn chỉnh, sẵn sàng khởi tạo 3 service.
Khởi động toàn bộ stack giám sát bằng lệnh `docker-compose up -d`. Đảm bảo user `admin` trong DB đã tồn tại và có quyền `SELECT` trên catalog.
cd /opt/prometheus && sudo docker-compose up -d
Kết quả: 3 container (prometheus, grafana, postgres-exporter) chạy trạng thái "running".
Import Dashboard Multi-Tenant vào Grafana
Sau khi Prometheus chạy, cần vào Grafana để thêm datasource và import dashboard chuyên biệt cho PostgreSQL Multi-Tenant.
Truy cập Grafana qua trình duyệt tại địa chỉ `http://:3000` với tài khoản `admin/admin`. Kết quả: Giao diện quản lý Grafana hiện ra.
Vào menu "Configuration" (biểu tượng bánh răng) > "Data sources" > "Add new data source". Chọn "Prometheus" và điền URL là `http://prometheus:9090`. Nhấn "Save & Test".
Kết quả: Thông báo "Data source is working" xuất hiện.
Để có dashboard tối ưu cho Multi-Tenant, mình sẽ import dashboard có ID 14869 (PostgreSQL Dashboard) từ Grafana.com. Vào menu "Dashboards" > "Import" > Nhập ID `14869` > Chọn datasource Prometheus vừa tạo > "Import".
Kết quả: Dashboard hiển thị các biểu đồ CPU, Memory, Connections, và Replication Lag.
Verify kết quả giám sát
Để xác nhận hệ thống hoạt động, truy cập vào Prometheus tại `http://:9090/graph` và gõ query `up{job="postgresql"}`. Kết quả phải trả về giá trị `1`.
Trong Grafana, quan sát biểu đồ "Connections" để thấy số lượng kết nối từ các tenant khác nhau. Nếu thấy spike bất thường, hệ thống đã bắt đầu cảnh báo.
Theo dõi các chỉ số quan trọng trong môi trường Multi-Tenant
Giám sát Connection Usage và Max Connections
Trong mô hình Multi-Tenant, tài nguyên kết nối là nút cổ chai lớn nhất. Cần theo dõi tỷ lệ sử dụng kết nối so với `max_connections` để tránh tình trạng "Too many connections".
Query Prometheus để lấy số lượng kết nối hiện tại của từng tenant (dựa trên `datname` nếu dùng mô hình Database riêng, hoặc `pg_stat_activity` nếu dùng Schema).
pg_stat_activity_count{datname="tenant_a"} - pg_stat_activity_count{datname="tenant_b"}
Kết quả: Hiển thị số lượng kết nối chênh lệch giữa hai tenant, giúp phát hiện tenant nào đang chiếm dụng quá nhiều.
Tạo Alert Rule trong Prometheus để cảnh báo khi tỷ lệ kết nối vượt quá 80% ngưỡng cho phép. Truy cập Prometheus UI > "Alerting" > "New Rule".
prometheus_alerts:
groups:
- name: postgresql_multi_tenant
rules:
- alert: HighConnectionUsage
expr: (pg_stat_activity_count / pg_settings_max_connections) > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "High connection usage detected on instance {{ \$labels.instance }}"
description: "Connection usage is at {{ \$value | humanizePercentage }}."
Kết quả: Alert rule được lưu, Prometheus sẽ kích hoạt cảnh báo khi điều kiện thỏa mãn.
Phát hiện Lock Wait và Deadlocks
Multi-Tenant thường xảy ra xung đột khi nhiều tenant cùng truy cập vào cùng một bảng hệ thống hoặc chỉ mục chung. Cần theo dõi thời gian chờ lock (lock wait time).
Query để tìm các transaction đang chờ lock lâu hơn 5 giây. Biểu thức này dựa vào metric `pg_locks_mode` hoặc `pg_stat_activity` nếu exporter hỗ trợ.
pg_stat_activity_lock_wait_seconds > 5
Kết quả: Danh sách các query đang bị block, kèm theo `datname` (tenant) và `query` gây ra block.
Để phát hiện Deadlock, cần theo dõi metric `pg_deadlocks_total`. Nếu metric này tăng đột ngột trong khoảng thời gian ngắn, có thể có lỗi logic trong code ứng dụng.
rate(pg_deadlocks_total[5m]) > 0
Kết quả: Cảnh báo khi có deadlock xảy ra, giúp admin can thiệp ngay lập tức.
Xác định Slow Queries theo Tenant
Quan trọng nhất là biết tenant nào đang chạy query chậm. Sử dụng `pg_stat_statements` extension đã được bật trong phần trước để lấy metrics.
Query Prometheus để lấy top 5 query chậm nhất của mỗi tenant, tính bằng thời gian thực thi trung bình (mean execution time).
topk(5, pg_stat_statements_mean_exec_time_seconds{datname=~"tenant_.*"} by (datname, query))
Kết quả: Danh sách các query chậm kèm tenant cụ thể, giúp ưu tiên tối ưu hóa.
Verify kết quả giám sát
Vào Grafana Dashboard, tìm panel "Connections" và "Lock Wait". Nếu có tenant bị lock, panel sẽ hiện màu đỏ. Query Prometheus trực tiếp để kiểm tra giá trị `pg_stat_activity_count` của tenant đang test.
Phân tích và tối ưu hóa truy vấn chậm (Slow Queries)
Cấu hình Log Slow Query trong PostgreSQL
Để phân tích sâu hơn, cần bật log cho các query chạy lâu hơn ngưỡng nhất định (ví dụ 1000ms). Điều này giúp ghi lại full query vào log file để phân tích.
Chỉnh sửa file cấu hình `postgresql.conf`. Tìm tham số `log_min_duration_statement` và đặt giá trị là `1000ms` (hoặc `1s`).
sudo tee -a /etc/postgresql/16/main/postgresql.conf
Kết quả: File config được cập nhật, cần reload config để.
Reload cấu hình PostgreSQL mà không cần restart service để áp dụng thay đổi ngay lập tức.
sudo systemctl reload postgresql
Kết quả: PostgreSQL reload config thành công, bắt đầu log các query > 1s vào log file.
Phân tích log với pgBadger hoặc grep
Log file thường nằm tại `/var/log/postgresql/postgresql-16-main.log`. Sử dụng `grep` để lọc các dòng chứa "duration" để tìm query chậm.
grep "duration" /var/log/postgresql/postgresql-16-main.log | grep "tenant_a" | head -n 10
Kết quả: Hiển thị 10 dòng log đầu tiên của các query chậm thuộc tenant `tenant_a`.
Để phân tích chuyên sâu, cài đặt công cụ `pgBadger` để tạo báo cáo HTML chi tiết về slow queries, bao gồm top query, index usage, và wait events.
sudo apt install -y pgbadger && pgbadger /var/log/postgresql/postgresql-16-main.log -o /var/log/postgresql/pgbadger_report.html
Kết quả: File HTML `pgbadger_report.html` được tạo, mở bằng trình duyệt để xem phân tích chi tiết.
Chiến lược tối ưu hóa cho Multi-Tenant
Nếu phát hiện một query chậm ảnh hưởng đến nhiều tenant, hãy xem xét việc thêm Index. Tuy nhiên, trong mô hình Schema riêng, index phải được tạo cho từng schema hoặc dùng partitioning.
Ví dụ: Tạo index cho bảng `orders` của tenant `tenant_a` nếu query `SELECT * FROM orders WHERE created_at > ...` bị chậm.
psql -U admin -d tenant_a -c "CREATE INDEX CONCURRENTLY idx_orders_created_at ON orders (created_at);"
Kết quả: Index được tạo không khóa bảng (CONCURRENTLY), không làm gián đoạn dịch vụ.
Đối với tenant có dữ liệu quá lớn, cân nhắc sử dụng Table Partitioning. Chia bảng theo tháng hoặc theo tenant ID để giảm độ lớn của từng partition, giúp query nhanh hơn.
psql -U admin -d postgres -c "ALTER TABLE orders PARTITION BY RANGE (created_at);"
Kết quả: Bảng `orders` được chuyển thành partitioned table, sẵn sàng thêm các child partition.
Verify kết quả tối ưu hóa
Chạy lại query chậm trước đó và quan sát thời gian thực hiện. Nếu có index, thời gian giảm đáng kể. Kiểm tra lại panel "Slow Queries" trong Grafana, giá trị `mean_exec_time` phải giảm xuống dưới ngưỡng cảnh báo.
Xử lý các lỗi thường gặp (Troubleshooting)
Khắc phục lỗi Connection Limit (Too many connections)
Khi hệ thống báo lỗi "too many connections", PostgreSQL đã đạt `max_connections`. Cần tăng giới hạn hoặc sử dụng Connection Pooler (PgBouncer).
Để tăng tạm thời `max_connections`, chỉnh sửa `postgresql.conf` thành giá trị cao hơn (ví dụ 500), sau đó restart service. Lưu ý: mỗi connection tốn RAM, cân nhắc tài nguyên server.
sudo sed -i 's/#max_connections = 100/max_connections = 500/' /etc/postgresql/16/main/postgresql.conf && sudo systemctl restart postgresql
Kết quả: PostgreSQL restart với giới hạn kết nối mới, lỗi tạm thời được giải quyết.
Giải pháp bền vững là cài đặt PgBouncer. PgBouncer đóng vai trò trung gian, pooling các kết nối từ ứng dụng và chia sẻ chúng cho PostgreSQL, giảm tải cho DB.
sudo apt install -y pgbouncer && sudo systemctl enable pgbouncer
Kết quả: PgBouncer được cài đặt và tự động khởi động.
Cấu hình file `pgbouncer.ini` để chỉ định pool mode là `transaction` (tối ưu cho web app) và giới hạn pool size.
sudo tee /etc/pgbouncer/pgbouncer.ini
Kết quả: PgBouncer cấu hình sẵn sàng, lắng nghe cổng 6432.
Xử lý lỗi Disk Full
Lỗi "no space left on device" thường do WAL files (Write-Ahead Log) hoặc backup files tràn ổ. PostgreSQL sẽ dừng hoạt động nếu không thể ghi WAL.
Đầu tiên, xóa các file backup cũ hoặc log không cần thiết để giải phóng dung lượng tạm thời.
sudo find /var/lib/postgresql/16/main/pg_wal -name "*.backup" -mtime +7 -delete
Kết quả: Các file backup WAL cũ hơn 7 ngày bị xóa, giải phóng dung lượng.
Nếu do WAL không được archive kịp thời, kiểm tra cấu hình `archive_mode` và `archive_command`. Nếu đang bật nhưng không chạy, tạm thời tắt archive_mode để DB hoạt động lại.
sudo -u postgres psql -c "ALTER SYSTEM SET archive_mode = off; SELECT pg_reload_conf();"
Kết quả: Archive mode tắt, PostgreSQL tiếp tục ghi WAL vào thư mục local mà không cần gọi command archive, DB hoạt động trở lại.
Xử lý lỗi OOM Killer (Out of Memory)
Khi server hết RAM, Linux OOM Killer sẽ giết tiến trình PostgreSQL để bảo vệ hệ thống. Dấu hiệu là PostgreSQL tự tắt đột ngột.
Giảm tham số `shared_buffers` và `work_mem` trong `postgresql.conf` để giảm áp lực RAM. Không nên đặt `shared_buffers` quá 25% tổng RAM.
sudo tee /etc/postgresql/16/main/postgresql.conf
Kết quả: File config được cập nhật với các giá trị an toàn hơn.
Khởi động lại PostgreSQL để áp dụng. Sau đó, cấu hình `swappiness` của Linux để ưu tiên dùng RAM trước khi swap, giúp DB ổn định hơn.
sudo sysctl -w vm.swappiness=10 && echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
Kết quả: Hệ thống ưu tiên giữ dữ liệu trong RAM, giảm khả năng OOM Killer can thiệp.
Verify kết quả Troubleshooting
Restart ứng dụng và tạo load test. Quan sát log `/var/log/syslog` để đảm bảo không còn thông báo "Out of memory: Kill process". Kiểm tra lại số lượng kết nối trong Grafana, đảm bảo không vượt ngưỡng.
Các mẹo nâng cao để cân bằng tải (Load Balancing) giữa các Tenant
Phân bổ tài nguyên theo Tenant (Resource Quotas)
Để một tenant không chiếm hết tài nguyên, sử dụng extension `pg_cron` hoặc `pg_stat_statements` để giới hạn số lượng query hoặc thời gian CPU cho từng tenant.
Một cách hiệu quả là sử dụng `pg_prewarm` để preload các tenant thường xuyên truy cập vào RAM, giảm I/O disk.
sudo apt install -y postgresql-16-prewarm && sudo -u postgres psql -c "CREATE EXTENSION IF NOT EXISTS pg_prewarm;"
Kết quả: Extension `pg_prewarm` được cài đặt, sẵn sàng preload data.
Chạy lệnh prewarm cho tenant quan trọng (ví dụ `tenant_vip`) để đảm bảo dữ liệu nằm trong shared buffers.
sudo -u postgres psql -d tenant_vip -c "SELECT pg_prewarm('orders');"
Kết quả: Dữ liệu bảng `orders` của tenant VIP được load vào RAM, truy vấn nhanh hơn.
Sử dụng PgBouncer cho Load Balancing
Trong môi trường Multi-Tenant, PgBouncer không chỉ pool kết nối mà còn có thể cân bằng tải bằng cách phân phối query đến các instance PostgreSQL khác nhau (nếu có cluster).
Cấu hình `pgbouncer.ini` để định nghĩa nhiều database backend và sử dụng chế độ `session` pool nếu cần stateful connection, hoặc `transaction` cho stateless.
sudo tee /etc/pgbouncer/pgbouncer.ini
Kết quả: PgBouncer định tuyến kết nối của tenant A sang DB1 và tenant B sang DB2, phân tán tải.
Partitioning động và Sharding
Khi số lượng tenant vượt quá khả năng của một node, cần chia nhỏ dữ liệu (Sharding). Sử dụng extension `pg_shard` hoặc `Citus` để tự động phân tán dữ liệu.
Đối với PostgreSQL thuần, có thể tự động tạo schema mới cho tenant mới và gán vào node khác thông qua script automation.
sudo apt install -y postgresql-16-citus && sudo -u postgres psql -c "CREATE EXTENSION citus;"
Kết quả: Extension Citus được cài đặt, sẵn sàng phân tán dữ liệu.
Phân phối bảng `orders` của tất cả tenant vào 4 node khác nhau để cân bằng tải I/O và CPU.
sudo -u postgres psql -c "SELECT create_distributed_table('orders', 'tenant_id');"
Kết quả: Bảng `orders` được phân tán (distributed) dựa trên `tenant_id`, các tenant được tự động phân bổ vào các node khác nhau.
Verify kết quả Load Balancing
Tạo load test song song cho 10 tenant khác nhau. Quan sát CPU và I/O trên các node backend. Nếu cân bằng tốt, tải sẽ được phân đều, không có node nào quá nóng. Kiểm tra lại dashboard Grafana, các chỉ số CPU của các node phải ở mức tương đương nhau.
Điều hướng series:
Mục lục: Series: Triển khai Database Multi-Tenant với PostgreSQL và Ubuntu 24.04
« Phần 7: Bảo mật nâng cao: Phân quyền, Audit và mã hóa dữ liệu