Sao lưu là việc ai cũng làm và ít ai đo. Phần này đo năm cách trên cùng một cơ sở dữ liệu, và chỉ ra bốn thứ mà cách phổ biến nhất không hề lấy.
Năm cách, cùng một cơ sở dữ liệu 520 MB
| Cách | Thời gian | Kích thước | Khôi phục |
|---|---|---|---|
pg_dump -Fc -Z0 (custom, không nén) |
0,7 s | 269,4 MB | 3,5 s |
pg_dump (văn bản SQL) |
0,8 s | 255,1 MB | 3,8 s |
pg_dump -Fd -j4 (thư mục, 4 luồng) |
5,8 s | 135,1 MB | 3,0 s |
pg_dump -Fc (custom, nén mặc định) |
5,9 s | 135,3 MB | 2,9 s |
pg_basebackup -Ft -z -Xf (cả cụm) |
10,3 s | 187,6 MB | — |
Hai điều đọc ra được.
Nén tốn 8 lần thời gian để giảm 2 lần dung lượng. 0,7 giây thành 5,9 giây, đổi lấy 269 MB thành 135 MB. Với sao lưu chạy hàng đêm thì đổi này hợp lý; với sao lưu chạy trước mỗi lần triển khai thì có thể không.
Bốn luồng song song không giúp gì. -Fd -j4 mất 5,8 giây, gần đúng bằng bản một luồng 5,9 giây. Nút thắt là chính việc nén chứ không phải đọc dữ liệu — và pg_dump song song hoá theo bảng, mà cơ sở dữ liệu thử của tôi chỉ có hai bảng, một cái chiếm gần hết.
Song song hoá chỉ giúp khi có nhiều bảng lớn cỡ nhau. Với một bảng khổng lồ và vài bảng nhỏ — hình dạng rất phổ biến — nó không làm được gì.
Khôi phục nhanh hơn sao lưu
| Cách | Thời gian |
|---|---|
psql < bản SQL |
3,8 s |
pg_restore từ bản custom |
3,5 s |
pg_restore -j4 từ bản custom |
2,9 s |
pg_restore -j4 từ thư mục |
3,0 s |
Ở đây -j4 có giúp — 3,5 xuống 2,9 giây — vì lúc khôi phục, việc dựng chỉ mục song song hoá được, và đó là phần tốn thời gian nhất.
Chú ý psql < bản SQL là cách duy nhất không song song hoá được, vì tệp SQL là một chuỗi lệnh tuần tự. Đó là lý do chính để chọn -Fc thay vì bản văn bản.
pg_dump chụp một ảnh nhất quán và không khoá ghi
Trong lúc pg_dump đang chạy:
insert into nho values (99999, 'ghi trong lúc đang dump');
-- thành công, không bị chặn
Và bản dump không chứa dòng đó — tôi kiểm bằng grep, kết quả 0.
Đây là hệ quả trực tiếp của MVCC (phần 21): pg_dump mở một giao dịch ở mức repeatable read và đọc ảnh chụp tại thời điểm bắt đầu. Ứng dụng chạy bình thường suốt thời gian đó.
Nhưng nó cũng có nghĩa là pg_dump giữ một giao dịch mở suốt thời gian chạy. Phần 21 đã đo hậu quả: VACUUM không dọn được gì trong lúc đó, và bảng phình lên. Với cơ sở dữ liệu 520 MB thì 6 giây không sao; với 500 GB thì đó là hàng giờ giao dịch mở.
Đó là lý do sao lưu logic không phù hợp cho cơ sở dữ liệu rất lớn, và pg_basebackup tồn tại.
Bốn thứ pg_dump không lấy
Đây là phần dễ phát hiện quá muộn.
| Không có trong bản dump | Trên máy thử của tôi |
|---|---|
| Vai trò và mật khẩu | 15 vai trò trong cụm |
| Các cơ sở dữ liệu khác | pg_dump chỉ lấy đúng một |
| Định nghĩa tablespace | 2 cái, chỉ còn tên |
postgresql.conf, pg_hba.conf |
không nằm trong CSDL |
Mục đầu tiên là mục nguy hiểm nhất. Bạn khôi phục thành công, mọi bảng có đủ, mọi dòng có đủ — và không ai đăng nhập được, vì tài khoản không tồn tại. Quyền GRANT trên bảng thì có trong dump, nhưng chính vai trò được cấp quyền thì không.
Cách chữa là chạy kèm một lệnh nữa:
pg_dumpall --globals-only -U postgres > globals.sql
pg_dump -Fc -U postgres ten_csdl > data.dump
Khôi phục thì globals.sql trước, data.dump sau.
Nếu bạn chỉ có một lệnh trong script sao lưu, bạn đang thiếu một nửa.
pg_dump so với pg_basebackup
Hai công cụ này giải hai bài toán khác nhau, không thay thế nhau.
pg_dump |
pg_basebackup |
|
|---|---|---|
| Lấy gì | Một CSDL, dạng logic | Cả cụm, dạng vật lý |
| Khôi phục sang phiên bản khác | Được | Không |
| Khôi phục sang kiến trúc CPU khác | Được | Không |
| Khôi phục chọn lọc một bảng | Được | Không |
| Nền cho khôi phục theo thời điểm | Không | Được |
| Chi phí trên CSDL rất lớn | Cao — giữ giao dịch mở | Thấp hơn |
Dòng thứ hai đáng chú ý: bản pg_dump từ PostgreSQL 13 khôi phục được vào PostgreSQL 16. Bản pg_basebackup thì không — nó là bản sao byte của tệp dữ liệu, và định dạng đó gắn với phiên bản.
Đó là lý do nâng cấp lớn thường đi qua pg_dump, và cũng là lý do nên có cả hai loại sao lưu.
Vài lưu ý thực dụng
-Xf và -Xs của pg_basebackup. Tôi thử -Xs (truyền WAL song song) và nó treo; -Xf (lấy WAL ở cuối) chạy được trong 10,3 giây. Trên máy chủ thật -Xs là lựa chọn tốt hơn vì nó không cần giữ WAL trên máy nguồn, nhưng nó cần một kết nối sao chép thứ hai — kiểm max_wal_senders trước.
pg_dump không sao lưu được cụm đang chạy phiên bản mới hơn pg_dump. Luôn dùng pg_dump của phiên bản đích, không phải của phiên bản nguồn.
Kiểm bản sao lưu bằng cách khôi phục thật. Số đo ở trên cho thấy khôi phục 3 triệu dòng mất 3 giây. Không có lý do gì để không có một công việc định kỳ khôi phục bản sao lưu vào một cơ sở dữ liệu tạm rồi đếm số dòng.
Một bản sao lưu chưa từng được khôi phục thử thì không phải bản sao lưu — nó là một tệp mà bạn hy vọng là bản sao lưu.
Kịch bản sao lưu tối thiểu
#!/bin/sh
set -e
NGAY=$(date +%F)
DICH=/backup/$NGAY
mkdir -p "$DICH"
# 1. vai trò, mật khẩu, tablespace
pg_dumpall -U postgres --globals-only > "$DICH/globals.sql"
# 2. từng cơ sở dữ liệu
for db in $(psql -U postgres -qAt -c \
"select datname from pg_database where not datistemplate and datallowconn"); do
pg_dump -U postgres -Fc -f "$DICH/$db.dump" "$db"
done
# 3. tệp cấu hình
cp /etc/postgresql/*/main/*.conf "$DICH/" 2>/dev/null || true
# 4. kiểm ngay: khôi phục vào CSDL tạm rồi đếm
createdb -U postgres kiem_tra_$NGAY
pg_restore -U postgres -d kiem_tra_$NGAY -j4 "$DICH/postgres.dump"
psql -U postgres -d kiem_tra_$NGAY -qAt -c "select count(*) from d"
dropdb -U postgres kiem_tra_$NGAY
Bước 4 là bước hay bị bỏ, và là bước duy nhất biến ba bước trên thành một bản sao lưu thật.
Thử ba mươi giây
select
(select count(*) from pg_roles) as so_vai_tro,
(select count(*) from pg_database where not datistemplate) as so_csdl,
(select count(*) from pg_tablespace) as so_tablespace;
Rồi mở kịch bản sao lưu hiện tại của bạn và kiểm xem nó có pg_dumpall --globals-only không, và có lặp qua mọi cơ sở dữ liệu không.
Nếu thiếu một trong hai, hãy thử khôi phục vào một máy trống ngay hôm nay — trước khi phải làm việc đó vào lúc hai giờ sáng.
Phần sau đo khôi phục theo thời điểm: quy trình đầy đủ từ ảnh chụp gốc và nhật ký WAL, để quay ngược về đúng một giây trước khi ai đó gõ nhầm câu DELETE.