PostgreSQL cấp transaction id (XID) 32-bit, nên chỉ có khoảng 4 tỉ giá trị. Việc so sánh XID dùng số học vòng tròn: với một XID bất kỳ, khoảng 2 tỉ XID được coi là "quá khứ" và 2 tỉ là "tương lai".
Nếu một tuple cũ hơn 2 tỉ transaction mà chưa được xử lý, XID của nó sẽ rơi vào vùng "tương lai" → dòng dữ liệu đột nhiên biến mất khỏi tầm nhìn. Đây là mất dữ liệu logic, nên PostgreSQL chặn trước.
Cơ chế bảo vệ: VACUUM freeze các tuple đủ cũ — đánh dấu chúng là "luôn nhìn thấy được", không còn phụ thuộc XID nữa.
Khi vacuum không chạy:
- Còn ~40 triệu XID: log cảnh báo database is not accepting commands... sắp tới.
- Còn ~3 triệu XID: server từ chối mọi lệnh ghi, chỉ cho phép vacuum trong single-user mode để cứu.
Giám sát:
select datname, age(datfrozenxid)
from pg_database
order by age(datfrozenxid) desc;age tiến gần autovacuum_freeze_max_age (mặc định 200 triệu) là dấu hiệu phải xử lý.
Nguyên nhân điển hình: transaction bỏ quên ở trạng thái idle in transaction, replication slot không active giữ WAL, hoặc prepared transaction chưa commit — tất cả đều chặn việc đẩy freeze horizon.