Bỏ qua tới nội dung chính
← Tất cả bài viết
Database

Tối ưu RDS từ query trước khi giảm cấu hình instance

Hạ instance dựa trên CPU trung bình thường chỉ chuyển vấn đề từ hóa đơn sang độ trễ. Bài viết đi qua thứ tự tối ưu RDS theo query, storage và backup, rồi mới tới instance, kèm cách chọn Aurora Standard hay I/O-Optimized.

CostKernel Engineering9 phút đọc

Khi hóa đơn RDS tăng, phản xạ thường gặp là mở CloudWatch, thấy CPU trung bình chỉ khoảng 20%, rồi hạ instance xuống một cỡ. Cách làm này đôi khi đúng, nhưng thường bỏ qua câu hỏi quan trọng hơn: vì sao database cần nhiều tài nguyên như vậy ngay từ đầu. Bài viết này đi qua thứ tự chúng tôi hay dùng khi tối ưu RDS: bắt đầu từ query, sau đó tới storage, và chỉ thay đổi instance ở bước cuối.

Vì sao rightsizing dựa trên CPU dễ gây hiểu lầm

CPU trung bình theo giờ che đi gần như mọi thứ đáng quan tâm ở một database. Một instance có CPU trung bình 20% vẫn có thể chạm 90% trong vài phút mỗi khi một batch job chạy, và chính những phút đó quyết định instance có đủ lớn hay không. Hạ cấu hình dựa trên con số trung bình thường dẫn tới việc phải nâng lại sau một sự cố.

Ngoài CPU, có ba lý do khác khiến một database trông như cần instance lớn:

  • Bộ nhớ: working set không vừa trong buffer cache, nên database liên tục đọc từ đĩa. Đội vận hành nâng instance để có thêm RAM, trong khi nguyên nhân là một vài query quét cả bảng.
  • Số kết nối: ứng dụng mở hàng trăm hoặc hàng nghìn kết nối, mỗi kết nối chiếm bộ nhớ trên server. Instance được nâng chỉ để chứa đủ kết nối.
  • IOPS: storage không đáp ứng kịp, nên độ trễ tăng và mọi người nghĩ database bị thiếu sức mạnh tính toán.

Cả ba trường hợp đều thường bắt nguồn từ cách ứng dụng dùng database, không phải từ việc instance quá nhỏ. Nếu giảm instance trước khi xử lý những nguyên nhân này, bạn chỉ chuyển vấn đề từ hóa đơn sang độ trễ.

Bước 1: tìm những query tốn tài nguyên nhất

Mục tiêu của bước này là có một danh sách ngắn các query chiếm phần lớn thời gian và I/O của database. Với hầu hết hệ thống, danh sách đó chỉ có khoảng mười tới hai mươi query.

  • Performance Insights cho thấy database load theo thời gian, chia theo wait event và theo câu SQL. Đây là nơi nên nhìn đầu tiên vì nó cho biết database đang chờ CPU, chờ I/O hay chờ lock.
  • pg_stat_statements (PostgreSQL) tổng hợp thời gian chạy, số lần gọi và số block đọc của từng query đã được chuẩn hóa.
  • Slow query log (MySQL và MariaDB) ghi lại các query vượt ngưỡng thời gian bạn đặt. Có thể dùng pt-query-digest để gom nhóm kết quả.
-- PostgreSQL: các query chiếm nhiều thời gian nhất
SELECT
  round(total_exec_time::numeric / 1000, 1) AS total_seconds,
  calls,
  round(mean_exec_time::numeric, 1)         AS mean_ms,
  shared_blks_read,
  left(query, 120)                          AS query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

Khi sắp xếp theo tổng thời gian thay vì thời gian trung bình, bạn sẽ thấy cả những query nhanh nhưng được gọi hàng triệu lần. Nhóm này thường là dấu hiệu của N+1: ứng dụng gọi một query cho mỗi phần tử trong danh sách thay vì lấy tất cả trong một lần.

Bước 2: sửa query, index và cách ứng dụng kết nối

Với danh sách query trong tay, các thay đổi thường gặp nhất là:

  • Thêm index phù hợp cho các điều kiện lọc và sắp xếp thường dùng. Chạy EXPLAIN (ANALYZE, BUFFERS) trước và sau để chắc chắn query planner thực sự dùng index mới.
  • Xóa index không dùng tới. Mỗi index làm chậm thao tác ghi và chiếm thêm storage. pg_stat_user_indexes cho biết index nào gần như không bao giờ được đọc.
  • Gộp các query N+1 bằng JOIN hoặc WHERE id IN (...), hoặc dùng cơ chế eager loading của ORM.
  • Đưa các báo cáo nặng sang read replica hoặc sang một kho dữ liệu phân tích, thay vì chạy trên primary.

Số kết nối cũng nên được xử lý ở bước này. Nếu ứng dụng chạy trên Lambda hoặc trên nhiều container, mỗi bản sao mở pool kết nối riêng, và tổng số kết nối tăng theo số bản sao. RDS Proxy hoặc PgBouncer gom các kết nối đó lại, giúp database phục vụ cùng lượng request với ít kết nối hơn. RDS Proxy là dịch vụ managed và có tính phí riêng. PgBouncer miễn phí nhưng bạn tự vận hành, nên cần cân nhắc theo khả năng của đội.

Ví dụ giả định

Một API có endpoint danh sách đơn hàng gọi thêm một query để lấy thông tin khách hàng cho mỗi đơn. Với trang 50 đơn, mỗi request tạo ra 51 query. Sau khi gộp thành một query có JOIN, số query trên database giảm khoảng 50 lần cho endpoint đó, và lượng CPU tương ứng cũng giảm theo. Lúc này con số CPU trung bình mới phản ánh đúng nhu cầu thật của hệ thống.

Bước 3: xem lại storage và backup

Storage là phần chi phí ít được để ý vì nó tăng chậm và không gây sự cố. Có ba điểm chúng tôi luôn kiểm tra.

gp2 sang gp3. Với gp2, IOPS tỷ lệ thuận với dung lượng, nên nhiều đội tăng dung lượng ổ đĩa chỉ để có thêm IOPS. gp3 tách hai yếu tố này ra: mỗi volume gp3 có sẵn tối thiểu 3.000 IOPS và 125 MiB/s, và bạn có thể cấu hình thêm IOPS hay throughput mà không phải tăng dung lượng. Việc chuyển từ gp2 sang gp3 thực hiện online, nhưng nên làm ngoài giờ cao điểm và theo dõi độ trễ trong lúc chuyển. Mức baseline và khoảng có thể cấu hình khác nhau theo engine và dung lượng, nên bạn nên kiểm tra tài liệu RDS cho engine của mình.

Provisioned IOPS dư thừa. io1 và io2 tính phí theo số IOPS bạn cấu hình, bất kể database có dùng hết hay không. So sánh số IOPS đã cấu hình với ReadIOPS và WriteIOPS thực tế ở mức p99 trong vài tuần. Nếu khoảng cách lớn, có thể giảm số IOPS hoặc chuyển sang gp3.

Backup và snapshot. AWS miễn phí backup storage tới mức bằng tổng dung lượng database đã cấp phát trong mỗi region. Phần vượt quá mới bị tính phí, và manual snapshot cũng được tính vào cùng hạn mức này. Những khoản hay gặp là thời gian lưu automated backup đặt dài hơn mức cần thiết, manual snapshot được tạo trước mỗi lần deploy rồi không bao giờ xóa, và snapshot của các database đã bị xóa từ lâu. Cross-region backup có chi phí lưu trữ và truyền dữ liệu riêng.

Bước 4: lúc này mới xét tới instance

Sau khi query và storage đã ổn, các chỉ số của database phản ánh đúng nhu cầu, và việc chọn instance trở nên đáng tin hơn nhiều.

  • Dùng p95 hoặc p99 thay vì trung bình cho CPU, bộ nhớ trống (FreeableMemory) và số kết nối, trong một khoảng thời gian đủ dài để bao gồm cả các đợt batch và cao điểm cuối tháng.
  • Graviton: các instance họ m7g, r7g thường có giá thấp hơn instance x86 tương đương. PostgreSQL, MySQL và MariaDB trên RDS đều hỗ trợ Graviton, nhưng bạn vẫn nên chạy thử tải thực tế trước khi chuyển production.
  • Multi-AZ: standby của Multi-AZ được tính phí như một instance thứ hai. Production nên giữ Multi-AZ, nhưng môi trường dev và staging thường không cần.
  • Read replica: mỗi replica là một instance đầy đủ. Kiểm tra xem replica có thực sự nhận traffic đọc không, và cỡ của nó có cần bằng primary không.
  • Reserved Instances chỉ nên mua sau khi cấu hình đã ổn định, vì cam kết một hoặc ba năm cho một instance quá lớn sẽ giữ chi phí sai đó lâu dài.

Aurora: chọn Standard hay I/O-Optimized

Nếu bạn dùng Aurora, có thêm một quyết định về cách tính phí I/O. Aurora Standard tính phí theo số lượng I/O request, còn Aurora I/O-Optimized không tính phí I/O nhưng giá instance và storage cao hơn. Theo hướng dẫn của AWS, I/O-Optimized thường có lợi khi chi phí I/O chiếm từ 25% tổng chi phí Aurora trở lên.

Bạn có thể xem tỷ lệ này trong Cost and Usage Report bằng cách lọc các usage type có chứa Aurora:StorageIOUsage và so với tổng chi phí của cluster. Bạn có thể chuyển sang I/O-Optimized tối đa một lần mỗi 30 ngày, và chuyển ngược về Standard bất cứ lúc nào. Một điểm cần lưu ý: sau khi tối ưu query ở bước 1 và 2, lượng I/O thường giảm đáng kể, nên hãy đo lại tỷ lệ này trước khi quyết định.

Thứ tự nên làm
  • Tìm và sửa các query tốn tài nguyên nhất
  • Gom kết nối bằng RDS Proxy hoặc PgBouncer
  • Chuyển gp2 sang gp3, giảm IOPS dư thừa
  • Dọn backup và snapshot
  • Chọn lại instance dựa trên p95/p99, cân nhắc Graviton
Những cách hay gây rủi ro
  • Hạ instance dựa trên CPU trung bình
  • Tăng dung lượng gp2 chỉ để có thêm IOPS
  • Mua Reserved Instance trước khi tối ưu
  • Tắt Multi-AZ ở production để tiết kiệm
  • Chuyển Aurora I/O-Optimized mà không đo tỷ lệ I/O

Tóm lại

Database thường là nơi chi phí và hiệu năng gắn chặt với nhau nhất. Khi bắt đầu từ query, bạn vừa giảm được chi phí vừa giảm được độ trễ, và các chỉ số sau đó đủ đáng tin để quyết định cỡ instance. Khi bắt đầu từ instance, bạn thường tiết kiệm được một ít trong ngắn hạn nhưng phải trả lại bằng sự cố.

Trong một Assessment, chúng tôi đọc Performance Insights, pg_stat_statements hoặc slow query log cùng với dữ liệu billing, rồi đề xuất từng thay đổi kèm ước tính tiết kiệm và rủi ro, để đội của bạn quyết định thứ tự triển khai.