Tối ưu 5 — Quản trị & bảo trì: VACUUM, cấu hình, backup
Tối ưu 5 — Quản trị & bảo trì: VACUUM, cấu hình, backup
Một cơ sở dữ liệu (CSDL) chạy tốt trong ngày đầu không có nghĩa là nó sẽ khoẻ mãi. Sau vài tháng với hàng triệu lần UPDATE/DELETE, bảng bắt đầu phình to (bloat), truy vấn chậm dần, đĩa đầy lên dù số dòng "thật" không tăng, và một ngày đẹp trời CSDL từ chối ghi vì lý do bạn chưa từng nghe. Đây là địa hạt của công việc bảo trì (maintenance) — phần ít hào nhoáng nhưng quyết định CSDL của bạn sống được bao lâu.
Bài này tập trung vào PostgreSQL và đi qua sáu nhóm việc cốt lõi mà một DBA (Database Administrator) phải nắm: cơ chế MVCC và tại sao sinh ra rác; VACUUM/autovacuum để dọn rác; các tham số cấu hình then chốt về bộ nhớ; connection pooling với PgBouncer; WAL & checkpoint; và cuối cùng là backup/PITR — tấm lưới an toàn cuối cùng.
Lưu ý đọc bài: Gần như toàn bộ lệnh ở đây là lệnh quản trị (VACUUM, ALTER SYSTEM, backup, restore). Chúng KHÔNG chạy trong SQL sandbox và được đánh dấu (minh hoạ). Bài này không có ví dụ "▶ Chạy được".
MVCC: vì sao CSDL sinh ra rác
PostgreSQL dùng MVCC — Multi-Version Concurrency Control (điều khiển đồng thời bằng đa phiên bản). Ý tưởng: thay vì khoá dòng khi có người sửa, mỗi lần thay đổi tạo ra một phiên bản mới của dòng, còn phiên bản cũ vẫn nằm nguyên tại chỗ cho đến khi không còn giao dịch nào cần nhìn thấy nó. Nhờ vậy người đọc không chặn người ghi và ngược lại — một transaction đang đọc vẫn thấy ảnh chụp (snapshot) nhất quán của dữ liệu tại thời điểm nó bắt đầu.
Hệ quả trực tiếp: một hàng trong bảng thực chất là một tuple vật lý có gắn hai dấu mốc xmin (transaction tạo ra nó) và xmax (transaction xoá/thay thế nó). Khi bạn:
UPDATEmột dòng → PostgreSQL không sửa tại chỗ. Nó viết một tuple mới, đánh dấu tuple cũ là "chết" (xmaxđược set).DELETEmột dòng → tuple bị đánh dấu chết, nhưng không giải phóng không gian ngay.
Những tuple chết này gọi là dead tuples. Chúng vẫn chiếm chỗ vật lý trên đĩa, vẫn nằm trong các trang (page) 8KB, và index vẫn có thể trỏ tới chúng. Khi số dead tuple tích tụ, bảng và index phình to (bloat): kích thước vật lý lớn hơn nhiều so với dữ liệu sống. Bloat làm mọi thứ chậm hơn vì mỗi lần quét bảng, PostgreSQL phải đọc cả các trang chứa rác, đọc nhiều trang hơn từ đĩa, và cache (shared_buffers) bị lấp bởi dữ liệu vô dụng.
Bloat không phải lỗi — nó là cái giá của MVCC. Việc dọn rác được giao cho một tiến trình riêng: VACUUM.
VACUUM & autovacuum
VACUUM làm gì
VACUUM quét bảng, xác định các dead tuple mà không còn transaction nào cần thấy nữa, rồi đánh dấu không gian đó là có thể dùng lại (reusable). Nó cập nhật một cấu trúc gọi là free space map để các lần INSERT/UPDATE sau ghi đè lên chỗ trống này.
Điểm quan trọng cần hiểu: VACUUM (thường) không trả lại đĩa cho hệ điều hành và không làm bảng nhỏ lại trên đĩa. Nó chỉ tái sử dụng không gian bên trong file bảng. Nghĩa là bảng đạt trạng thái cân bằng — không phình mãi — chứ không co lại.
-- (minh hoạ)
VACUUM orders; -- dọn rác một bảng, dùng lại không gian nội bộ
VACUUM (VERBOSE) orders; -- in báo cáo chi tiết số tuple đã dọn
VACUUM (ANALYZE) orders; -- vừa vacuum vừa cập nhật thống kê cho planner
VACUUM thường chạy song song với hoạt động bình thường — nó chỉ lấy khoá nhẹ (SHARE UPDATE EXCLUSIVE), cho phép SELECT/INSERT/UPDATE/DELETE tiếp tục.
VACUUM FULL — mạnh nhưng nguy hiểm
-- (minh hoạ) CẨN THẬN: khoá bảng độc quyền!
VACUUM FULL orders;
VACUUM FULL viết lại toàn bộ bảng vào một file mới, gọn chặt, rồi trả đĩa lại cho hệ điều hành. Nghe hấp dẫn, nhưng nó cần ACCESS EXCLUSIVE LOCK — khoá độc quyền, chặn mọi truy cập kể cả SELECT — trong suốt thời gian chạy, và cần dung lượng đĩa trống bằng cỡ bảng để viết bản mới. Trên bảng lớn ở production, điều này đồng nghĩa downtime.
Quy tắc thực dụng: tránh VACUUM FULL trên hệ đang chạy. Nếu thực sự phải giảm bloat nghiêm trọng, hãy dùng công cụ như pg_repack (tái tổ chức bảng gần như không khoá) hoặc lên lịch cửa sổ bảo trì.
autovacuum — người dọn dẹp tự động
Bạn không nên chạy VACUUM tay định kỳ. PostgreSQL có tiến trình nền autovacuum tự động vacuum và analyze các bảng khi "đủ rác". Nó bật mặc định và nên luôn để bật.
Autovacuum quyết định chạy trên một bảng khi số dead tuple vượt ngưỡng:
ngưỡng = autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor × số_dòng_bảng
Mặc định threshold = 50 và scale_factor = 0.2 (20%). Với bảng nhỏ điều này ổn, nhưng với bảng cực lớn (ví dụ 100 triệu dòng) thì 20% = 20 triệu dead tuple mới trigger — quá muộn, bảng đã phình nặng. Vì vậy với bảng lớn ghi nhiều, ta giảm scale_factor riêng cho bảng đó:
-- (minh hoạ) autovacuum tích cực hơn cho bảng nóng
ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.02, -- 2% thay vì 20%
autovacuum_vacuum_threshold = 5000
);
Vài tham số autovacuum then chốt:
autovacuum_max_workers: số worker chạy đồng thời (mặc định 3).autovacuum_vacuum_cost_delayvàautovacuum_vacuum_cost_limit: điều tiết tốc độ để autovacuum không ngốn hết I/O. Trên phần cứng hiện đại (SSD, nhiều I/O dư), việc giảm cost_delay (hoặc tăng cost_limit) giúp autovacuum theo kịp bảng ghi nặng.autovacuum_analyze_scale_factor: ngưỡng riêng cho việc chạy ANALYZE tự động.
ANALYZE — nuôi bộ não của planner
ANALYZE thu thập thống kê về phân bố dữ liệu: số dòng ước lượng, số giá trị phân biệt, các giá trị phổ biến nhất, histogram... Query planner dùng những con số này để chọn kế hoạch thực thi (dùng index hay quét bảng, thứ tự join...). Thống kê lạc hậu dẫn tới kế hoạch tồi — đây là nguyên nhân phổ biến của "truy vấn tự dưng chậm" sau khi nạp lượng lớn dữ liệu.
-- (minh hoạ)
ANALYZE orders; -- cập nhật thống kê một bảng
ANALYZE; -- toàn bộ database
Autovacuum cũng tự chạy ANALYZE, nhưng sau khi nạp dữ liệu hàng loạt (bulk load, restore, migration) nên chạy ANALYZE tay ngay để planner không phải "mò mẫm". Xem thêm cách đọc kế hoạch trong bài Chỉ mục & tối ưu truy vấn và cách phát hiện query xấu trong bài Giám sát.
Transaction ID wraparound — mối nguy phải hiểu
Đây là phần nhiều người bỏ qua cho đến khi CSDL... ngừng nhận ghi. Mỗi transaction có một XID (transaction ID) là số nguyên 32-bit — nghĩa là chỉ khoảng 4 tỉ giá trị rồi quay vòng (wrap around). PostgreSQL dùng XID để quyết định tuple nào "cũ hơn" và visible với snapshot nào. Nếu XID quay vòng mà không xử lý, các transaction quá khứ có thể bỗng nhiên trông như tương lai → dữ liệu cũ biến mất một cách logic. Thảm hoạ.
Giải pháp là freeze: VACUUM đánh dấu các tuple đủ già bằng trạng thái "frozen" (đóng băng), nghĩa là "luôn visible với mọi snapshot", loại chúng khỏi trò chơi so sánh XID. Autovacuum tự động kích hoạt aggressive/anti-wraparound vacuum khi tuổi XID của bảng vượt autovacuum_freeze_max_age (mặc định ~200 triệu).
Nguy hiểm ở chỗ: nếu autovacuum bị tắt, bị chặn (ví dụ có transaction cực dài giữ XID, hoặc lock), hoặc không theo kịp trên bảng ghi khủng, tuổi XID cứ tăng. Khi tới ngưỡng nguy hiểm, PostgreSQL đầu tiên cảnh báo trong log, rồi khi tới hạn cứng nó từ chối mọi lệnh ghi để tự bảo vệ, buộc bạn chạy vacuum thủ công ở chế độ single-user. Cách phòng: luôn để autovacuum bật, giám sát tuổi XID:
-- (minh hoạ) bảng nào sắp tới hạn wraparound?
SELECT relname,
age(relfrozenxid) AS xid_age
FROM pg_class
WHERE relkind = 'r'
ORDER BY xid_age DESC
LIMIT 20;
Vòng đời tuple với MVCC & VACUUM
REINDEX & index bloat
Index cũng bị bloat. Sau nhiều UPDATE/DELETE, các trang index chứa entry trỏ tới tuple đã chết, hoặc bị phân mảnh, khiến index to hơn cần thiết và tra cứu chậm hơn. REINDEX xây lại index từ đầu, gọn và cân bằng.
-- (minh hoạ) REINDEX thường lấy khoá chặn ghi trên bảng
REINDEX INDEX idx_orders_customer;
REINDEX TABLE orders;
-- Bản CONCURRENTLY: xây index mới song song rồi tráo — KHÔNG chặn ghi
REINDEX INDEX CONCURRENTLY idx_orders_customer;
REINDEX ... CONCURRENTLY (từ PostgreSQL 12) là lựa chọn ưu tiên trên production vì nó không chặn đọc/ghi, đổi lại chạy chậm hơn và tốn thêm đĩa tạm thời. Dùng nó khi phát hiện index phình (có thể đo bằng extension pgstattuple).
Tham số cấu hình then chốt
PostgreSQL mặc định cấu hình rất bảo thủ để chạy được trên máy nhỏ. Trên server thật bạn gần như luôn phải chỉnh. Các tham số ảnh hưởng lớn nhất:
| Tham số | Ý nghĩa | Gợi ý khởi điểm |
|---|---|---|
shared_buffers | Bộ nhớ PostgreSQL tự cache trang dữ liệu | ~25% RAM |
effective_cache_size | Ước lượng tổng cache khả dụng (PG + OS) — chỉ để planner tính, không cấp phát | ~50–75% RAM |
work_mem | Bộ nhớ cho mỗi thao tác sort/hash trong một truy vấn | 16–64MB (cẩn thận!) |
maintenance_work_mem | Bộ nhớ cho VACUUM, CREATE INDEX, REINDEX | 256MB–1GB+ |
max_connections | Số kết nối đồng thời tối đa | Giữ thấp + pooling |
random_page_cost | Chi phí ước lượng khi đọc trang ngẫu nhiên | 1.1 cho SSD (mặc định 4.0 cho HDD) |
# (minh hoạ) postgresql.conf trên server 32GB RAM, SSD
shared_buffers = 8GB
effective_cache_size = 24GB
work_mem = 32MB
maintenance_work_mem = 1GB
random_page_cost = 1.1
Vài điểm dễ sai:
shared_bufferskhông phải càng lớn càng tốt. PostgreSQL dựa nhiều vào cache của hệ điều hành. ~25% RAM là điểm khởi đầu tốt; đẩy quá cao có thể phản tác dụng.work_memlà cái bẫy nhân số. Nó áp dụng cho mỗi node sort/hash của mỗi truy vấn, và mỗi kết nối có thể chạy nhiều node cùng lúc. Bộ nhớ đỉnh xấp xỉwork_mem × số_node_đồng_thời × số_connection. Đặtwork_mem = 256MBvớimax_connections = 500là công thức để bị OOM (hết bộ nhớ, kernel giết tiến trình). Giữwork_memvừa phải, và giảm số connection thực bằng pooling (xem dưới).effective_cache_sizechỉ là con số cho planner — nó không cấp phát byte nào. Đặt nó phản ánh đúng RAM giúp planner ưu tiên dùng index thay vì quét bảng.random_page_cost = 1.1trên SSD phản ánh đúng thực tế đọc ngẫu nhiên gần bằng đọc tuần tự, giúp planner chọn index nhiều hơn.
Thay đổi tham số ở runtime (từ PostgreSQL 9.4+) tốt nhất qua ALTER SYSTEM, ghi vào postgresql.auto.conf:
-- (minh hoạ)
ALTER SYSTEM SET work_mem = '32MB';
SELECT pg_reload_conf(); -- tham số dạng reload có hiệu lực ngay
-- shared_buffers cần RESTART service mới có hiệu lực
Connection pooling & PgBouncer
PostgreSQL dùng mô hình một tiến trình (process) cho mỗi kết nối. Mỗi connection là một OS process thật với bộ nhớ riêng — mở kết nối tốn kém, và hàng nghìn kết nối đồng nghĩa hàng nghìn process cạnh tranh CPU, RAM và lock nội bộ. Ứng dụng web thường mở nhiều kết nối ngắn (mỗi request một cái), khiến chi phí thiết lập/huỷ kết nối trở thành nút thắt.
Giải pháp là connection pooler đặt giữa ứng dụng và PostgreSQL, giữ sẵn một hồ (pool) kết nối thật tới CSDL và ghép nhiều client vào số ít kết nối đó. Phổ biến nhất là PgBouncer — nhẹ, một tiến trình, gần như không tốn tài nguyên.
PgBouncer có ba chế độ pooling, khác nhau ở thời điểm trả kết nối về hồ:
- session pooling: một client giữ một kết nối server suốt phiên (từ connect tới disconnect). Ít lợi ích nhất về ghép nối.
- transaction pooling: kết nối server chỉ gán cho client trong lúc chạy một transaction, trả về hồ ngay khi commit/rollback. Đây là chế độ được dùng nhiều nhất cho web vì cho tỉ lệ ghép cao nhất. Lưu ý: không dùng được các tính năng bám session như prepared statement kiểu cũ,
SETcấp session, advisory lock kéo dài... - statement pooling: trả kết nối sau mỗi câu lệnh — hung hãn nhất, cấm cả transaction nhiều câu.
Kết quả thực tế: ứng dụng "thấy" như có 500 kết nối, nhưng PgBouncer chỉ mở 20–50 kết nối thật tới PostgreSQL. Bạn có thể để max_connections của Postgres thấp (giảm áp lực bộ nhớ, cho phép work_mem cao hơn an toàn), trong khi vẫn phục vụ tải lớn.
# (minh hoạ) pgbouncer.ini
[databases]
shopdb = host=127.0.0.1 port=5432 dbname=shopdb
[pgbouncer]
pool_mode = transaction
max_client_conn = 1000 ; số client tối đa nối vào PgBouncer
default_pool_size = 25 ; số kết nối THẬT tới Postgres mỗi database
WAL & checkpoint
WAL là gì
WAL — Write-Ahead Log (nhật ký ghi trước) là nền tảng của độ bền (durability) trong PostgreSQL. Nguyên tắc: trước khi thay đổi được ghi vào file dữ liệu, mô tả của thay đổi đó phải được ghi (và fsync) vào WAL trên đĩa. Nhờ vậy nếu CSDL crash giữa chừng, khi khởi động lại nó phát lại (replay) WAL để đưa dữ liệu về trạng thái nhất quán — không mất giao dịch đã commit.
WAL cũng là "nguồn sự thật" cho replication (bản sao đọc WAL để đồng bộ) và cho PITR (nói ở phần backup). Xem sâu hơn ở bài Replication & HA.
Checkpoint
Nếu WAL cứ dồn mãi thì crash recovery sẽ phải replay quá nhiều. Checkpoint là thời điểm PostgreSQL đảm bảo mọi thay đổi trong bộ nhớ tính đến lúc đó đã được flush xuống file dữ liệu, và ghi một mốc trong WAL. Sau checkpoint, phần WAL cũ hơn không còn cần cho recovery nữa (có thể tái sử dụng/archive).
Hai tham số điều tiết:
checkpoint_timeout: khoảng thời gian tối đa giữa hai checkpoint (mặc định 5 phút; thường tăng lên 15–30 phút).max_wal_size: kích thước WAL tích luỹ trước khi buộc checkpoint.
Checkpoint quá thưa → recovery lâu và WAL phình; quá dày → I/O tăng vọt (flush liên tục). checkpoint_completion_target trải việc ghi ra để tránh "bão I/O".
fsync & synchronous_commit
fsync = on(mặc định, không bao giờ tắt trên production): buộc dữ liệu thực sự xuống đĩa vật lý, không kẹt ở cache. Tắt đi tăng tốc nhưng đánh đổi bằng nguy cơ hỏng dữ liệu khi mất điện.synchronous_commit: nếuon, mỗi COMMIT chờ WAL được fsync mới báo thành công (an toàn tuyệt đối). Đặtoffcho phép COMMIT trả về sớm, nhanh hơn, đổi lại có thể mất vài giao dịch cuối cùng khi crash (nhưng không hỏng dữ liệu — khác hẳn tắt fsync). Đây là đánh đổi hợp lý cho dữ liệu chấp nhận mất vài mili-giây cuối.
Backup & phục hồi
Sao lưu là tấm lưới cuối cùng. Có hai họ chiến lược.
Logical backup — pg_dump / pg_restore
pg_dump xuất nội dung logic (câu lệnh SQL hoặc định dạng archive) của một database. Nó chạy được khi CSDL đang online, di động giữa các phiên bản/kiến trúc, và cho phép phục hồi chọn lọc (một bảng, một schema).
# (minh hoạ)
# Sao lưu định dạng custom (nén, restore linh hoạt)
pg_dump -Fc -d shopdb -f shopdb.dump
# Phục hồi
pg_restore -d shopdb_new shopdb.dump
# Sao lưu toàn cluster kèm role/tablespace
pg_dumpall > all.sql
Nhược điểm: chậm và nặng với DB lớn, và không hỗ trợ PITR — bạn chỉ khôi phục về đúng thời điểm dump. Phù hợp DB nhỏ/vừa, migration, tách bảng.
Physical backup — pg_basebackup
pg_basebackup sao chép nguyên các file dữ liệu ở mức byte của cả cluster. Nhanh với DB lớn, và quan trọng nhất: là nền cho PITR và cho dựng replica.
# (minh hoạ) chụp base backup toàn cluster
pg_basebackup -D /backup/base -Ft -z -P
PITR — Point-In-Time Recovery
PITR kết hợp một base backup + luồng WAL được lưu trữ (archive) để phục hồi tới bất kỳ thời điểm nào trong quá khứ — kể cả "5 phút trước khi ai đó lỡ tay DELETE mất bảng khách hàng". Cơ chế: base backup cho điểm xuất phát, rồi replay WAL tuần tự cho tới đúng thời điểm bạn chỉ định.
# (minh hoạ) postgresql.conf — bật archive WAL
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /wal_archive/%f && cp %p /wal_archive/%f'
# (minh hoạ) recovery target khi phục hồi (postgresql.conf/recovery)
restore_command = 'cp /wal_archive/%f %p'
recovery_target_time = '2026-06-30 14:25:00'
Ngày nay nhiều đội dùng công cụ chuyên trách (pgBackRest, Barman, WAL-G) để quản lý base backup + WAL archive + PITR một cách tin cậy, có nén, khử trùng lặp, và kiểm tra tính toàn vẹn.
RPO, RTO & kiểm thử phục hồi
Hai chỉ số định hình chiến lược backup:
- RPO — Recovery Point Objective: chấp nhận mất tối đa bao nhiêu dữ liệu (tính theo thời gian). Backup mỗi ngày → RPO 24h. WAL archive liên tục → RPO gần bằng 0.
- RTO — Recovery Time Objective: phục hồi phải xong trong bao lâu. DB lớn restore từ dump có thể mất nhiều giờ → RTO cao.
Nguyên tắc bất di bất dịch: một bản backup chưa được test restore là một bản backup không tồn tại. Rất nhiều tổ chức chỉ phát hiện backup hỏng/thiếu vào đúng lúc cần nó. Hãy định kỳ phục hồi thử vào môi trường tách biệt, đo thời gian (kiểm chứng RTO) và kiểm tra dữ liệu.
Use case thực tế: bảng phình vì autovacuum không kịp
Bối cảnh. Bảng sessions lưu phiên đăng nhập: mỗi request UPDATE cột last_seen, mỗi đêm DELETE phiên hết hạn. Sau vài tuần, đĩa báo động: sessions chiếm 40GB dù chỉ có ~2 triệu dòng sống. Truy vấn theo user_id chậm dần.
Chẩn đoán. Kiểm tra tỉ lệ dead tuple và lần vacuum gần nhất:
-- (minh hoạ)
SELECT relname,
n_live_tup,
n_dead_tup,
round(n_dead_tup::numeric / nullif(n_live_tup,0), 2) AS dead_ratio,
last_autovacuum
FROM pg_stat_user_tables
WHERE relname = 'sessions';
Kết quả: n_dead_tup cao gấp nhiều lần n_live_tup, last_autovacuum là... vài ngày trước. Autovacuum có chạy nhưng không theo kịp tốc độ sinh rác vì (1) scale_factor mặc định 20% quá cao với bảng ghi cực nhiều, và (2) cost_delay khiến nó bị điều tiết quá chậm.
Xử lý.
- Vacuum tay ngay để chặn đà (không dùng
VACUUM FULLvì bảng đang phục vụ):-- (minh hoạ) VACUUM (VERBOSE, ANALYZE) sessions; - Chỉnh autovacuum riêng cho bảng này để nó chạy sớm và nhanh:
-- (minh hoạ) ALTER TABLE sessions SET ( autovacuum_vacuum_scale_factor = 0.01, autovacuum_vacuum_threshold = 1000, autovacuum_vacuum_cost_delay = 0 ); - Không gian đã phình (40GB) sẽ không tự co lại — VACUUM thường chỉ cho tái dùng nội bộ. Nếu cần trả đĩa, lên cửa sổ bảo trì để dùng
pg_repack(gần như không khoá) thay choVACUUM FULL. - Về lâu dài: cân nhắc phân vùng theo thời gian (partition) rồi
DROPpartition cũ thay vìDELETEhàng loạt —DROPgiải phóng đĩa tức thì mà không sinh dead tuple.
Sau khi chỉnh, dead_ratio giữ ở mức thấp ổn định và bảng ngừng phình. Việc theo dõi liên tục các chỉ số này thuộc về công việc Giám sát.
Bảo trì định kỳ
Một lịch bảo trì gọn cho DBA:
- Theo dõi bloat (
pg_stat_user_tables,pgstattuple) và tuổi XID (age(relfrozenxid)) — cảnh báo sớm. - Đảm bảo autovacuum bật và theo kịp; chỉnh tham số riêng cho các bảng nóng.
- Chạy
ANALYZEsau các đợt nạp dữ liệu lớn để planner không dùng thống kê cũ. - REINDEX CONCURRENTLY các index phình khi cần.
- Backup định kỳ + WAL archive, và test restore thường xuyên (đo RTO).
- Dọn log cũ, xoay (rotate) log, dọn metadata/temp files.
- Nâng cấp phiên bản: vá minor thường xuyên (sửa bug/bảo mật, thường chỉ cần restart); nâng major (13→14→...) cần kế hoạch (
pg_upgradehoặc logical replication) và test kỹ.
Ghi nhớ
- MVCC làm
UPDATE/DELETEsinh dead tuple → bảng/index bloat. Đây là bản chất, không phải lỗi. - VACUUM thu hồi không gian để tái sử dụng nội bộ (không co bảng, không trả đĩa). VACUUM FULL viết lại bảng nhưng khoá độc quyền — tránh trên production; ưu tiên
pg_repack. - Luôn để autovacuum bật. Với bảng lớn/ghi nhiều, giảm
scale_factorvà cân nhắc giảmcost_delayđể nó theo kịp. ANALYZEgiữ thống kê tươi cho planner; chạy tay sau bulk load.- XID wraparound có thể làm CSDL ngừng nhận ghi. Autovacuum tự freeze để phòng — đừng chặn nó bằng transaction siêu dài.
- Cấu hình cốt lõi:
shared_buffers~25% RAM,effective_cache_sizephản ánh tổng cache,work_memvừa phải (nhân theo connection!),random_page_cost = 1.1cho SSD. - PgBouncer (transaction pooling) cho phép giữ
max_connectionsthấp mà vẫn phục vụ nhiều client. - WAL đảm bảo durability và là nền của PITR + replication; checkpoint giới hạn thời gian recovery. Không tắt
fsynctrên production. - Backup: logical (
pg_dump) linh hoạt nhưng không PITR; physical (pg_basebackup) + WAL archive cho PITR. Backup chưa test restore = không có backup. Luôn xác định RPO/RTO.
Bài liên quan: Chỉ mục & tối ưu truy vấn · Giám sát · Replication & HA
Bài viết liên quan
Cách index hoạt động (B-Tree), đọc EXPLAIN, seq scan vs index scan và mẫu tối ưu truy vấn.
Khoá, ràng buộc, quan hệ và chuẩn hoá 1NF/2NF/3NF — thiết kế lược đồ đúng từ đầu.
Kết nhiều bảng đúng cách: các loại JOIN, bẫy thường gặp và phép hợp tập.
Truy vấn lồng, CTE (WITH), CTE đệ quy và hàm cửa sổ — vũ khí cho phân tích nâng cao.