PostgreSQL 18 Deep Dive 2026 - Asynchronous I/O, Skip Scan, Virtual Generated Columns, UUIDv7 và OAuth 2.0: Cuộc Cách Mạng Hiệu Năng của OLTP Database Open-Source

Posted on: 4/15/2026 7:38:33 PM

Table of contents

  1. 1. PostgreSQL 18 - Một bản phát hành mang dáng dấp cách mạng
    1. Tại sao PostgreSQL 18 quan trọng với kiến trúc của bạn?
  2. 2. Từ PostgreSQL 15 đến PostgreSQL 18 - Bản đồ tiến hóa
  3. 3. Asynchronous I/O - Lớp nền tảng được viết lại
    1. 3.1. Mô hình cũ - Synchronous I/O với readahead thụ động
    2. 3.2. Mô hình mới - AIO với io_uring, worker và direct I/O
    3. 3.3. Tác động thực tế lên các loại query
      1. Gợi ý cấu hình khởi đầu
  4. 4. B-tree Skip Scan - Giải thoát khỏi giới hạn left-most prefix
    1. 4.1. Vấn đề thực tế
    2. 4.2. Cơ chế Skip Scan hoạt động
    3. 4.3. Con số benchmark cụ thể
      1. Hai điều cần lưu ý
  5. 5. Virtual Generated Columns - Mặc định mới
    1. 5.1. Khi nào nên dùng VIRTUAL, khi nào nên dùng STORED
      1. Điểm đáng chú ý cho logical replication
  6. 6. UUIDv7 - Kết thúc cuộc tranh cãi về primary key
    1. 6.1. Cấu trúc bit của UUIDv7
    2. 6.2. Hàm mới trong PostgreSQL 18
      1. Chiến lược migration primary key
  7. 7. OAuth 2.0 Authentication - Chia tay cơ chế password đơn thuần
    1. 7.1. Cấu hình trong pg_hba.conf
    2. 7.2. Hai luồng xác thực phổ biến
      1. md5 password đang trên đường bị loại bỏ
  8. 8. RETURNING OLD/NEW - Audit trail viết lại gọn hơn
  9. 9. Temporal Constraints - WITHOUT OVERLAPS và PERIOD
  10. 10. pg_upgrade và bảo toàn thống kê planner
    1. Chiến lược chuyển đổi thực tế
  11. 11. Logical Replication - Những cải tiến lặng lẽ nhưng quan trọng
  12. 12. Wire Protocol 3.2 - Cập nhật đầu tiên sau 22 năm
  13. 13. Observability - EXPLAIN và pg_stat_io nâng cấp
  14. 14. Unicode, casefold và tìm kiếm không phân biệt hoa thường
  15. 15. Checklist chuyển đổi PostgreSQL 17 → 18
  16. 16. Lời kết
  17. 17. Nguồn tham khảo
PostgreSQL 18 Async I/O Skip Scan UUIDv7 OAuth 2.0 Database Internals System Design

1. PostgreSQL 18 - Một bản phát hành mang dáng dấp cách mạng

Ngày 25 tháng 9 năm 2025, cộng đồng PostgreSQL chính thức phát hành PostgreSQL 18 - một trong những bản nâng cấp có sức nặng nhất kể từ khi MVCC được hoàn thiện. Nếu PostgreSQL 16 là bản "tinh chỉnh trưởng thành" và PostgreSQL 17 là bản "nâng cấp công cụ vận hành", thì PostgreSQL 18 chính là bản viết lại lớp I/O - nơi sâu nhất và nhạy cảm nhất của một cơ sở dữ liệu quan hệ.

Trong suốt hai thập kỷ, PostgreSQL luôn dựa trên mô hình đọc ghi đồng bộ: tiến trình backend gọi read(), kernel chặn tiến trình đó cho đến khi trả dữ liệu về, rồi mới xử lý tiếp. Mô hình này đơn giản, dễ hiểu và an toàn, nhưng bỏ phí hiệu năng đáng kể trên phần cứng hiện đại như NVMe SSD, PCIe 5.0 hay NVMe-over-Fabrics. PostgreSQL 18 thay đổi nền tảng đó bằng hệ thống Asynchronous I/O (AIO), cho phép các tiến trình gửi nhiều yêu cầu I/O song song trước khi kết quả đầu tiên trở về, khai thác đúng thế mạnh của storage hiện đại.

25/09/2025Ngày phát hành chính thức
3xTăng tốc đọc tuần tự với AIO
~75%Giảm thời gian truy vấn nhờ Skip Scan
3.2Wire protocol version mới đầu tiên từ 2003

Tại sao PostgreSQL 18 quan trọng với kiến trúc của bạn?

Nếu bạn đang vận hành hệ thống OLTP quy mô hàng chục đến hàng trăm nghìn giao dịch mỗi giây, PostgreSQL 18 mang lại bốn thay đổi nền tảng tác động trực tiếp đến thiết kế bảng, thiết kế index, thiết kế schema migration và thiết kế xác thực kết nối. Đây không phải là một phiên bản "nâng cấp tùy chọn" - đây là phiên bản bạn nên lên kế hoạch kiểm thử và chuyển đổi nghiêm túc trong chu kỳ hai quý tới.

2. Từ PostgreSQL 15 đến PostgreSQL 18 - Bản đồ tiến hóa

Để hiểu được sự khác biệt của PostgreSQL 18, cần nhìn lại bốn bản phát hành gần nhất - mỗi bản giải một bài toán khác nhau, và PostgreSQL 18 chính là mảnh ghép cuối để PostgreSQL đứng vững ở tầng dữ liệu lõi của các kiến trúc cloud-native hiện đại.

Tháng 10/2022 - PostgreSQL 15
MERGE chuẩn SQL, cải thiện sort performance, logical replication theo hàng/cột, ICU collation mặc định cho cluster. Đây là bản đánh dấu sự trở lại của PostgreSQL với các tính năng SQL tiêu chuẩn.
Tháng 9/2023 - PostgreSQL 16
Logical replication từ standby, SIMD cho xử lý chuỗi, load balancing ở mức libpq, tối ưu bulk loading. Bản này tập trung vào scale-out và high availability.
Tháng 9/2024 - PostgreSQL 17
Incremental backup với pg_basebackup --incremental, MAINTAIN privilege, JSON_TABLE, pg_createsubscriber. Bản này nâng cấp công cụ vận hành và backup.
Tháng 9/2025 - PostgreSQL 18
Asynchronous I/O subsystem với io_uring, B-tree Skip Scan, Virtual Generated Columns mặc định, UUIDv7, OAuth 2.0 authentication, RETURNING OLD/NEW, temporal constraints WITHOUT OVERLAPS, protocol 3.2.

3. Asynchronous I/O - Lớp nền tảng được viết lại

Đây là tính năng lớn nhất và cũng là tính năng khó hình dung nhất của PostgreSQL 18. Để hiểu được giá trị của AIO, cần nhìn lại cách PostgreSQL xử lý một lần đọc heap block trước khi nâng cấp.

3.1. Mô hình cũ - Synchronous I/O với readahead thụ động

Với sequential scan trên một bảng lớn, PostgreSQL 17 trở về trước hoạt động đại khái như sau:

  1. Tiến trình backend gọi ReadBuffer() để lấy block tiếp theo.
  2. Nếu block không có trong shared_buffers, hàm smgrread() thực hiện system call pread().
  3. Kernel chặn tiến trình (state D - uninterruptible sleep) cho đến khi disk trả về trang 8KB.
  4. Tiến trình được đánh thức, xử lý trang đó, rồi lặp lại bước 1 cho block kế tiếp.

Nhìn bề ngoài thì OS đã có readahead (đọc trước) ở tầng block layer, giúp phần nào che giấu độ trễ. Nhưng readahead của OS là suy đoán: nó chỉ hoạt động tốt khi access pattern liên tục và kích thước block phù hợp. Đối với bitmap heap scan - vốn đọc các block phân tán theo vị trí trong index - OS readahead gần như vô dụng, và PostgreSQL phải chịu đầy đủ độ trễ của từng seek.

sequenceDiagram
    participant B as Backend
    participant K as Kernel
    participant D as Disk
    B->>K: pread block 1
    K->>D: issue read
    D-->>K: data block 1
    K-->>B: return
    Note over B: process block 1
    B->>K: pread block 2
    K->>D: issue read
    D-->>K: data block 2
    K-->>B: return

Hình 1: Mô hình Synchronous I/O truyền thống - mỗi lần đọc là một lần chờ tuần tự

3.2. Mô hình mới - AIO với io_uring, worker và direct I/O

PostgreSQL 18 giới thiệu một lớp abstraction mới gọi là AIO Handle. Thay vì gọi trực tiếp pread(), tiến trình backend đăng ký yêu cầu đọc với AIO subsystem, rồi tiếp tục xử lý các công việc khác như tính toán predicate, giải nén cột, hay gọi operator. Khi block đã sẵn sàng, subsystem đánh thức backend để hoàn tất.

Tham số mới quan trọng nhất là io_method, có thể nhận một trong ba giá trị:

io_methodCơ chế hoạt độngKhi nào nên dùng
syncGiữ nguyên mô hình đồng bộ cũ - chỉ dùng làm fallbackMôi trường cần hành vi y hệt PostgreSQL 17 để so sánh
workerDùng một pool các I/O worker nền (mặc định 3 worker), backend đẩy yêu cầu vào queue và đợi kết quả qua shared memoryLinux kernel cũ, macOS, FreeBSD, mọi nền tảng không có io_uring
io_uringDùng trực tiếp io_uring của Linux (kernel ≥ 5.1). Mỗi backend có một submission queue riêng, yêu cầu được đẩy xuống kernel qua ring buffer không copyLinux hiện đại, NVMe, NVMe-over-Fabrics, cần throughput tối đa
sequenceDiagram
    participant B as Backend
    participant A as AIO Subsystem
    participant K as Kernel io_uring
    participant D as NVMe
    B->>A: submit read block 1..32
    A->>K: io_uring_enter SQEs
    par Parallel dispatch
        K->>D: read block 1
    and
        K->>D: read block 2
    and
        K->>D: read block 32
    end
    Note over B: CPU works on predicates
    D-->>K: completions
    K-->>A: CQEs via ring
    A-->>B: wake backend when needed

Hình 2: AIO với io_uring - backend gửi batch 32 yêu cầu đọc rồi tiếp tục làm việc CPU

3.3. Tác động thực tế lên các loại query

Điểm cần nhớ: AIO không phải "đũa thần" tăng tốc mọi query. Hiệu ứng phân biệt rõ theo loại access pattern:

  • Sequential Scan và Bitmap Heap Scan: tăng tốc 2-3 lần, đặc biệt trên NVMe và storage có độ trễ cao nhưng băng thông lớn.
  • VACUUM và ANALYZE: nhanh hơn đáng kể do cả hai đều đọc block theo phân đoạn rộng.
  • Index Scan (nhất là B-tree random lookup): chưa được hưởng lợi nhiều trong phiên bản này - đây là công việc còn dang dở, dự kiến hoàn thiện ở PostgreSQL 19.
  • Short OLTP query (PK lookup, update theo id): hầu như không thay đổi, vì các block đã nằm sẵn trong shared_buffers.

Gợi ý cấu hình khởi đầu

Trên Linux 6.x với NVMe: bắt đầu với io_method = io_uring, effective_io_concurrency = 64maintenance_io_concurrency = 128. Theo dõi pg_stat_io (đã có từ PostgreSQL 16) trước và sau chuyển đổi để định lượng chính xác lợi ích thay vì đoán mò.

4. B-tree Skip Scan - Giải thoát khỏi giới hạn left-most prefix

Đây là tính năng mà các DBA dày dạn sẽ reo lên khi nghe tin. Trong suốt lịch sử PostgreSQL, một quy tắc bất thành văn đã tồn tại: "index multi-column chỉ dùng được khi truy vấn có điều kiện trên cột đầu tiên". Giới hạn này xuất phát từ cách B-tree sắp xếp entry theo thứ tự từ điển - nếu bạn không biết giá trị cột đầu, bạn không biết bắt đầu đọc ở đâu trong cây.

4.1. Vấn đề thực tế

Hãy tưởng tượng bạn có bảng orders với hơn 100 triệu dòng và một index composite:

CREATE INDEX idx_orders_status_customer_date
    ON orders (status, customer_id, order_date);

Truy vấn sau đây trên PostgreSQL 17 sẽ không dùng được index và buộc phải chạy Sequential Scan:

SELECT * FROM orders
WHERE customer_id = 42
  AND order_date >= '2026-01-01';

Để tránh, DBA buộc phải tạo thêm index (customer_id, order_date) - nhân đôi chi phí storage, chi phí write amplification và chi phí bảo trì VACUUM.

4.2. Cơ chế Skip Scan hoạt động

PostgreSQL 18 bổ sung một bước mới trong B-tree traversal: khi planner phát hiện index có cột đầu low cardinality (số giá trị phân biệt thấp, ví dụ status chỉ có 5 giá trị), nó tự động tạo ra một loạt "probe" - mỗi probe là một lần tìm kiếm riêng biệt với một giá trị cụ thể của cột đầu. Về mặt logic, planner biến truy vấn gốc thành:

SELECT * FROM orders
WHERE status IN ('new', 'paid', 'shipped', 'delivered', 'cancelled')
  AND customer_id = 42
  AND order_date >= '2026-01-01';

Nhưng danh sách giá trị được lấy động từ chính B-tree, không cần bảng meta và không cần lookup trực tiếp.

4.3. Con số benchmark cụ thể

Trên một bảng 1 triệu dòng với index ba cột, tài liệu kỹ thuật của pgEdge đo được:

Phiên bảnKế hoạch thực thiThời gian thực thi
PostgreSQL 17Sequential Scan48.527 ms
PostgreSQL 18Index Only Scan (skip scan)12.801 ms

Tỉ lệ cải thiện khoảng 74%. Nhưng con số quan trọng hơn là giảm đáng kể số lượng index dư thừa cần tạo - trên các hệ thống cũ có hàng chục index chồng chéo vì từng phải "phá giới hạn left-most", lợi ích dài hạn về storage và tốc độ write có thể còn lớn hơn chính con số 74% kia.

Hai điều cần lưu ý

Thứ nhất, Skip Scan chỉ hoạt động hiệu quả khi cột đầu có cardinality thấp - nếu cột đầu là customer_id với hàng triệu giá trị, planner sẽ không chọn skip scan vì chi phí probe lớn hơn full scan. Thứ hai, điều kiện trên các cột sau phải là equality hoặc range hợp lý; nếu là hàm phức tạp như lower(email) LIKE '%abc%', B-tree vẫn bó tay.

5. Virtual Generated Columns - Mặc định mới

PostgreSQL 12 đã giới thiệu Generated Columns, nhưng chỉ hỗ trợ STORED - tức giá trị được tính tại thời điểm ghi và lưu vật lý trong heap. Cách này nhanh khi đọc, nhưng tốn dung lượng và làm UPDATE nặng hơn khi công thức sinh cột phức tạp. PostgreSQL 18 đảo ngược mặc định: từ nay, GENERATED ALWAYS AS ... sẽ được hiểu là VIRTUAL, tức tính tại thời điểm đọc, không chiếm dung lượng vật lý.

CREATE TABLE user_profile (
    user_id      BIGINT PRIMARY KEY,
    settings     JSONB NOT NULL,
    username     VARCHAR(100)
        GENERATED ALWAYS AS (settings ->> 'username') VIRTUAL,
    display_name VARCHAR(200)
        GENERATED ALWAYS AS (
            coalesce(settings ->> 'display_name', settings ->> 'username')
        ) VIRTUAL
);

Trong ví dụ này, hai cột usernamedisplay_name không chiếm một byte nào trên đĩa. Chúng chỉ được tính khi SELECT, dùng toán tử ->> bóc từ trường settings. Khi payload JSON thay đổi, bạn không cần chạy UPDATE trên cột sinh - chính là ưu điểm lớn nhất so với cách cũ.

5.1. Khi nào nên dùng VIRTUAL, khi nào nên dùng STORED

Tiêu chíVIRTUALSTORED
Chi phí ghiRất thấpCao nếu công thức phức tạp
Chi phí đọcTăng nhẹ (tính lại mỗi lần)Rất thấp
Dung lượng đĩa0 byteĐầy đủ kích thước cột
Index trên cột sinhĐược phép (expression index)Được phép
Logical replicationKhông replicate giá trịReplicate (mới trong PG18)
Trường hợp dùng điển hìnhView-like projection từ JSONBCột tính toán nặng dùng thường xuyên

Điểm đáng chú ý cho logical replication

PostgreSQL 18 bổ sung khả năng logical replication cho STORED generated columns. Trước đây, bạn phải tự tính lại cột sinh bên subscriber - nguyên nhân của vô số lỗi lệch dữ liệu. Từ PG18, giá trị được streaming nguyên vẹn, giảm đáng kể áp lực CPU ở subscriber trong các kiến trúc replica phân vùng theo tenant.

6. UUIDv7 - Kết thúc cuộc tranh cãi về primary key

UUID v4 từ lâu đã là lựa chọn mặc định cho primary key trong các hệ thống phân tán, nhờ tính ngẫu nhiên và không cần đồng bộ giữa các node. Nhưng chính tính ngẫu nhiên đó lại là vấn đề: khi hàng mới rải đều trên toàn bộ không gian 128-bit, B-tree phải thực hiện nhiều page split, write amplification cao, và cache locality gần như bằng không.

6.1. Cấu trúc bit của UUIDv7

UUIDv7 theo RFC 9562 có cấu trúc:

graph LR
    A["48 bits timestamp Unix ms"] --> B["4 bits version 7"]
    B --> C["12 bits rand_a sub-ms counter"]
    C --> D["2 bits variant"]
    D --> E["62 bits rand_b random"]
    style A fill:#e94560,stroke:#fff,color:#fff
    style B fill:#0f3460,stroke:#fff,color:#fff
    style C fill:#4CAF50,stroke:#fff,color:#fff
    style D fill:#0f3460,stroke:#fff,color:#fff
    style E fill:#ff9800,stroke:#fff,color:#fff

Hình 3: Bố cục bit của UUIDv7 - 48 bit đầu là timestamp, đảm bảo thứ tự thời gian

Nhờ 48 bit timestamp ở đầu, UUIDv7 được sắp xếp gần đúng theo thời gian. Khi dùng làm primary key, các hàng mới được chèn vào cuối cây B-tree - giống hệt hành vi của bigserial - loại bỏ hoàn toàn hiện tượng page split rải rác.

6.2. Hàm mới trong PostgreSQL 18

-- Sinh UUIDv7 hiện tại
SELECT uuidv7();

-- Sinh UUIDv7 với offset (hữu ích cho test dữ liệu quá khứ)
SELECT uuidv7(interval '-1 day');

-- Trích timestamp từ UUIDv7
SELECT uuid_extract_timestamp('018f5a1e-3c80-7000-8000-0123456789ab'::uuid);

-- Alias mới cho gen_random_uuid
SELECT uuidv4();

Điểm tinh tế là 12 bit rand_a được PostgreSQL dùng làm bộ đếm phụ trong khoảng sub-millisecond, đảm bảo hai UUIDv7 sinh trong cùng một backend và cùng một millisecond vẫn có thứ tự tăng dần. Tính monotonicity này chỉ đúng trong phạm vi một backend - giữa nhiều connection song song, thứ tự chỉ gần đúng theo millisecond.

Chiến lược migration primary key

Bạn không cần chuyển đổi các bảng đang dùng UUIDv4. Chỉ cần thay DEFAULT gen_random_uuid() bằng DEFAULT uuidv7() cho các bảng mới hoặc cột mới, và theo dõi chỉ số cache hit ratio trong vài tuần. Trên hệ thống với tỉ lệ insert cao, bạn sẽ thấy shared_buffers hit rate tăng rõ rệt do cache locality tốt hơn.

7. OAuth 2.0 Authentication - Chia tay cơ chế password đơn thuần

PostgreSQL 18 bổ sung phương thức xác thực oauth trong pg_hba.conf, cho phép kết nối dùng bearer token từ Identity Provider (Keycloak, Okta, Azure AD, Authentik, Google Identity...) thay vì password. Đây là lần đầu tiên PostgreSQL tích hợp OAuth ở cấp giao thức, chứ không phải qua extension bên ngoài.

sequenceDiagram
    participant App as Application
    participant IdP as Identity Provider
    participant PG as PostgreSQL 18
    App->>IdP: request token scope db.read
    IdP-->>App: access_token JWT
    App->>PG: connect oauth_token=...
    PG->>IdP: validate token
    IdP-->>PG: active true sub user
    PG-->>App: session established
    Note over PG,App: subsequent SQL with role mapped from sub

Hình 4: Luồng xác thực OAuth 2.0 Client Credentials giữa ứng dụng và PostgreSQL 18

7.1. Cấu hình trong pg_hba.conf

host    all    all    0.0.0.0/0    oauth    issuer="https://idp.example.com/realms/anhtu" \
                                             scope="openid db.connect" \
                                             map=oauth_role_map

File pg_ident.conf dùng để ánh xạ từ trường sub hoặc email trong JWT sang role PostgreSQL, cho phép quản lý quyền ở tầng database mà không cần tạo password riêng cho từng người dùng.

7.2. Hai luồng xác thực phổ biến

Luồng Device Authorization dùng cho psql tương tác - người dùng chạy psql, PostgreSQL trả về mã xác thực, người dùng mở trình duyệt và xác thực:

psql "host=db.anhtu.dev oauth_issuer=https://idp.example.com \
      oauth_client_id=psql-cli dbname=app"
# Visit https://idp.example.com/device and enter code FPQ2-M4BG

Luồng Bearer Token dùng cho ứng dụng đã tự lấy token từ IdP (ví dụ service dùng client credentials grant):

psql "host=db.anhtu.dev oauth_issuer=https://idp.example.com \
      oauth_client_id=backend-svc oauth_token=eyJhbGciOi... dbname=app"

md5 password đang trên đường bị loại bỏ

PostgreSQL 18 chính thức deprecate md5 password authentication. Cộng đồng khuyến nghị chuyển sang SCRAM-SHA-256 ngay lập tức. Nếu bạn vẫn dùng md5 vì lý do tương thích client cũ, hãy lên kế hoạch rollover trước khi bản phát hành tiếp theo loại bỏ hoàn toàn.

8. RETURNING OLD/NEW - Audit trail viết lại gọn hơn

Mệnh đề RETURNING vốn rất tiện cho việc lấy giá trị sau INSERT hoặc UPDATE. PostgreSQL 18 mở rộng nó để có thể lấy đồng thời giá trị cũ và mới trong cùng một câu lệnh, áp dụng cho INSERT, UPDATE, DELETEMERGE:

UPDATE orders
SET    status = 'paid',
       paid_at = now()
WHERE  id = 1024
RETURNING OLD.status AS old_status,
          NEW.status AS new_status,
          OLD.paid_at AS old_paid_at,
          NEW.paid_at AS new_paid_at;

Trước đây, để có đủ hai phiên bản dữ liệu phục vụ audit trail, bạn phải viết CTE hai bước hoặc dùng trigger BEFORE UPDATE lưu snapshot. PostgreSQL 18 cho phép làm điều đó trong một statement, giảm một round trip và gọn hơn hẳn trong ORM như Entity Framework Core, Npgsql hay sqlx.

9. Temporal Constraints - WITHOUT OVERLAPS và PERIOD

Đây là một trong những tính năng ít người chú ý nhưng cực kỳ hữu ích cho các hệ thống đặt phòng, booking, lịch làm việc, tenant billing - bất cứ nơi nào cần ràng buộc "không được chồng khoảng thời gian". PostgreSQL 18 bổ sung:

CREATE TABLE room_booking (
    id           BIGSERIAL,
    room_id      BIGINT NOT NULL,
    period       TSTZRANGE NOT NULL,
    PRIMARY KEY (room_id, period WITHOUT OVERLAPS)
);

Với khai báo trên, PostgreSQL sẽ tự động từ chối bất kỳ dòng nào có cùng room_id và khoảng period giao cắt với một bản ghi đã tồn tại. Trước PG18, bạn buộc phải dùng EXCLUDE USING gist - đúng, nhưng cú pháp dài và khó giải thích cho developer mới vào nghề.

Tương tự, foreign key có thể khai báo PERIOD period để đảm bảo khoảng thời gian của hàng con nằm gọn trong khoảng của hàng cha - lần đầu tiên PostgreSQL hỗ trợ ngữ nghĩa temporal referential integrity ở mức DDL.

10. pg_upgrade và bảo toàn thống kê planner

Một trong những nỗi đau kinh điển của các DBA là: sau pg_upgrade, thống kê planner bị xóa sạch, phải chạy ANALYZE toàn bộ cluster, và trong khoảng thời gian đó hệ thống chạy chậm thảm hại do planner dùng kế hoạch tồi. PostgreSQL 18 giải bài toán này theo hai hướng:

  1. Bảo toàn thống kê optimizer: pg_upgrade chuyển thống kê trong pg_statistic sang cluster mới thay vì bỏ qua. Hệ thống sẵn sàng phục vụ traffic production ngay sau khi upgrade hoàn tất.
  2. Cờ --swap mới: thay vì copy toàn bộ thư mục dữ liệu hoặc dùng hard link, pg_upgrade có thể hoán đổi trực tiếp data directory. Đặc biệt hữu ích với cluster hàng TB.
  3. Cờ --jobs nâng cấp: song song hóa phần vận hành dump/restore ở mức schema, rút ngắn downtime đáng kể với cluster có hàng chục nghìn bảng.

Chiến lược chuyển đổi thực tế

Với cluster nhỏ dưới 500GB, bạn có thể upgrade in-place trong cửa sổ bảo trì ngắn. Với cluster lớn hơn, khuyến nghị dùng logical replication để chuẩn bị replica PG18 song song, sau đó switchover - thay vì chịu downtime. PostgreSQL 17 đã hỗ trợ logical replication từ standby, nên thao tác này ít rủi ro hơn nhiều so với vài năm trước.

11. Logical Replication - Những cải tiến lặng lẽ nhưng quan trọng

PostgreSQL 18 tiếp tục hoàn thiện logical replication, tập trung vào ba vấn đề thường gặp trong production:

  • Idle replication slot timeout: tham số mới idle_replication_slot_timeout cho phép tự động xóa các slot không hoạt động trong khoảng thời gian cho trước. Kết thúc thời kỳ "WAL tràn ổ đĩa vì một slot bị quên" - nguyên nhân hàng đầu của sự cố production PostgreSQL suốt 5 năm qua.
  • Parallel streaming mặc định: CREATE SUBSCRIPTION mặc định bật streaming song song cho các transaction lớn, không cần tinh chỉnh thủ công.
  • Báo cáo write conflict: khi subscriber gặp xung đột ghi (ví dụ duplicate key), chi tiết được ghi vào log và view pg_stat_subscription_stats, thay vì chỉ báo lỗi chung chung như trước.
  • pg_createsubscriber --all: tạo logical replica cho tất cả database trong cluster bằng một lệnh duy nhất.

12. Wire Protocol 3.2 - Cập nhật đầu tiên sau 22 năm

Đây là sự kiện ngầm rất đáng chú ý: wire protocol version 3.2 là bản nâng cấp đầu tiên kể từ version 3.0 phát hành cùng PostgreSQL 7.4 năm 2003. Thay đổi lớn nhất là mở rộng cancellation secret từ 4 byte cố định lên độ dài biến đổi, tăng cường bảo mật chống tấn công brute force hủy query. Ngoài ra protocol 3.2 còn đặt nền cho các tính năng giao tiếp streaming hiệu năng cao trong tương lai.

Để giữ tương thích, libpq mặc định vẫn dùng 3.0 - client chủ động opt-in qua tham số min_protocol_version=3.2 khi cần. Các driver cấp cao hơn (Npgsql, JDBC, asyncpg, pgx) sẽ bổ sung hỗ trợ dần dần trong các bản minor sắp tới.

13. Observability - EXPLAIN và pg_stat_io nâng cấp

PostgreSQL 18 làm cho việc "đọc kế hoạch thực thi" trở nên dễ hiểu hơn đáng kể:

  • EXPLAIN ANALYZE giờ tự động hiển thị buffer access counts, không cần thêm BUFFERS. Bạn lập tức thấy ngay một plan đang "đốt" bao nhiêu shared hit, bao nhiêu read từ đĩa.
  • Trong EXPLAIN ANALYZE VERBOSE, có thêm thống kê CPU time, WAL generated, average read time cho mỗi node - cực kỳ hữu ích để tách biệt vấn đề do CPU hay do I/O.
  • pg_stat_all_tables bổ sung thời gian của các thao tác vacuum (heap scan, index vacuum, heap truncate...), giúp phân tích nguyên nhân "vacuum bị chậm".
  • pg_stat_activitypg_stat_io bổ sung thống kê per-connection về I/O và WAL, thay vì chỉ có ở mức global.

14. Unicode, casefold và tìm kiếm không phân biệt hoa thường

Với ứng dụng đa ngôn ngữ, PostgreSQL 18 mang đến hai cải tiến đáng giá:

-- Collation mới, ngữ nghĩa Unicode đầy đủ với tốc độ cao
CREATE COLLATION unicode_fast (
    provider = builtin,
    locale = 'PG_UNICODE_FAST',
    deterministic = false
);

-- Hàm casefold - thay thế lower() cho so sánh không phân biệt hoa thường
SELECT casefold('TU Anh') = casefold('tu anh');  -- true

casefold() đặc biệt hữu ích khi so sánh tên tiếng Đức (có ß), tiếng Thổ Nhĩ Kỳ (có dotless i), hay bất cứ ngôn ngữ nào có ngữ nghĩa case khác tiếng Anh - những trường hợp mà lower() cho kết quả sai. LIKE giờ cũng hỗ trợ pattern matching với nondeterministic collation, điều mà trước đây phải né tránh bằng regex phức tạp.

15. Checklist chuyển đổi PostgreSQL 17 → 18

Dưới đây là danh sách các bước thực tế mà một đội ngũ vận hành có thể áp dụng để chuyển đổi an toàn:

  1. Audit các extension đang dùng. Kiểm tra pgvector, TimescaleDB, PostGIS, citus, pg_cron... đã có bản hỗ trợ PG18 chưa. Đây thường là vật cản chính.
  2. Kiểm tra pgbouncer và connection pooler. Nâng lên phiên bản tương thích protocol 3.2, kể cả khi bạn chưa opt-in, để tránh regression trong tương lai.
  3. Chuyển md5 sang SCRAM. Rà soát pg_hba.conf, đổi các dòng md5 thành scram-sha-256, và chạy ALTER USER ... WITH PASSWORD để tái sinh password hash dưới dạng mới.
  4. Thử nghiệm AIO trong staging. Bắt đầu với io_method = worker, đo benchmark với pgbench và workload thực tế, rồi chuyển sang io_uring nếu kernel ≥ 5.1.
  5. Rà soát generated column. Các cột GENERATED STORED trong schema cũ không đổi tự động. Nhưng các cột mới cần khai báo rõ STORED nếu không muốn nhận mặc định mới là VIRTUAL.
  6. Thí điểm Skip Scan. Tìm các bảng lớn có index multi-column không dùng được vì left-most prefix, chạy lại EXPLAIN ANALYZE trên PG18 để xác minh planner đã chọn skip scan.
  7. Kiểm tra backup/restore pipeline. Xác nhận công cụ backup của bạn (pgBackRest, Barman, wal-g) đã nhận diện pg_upgrade --swap và các thay đổi về WAL format.
  8. Cập nhật monitoring. Thêm metric từ pg_stat_io mới, dashboard theo dõi idle_replication_slot_timeout, và cảnh báo trên các trường mới trong pg_stat_subscription_stats.

16. Lời kết

Nếu nhìn dưới lăng kính "feature list" đơn thuần, PostgreSQL 18 có vẻ là một bản cập nhật dày dặn nhưng không cách mạng. Nhưng nếu nhìn dưới lăng kính kiến trúc, đây là phiên bản đặt lại nền móng: lớp I/O được viết lại, planner học một trick mới mà giới DBA mong đợi từ hai thập kỷ, wire protocol cập nhật lần đầu sau 22 năm, và cơ chế xác thực bước vào kỷ nguyên token-based thay vì password.

Với các hệ thống đang chạy PostgreSQL 15 hoặc 16, đây là thời điểm hợp lý để lên kế hoạch chuyển đổi trong 6-9 tháng tới. Với những hệ thống mới bắt đầu, không có lý do gì để không chọn thẳng PostgreSQL 18 ngay từ đầu - đặc biệt khi các nhà cung cấp cloud lớn như AWS RDS, GCP Cloud SQL, Azure Database for PostgreSQL, Aiven, Neon, Supabase đều đã hoặc đang hoàn tất việc đưa PG18 lên sản phẩm.

Điều cần nhớ: hiệu năng của PostgreSQL 18 không tự đến. AIO cần cấu hình đúng, Skip Scan cần đúng hình dạng index, UUIDv7 cần được dùng cho các bảng mới, OAuth cần IdP hoạt động ổn định. Đây là một công cụ mạnh, nhưng giá trị của nó phụ thuộc vào mức độ hiểu biết của đội ngũ vận hành - và đó chính là lý do bài viết này tồn tại: để bạn có được bức tranh đầy đủ trước khi bắt tay vào thực hiện chuyển đổi.

17. Nguồn tham khảo