Phần 1 đo xong việc tạo bản dump: 75 MB, vài giây. Bản dump nằm đó rất yên tâm cho tới ngày phải dùng, và lúc đó câu hỏi không còn là "tệp bao nhiêu MB" mà là "bao lâu thì có lại cơ sở dữ liệu, và nó có giống cái cũ không". Phần này khôi phục bản dump đó vào cơ sở dữ liệu trống và đo từng khúc: thời gian đi đâu, -j giúp được bao nhiêu và khi nào, và những thứ bản dump không mang theo — mỗi thứ một bằng chứng cụ thể.

Ba section, song song theo bảng, và phần thiếu

Ảnh chụp đoạn mã nền tối khôi phục từ pg_dump ba section song song và phần thiếu với PostgreSQL 16 dump Fc 75 MB của CSDL 613 MB gồm 5 bảng pgbench docs 1M dòng và events có role app_reader FK ALTER DATABASE SET, một bản dump custom có ba section pg_restore chạy đúng thứ tự đó gồm section pre-data CREATE TABLE TYPE FUNCTION chưa có index, section data COPY từng bảng vào bảng chưa có index nên nhanh, section post-data CREATE INDEX PRIMARY KEY FOREIGN KEY TRIGGER dựng sau khi có dữ liệu, index và FK không được chép chúng được dựng lại thời gian này không có trong kích thước tệp dump, song song j N chia theo bảng và theo index không chia trong một bảng gồm pg_restore j 4 d lab_r dump.custom 4 kết nối chỉ với Fc hoặc Fd, psql f dump.sql SQL thuần một kết nối tuần tự không j được, một bảng chiếm 73 phần trăm dữ liệu thì j gần như vô dụng 8 bảng bằng nhau thì j có ích, thứ pg_dump không mang theo và cách thấy nó gồm role password membership dùng pg_dumpall globals-only nạp trước pg_restore, ALTER DATABASE SET work_mem không có trừ khi pg_dump create kiểm bằng SHOW sau restore, thống kê planner pg_statistic không có phải ANALYZE sau restore nếu không plan sai, postgresql.conf pg_hba.conf không bao giờ chép tay, có mang theo giá trị sequence setval GRANT nhưng lỗi nếu role chưa có FK kể cả NOT VALID, đo gì gồm A pg_restore j 1 2 4 8 vào CSDL trống, B thời gian từng section j1 và post-data j4, C psql f dump.sql so với pg_restore, D EXPLAIN trước và sau ANALYZE, E F work_mem sequence globals restore vào cụm trống có hoặc không globals.sql đếm lỗi, G 8 bảng bằng nhau 4M dòng j 1 2 4 8

Hình 1: Bản dump custom có ba section và pg_restore dựng lại index sau khi nạp dữ liệu. Song song chia theo bảng, không chia trong một bảng.

Một bản dump định dạng custom không phải một khối: nó có ba section và pg_restore chạy đúng thứ tự đó. pre-data tạo bảng, kiểu, hàm — chưa có index. data là COPY từng bảng, vào bảng còn trống index nên nhanh nhất có thể. post-data dựng index, khoá chính, khoá ngoại, trigger — sau khi dữ liệu đã nằm trong bảng. Điểm quan trọng: index và khoá ngoại không được chép, chúng được dựng lại từ đầu, và thời gian đó không có mặt trong kích thước tệp dump.

Song song với -j N mở N kết nối và chia việc theo bảng (và theo từng index ở post-data); một bảng không bao giờ được chia nhỏ. Định dạng SQL thuần (-Fp) không có khái niệm section tách rời hay -j: psql -f chạy một kết nối, tuần tự từ đầu tới cuối.

Những thứ pg_dump không mang theo: role và mật khẩu (thuộc cụm, không thuộc database), ALTER DATABASE … SET, thống kê planner, và mọi tệp cấu hình. Thứ nó có mang: giá trị hiện tại của sequence, GRANT (nhưng chỉ chạy được nếu role đã tồn tại), và khoá ngoại kể cả trạng thái NOT VALID.

Dữ liệu đo: bản dump của CSDL 613 MB từ phần 1, thêm role app_reader có GRANT SELECT, một khoá ngoại NOT VALID, và ALTER DATABASE lab SET work_mem = '32MB' để có thứ mà kiểm.

Đo thật

Ảnh chụp output thật nền tối 80 phần trăm thời gian là COPY j chỉ giúp khi nhiều bảng với pg-prim postgres 16 10 CPU dump Fc 75 MB 3,45 giây dump Fp 416 MB 0,94 giây CSDL 613 MB bảng lớn nhất 73 phần trăm, A pg_restore vào CSDL trống đổi j gồm j1 2848 ms j2 2179 ms j4 2183 ms j8 2251 ms chỉ nhanh 1,3 lần vì pgbench_accounts 394 MB bằng 73 phần trăm, psql f dump.sql SQL thuần 1 kết nối 3004 ms, B thời gian theo section j1 gồm pre-data CREATE TABLE 25 ms 1 phần trăm, data COPY 2267 ms 80 phần trăm đọc 75 MB nén ghi 613 MB heap và WAL, post-data index PK FK 731 ms 26 phần trăm j4 cho 651 ms 3 index lớn chạy song song PK accounts chiếm phần lớn, tổng section lớn hơn tổng j1 vì mỗi lần là một kết nối riêng tỉ lệ mới là thứ đáng nhìn, G cùng khoảng 4M dòng nhưng chia đều 8 bảng mỗi bảng PK và 1 index gồm j1 1929 ms j2 1159 ms j4 1092 ms j8 1175 ms là 1,77 lần chạm trần ở 4 vì cùng ghi vào một WAL, D sau restore trước ANALYZE planner đoán sai gồm explain select từ docs where id between 1000 and 2000 trước Bitmap Heap Scan on docs rows 5000 không có pg_statistic đoán, ANALYZE 254 ms, sau Index Scan using docs_pkey rows 943 đúng plan đúng ước lượng thật 1001, E F thứ không nằm trong dump gồm work_mem gốc 32MB ALTER DATABASE SET sau restore 4MB vì ALTER DATABASE không nằm trong pg_dump, sequence events_id_seq gốc 4000 sau restore 4000 setval có trong dump, restore vào cụm trống pg-fresh 1 lỗi role app_reader does not exist GRANT mất 1000000 dòng docs vẫn vào, nạp pg_dumpall globals-only 37 dòng 2 CREATE ROLE trước rồi restore 0 lỗi GRANT đủ, FK NOT VALID trong dump giữ nguyên NOT VALID restore không kiểm tra lại dữ liệu, nói thẳng 613 MB khôi phục 2,8 giây nên mọi chênh lệch đều nhỏ cái mang sang CSDL lớn là tỉ lệ 80 26 và j theo bảng dump đã có trong tay từ phần 1 thứ không có trong tay là globals.sql nó không tự sinh ra

Hình 2: COPY chiếm 80%, dựng index 26%; -j chỉ giúp khi dữ liệu chia đều nhiều bảng; planner đoán sai cho tới khi ANALYZE; thiếu role là một lỗi.

phép đo kết quả
pg_restore -j1 / -j2 / -j4 / -j8 (CSDL 613 MB, bảng lớn nhất 73%) 2.848 / 2.179 / 2.183 / 2.251 ms
psql -f dump.sql 3.004 ms
section pre-data / data / post-data 25 / 2.267 / 731 ms
post-data -j4 651 ms
8 bảng bằng nhau, -j1 / -j2 / -j4 / -j8 1.929 / 1.159 / 1.092 / 1.175 ms
EXPLAIN trước / sau ANALYZE (254 ms) rows=5000 Bitmap Scan / rows=943 Index Scan
restore vào cụm trống, không / có globals.sql 1 lỗi / 0 lỗi

80% là COPY, 26% là dựng lại, và tỉ lệ đó mới là thứ mang về. pre-data 25 ms — tạo bảng gần như miễn phí. data 2.267 ms — đọc 75 MB nén và ghi 613 MB heap cộng WAL. post-data 731 ms — dựng ba index lớn và khoá ngoại. Hai section sau cộng lại hơn tổng -j1 vì mỗi lần là một kết nối riêng, nên nhìn tỉ lệ chứ đừng cộng: với CSDL lớn gấp trăm, COPY là hàng chục phút và dựng index là hàng phút nữa không có trong kích thước tệp. Đây là lý do "dump 75 MB" không nói gì về RTO.

-j chia theo bảng, nên một bảng 73% là một trần. -j2 xuống 2.179 ms rồi -j4, -j8 không thêm gì — pgbench_accounts chiếm 394 MB trong 613 MB và một kết nối phải nạp hết nó dù có bao nhiêu kết nối khác rỗi. Chia cùng lượng dữ liệu thành tám bảng bằng nhau: -j1 1.929 ms, -j2 1.159, -j4 1.092 — 1,77 lần — rồi -j8 chậm hơn một chút. Trần thứ hai không phải bảng mà là WAL: tám kết nối cùng ghi vào một nhật ký, cùng fsync. Muốn -j có ích thật thì bảng phải chia đều và đĩa WAL phải theo kịp; muốn nhanh hơn nữa là chuyện của wal_level=minimal lúc khôi phục, không đo ở đây.

SQL thuần chậm hơn 5% và không có nút nào để vặn. psql -f dump.sql 3.004 ms so với pg_restore -j1 2.848 ms — gần nhau vì cùng là một kết nối. Khác biệt là -Fp không bao giờ có -j, không chọn được bảng, không tách được section. Dùng -Fp khi cần đọc bằng mắt hoặc grep; mọi lúc khác là -Fc.

Sau restore, planner đoán sai 5 lần cho tới khi ANALYZE. COPY không cập nhật pg_statistic. Truy vấn khoảng id between 1000 and 2000 trên docs một triệu dòng: planner ước lượng 5.000 dòng (không có thống kê, lấy mặc định) và chọn Bitmap Heap Scan; ANALYZE mất 254 ms, sau đó ước lượng 943 (thật 1.001) và chọn Index Scan. Một CSDL vừa khôi phục mà chưa ANALYZE chạy đúng nhưng chạy chậm, và không có lỗi nào để nhìn. pg_restore không làm bước này thay bạn.

Thiếu role là một lỗi — và chỉ một. Khôi phục vào cụm trống pg-fresh: pg_restore báo đúng một lỗi, role "app_reader" does not exist, bỏ qua câu GRANT và vẫn nạp đủ một triệu dòng. Nghĩa là CSDL "khôi phục xong" nhưng ứng dụng dùng app_reader sẽ không đăng nhập được, và pg_restore kết thúc với mã lỗi dễ bị script bỏ qua. Nạp pg_dumpall --globals-only (37 dòng, 2 CREATE ROLE, kèm mật khẩu đã băm SCRAM) trước rồi restore: 0 lỗi, quyền đầy đủ. Tệp globals này không tự sinh ra từ pg_dump; nó là bước riêng trong lịch sao lưu.

work_mem biến mất, sequence ở lại, NOT VALID giữ nguyên. ALTER DATABASE lab SET work_mem = '32MB' không có trong dump: CSDL khôi phục chạy 4MB mặc định — truy vấn nào từng nhờ 32MB mà sort trong RAM giờ tràn ra đĩa, lặng lẽ. Sequence events_id_seq khôi phục đúng 4000 nhờ setval trong dump — không có chuyện INSERT đầu tiên trùng khoá. Khoá ngoại NOT VALID được dựng lại nguyên trạng NOT VALID: pg_restore không kiểm tra lại dữ liệu cũ, cũng không tự VALIDATE.

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

Khôi phục logic trả giá bằng thời gian dựng lại, đổi lấy tự do. 2,85 giây cho 613 MB, so với 758 ms khởi động từ basebackup ở phần 1 — gấp gần bốn lần, và tỉ lệ này tăng theo số index. Đổi lại, dump khôi phục được sang phiên bản khác, chỉ một bảng, hoặc một schema sạch không mang theo bloat. Chọn theo câu hỏi: "cần lại đúng cụm này nhanh nhất" là basebackup; "cần dữ liệu ở chỗ khác" là dump.

-j không phải nút tăng tốc chung. Nó giúp khi dữ liệu chia đều nhiều bảng và đĩa WAL còn dư; với một bảng lớn thống trị, -j8 chỉ tốn thêm kết nối. Đo trên chính CSDL của bạn với --section=data để biết bảng nào chiếm bao nhiêu trước khi tin vào một con số từ bài này.

Phạm vi đo. CSDL 613 MB trên SSD cùng máy, mọi thời gian dưới ba giây nên sai số vài phần trăm; chỉ tỉ lệ và các hiện tượng (role, work_mem, ANALYZE, NOT VALID) là thứ mang sang CSDL lớn. Chưa đo: --data-only vào bảng có sẵn index (chậm hơn nhiều), --disable-triggers, và khôi phục chọn lọc bằng -L.

Ba ý mang về

  1. Kích thước dump không nói gì về thời gian khôi phục: 75 MB thành 613 MB heap qua COPY (80%) rồi dựng lại mọi index và khoá ngoại (26%) — hai khúc đó mới là RTO, và chúng tỉ lệ với dữ liệu lẫn số index.
  2. -j chia theo bảng, chạm trần ở WAL: 1,3 lần khi một bảng chiếm 73%, 1,77 lần với tám bảng đều, không hơn ở -j8. Đo bằng section trên CSDL thật trước khi đặt con số vào runbook.
  3. "Khôi phục xong" còn bốn việc dump không làm: nạp globals.sql trước (thiếu role = GRANT mất, 1 lỗi dễ bị bỏ qua), đặt lại ALTER DATABASE SET, ANALYZE ngay (planner đoán sai 5 lần), và chép cấu hình bằng tay.

Nguồn

Phần sau xuống dưới cả hai kiểu sao lưu: WAL. Ghi một dòng sinh bao nhiêu byte nhật ký, vì sao dòng đầu tiên sau checkpoint đắt gấp nhiều lần, và checkpoint_timeout với max_wal_size đổi hình dạng I/O của máy chủ ra sao — đo bằng chính cụm này.