PostgreSQL Bài 2: Kiến trúc bên trong PostgreSQL: Client-Server Model, Backend Processes và Shared Memory
Ở Bài 1, chúng ta đã biết PostgreSQL vận hành theo kiến trúc đa tiến trình (process-based). Để tối ưu hóa truy vấn, cấu hình thông số chuẩn xác và debug các sự cố nghẽn cổ chai (bottleneck) trên Production, bạn không thể coi PostgreSQL như một chiếc "hộp đen".
Bài học này sẽ bóc tách toàn bộ kiến trúc bên trong của một PostgreSQL Database Cluster: từ mô hình kết nối Client-Server, cách phân bổ bộ nhớ (Memory Architecture) cho đến nhiệm vụ cụ thể của từng tiến trình ngầm (Background Processes).
1. Mô hình Client-Server và Vòng đời một kết nối
PostgreSQL sử dụng mô hình kết nối Process-per-Connection thông qua giao thức TCP/IP hoặc Unix Domain Sockets:
[ Client: psql / App ]
│ (Gửi yêu cầu kết nối)
▼
[ Postmaster Process (PID gốc) ]
│
├── 1. Xác thực (pg_hba.conf)
├── 2. Fork() tạo tiến trình con
▼
[ Backend Process (postgres) ] ◄── Trao đổi truy vấn trực tiếp với Client
│
▼ (Đọc/Ghi qua Shared Memory)
[ Shared Memory & Disk Storage ]
1.1. Postmaster (Master Server Process)
Khi PostgreSQL khởi động, tiến trình mẹ đầu tiên được chạy có tên là postgres (trong tài liệu kỹ thuật thường gọi là Postmaster). Postmaster đảm nhận 3 nhiệm vụ chính:
-
Khởi tạo vùng nhớ dùng chung (Shared Memory).
-
Khởi chạy các tiến trình phụ trợ (Background Processes).
-
Lắng nghe các kết nối mới từ phía Client trên cổng mặc định (thường là
5432).
1.2. Cơ chế Fork và Backend Process
Khi có một ứng dụng kết nối tới:
-
Postmaster nhận kết nối và tiến hành xác thực quyền truy cập dựa trên cấu hình trong file
pg_hba.conf. -
Sau khi xác thực thành công, Postmaster gọi hàm hệ thống
fork()để nhân bản ra một tiến trình con độc lập gọi là Backend Process (hoặc postgres backend). -
Postmaster bàn giao kết nối TCP cho Backend Process này và quay lại tiếp tục lắng nghe cổng
5432. -
Toàn bộ chu kỳ truy vấn, trả kết quả của Client đó sẽ do Backend Process phụ trách cho đến khi phiên làm việc (Session) đóng lại.
Hệ quả kỹ thuật: Vì mỗi kết nối là một tiến trình OS độc lập với bảng cấp phát bộ nhớ riêng, việc tạo mới/đóng kết nối liên tục sẽ gây tốn chi phí CPU Context Switch và tiêu hao RAM. Đây là lý do kiến trúc sản xuất luôn bắt buộc phải sử dụng Connection Pooler (như PgBouncer) ở phía trước.
2. Kiến trúc bộ nhớ (Memory Architecture)
Bộ nhớ trong PostgreSQL được chia làm hai khu vực hoàn toàn tách biệt: Shared Memory (dùng chung cho toàn bộ cluster) và Local Memory (riêng biệt cho từng backend process).
┌────────────────────────────────────────────────────────────────────────┐
│ POSTGRESQL MEMORY │
├──────────────────────────────────────┬─────────────────────────────────┤
│ SHARED MEMORY │ LOCAL MEMORY (Per Process) │
│ (Dùng chung cho tất cả các process) │ (Cấp phát động khi có truy vấn) │
│ │ │
│ ┌────────────────────────────────┐ │ ┌───────────────────────────┐ │
│ │ Shared Buffer Pool │ │ │ work_mem │ │
│ │ (Cache data pages từ đĩa) │ │ │ (Sort, Hash, Merge) │ │
│ └────────────────────────────────┘ │ └───────────────────────────┘ │
│ ┌────────────────────────────────┐ │ ┌───────────────────────────┐ │
│ │ WAL Buffers │ │ │ maintenance_work_mem │ │
│ │ (Ghi đệm transaction logs) │ │ │ (VACUUM, CREATE INDEX) │ │
│ └────────────────────────────────┘ │ └───────────────────────────┘ │
│ ┌────────────────────────────────┐ │ ┌───────────────────────────┐ │
│ │ Lock Space / IPC / Free Space │ │ │ temp_buffers │ │
│ │ (Bảng khóa, Shared Catalog) │ │ │ (Bảng tạm - Temp Tables) │ │
│ └────────────────────────────────┘ │ └───────────────────────────┘ │
└──────────────────────────────────────┴─────────────────────────────────┘
2.1. Shared Memory (Vùng nhớ dùng chung)
Vùng nhớ này được Postmaster xin cấp phát từ hệ điều hành ngay khi bật server:
-
Shared Buffer Pool (
shared_buffers):-
Đóng vai trò là bộ nhớ đệm (Cache) cho các trang dữ liệu (Pages/Blocks - mặc định 8KB).
-
Mọi thao tác
SELECTđều tìm kiếm block dữ liệu trên Shared Buffers trước. Nếu không có (Cache Miss), nó mới yêu cầu OS đọc từ ổ đĩa nạp vào Shared Buffers rồi mới trả cho Client. -
Mọi thao tác
INSERT/UPDATE/DELETEcũng sửa đổi trực tiếp trên Shared Buffers (biến trang dữ liệu thành Dirty Page) trước khi được ghi xuống đĩa sau đó. -
Quy tắc cấu hình: Thường thiết lập khoảng 25% tổng RAM của server (phần RAM còn lại để dành cho OS Page Cache và Local Memory).
-
-
WAL Buffers (
wal_buffers):-
Vùng nhớ đệm lưu trữ các bản ghi Write-Ahead Logging (nhật ký giao dịch) trước khi xả (flush) xuống file WAL trên đĩa.
-
Đảm bảo tính bền vững (Durability trong ACID). Mặc định PostgreSQL tự động điều chỉnh (thường tối đa khoảng 16MB).
-
-
Lock Space:
- Nơi lưu trữ bảng trạng thái khóa toàn cục (Global Lock Table). Mọi hành động lock bảng, lock dòng, hoặc transaction locks đều được ghi nhận tại đây để các tiến trình khác nhìn thấy và tránh xung đột dữ liệu.
2.2. Local Memory (Vùng nhớ tiến trình cục bộ)
Được cấp phát riêng cho từng Backend Process khi thực thi các truy vấn cụ thể:
-
work_mem:-
Không gian RAM dùng để thực hiện các phép toán phức tạp: sắp xếp (
ORDER BY), nhóm bảng băm (Hash AggregatetrongGROUP BY), hoặc các thuật toán kết hợp (Hash Join,Merge Join). -
Lưu ý quan trọng:
work_memđược cấp phát cho mỗi thao tác (per operation), chứ không phải mỗi kết nối. Một câu SQL phức tạp có 3 phép Sort và 2 phép Hash Join có thể ngốn gấp 5 lần giá trịwork_mem. Nếu thiếu RAM, PostgreSQL buộc phải ghi dữ liệu trung gian ra file tạm trên ổ đĩa (Spill to Disk), khiến tốc độ truy vấn sụt giảm nghiêm trọng.
-
-
maintenance_work_mem:- Dung lượng bộ nhớ lớn hơn dành riêng cho các tác vụ bảo trì hệ thống nặng như: chạy lệnh
VACUUM,CREATE INDEX, hoặc bổ sung Foreign Key.
- Dung lượng bộ nhớ lớn hơn dành riêng cho các tác vụ bảo trì hệ thống nặng như: chạy lệnh
-
temp_buffers:- Dùng để lưu trữ dữ liệu của các bảng tạm (
CREATE TEMP TABLE) riêng biệt của session đó.
- Dùng để lưu trữ dữ liệu của các bảng tạm (
3. Các Background Processes cốt lõi
Bên cạnh các Backend Process giao tiếp với người dùng, PostgreSQL có một đội ngũ tiến trình chạy ngầm phụ trách duy trì sự sống và độ tin cậy của toàn bộ hệ thống.
3.1. Background Writer (bgwriter)
-
Nhiệm vụ: Tìm kiếm các trang dữ liệu bị sửa đổi (Dirty Pages) nằm trong Shared Buffer Pool và ghi chúng xuống ổ đĩa một cách tuần tự, đều đặn.
-
Mục tiêu: Giữ cho Shared Buffer Pool luôn có sẵn một lượng trang trống (clean buffers). Nếu không có bgwriter, khi một Backend Process cần đọc dữ liệu mới vào mà bộ nhớ đã đầy, chính backend đó sẽ phải tự dừng lại để ghi dirty page xuống đĩa, gây ra hiện tượng giật/lag (latency spikes) cho ứng dụng.
3.2. Checkpointer
-
Nhiệm vụ: Định kỳ tạo ra các điểm kiểm tra (Checkpoint).
-
Tại thời điểm Checkpoint diễn ra:
-
Toàn bộ dirty buffers đang nằm trên RAM sẽ bị ép xả hết (flush) xuống đĩa cứng.
-
Ghi một bản ghi Checkpoint vào file WAL để đánh dấu: "Toàn bộ dữ liệu trước thời điểm này đã an toàn trên đĩa".
-
-
Ý nghĩa: Rút ngắn thời gian phục hồi hệ thống khi có sự cố sập nguồn (Crash Recovery). Khi khởi động lại, PostgreSQL chỉ cần đọc và re-play lại WAL kể từ điểm Checkpoint gần nhất thay vì quét lại từ đầu lịch sử.
3.3. WAL Writer
-
Nhiệm vụ: Định kỳ ghi dữ liệu từ WAL Buffers xuống các file WAL vật lý trên đĩa cứng (thư mục
pg_wal). -
Đảm bảo ngay cả khi các transaction chưa commit hoặc commit ngầm, dữ liệu nhật ký giao dịch vẫn liên tục được bảo toàn.
3.4. Autovacuum Launcher & Workers
-
Nhiệm vụ: Tự động quét các bảng để tìm và dọn dẹp các bản ghi rác (Dead Tuples do câu lệnh
UPDATE,DELETEđể lại - chi tiết sẽ học ở bài MVCC). -
Đồng thời cập nhật lại thống kê phân phối dữ liệu (Statistics) giúp Query Planner đưa ra kế hoạch thực thi tối ưu nhất.
3.5. Stats Collector (hoặc Shared-memory Stats từ PostgreSQL 15+)
-
Nhiệm vụ: Thu thập số liệu hoạt động của toàn bộ server: số lần bảng được truy cập, số lần quét index, số dòng được nạp, thời gian thực thi câu lệnh.
-
Cung cấp dữ liệu phục vụ các view giám sát hiệu năng quan trọng như
pg_stat_activity,pg_stat_user_tables.
4. Bóc tách luồng đi của một câu lệnh: READ vs WRITE
Để thấy bức tranh toàn cảnh về cách các thành phần này phối hợp nhịp nhàng, hãy quan sát hai luồng xử lý:
LUỒNG ĐỌC (SELECT):
Client ──► Backend Process ──► Kiểm tra Shared Buffers
│
┌─────────────────┴─────────────────┐
▼ (Hit) ▼ (Miss)
Trả dữ liệu ngay Đọc block 8KB từ Disk/OS Cache
Nạp vào Shared Buffers ──► Trả về Client
LUỒNG GHI (INSERT/UPDATE):
Client ──► Backend Process ──► Ghi thay đổi vào WAL Buffer
│
├──► Sửa block trên Shared Buffers (thành Dirty Page)
│
├──► Ghi WAL Buffer xuống Disk WAL (Commit thành công)
▼
(bgwriter & checkpointer sẽ lo việc đẩy Dirty Page xuống đĩa sau đó)
-
Luồng Đọc (SELECT):
-
Backend process nhận câu truy vấn, parse cú pháp, tối ưu kế hoạch thực thi.
-
Quét vào Shared Buffers: Nếu block dữ liệu đã có sẵn (Buffer Hit), đọc trực tiếp trên RAM.
-
Nếu chưa có (Buffer Miss), tiến trình gọi hệ điều hành đọc block 8KB từ file lưu trữ dưới đĩa lên, ghi vào Shared Buffers rồi gửi kết quả về cho Client.
-
-
Luồng Ghi (UPDATE / INSERT):
-
Thay đổi bắt buộc phải được ghi vào WAL Buffers trước (quy tắc Write-Ahead Logging).
-
Tiếp theo, Backend Process sửa trực tiếp block tương ứng trên Shared Buffers (lúc này block trở thành Dirty Page).
-
Khi lệnh
COMMITđược phát ra, PostgreSQL chỉ cần đảm bảo WAL Buffer liên quan được ghi cưỡng bức (flush) xuống đĩa WAL. Khi WAL đã an toàn trên đĩa, PostgreSQL báo commit thành công cho Client ngay lập tức. -
Dữ liệu thực tế trên bảng ở Shared Buffers vẫn là Dirty Page và sẽ được bgwriter hoặc checkpointer xả xuống file dữ liệu chính một cách bất đồng bộ sau đó. Kỹ thuật này giảm thiểu tối đa các thao tác đọc/ghi ngẫu nhiên (Random I/O) trên đĩa.
-
5. Tóm tắt các thông số cốt lõi cần nhớ
| Tham số cấu hình | Thuộc vùng nhớ | Ý nghĩa thực chiến |
|---|---|---|
| max_connections | Process Limit | Giới hạn số connection đồng thời. Không nên đặt quá cao (ví dụ > 500) mà hãy dùng connection pooler. |
| shared_buffers | Shared Memory | Bộ nhớ cache dữ liệu. Khuyến nghị thiết lập 25% tổng dung lượng RAM của hệ thống. |
| work_mem | Local Memory | RAM cho mỗi phép toán Sort/Hash. Đặt quá nhỏ -> ghi file tạm ra đĩa (chậm); đặt quá lớn x nhiều connection -> lỗi tràn bộ nhớ (Out-Of-Memory / OOM Killer). |
| maintenance_work_mem | Local Memory | RAM cho tác vụ bảo trì (Vacuum, tạo Index). Thường đặt từ vài trăm MB đến 1-2 GB tùy dung lượng RAM máy chủ. |
Bài 3 xem tiếp: Thiết lập môi trường thực hành chuẩn với Docker, Docker Compose và kết nối qua psql — chúng ta sẽ bắt tay thực hành dựng một instance PostgreSQL chuẩn Production trên môi trường Docker, mount volume an toàn, cấu hình biến môi trường và sử dụng các câu lệnh tắt quyền lực trong psql.
All rights reserved