Câu hỏi "sao lưu PostgreSQL thế nào" thường được trả lời bằng một lệnh pg_dump đặt trong cron. Lệnh đó đúng, và nó trả lời một câu khác với câu bạn sẽ hỏi lúc 3 giờ sáng: khôi phục được về thời điểm nào? PostgreSQL có hai kiểu sao lưu khác bản chất — logic và vật lý — và chúng khác nhau ở thứ không nhìn thấy trong kích thước tệp: thời điểm mà bản sao đại diện. Phần này dựng một cơ sở dữ liệu 613 MB rồi đo cả hai: thời gian, kích thước, ảnh hưởng lên tải đang chạy, và bản sao chứa gì khi có người ghi trong lúc sao lưu.

Hai kiểu sao lưu, hai cơ chế

Ảnh chụp đoạn mã nền tối hai kiểu sao lưu PostgreSQL logic và vật lý với PostgreSQL 16 cơ sở dữ liệu 613 MB gồm pgbench scale 30 449 MB và bảng docs 1 triệu dòng md5 156 MB archive_mode on, pg_dump sao lưu logic một giao dịch đọc xuất ra câu lệnh và dữ liệu gồm pg_dump Fp ra SQL thuần psql đọc lại được, Fc định dạng custom nén pg_restore chọn bảng được, Fc Z0 custom tắt nén, Fd j4 thư mục 4 tiến trình song song, chụp ảnh MVCC tại lúc bắt đầu REPEATABLE READ dòng ghi sau khi dump khởi động không có trong dump, không mang theo role tablespace postgresql.conf pg_hba.conf cần thêm pg_dumpall globals-only, pg_basebackup sao lưu vật lý chép nguyên thư mục dữ liệu qua giao thức replication gồm D base Fp Xstream c fast thư mục kèm WAL phát sinh trong lúc chép, Ft z Xstream ra base.tar.gz và pg_wal.tar.gz, bản chép không nhất quán tự thân vì tệp chép lúc khác nhau backup_label ghi START WAL LOCATION, khởi động lên là replay WAL từ START tới STOP nên nhất quán tại lúc kết thúc sao lưu, cộng WAL lưu trữ qua archive_command thì khôi phục tới bất kỳ thời điểm nào sau đó PITR phần 4, đo gì gồm A thời gian và kích thước của 6 biến thể trên cùng 613 MB, B pgbench c 8 T 15 tps khi không sao lưu trong lúc pg_dump trong lúc pg_basebackup, C chèn 1000 dòng trong lúc dump và trong lúc basebackup xem mỗi bản sao chứa bao nhiêu dòng, D khởi động container mới từ basebackup xem replay WAL mất bao lâu và đếm lại dòng

Hình 1: pg_dump đọc qua một giao dịch và xuất ra dữ liệu; pg_basebackup chép tệp qua giao thức replication và dựa vào WAL để nhất quán.

pg_dump là sao lưu logic. Nó mở một giao dịch REPEATABLE READ, đọc catalog và từng bảng, xuất ra câu lệnh CREATE và dữ liệu COPY. Vì là một giao dịch, bản dump là ảnh chụp MVCC tại lúc bắt đầu: mọi thứ commit sau thời điểm đó không có trong dump, dù dump chạy mười phút. Định dạng -Fc (custom) nén và cho pg_restore chọn từng bảng; -Fd -jN chia thành thư mục để chạy song song. Dump không mang theo role, tablespace, postgresql.conf, pg_hba.conf — thứ cần pg_dumpall --globals-only và một bản chép cấu hình riêng.

pg_basebackup là sao lưu vật lý. Nó nối vào máy chủ qua giao thức replication và chép nguyên thư mục dữ liệu: các tệp heap, index, catalog, đúng byte trên đĩa. Vì tệp được chép lần lượt trong khi máy chủ vẫn ghi, bản chép tự thân không nhất quán; tệp backup_label ghi vị trí WAL bắt đầu, và khi khởi động, PostgreSQL replay WAL từ đó tới điểm kết thúc sao lưu để đưa mọi tệp về cùng một thời điểm. -Xstream gói luôn phần WAL phát sinh trong lúc chép. Nếu WAL sau đó còn được lưu trữ (archive_command), bản basebackup này là điểm xuất phát để khôi phục tới bất kỳ thời điểm nào — đó là PITR, phần 4.

Môi trường: PostgreSQL 16 trong container, archive_mode=on, dữ liệu pgbench scale 30 (449 MB) cộng một bảng docs một triệu dòng chuỗi md5 ngẫu nhiên (156 MB) để không phải mọi thứ đều nén tốt như dữ liệu pgbench.

Đo thật: thời gian, kích thước, tải, và thời điểm

Ảnh chụp output thật nền tối 613 MB dump 69 MB trong 2,9 giây basebackup 651 MB trong 0,9 giây với pg-prim postgres 16 10 CPU pgbench scale 30 và docs 1 triệu dòng md5 tất cả chạy trong container, A sáu biến thể trên cùng một CSDL 613 MB gồm pg_dump Fp 0,89 giây 407 MB SQL thuần không nén, pg_dump Fc nén mặc định 2,92 giây 69 MB nén ăn 2 giây CPU pgbench filler nén rất tốt, pg_dump Fc Z0 1,05 giây 426 MB custom không nén xấp xỉ Fp, pg_dump Fd j4 2,33 giây 69 MB song song 4 tiến trình nén, pg_basebackup Fp Xstream 0,89 giây 651 MB chép nguyên tệp cộng 16 MB WAL to hơn CSDL, pg_basebackup Ft z Xstream 6,10 giây 100 MB gzip một luồng chậm nhất, B tải đang chạy pgbench c 8 T 15 sao lưu chạy song song gồm không sao lưu tps 10392 và lại 10475, trong lúc pg_dump Fc tps 9474 giảm 9 phần trăm dump mất 3,6 giây thay vì 2,9, trong lúc basebackup z tps 8891 giảm 15 phần trăm basebackup mất 7,8 giây thay vì 6,1, trong lúc basebackup Fp tps 10140 giảm 2 phần trăm mất 1,8 giây thay vì 0,9, cả hai đều không khoá bảng cái tốn là CPU nén và I O đọc không phải lock, C chèn 1000 dòng trong lúc sao lưu bản sao chứa bao nhiêu gồm bảng events 1000 dòng trước rồi chèn 5 lần 200 dòng trong lúc sao lưu chạy DB cuối cùng 2000 dòng, pg_dump Fc bản sao có 1000 dòng là ảnh chụp MVCC tại lúc bắt đầu 1000 dòng sau không có, pg_basebackup Fp bản sao có 1200 dòng tới lúc kết thúc chép 200 dòng kịp commit WAL replay lo phần còn lại, D khởi động container mới từ basebackup không cần pg_restore gồm starting backup recovery with redo LSN 0 C4000028 consistent recovery state reached at 0 C400A5A8 database system is ready to accept connections sau 758 ms kể cả khởi động container, count events 1200 docs 1000000 pg_is_in_recovery f, WAL đã lưu trữ qua archive_command 47 tệp 673 MB với nó bản basebackup này khôi phục được tới bất kỳ giây nào sau 0 C400A5A8, nói thẳng dữ liệu pgbench nén 6 lần dữ liệu thật nén kém hơn nhiều nhưng tỉ lệ thời gian và cái dump chụp lúc bắt đầu thì không đổi khôi phục dump dựng lại index FK chưa đo ở đây đó là phần 2

Hình 2: Cùng 613 MB, sáu bản sao từ 69 MB tới 651 MB; dưới tải TPS giảm 2% đến 15%; dump giữ 1.000 dòng, basebackup giữ 1.200.

biến thể thời gian kích thước TPS dưới tải
pg_dump -Fp 0,89 s 407 MB —
pg_dump -Fc (nén) 2,92 s 69 MB 9.474 (−9%)
pg_dump -Fc -Z0 1,05 s 426 MB —
pg_dump -Fd -j4 2,33 s 69 MB —
pg_basebackup -Fp 0,89 s 651 MB 10.140 (−2%)
pg_basebackup -Ft -z 6,10 s 100 MB 8.891 (−15%)
không sao lưu 10.392 / 10.475

Nén là phần đắt, không phải đọc. Dump SQL thuần và basebackup thư mục cùng mất 0,89 giây cho 613 MB — đọc đĩa và chép là chuyện rẻ. Bật nén, dump lên 2,92 giây và basebackup gzip lên 6,10 giây: hai đến sáu giây CPU để đổi 400–650 MB lấy 69–100 MB. -Fd -j4 lấy lại một phần bằng cách nén trên bốn tiến trình (2,33 giây), còn pg_basebackup -z nén một luồng nên chậm nhất. Với CSDL lớn gấp trăm lần, con số này là phút và giờ; chọn nén ở đâu (trong pg_dump, ngoài bằng zstd -T, hay không nén rồi để hệ tệp lo) là quyết định có đo được, không phải mặc định.

Dữ liệu pgbench nén 6 lần, và đó là cảnh báo. Cột filler của pgbench toàn khoảng trắng, nên 449 MB thành vài MB. Bảng docs md5 ngẫu nhiên mới là đại diện cho dữ liệu thật: 156 MB gần như không nén. Kích thước 69 MB ở đây là kết quả của dữ liệu giả; tỉ lệ thời gian và mọi kết luận khác thì không phụ thuộc vào đó.

Không lệnh nào khoá bảng; cái mất là CPU và I/O. pgbench 8 client chạy 10.392 TPS khi yên; trong lúc dump nén còn 9.474 (−9%), basebackup gzip còn 8.891 (−15%), basebackup không nén 10.140 (−2%). Không có truy vấn nào bị chặn — pg_dump chỉ giữ ACCESS SHARE, basebackup không giữ khoá nào — chỉ có tranh CPU với tiến trình nén và tranh đĩa với tiến trình đọc. Đổi lại, sao lưu chạy chậm hơn dưới tải: dump 3,6 giây thay vì 2,9, basebackup gzip 7,8 thay vì 6,1. Trên máy chủ thật có pg_dump lúc 2 giờ sáng là vì CPU, không phải vì lock.

Dump chụp lúc bắt đầu, basebackup nhất quán lúc kết thúc. Thí nghiệm C: bảng events có 1.000 dòng, chèn thêm 5 × 200 dòng trong lúc sao lưu chạy, kết thúc với 2.000 dòng. Bản dump khôi phục ra đúng 1.000 — ảnh chụp MVCC tại thời điểm pg_dump mở giao dịch, 1.000 dòng sau không tồn tại với nó. Bản basebackup khởi động lên có 1.200 — 200 dòng kịp commit trước khi chép xong được WAL replay mang vào, 800 dòng sau điểm kết thúc thì không. Hai con số này là định nghĩa thực dụng của "logic" và "vật lý": dump trả lời "lúc bắt đầu có gì", basebackup trả lời "lúc kết thúc có gì" và, kèm WAL lưu trữ, "lúc bất kỳ có gì".

Khôi phục basebackup là khởi động, không phải nhập lại. Container mới chạy trên thư mục basebackup: starting backup recovery … consistent recovery state reached … ready to accept connections sau 758 ms, kể cả thời gian khởi động container, với đủ một triệu dòng docs. Không có CREATE INDEX, không dựng lại khoá ngoại, không COPY — tệp đã là tệp. Khôi phục dump thì ngược lại: phải chạy lại mọi COPY và dựng lại mọi index, và đó là thứ phần 2 đo.

Đánh đổi cần cân nhắc

Dump nhỏ, di động, chọn lọc được; basebackup to, gắn với phiên bản và kiến trúc. Dump 69 MB khôi phục được vào PostgreSQL 17 trên máy khác, hoặc chỉ một bảng. Basebackup 651 MB chỉ khởi động được trên cùng phiên bản lớn, cùng kiến trúc CPU, cùng bố cục tablespace — nhưng nó là thứ duy nhất khôi phục được "về 10:47:12" thay vì "về lúc cron chạy".

Basebackup lớn hơn chính CSDL. 651 MB cho CSDL 613 MB: cộng WAL stream và tệp trống chưa bị VACUUM thu hồi. Nén gzip đưa về 100 MB nhưng tốn 6 giây đơn luồng; PostgreSQL 15+ có --compress=zstd song song, không đo ở đây.

Phạm vi đo. 613 MB trên SSD cùng máy, không có mạng giữa máy chủ và nơi lưu; CSDL thật đi qua mạng và đĩa chậm hơn sẽ dịch mọi con số, nhưng không đổi thứ tự. Khôi phục dump, kiểm tra tính toàn vẹn (pg_verifybackup), và tự động hoá đều chưa chạm — phần 2 và phần 4.

Ba ý mang về

  1. Hai lệnh trả lời hai câu khác nhau: dump là ảnh chụp lúc bắt đầu (1.000/2.000 dòng), basebackup là trạng thái lúc kết thúc (1.200) và kèm WAL lưu trữ là bất kỳ lúc nào. Chọn theo câu hỏi "khôi phục về khi nào", không theo kích thước tệp.
  2. Đọc rẻ, nén đắt: 0,89 giây cho cả 613 MB dù dump hay basebackup; nén thêm 2–6 giây CPU và chính CPU đó làm TPS giảm 9–15%. Không lệnh nào khoá bảng.
  3. Khôi phục vật lý là khởi động (758 ms), khôi phục logic là nhập lại — và kích thước 69 MB của dump đến từ dữ liệu pgbench nén 6 lần, đừng đem con số đó đi ước lượng cho dữ liệu thật.

Nguồn

Phần sau khôi phục bản dump 69 MB này và đo thứ không nằm trong kích thước tệp: thứ tự dựng lại, pg_restore -j song song giúp được bao nhiêu, khi nào index và khoá ngoại được dựng, và những thứ dump không mang theo.