Ứng dụng của bạn đang chạy chậm? Trong 80% trường hợp, thủ phạm là database query. Không phải do PostgreSQL không đủ mạnh — mà do chúng ta chưa dùng đúng cách.
1. EXPLAIN ANALYZE — Bắt Đầu Từ Đây
Trước khi tối ưu bất cứ thứ gì, hãy đo lường. EXPLAIN ANALYZE cho bạn biết PostgreSQL thực sự làm gì:
EXPLAIN ANALYZE
SELECT p.title, u.username
FROM posts p
JOIN auth_user u ON p.author_id = u.id
WHERE p.status = 'published'
ORDER BY p.published_at DESC
LIMIT 20;
Tìm kiếm những điểm đỏ: Seq Scan trên bảng lớn, cost cao, rows estimate sai xa thực tế.
2. Index Đúng Chỗ
Index là công cụ mạnh nhất nhưng cũng dễ dùng sai nhất:
-- Index cho cột thường dùng trong WHERE
CREATE INDEX idx_posts_status ON posts(status);
-- Composite index theo thứ tự filter/sort
CREATE INDEX idx_posts_status_date
ON posts(status, published_at DESC);
-- Index cho foreign key (Django không tự tạo!)
CREATE INDEX idx_posts_author ON posts(author_id);
Lưu ý: Index không phải là miễn phí — chúng chiếm disk space và làm chậm INSERT/UPDATE. Chỉ tạo index cho những cột thực sự cần.
3. Tránh N+1 Queries
Đây là vấn đề phổ biến nhất với ORM. Trong Django:
# Xấu — N+1: 1 query lấy posts + N query cho mỗi author
posts = Post.objects.filter(status='published')
for post in posts:
print(post.author.username) # Query riêng mỗi lần!
# Tốt — 1 query với JOIN
posts = Post.objects.filter(status='published').select_related('author')
# Cho many-to-many (tags)
posts = Post.objects.prefetch_related('tags')
4. Partial Indexes
Khi chỉ query một subset của data, partial index nhỏ hơn và nhanh hơn:
-- Chỉ index bài published
CREATE INDEX idx_published_posts
ON posts(published_at DESC)
WHERE status = 'published';
-- Query này sẽ dùng index trên
SELECT * FROM posts
WHERE status = 'published'
ORDER BY published_at DESC;
5. Connection Pooling
Mỗi connection PostgreSQL tốn ~5MB RAM. Dùng PgBouncer hoặc Django database pooling để tái sử dụng connections:
# Django settings với pgbouncer
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'CONN_MAX_AGE': 600, # Giữ connection 10 phút
# ... other settings
}
}
6. Tránh SELECT *
# Xấu — lấy tất cả columns kể cả content (có thể MB)
posts = Post.objects.all()
# Tốt — chỉ lấy những gì cần
posts = Post.objects.values('id', 'title', 'slug', 'published_at')
7. Vacuum Và Statistics
PostgreSQL cần được "dọn dẹp" định kỳ. Trong production, đảm bảo autovacuum đang chạy và cập nhật statistics thường xuyên:
-- Cập nhật statistics để query planner chính xác hơn
ANALYZE posts;
-- Xem bảng nào cần vacuum
SELECT relname, n_dead_tup, n_live_tup
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;
Đừng tối ưu premature. Đo lường trước, tối ưu sau — và luôn verify kết quả bằng benchmark thực tế.
Kết Luận
PostgreSQL cực kỳ mạnh mẽ khi được dùng đúng cách. Bắt đầu với EXPLAIN ANALYZE để tìm bottleneck thực sự, sau đó áp dụng từng kỹ thuật một cách có đo lường. Performance optimization là quá trình lặp đi lặp lại, không phải một lần là xong.
Chưa có bình luận. Hãy là người đầu tiên!