Năm mươi tám phần trước đo từng cơ chế trên container Docker. Phần này quay lại chính cái container đó và hỏi: cấu hình mặc định của nó có dùng được cho máy chủ thật không.

Tham số mặc định, pg_hba, hành vi khi dừng, và biến môi trường

Image dùng mặc định gốc cho mọi tham số

Không có tinh chỉnh nào. Mọi giá trị đúng bằng boot_val của PostgreSQL:

Tham số Image mặc định Nên đặt Đo ở phần
shared_buffers 128 MB 25% RAM 44
work_mem 4 MB để nguyên 45
max_wal_size 1 GB 2–8 GB 46, 47
checkpoint_completion_target 0,9 giữ 0,9 47
track_io_timing off on 44, 45
shared_preload_libraries (rỗng) pg_stat_statements 54
log_min_duration_statement −1 1000 54
effective_cache_size 4 GB 50–75% RAM 44

Hai dòng đáng nói riêng.

work_mem để nguyên 4 MB. Phần 45 đã đo ba lần rằng tăng nó gần như không giúp, và nó nhân theo số nút × số tiến trình × số kết nối. Đây là một trong ít tham số mà mặc định là đúng.

checkpoint_completion_target = 0,9 đã là mặc định từ PostgreSQL 14. Phần 47 đo được nó là thứ quyết định độ mượt — nếu bạn dùng image cũ hơn hoặc tệp cấu hình kế thừa, kiểm lại.

Cách đặt trong Compose:

services:
  db:
    image: postgres:16-alpine
    command:
      - postgres
      - -c=shared_buffers=2GB
      - -c=effective_cache_size=6GB
      - -c=max_wal_size=4GB
      - -c=track_io_timing=on
      - -c=shared_preload_libraries=pg_stat_statements
      - -c=log_min_duration_statement=1000
      - -c=log_checkpoints=on

Cách này ghi đè cả postgresql.conf lẫn ALTER SYSTEM — phần 52 đã vấp đúng chuyện đó khi alter system set wal_level không có tác dụng vì dòng lệnh Docker đã đặt sẵn.

pg_hba.conf mặc định: trust

local  all  all                    trust
host   all  all   127.0.0.1/32     trust
host   all  all   ::1/128          trust
host   all  all   all              scram-sha-256

Ba dòng đầu nghĩa là ai vào được container là vào được cơ sở dữ liệu, không cần mật khẩu.

Đó là lý do mọi lệnh psql trong sáu mươi phần này chạy được mà không khai mật khẩu — tiện cho việc đo, nguy hiểm cho máy chủ thật.

Rủi ro cụ thể: bất kỳ tiến trình nào chạy được docker exec — hoặc bất kỳ lỗ hổng nào cho phép thực thi lệnh bên trong container — đều có quyền superuser trên cơ sở dữ liệu.

Sửa bằng cách gắn tệp riêng:

volumes:
  - ./pg_hba.conf:/etc/postgresql/pg_hba.conf:ro
command:
  - postgres
  - -c=hba_file=/etc/postgresql/pg_hba.conf

Với nội dung:

local  all  all                    scram-sha-256
host   all  all   10.0.0.0/8       scram-sha-256
host   all  all   all              reject

Phần 58 đã nói: dòng khớp đầu tiên thắng, nên dòng reject phải nằm cuối.

Dừng container: sạch hay là sự cố

Tin tốt trước. docker stop với thời gian chờ mặc định 10 giây, trong lúc tám kết nối đang ghi liên tục:

xong trong 0,3 giây
checkpoint complete: wrote 32613 buffers (99.5%)
database system is shut down

Khởi động lại không cần khôi phục. PID 1 trong container là chính postgres, nên nó nhận SIGTERM trực tiếp và tắt đàng hoàng — không có vấn đề "PID 1 không chuyển tiếp tín hiệu" như nhiều image khác.

Nhưng cùng phép thử với docker stop -t 0:

database system was not properly shut down; automatic recovery in progress
redo done ... elapsed: 0.32 s

Sự cố thật. Và phần 55 đã đo hệ quả kèm theo: mọi thống kê pg_stat về 0.

Ở đây khôi phục chỉ mất 0,32 giây vì shared_buffers là 256 MB. Với 8 GB trên đĩa chậm, checkpoint lúc tắt có thể vượt quá 10 giây — và khi đó Docker gửi SIGKILL, và bạn có đúng tình huống trên mà không hề gõ -t 0.

Cách phòng:

services:
  db:
    stop_grace_period: 120s

Hoặc docker stop -t 120. Con số nên lớn hơn thời gian checkpoint lúc tắt, mà bạn ước được từ shared_buffers chia cho tốc độ ghi đĩa.

Biến môi trường chỉ có tác dụng ở lần chạy đầu

lần 1:  POSTGRES_DB=csdl1   ->  CSDL có: postgres, csdl1
lần 2:  POSTGRES_DB=csdl2   ->  CSDL có: postgres, csdl1

csdl2 không được tạo.

POSTGRES_PASSWORD, POSTGRES_DB, POSTGRES_USER, và các kịch bản trong /docker-entrypoint-initdb.d/ chỉ được dùng bởi initdb, và initdb chỉ chạy khi thư mục dữ liệu còn trống.

Đây là nguồn của một hiểu nhầm phổ biến: sửa POSTGRES_PASSWORD trong docker-compose.yml rồi docker compose up -d và tưởng mật khẩu đã đổi. Nó không đổi. Phải:

alter role postgres password 'mat_khau_moi';

Cũng vậy với kịch bản khởi tạo: thêm một tệp .sql vào docker-entrypoint-initdb.d/ sau khi cơ sở dữ liệu đã tồn tại thì nó không bao giờ chạy.

Dữ liệu nằm ở đâu

Nếu không khai volume, Docker tự tạo một volume ẩn:

volume  nguồn=/var/lib/docker/volumes/9b4b8850.../_data  đích=/var/lib/postgresql/data

Dữ liệu vẫn sống qua docker rm, nhưng docker rm -v xoá luôn, và docker volume prune cũng vậy. Không có tên nên không ai nhận ra nó là gì.

Luôn khai volume có tên:

volumes:
  - pgdata:/var/lib/postgresql/data

Về việc dùng bind mount thay volume: nó tiện để sao lưu nhưng có rủi ro quyền và hiệu năng trên macOS/Windows. Với máy chủ Linux thật thì cả hai đều được.

Ba việc nữa cho máy chủ thật

Ghim phiên bản đầy đủ. postgres:16 sẽ thành 16.15, rồi 16.16 sau mỗi lần docker compose pull — và một lần nâng cấp phiên bản nhỏ ngoài ý muốn giữa lúc đang chạy là chuyện không nên có. Dùng postgres:16.14-alpine.

Đặt giới hạn bộ nhớ, và đặt cao hơn tổng bộ nhớ PostgreSQL có thể dùng. Phần 45 đã đo phép nhân: work_mem × số nút × số tiến trình × số kết nối. Nếu container bị OOM killer giết, đó là sự cố — với đủ hậu quả ở mục trên.

Không chạy postgres cùng ứng dụng trong một Compose cho môi trường thật nếu bạn cần chuyển đổi dự phòng, sao lưu liên tục, hay sao chép. Phần 51 tới 53 cho thấy những thứ đó cần nhiều hơn một container.

Một tệp Compose đủ dùng

services:
  db:
    image: postgres:16.14-alpine
    restart: unless-stopped
    stop_grace_period: 120s
    shm_size: 1gb
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/pg_pass
      PGDATA: /var/lib/postgresql/data/pgdata
    secrets: [pg_pass]
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./pg_hba.conf:/etc/postgresql/pg_hba.conf:ro
    command:
      - postgres
      - -c=hba_file=/etc/postgresql/pg_hba.conf
      - -c=shared_buffers=2GB
      - -c=effective_cache_size=6GB
      - -c=max_wal_size=4GB
      - -c=track_io_timing=on
      - -c=shared_preload_libraries=pg_stat_statements
      - -c=log_min_duration_statement=1000
      - -c=log_checkpoints=on
      - -c=wal_log_hints=on
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  pgdata:
secrets:
  pg_pass:
    file: ./pg_pass.txt

Ba dòng ít gặp trong đó:

shm_size: 1gb — mặc định của Docker là 64 MB, và truy vấn chạy song song dùng bộ nhớ chia sẻ. Với parallel_workers cao, 64 MB gây lỗi "could not resize shared memory segment".

PGDATA trỏ vào thư mục con — để initdb không phải làm việc trong thư mục gốc của volume, tránh vấn đề khi volume có sẵn tệp lost+found.

wal_log_hints=on — phần 53 đã đo: không có nó thì pg_rewind từ chối chạy, và mỗi lần chuyển đổi dự phòng là một lần dựng lại toàn bộ.

Thử ba mươi giây

select name, setting, source from pg_settings
where source not in ('default', 'override') order by source, name;

Nếu danh sách rỗng, container của bạn đang chạy hoàn toàn bằng mặc định gốc — và tám dòng trong bảng đầu bài là tám việc đáng làm.

docker inspect <container> --format '{{.Config.Image}} {{range .Mounts}}{{.Type}}:{{.Name}} {{end}}'

Nếu image không có số phiên bản nhỏ, hoặc mount là volume không tên, đó là hai việc nữa.

Phần sau là phần cuối của sê-ri: danh sách kiểm trước khi lên sản xuất, gom lại mọi con số đã đo trong sáu mươi phần.