PostgreSQL WAL: Những bài học thực tế về hiệu năng và phục hồi dữ liệu

Database tutorial - IT technology blog
Database tutorial - IT technology blog

Sáu tháng làm việc với PostgreSQL WAL trong môi trường Production

Sáu tháng trước, tôi đã chuyển đổi các dịch vụ tài chính cốt lõi của chúng tôi sang một cụm PostgreSQL có lưu lượng truy cập cao, xử lý hơn 5.000 giao dịch mỗi giây. Trong những đêm thức trắng để gỡ lỗi và tinh chỉnh hiệu năng, tôi nhận ra rằng Write-Ahead Log (WAL) chính là động cơ cốt lõi của Postgres. Hiểu về WAL không chỉ là nhiệm vụ của các DBA. Nó cực kỳ quan trọng đối với bất kỳ kỹ sư nào cần đảm bảo tính toàn vẹn của dữ liệu và băng thông cao.

Hãy coi WAL như một cuốn nhật ký của cơ sở dữ liệu. Thay vì ghi mọi thay đổi vào các tệp dữ liệu nặng nề — vốn gây ra các hoạt động I/O ngẫu nhiên (random I/O) tốn kém — Postgres sẽ ghi lại thay đổi vào một tệp nhật ký tuần tự trước. Lựa chọn thiết kế này mang lại mọi thứ, từ tính an toàn ACID cho đến các cụm server có tính sẵn sàng cao (high-availability).

Kiến trúc: WAL so với Cập nhật dữ liệu trực tiếp

Để thấy tại sao WAL lại quan trọng, chúng ta phải xem cách một cơ sở dữ liệu hoạt động nếu không có nó. Nếu Postgres ghi mọi lệnh UPDATE hoặc INSERT trực tiếp vào các tệp bảng ngay lập tức, chúng ta sẽ gặp phải hai rào cản: nghẽn cổ chai hiệu năng và hỏng dữ liệu.

Vấn đề của việc Ghi trực tiếp (Direct-Write)

Trong mô hình ghi trực tiếp, mỗi giao dịch buộc hệ điều hành phải tìm và sửa đổi các khối (block) cụ thể trên đĩa. Vì dữ liệu bị phân tán, điều này dẫn đến I/O ngẫu nhiên. Độ trễ thường tăng vọt từ 0,1ms lên hơn 10ms, ngay cả trên ổ cứng NVMe. Nếu mất điện giữa chừng khi đang ghi, các tệp dữ liệu của bạn sẽ trở nên không nhất quán và có khả năng không thể phục hồi.

Giải pháp WAL

Postgres sử dụng WAL để biến I/O ngẫu nhiên chậm chạp thành I/O tuần tự nhanh chóng. Khi một giao dịch xảy ra, quy trình sẽ tuân theo một lộ trình nghiêm ngặt:

  1. Hệ thống mô tả thay đổi trong một bản ghi (record) WAL.
  2. Bản ghi này được nối thêm vào tệp nhật ký hiện tại trong thư mục pg_wal.
  3. Giao dịch chỉ được commit khi bản ghi WAL đã được flush an sauống đĩa.
  4. Các tệp dữ liệu thực tế (heaps và indexes) vẫn nằm trong bộ nhớ và sẽ được đồng bộ xuống đĩa sau đó trong quá trình “checkpoint.”

Nếu hệ thống gặp sự cố (crash), Postgres chỉ cần phát lại (replay) các nhật ký từ lần checkpoint cuối cùng. Điều này giúp khôi phục cơ sở dữ liệu về trạng thái nhất quán mà không làm mất bất kỳ hàng dữ liệu nào đã được commit.

Đánh đổi trong thực tế

Vận hành một môi trường quy mô lớn sẽ bộc lộ những ưu và nhược điểm thực tế của cơ chế này.

Ưu điểm

  • Tính bền vững (Durability): Dữ liệu đã commit được đảm bảo tồn tại ngay cả khi phần cứng gặp lỗi.
  • Băng thông hiệu quả: Chỉ nhật ký tuần tự mới cần flush ngay lập tức. Điều này cho phép cơ sở dữ liệu gom nhóm (batch) các lần ghi vào tệp dữ liệu để đạt hiệu suất tốt hơn.
  • Streaming Replication: WAL là nền tảng cho tính sẵn sàng cao. Server chính (primary) truyền các bản ghi WAL của nó đến các server dự phòng (standby) theo thời gian thực.
  • Phục hồi chi tiết: Bạn có thể giữ lại lịch sử các tệp WAL để khôi phục bản sao lưu đến một thời điểm chính xác đến từng mili giây trong quá khứ.

Thách thức

  • Khuếch đại ghi (Write Amplification): Mọi thay đổi đều được ghi hai lần. Với full_page_writes được bật, lần ghi đầu tiên sau một checkpoint có thể làm đầy một phân đoạn WAL 16MB chỉ trong vài giây.
  • Áp lực lên đĩa cứng: Nếu quá trình lưu trữ (archiving) thất bại, thư mục pg_wal sẽ phình to cho đến khi đầy đĩa. Điều này sẽ làm sập server của bạn.
  • Độ phức tạp khi cấu hình: Việc tìm kiếm sự cân bằng hợp lý giữa thời gian lưu trữ và việc archiving đòi hỏi phải giám sát liên tục.

Khi tôi cần chuyển đổi CSV sang JSON để nhập dữ liệu hoặc kiểm tra việc replay WAL với các tập dữ liệu cụ thể, tôi sử dụng toolcraft.app/vi/tools/data/csv-to-json. Nó chạy cục bộ trong trình duyệt, giúp giữ kín các schema production nhạy cảm trong khi tôi hoàn thành công việc.

Hướng dẫn cấu hình Production

Các thiết lập mặc định của Postgres quá thận trọng đối với phần cứng hiện đại. Đối với môi trường production của chúng tôi, chúng tôi đã sửa đổi các tham số postgresql.conf này để cân bằng giữa tốc độ và an toàn.

# Mức độ WAL: 'replica' là bắt buộc để replication và PITR hoạt động
wal_level = replica

# Checkpoints: Giá trị cao hơn giúp giảm đột biến I/O nhưng làm tăng thời gian phục hồi
checkpoint_timeout = 15min
max_wal_size = 16GB
min_wal_size = 4GB

# Archiving: Quan trọng cho PITR và giữ cho thư mục pg_wal gọn nhẹ
archive_mode = on
archive_command = 'test ! -f /mnt/storage/archive/%f && cp %p /mnt/storage/archive/%f'

# Tối ưu hiệu năng
full_page_writes = on
wal_compression = on

Trong trường hợp của chúng tôi, việc tăng checkpoint_timeout lên 15 phút đã giúp giảm đáng kể độ trễ I/O của đĩa. Phục hồi sau sự cố có thể mất thêm một phút, nhưng hiệu năng tăng thêm hàng ngày là một sự đánh đổi xứng đáng.

Thiết lập Point-in-Time Recovery (PITR)

PITR là tấm lưới an toàn cuối cùng của bạn. Đây là cách triển khai nó bằng phương pháp archiving tiêu chuẩn.

1. Bật WAL Archiving

Đảm bảo archive_command của bạn sao chép các phân đoạn WAL 16MB sang một vị trí bên ngoài an toàn như S3 hoặc một ổ NAS đã mount.

# Tạo thư mục lưu trữ
mkdir -p /var/lib/postgresql/archive
chown postgres:postgres /var/lib/postgresql/archive

# Khởi động lại Postgres sau khi chỉnh sửa postgresql.conf
sudo systemctl restart postgresql

2. Tạo bản sao lưu gốc (Base Backup)

Base backup là một ảnh chụp nhanh (snapshot) dữ liệu của bạn. Kết hợp với kho lưu trữ WAL, bạn có thể xây dựng lại cơ sở dữ liệu tại bất kỳ thời điểm nào trong lịch sử.

pg_basebackup -D /var/lib/postgresql/backup -Ft -z -P -R

3. Thực hiện phục hồi

Giả sử một lập trình viên vô tình xóa một bảng vào lúc 10:30 sáng. Để khôi phục về thời điểm 10:29 sáng, hãy làm theo các bước sau:

  1. Dừng dịch vụ cơ sở dữ liệu.
  2. Di chuyển thư mục dữ liệu bị hỏng sang một vị trí sao lưu.
  3. Khôi phục các tệp base backup của bạn.
  4. Tạo một tệp recovery.signal trống trong thư mục dữ liệu.
  5. Thiết lập recovery_target_time trong postgresql.conf.
# Thêm các dòng này vào postgresql.conf
restore_command = 'cp /var/lib/postgresql/archive/%f %p'
recovery_target_time = '2023-10-27 10:29:00'
recovery_target_action = 'promote'

Khởi động server, và Postgres sẽ phát lại mọi giao dịch từ kho lưu trữ cho đến khi chạm tới thời điểm mục tiêu của bạn.

Giám sát và Kiểm tra sức khỏe

Bạn không thể “thiết lập rồi bỏ đấy” với WAL. Hãy sử dụng truy vấn này để theo dõi lượng lưu lượng WAL mà ứng dụng của bạn tạo ra. Điều này rất thiết yếu để lập kế hoạch dung lượng đĩa.

SELECT 
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0')) AS tong_luong_wal_da_ghi,
    now() AS thoi_gian_quan_sat;

Kiểm tra thông số này hàng giờ để tìm “tốc độ tạo WAL” của bạn. Các mức tăng đột ngột thường chỉ ra việc nhập dữ liệu hàng loạt hoặc các tác vụ batch chưa được tối ưu. Làm chủ WAL đã thay đổi cách tôi xử lý độ tin cậy của cơ sở dữ liệu. Đó là sự khác biệt giữa một hệ thống sống sót sau sự cố phần cứng và một hệ thống làm mất hàng giờ dữ liệu của người dùng.

Share: