Giám sát tài nguyên hệ thống và tối ưu bộ nhớ RAM/Swap trên Linux

Trong vận hành hạ tầng máy chủ vật lý (Bare-Metal), cụm nút xử lý Kubernetes và hệ thống cơ sở dữ liệu hiệu năng cao trên Linux Kernel 6.12 LTS năm 2026, quản trị viên hệ thống thường xuyên đối mặt với hai hiện tượng trái ngược: hoặc hoảng hốt nâng cấp phần cứng oan uổng khi thấy cột free trong lệnh free -h giảm xuống chỉ còn vài trăm Megabyte dù máy chủ thực chất đang tận dụng tối đa bộ đệm trang (Page Cache), hoặc ngược lại, để mặc cấu hình mặc định của hệ điều hành khiến các luồng ứng dụng bị đóng băng hàng trăm mili-giây do rơi vào chu trình thu hồi trang đồng bộ (Synchronous Direct Reclaim) và hao mòn ổ cứng NVMe vì hoán đổi bộ nhớ (Swap Thrashing).

Khác với bài viết chuyên trách về tuning Linux Kernel sysctl cho máy chủ chịu tải cao vốn tập trung vào ngăn xếp mạng TCP/IP (net.core.*, net.ipv4.*) và bài viết chẩn đoán và xử lý sự cố OOM Killer trên Linux chuyên giải quyết điều tra pháp y sau sự cố sập dịch vụ lúc 3 giờ sáng, hướng dẫn chuyên sâu này thiết lập quy trình giám sát tài nguyên và cấu hình tối ưu bộ nhớ RAM Swap trên Linux theo hướng Phòng vệ Chủ động (Day-1 / Day-2 Proactive Capacity & Virtual Memory Engineering). Chúng ta sẽ giải phẫu kiến trúc bộ nhớ ảo trên Linux Kernel 6.12 LTS, công thức tính MemAvailable và ngưỡng nước Watermark (min, low, high), kích hoạt thuật toán thu hồi đa thế hệ Multi-Generational LRU (MGLRU), xây dựng kiến trúc phân tầng bộ nhớ 3 lớp với nén RAM zram/zswap (zstd) trên thang đo vm.swappiness (0–200), tối ưu hóa đa socket NUMA / Transparent Huge Pages (THP), và thiết lập giám sát định lượng qua sysstat (sar -r -B -W).


Mục Lục Nội Dung

1. Giải phẫu Kiến trúc Bộ nhớ Ảo (Virtual Memory) trên Linux Kernel 6.12 LTS và /proc/meminfo

Nguyên lý triết học cốt lõi của bộ quản lý bộ nhớ nhân Linux (mm/) là: "Bộ nhớ RAM bỏ trống hoàn toàn (MemFree) là bộ nhớ RAM bị lãng phí". Bất cứ khi nào có các khung trang vật lý (Physical Page Frames, mỗi trang kích thước chuẩn 4 KB) chưa được cấp phát cho tiến trình người dùng, nhân Linux sẽ mượn ngay các khung trang đó để lưu đệm dữ liệu đọc/ghi từ đĩa cứng nhằm tăng tốc độ I/O từ hàng mili-giây xuống hàng nano-giây.

flowchart TD
    subgraph Tier0["Tầng 0: Bộ nhớ DRAM Vật lý (Local & Remote NUMA Nodes)"]
        AnonPages["Anonymous Pages (Heap malloc, Stack, mmap MAP_ANONYMOUS)"]
        FileCache["File-Backed Page Cache (Đệm đọc/ghi tệp & mmap tệp)"]
        KernelSlab["Kernel Slab Allocator: SReclaimable (dentry/inode) vs SUnreclaim"]
        MGLRU["Thuật toán Multi-Generational LRU (MGLRU Gen 0 Cold -> Gen 3 Hot)"]
    end

    AnonPages --> MGLRU
    FileCache --> MGLRU

    MGLRU -->|"Áp lực bộ nhớ chạm WMARK_LOW (kswapd)"| Balance{"Tỷ số ưu tiên vm.swappiness (0 - 200)"}
    Balance -->|"Giải phóng trang tệp sạch (Clean File Pages)"| DropCache["Thu hồi tức thì không tốn I/O (Hoặc flush Dirty Pages nếu vượt vm.dirty_ratio)"]
    Balance -->|"Hoán đổi trang vô danh lạnh (Cold Anonymous Pages)"| Tier1["Tầng 1: Bộ nhớ RAM Nén Tốc độ cao (zram / zswap với thuật toán zstd ~3.2x)"]
    Tier1 -->|"Tràn bể nén zswap hoặc Tiering"| Tier2["Tầng 2: Bộ nhớ CXL Mở rộng / Phân vùng NVMe Swap Vật lý"]

1.1. Phân biệt Anonymous Pages, File-Backed Page Cache và Slab (SReclaimable vs SUnreclaim)

Để đọc hiểu chính xác bảng hạch toán bộ nhớ trong /proc/meminfo, kỹ sư hệ thống phải phân biệt rõ 3 nhóm trang bộ nhớ có cơ chế thu hồi hoàn toàn khác nhau:

  1. Anonymous Pages (AnonPages — Bộ nhớ Vô danh của Tiến trình):
    Là vùng nhớ không gắn với bất kỳ tệp tin nào trên đĩa cứng, được cấp phát động khi ứng dụng gọi malloc(), new, khởi tạo vùng nhớ ngăn xếp (Thread Stack), hoặc gọi mmap(MAP_ANONYMOUS) (ví dụ: JVM Heap, Go Runtime Heap, Python Objects, hoặc innodb_buffer_pool).

    • Đặc tính sống còn: Vì không có tệp gốc trên đĩa, nhân Linux không bao giờ được phép xóa bỏ (drop) các trang AnonPages. Cách duy nhất để giải phóng RAM đang chứa AnonPages (khi tiến trình chưa tự free()) là nén và di chuyển chúng sang thiết bị Swap (zram hoặc đĩa NVMe). Nếu máy chủ tắt hoàn toàn Swap (swapoff -a), 100% các trang AnonPages sẽ bị ghim cứng trong RAM vật lý!
  2. File-Backed Page Cache (Cached & Buffers — Bộ đệm Trang gắn với Tệp):
    Chứa nội dung các khối tệp tin vừa được đọc từ hệ thống tệp (ext4, XFS) hoặc các trang bộ nhớ ánh xạ tệp (mmap(MAP_SHARED) như chỉ mục đồ thị của cơ sở dữ liệu).

    • Trang sạch (Clean File Pages): Đã đồng bộ hoàn toàn với đĩa cứng; nhân Linux có thể thu hồi và xóa bỏ ngay lập tức trong vài micro-giây để nhường RAM cho ứng dụng mà không tốn bất kỳ thao tác ghi I/O nào.
    • Trang bẩn (Dirty Pages — trường Dirty: trong /proc/meminfo): Đã được ứng dụng sửa đổi trong RAM nhưng chưa kịp ghi xuống đĩa (Writeback). Nhân Linux buộc phải gọi luồng nền flush ghi xuống đĩa trước khi có thể thu hồi các trang này.
  3. Kernel Slab Cache (Slab = SReclaimable + SUnreclaim):
    Vùng nhớ nhân dùng để lưu trữ các cấu trúc dữ liệu nội bộ của hệ điều hành như bảng mục thư mục (dentry_cache) và siêu dữ liệu tệp (inode_cache). Phần SReclaimable có thể được nhân thu hồi tự động khi thiếu RAM (điều khiển bởi thông số vm.vfs_cache_pressure), trong khi phần SUnreclaim bị khóa cứng bởi các trình điều khiển hạt nhân.

1.2. Công thức tính MemAvailable trong Kernel và cơ chế Watermark (min, low, high) kích hoạt kswapd

Thay vì nhìn vào MemFree, chỉ số duy nhất phản ánh trung thực dung lượng bộ nhớ khả dụng để khởi chạy ứng dụng mới mà không bị đẩy vào vòng xoáy Swap là MemAvailable (được tính toán trực tiếp trong hàm si_mem_available() của mã nguồn nhân mm/page_alloc.c):

$$text{MemAvailable} approx text{MemFree} + left( text{Active(file)} + text{Inactive(file)} - text{Watermark}{text{low}} right) + left( text{SReclaimable} - minleft(frac{text{SReclaimable}}{2}, text{Watermark}{text{low}}right) right)$$

Bên trong mỗi vùng bộ nhớ vật lý (Memory Zone), nhân Linux duy trì 3 ngưỡng mực nước (Zone Watermarks) tính dựa trên tham số vm.min_free_kbytes và vm.watermark_scale_factor:

sequenceDiagram
    autonumber
    participant AppThread as Luồng Ứng dụng (PostgreSQL / Go / Python)
    participant PageAlloc as Bộ cấp phát __alloc_pages() (mm/page_alloc.c)
    participant KswapdDaemon as Tiến trình hạt nhân kswapd (Async Reclaim)
    participant MGLRUEngine as Bộ quét Multi-Generational LRU (MGLRU)
    participant ZramDev as Thiết bị nén RAM /dev/zram0 (zstd)

    AppThread->>PageAlloc: Yêu cầu cấp phát khung trang RAM mới (Page Fault)
    alt RAM trống > WMARK_LOW (Trạng thái Xanh)
        PageAlloc-->>AppThread: Cấp phát tức thì từ Free List (< 100ns)
    else WMARK_MIN < RAM trống <= WMARK_LOW (Trạng thái Vàng)
        PageAlloc->>KswapdDaemon: Đánh thức kswapd chạy ngầm bất đồng bộ
        PageAlloc-->>AppThread: Vẫn cấp phát RAM ngay cho ứng dụng (Không gây trễ!)
        KswapdDaemon->>MGLRUEngine: Quét thế hệ trang lạnh nhất (Coldest Generation Gen 0)
        MGLRUEngine->>ZramDev: Nén trang Anon lạnh bằng zstd & giải phóng Clean File Cache
        Note over KswapdDaemon,MGLRUEngine: Thu hồi cho tới khi RAM trống quay về ngưỡng WMARK_HIGH
    else RAM trống <= WMARK_MIN (Trạng thái Đỏ - Cạn kiệt đệm!)
        PageAlloc->>AppThread: ĐÓNG BĂNG luồng ứng dụng để chạy Synchronous Direct Reclaim!
        AppThread->>MGLRUEngine: Luồng ứng dụng tự quét & chờ giải phóng trang (Gây nhảy vọt Latency p99!)
    end

  • WMARK_HIGH: Mức an toàn. Khi RAM trống nằm trên ngưỡng này, tiến trình thu hồi nền kswapd ngủ hoàn toàn.
  • WMARK_LOW: Khi lượng RAM trống tụt xuống dưới WMARK_LOW, nhân đánh thức tiến trình kswapd chạy ngầm bất đồng bộ để thu hồi trang lạnh cho tới khi chạm lại WMARK_HIGH.
  • WMARK_MIN: Ngưỡng nguy hiểm tuyệt đối. Nếu tốc độ cấp phát bộ nhớ của ứng dụng nhanh hơn tốc độ dọn dẹp của kswapd khiến RAM trống chạm đáy WMARK_MIN, mọi luồng ứng dụng gọi cấp phát bộ nhớ sẽ bị chặn đứng đồng bộ (Synchronous Direct Reclaim — ghi nhận vào bộ đếm allocstall trong /proc/vmstat) để tự đi thu hồi từng trang bộ nhớ, gây ra các đợt treo cứng độ trễ p99 kéo dài hàng trăm mili-giây!

2. Cơ chế Thu hồi Trang Hiện đại: Multi-Generational LRU (MGLRU) và Watermark Scale Factor

2.1. Tại sao thuật toán Active/Inactive LRU truyền thống gây hiện tượng Page Thrashing dưới tải cao?

Trong suốt hơn hai thập kỷ trước Linux 6.1, nhân Linux quản lý tuổi thọ trang bộ nhớ bằng mô hình 2 danh sách (Active List và Inactive List) tách riêng cho trang vô danh (anon) và trang tệp (file). Mô hình cổ điển này bộc lộ 3 điểm yếu nghiêm trọng trên các máy chủ hiện đại có dung lượng RAM từ 64 GB đến 1 TB:

  1. Phân loại nhị phân quá thô: Một trang trong danh sách Inactive được truy cập 2 lần và một trang trong Active được truy cập 100 lần mỗi giây không được phân tầng chi tiết khi áp lực bộ nhớ tăng đột ngột.
  2. Chi phí quét bảng trang (Reverse Mapping — rmap) quá đắt đỏ: Để kiểm tra xem một trang có vừa được CPU truy cập thông qua bit phần cứng PTE Accessed Bit hay không, kswapd phải duyệt ngược qua cây rmap phức tạp, tiêu tốn tới 15% – 25% CPU của toàn máy chủ khi bộ nhớ đầy.
  3. Hiện tượng Page Cache Thrashing: Khi chạy một tác vụ sao lưu hoặc quét bảng lớn (Sequential Scan), hàng triệu trang đọc một lần tràn vào đẩy bật các trang mã thực thi (Executable Text Pages) và tập làm việc nóng (Working Set) của ứng dụng ra khỏi RAM, khiến máy chủ rơi vào vòng xoáy đọc lại từ đĩa liên tục.

2.2. Kích hoạt và tinh chỉnh Multi-Generational LRU (MGLRU) qua /sys/kernel/mm/lru_gen/ trên Linux 6.12 LTS

Trên Linux Kernel 6.12 LTS, cơ chế Multi-Generational LRU (MGLRU — CONFIG_LRU_GEN=y) thay thế hai danh sách cứng nhắc bằng một chuỗi nhiều thế hệ trang (Generations từ Gen 0 già nhất đến Gen N trẻ nhất):

  • Khi trang được truy cập, nó được thăng hạng lên thế hệ trẻ nhất (Youngest Generation) thông qua quá trình quét trực tiếp bảng trang của tiến trình theo thứ tự địa chỉ ảo tuyến tính (nhanh hơn gấp nhiều lần so với quét rmap).
  • Khi cần giải phóng RAM, kswapd chỉ việc thu hồi các trang nằm ở thế hệ già nhất (Gen 0 — những trang lâu nhất không có ai chạm tới).
  • Đặc biệt, tệp điều khiển /sys/kernel/mm/lru_gen/min_ttl_ms cho phép quản trị viên khóa cứng một "thời gian sống tối thiểu" (ví dụ 1000 ms = 1 giây): bất kỳ trang bộ nhớ nào vừa được truy cập trong vòng 1000 ms qua sẽ được bảo vệ tuyệt đối khỏi việc bị thu hồi, chấm dứt hoàn toàn hiện tượng giật cục (Thrashing) dưới tải cao!
Giá trị Bitmask trong /sys/kernel/mm/lru_gen/enabled Ý nghĩa Kỹ thuật của từng Bit (Bit 0, Bit 1, Bit 2) Khuyến nghị Cấu hình Production
0x0001 (1) Bật MGLRU cho việc thu hồi trang đệm tệp (Page Cache). Luôn bật.
0x0002 (2) Bật MGLRU cho việc thu hồi trang vô danh (Anonymous Pages). Luôn bật.
0x0004 (4) Bật cơ chế theo dõi tập làm việc thông minh (Working Set Protection & Tiering). Luôn bật.
Tổng hợp: 0x0007 (7 hoặc y) Kích hoạt toàn diện cả 3 tính năng của MGLRU. Chuẩn bắt buộc trên Linux 6.12 LTS kết hợp min_ttl_ms = 1000.

3. Thiết kế Kiến trúc Phân tầng Bộ nhớ (Memory Tiering): DRAM, Nén RAM zram/zswap (zstd) và NVMe Swap

Một quan niệm sai lầm rất phổ biến trong giới vận hành Kubernetes và Database cũ là "Luôn phải tắt hoàn toàn Swap (swapoff -a) vì Swap làm chậm hệ thống". Thực tế trên Linux 6.12 LTS và Kubernetes v1.32+ (NodeSwap đạt chuẩn ổn định với LimitedSwap), việc tắt sạch mọi tầng Swap khiến nhân Linux mất đi "van xả áp" cho các trang bộ nhớ khởi tạo một lần rồi không bao giờ dùng lại (Init-Once Cold Anonymous Pages, chiếm tới 10% – 20% RAM của các runtime Python/Java/Node.js), buộc nhân phải hy sinh bộ đệm Page Cache quý giá hoặc kích hoạt OOM Killer sớm.

3.1. Đặt lên bàn cân zram (In-Memory Compressed Block Device) và zswap (Compressed Writeback Cache) với thuật toán zstd

Thay vì đẩy các trang bộ nhớ lạnh trực tiếp xuống ổ cứng vật lý chậm chạp, Linux cung cấp hai công nghệ Nén Bộ nhớ ngay trên RAM (In-RAM Memory Compression) sử dụng thuật toán nén zstd (Zstandard) hoặc lz4:

Tiêu chí Kỹ thuật zram (/dev/zram0 — Compressed RAM Block Device) zswap (mm/zswap.c — Compressed Frontswap Cache) Phân vùng NVMe Swap Truyền thống (Không nén)
Kiến trúc Hoạt động Tạo một thiết bị khối ảo nằm 100% trong RAM vật lý; dữ liệu đẩy vào /dev/zram0 được nén tại chỗ và không bao giờ chạm xuống ổ cứng. Đứng làm bộ đệm nén (zpool=zsmalloc) phía trước một phân vùng Swap vật lý trên ổ NVMe; khi bể nén đầy mới đẩy trang cũ nhất (LRU) xuống đĩa. Ghi trực tiếp trang 4 KB thô xuống phân vùng đĩa SSD/NVMe qua tầng Block I/O.
Tỷ lệ Nén với thuật toán zstd 2.8x – 3.5x (4 GB RAM thực chứa được ~12 GB trang vô danh). 2.8x – 3.5x trong vùng nhớ đệm max_pool_percent (thường 20% RAM). 1.0x (Không nén).
Độ trễ Đọc/Ghi (Page-In / Page-Out) ~1.5 – 4 micro-giây (µs) (Nhanh hơn ổ NVMe từ 20 đến 100 lần!). ~2 µs khi trúng bộ đệm RAM (Cache Hit), ~50–200 µs khi phải đẩy xuống NVMe. ~50 – 500 micro-giây (µs) kèm hao mòn chu kỳ ghi TBW của SSD.
Kịch bản Triển khai Chuẩn Kubernetes Worker Nodes (v1.32+), Microservices, CI/CD Runners, Web Servers. Máy chủ vật lý chạy tác vụ hỗn hợp có gắn sẵn ổ NVMe tốc độ cao dự phòng. Chỉ dùng làm tầng dự phòng cuối cùng (Priority thấp hơn zram).

3.2. Giải mã thang đo vm.swappiness (0 – 200) trên Kernel mới và công thức cân bằng Anonymous vs File Cache

Hầu hết các bài viết cũ trên mạng đều nói rằng vm.swappiness có giá trị từ 0 đến 100. Trên thực tế, kể từ khi nhân Linux cập nhật bộ công thức cân bằng chi phí I/O (mm/vmscan.c, chuẩn hóa trong Linux 6.12 LTS), thang đo chính thức của vm.swappiness nằm trong dải từ 0 đến 200:

  • Nhân Linux dùng swappiness để tính hai trọng số đối lập:
    $$text{anon_prio} = text{swappiness}, qquad text{file_prio} = 200 - text{swappiness}$$
  • Khi vm.swappiness = 60 (Mặc định): $text{anon_prio} = 60$ và $text{file_prio} = 140$, nghĩa là nhân coi việc đọc lại một trang tệp từ đĩa rẻ hơn so với việc đẩy một trang vô danh ra Swap, nên ưu tiên thu hồi Page Cache trước.
  • Khi vm.swappiness = 100: Nhân đánh giá chi phí thu hồi trang vô danh (AnonPages) và trang đệm tệp (File Cache) là ngang bằng nhau (100 : 100).
  • Khi dùng zram với thuật toán zstd, việc nén một trang vô danh ngay trong RAM chỉ mất ~2 µs (không tốn I/O đĩa), trong khi xóa một trang Page Cache sẽ bắt ổ cứng phải đọc lại tệp tin mất ~100 µs! Do đó, tài liệu chính thức của Linux Kernel (docs.kernel.org/admin-guide/sysctl/vm.html) khẳng định: Khi chạy trên zram hoặc thiết bị nhớ tốc độ cao, nên đặt vm.swappiness ở mức 133 – 180 để nhân chủ động nén các trang AnonPages ngủ đông vào zram và giữ lại bộ đệm hệ thống tệp nóng trong RAM!
  • Ngược lại, trên các máy chủ Cơ sở dữ liệu OLTP (PostgreSQL 17 / MySQL 8.4) dùng Swap trên đĩa vật lý và đã tự quản lý bộ đệm riêng (shared_buffers / innodb_buffer_pool), bạn bắt buộc phải khóa vm.swappiness = 1 để nhân chỉ hoán đổi khi đứng trước bờ vực cạn kiệt bộ nhớ.

Dưới đây là tệp cấu hình tự động hóa systemd zram-generator kết hợp kích hoạt MGLRU và Transparent Huge Pages (madvise) chuẩn Production:

#!/usr/bin/env bash
# file: setup_zram_zstd_and_mglru_linux612.sh
# Runtime: Linux Kernel 6.12 LTS (Ubuntu 24.04+ / Debian 13 / RHEL 9.5+)
set -euo pipefail

# 1. Cấu hình systemd-zram-generator sử dụng thuật toán nén zstd ưu tiên cao (Priority 100)
cat << 'EOF' | sudo tee /etc/systemd/zram-generator.conf
[zram0]
# Cấp phát kích thước không gian nén tối đa bằng 50% RAM vật lý (giới hạn trần 16 GiB)
zram-size = min(ram / 2, 16384)
compression-algorithm = zstd
swap-priority = 100
fs-type = swap
EOF

# 2. Cấu hình tmpfiles.d để kích hoạt bền vững Multi-Generational LRU (MGLRU) & THP madvise khi boot
cat << 'EOF' | sudo tee /etc/tmpfiles.d/99-mglru-and-thp.conf
# Type Path                                               Mode UID  GID  Age Argument
w      /sys/kernel/mm/lru_gen/enabled                     -    -    -    -   0x0007
w      /sys/kernel/mm/lru_gen/min_ttl_ms                  -    -    -    -   1000
w      /sys/kernel/mm/transparent_hugepage/enabled        -    -    -    -   madvise
w      /sys/kernel/mm/transparent_hugepage/defrag         -    -    -    -   defer+madvise
w      /sys/kernel/mm/transparent_hugepage/shmem_enabled  -    -    -    -   advise
EOF

# 3. Áp dụng ngay lập tức mà không cần khởi động lại máy chủ
sudo systemd-tmpfiles --create /etc/tmpfiles.d/99-mglru-and-thp.conf
sudo systemctl daemon-reload
sudo systemctl start [email protected] || true

echo "=== Kiểm tra trạng thái MGLRU, THP và thiết bị nén zram0 ==="
cat /sys/kernel/mm/lru_gen/enabled
cat /sys/kernel/mm/lru_gen/min_ttl_ms
cat /sys/kernel/mm/transparent_hugepage/enabled
zramctl --output-all


4. Tối ưu Kiến trúc Đa Socket NUMA, Transparent Huge Pages (THP) và Hồ sơ sysctl theo Tải công việc

4.1. Giám sát numastat, chính sách kernel.numa_balancing (Memory Tiering CXL) và kiểm soát THP (madvise)

Trên các máy chủ vật lý trang bị từ 2 đế vi xử lý trở lên (Dual-Socket AMD EPYC hoặc Intel Xeon Scalable) hoặc các máy chủ gắn bộ nhớ mở rộng qua giao thức CXL (Compute Express Link), bộ nhớ RAM được chia thành các nút NUMA (Non-Uniform Memory Access):

  • Khi một nhân CPU thuộc Node 0 truy cập thanh RAM cắm trực tiếp trên Node 0 (Local NUMA Access — numa_hit), độ trễ chỉ khoảng ~75 – 85 ns.
  • Nhưng nếu Node 0 đã hết RAM trống khiến nhân phải cấp phát tràn sang thanh RAM của Node 1 (Remote NUMA Access — numa_miss / numa_foreign) đi qua cầu nối liên socket (AMD Infinity Fabric hoặc Intel UPI), độ trễ truy cập tăng vọt lên ~140 – 180 ns và băng thông giảm gần một nửa!
  • Chính sách kernel.numa_balancing trên Linux 6.12 LTS:
    • Đặt kernel.numa_balancing = 1 cho các máy chủ chạy đa dịch vụ tổng quát để nhân tự động di trú trang bộ nhớ về gần nhân CPU đang xử lý.
    • Đặt kernel.numa_balancing = 2 (NUMA_BALANCING_MEMORY_TIERING) trên các máy chủ có bộ nhớ phân tầng DRAM + CXL để nhân tự động thăng hạng (promote) các trang nóng từ CXL lên DRAM tốc độ cao.
    • Đặt kernel.numa_balancing = 0 và dùng numactl --interleave=all (hoặc ghim cứng từng tiến trình vào từng NUMA Node qua NUMAPolicy=bind của systemd) đối với các cơ sở dữ liệu lớn như PostgreSQL/MySQL để tránh hiện tượng nhân di chuyển các khối Buffer Pool nhiều Gigabyte qua lại giữa các socket gây nghẽn băng thông.
  • Kiểm soát Transparent Huge Pages (THP): Trang bộ nhớ mặc định chỉ rộng 4 KB, khiến một Buffer Pool 128 GB cần tới 33.5 triệu mục trong bảng phân trang (Page Table Entries), gây tràn bộ đệm dịch địa chỉ phần cứng TLB (Translation Lookaside Buffer). Tuy nhiên, nếu bật THP = always, tiến trình nền khugepaged sẽ liên tục khóa bộ nhớ để gom mảnh ghép thành các trang khổng lồ 2 MB ngay trong lúc ứng dụng đang chạy, gây ra các đợt khựng độ trễ (Latency Spikes) nghiêm trọng trên Redis, MongoDB và PostgreSQL. Do đó, cấu hình chuẩn vàng năm 2026 là đặt THP = madvise (chỉ gom trang 2 MB cho những vùng nhớ mà ứng dụng chủ động yêu cầu qua lệnh gọi madvise(MADV_HUGEPAGE)), còn với PostgreSQL/MySQL thì cấp phát tĩnh Explicit HugeTLB (vm.nr_hugepages) ngay từ lúc khởi động.

4.2. Ba bộ hồ sơ /etc/sysctl.d/99-memory-tiering.conf chuẩn Production: Database Server, Kubernetes Node và AI/Vector Host

Khi cấu hình hệ thống sản xuất, bạn nên áp dụng đồng bộ các tham số mạng trong bài tuning Linux Kernel sysctl cho máy chủ chịu tải cao cùng với một trong ba hồ sơ bộ nhớ ảo /etc/sysctl.d/99-linux612-memory-tiering.conf chuyên biệt dưới đây:

Tham số sysctl (vm.*) Hồ sơ 1: OLTP Database (PostgreSQL 17 / MySQL 8.4) Hồ sơ 2: Kubernetes Worker Node (v1.32+ chạy zram) Hồ sơ 3: Vector DB (Milvus/Qdrant) & AI Host Giải thích Tác động Kỹ thuật
vm.swappiness 1 150 1 1 cho DB/Vector tránh đẩy Buffer/HNSW ra đĩa; 150 cho K8s Node dùng zram nén trang vô danh lạnh.
vm.vfs_cache_pressure 50 100 50 Giảm xuống 50 giúp nhân giữ lại bộ đệm dentry/inode của các tệp bảng dữ liệu lâu hơn trong Slab.
vm.dirty_background_ratio 3 (3%) 5 (5%) 5 (5%) Đánh thức luồng flush ghi trang bẩn xuống NVMe sớm ở mức 3% để tránh tích tụ lượng dữ liệu chưa ghi quá lớn.
vm.dirty_ratio 10 (10%) 15 (15%) 15 (15%) Ngưỡng trần chặn ghi đồng bộ; giảm từ 20% mặc định xuống 10% giúp triệt tiêu hiện tượng "bão I/O" (I/O Flush Storm).
vm.watermark_scale_factor 200 (2.0%) 150 (1.5%) 150 (1.5%) Mở rộng khoảng cách giữa WMARK_LOW và WMARK_HIGH (mặc định chỉ 10 = 0.1%), giúp kswapd chạy sớm và chặn đứng Direct Reclaim.
vm.min_free_kbytes 524288 (512 MB) 262144 (256 MB) 524288 (512 MB) Dự trữ vùng đệm RAM nguyên tử cho trình điều khiển mạng và hệ thống tệp trên máy chủ >= 64 GB RAM.
vm.overcommit_memory 2 (Strict) hoặc 0 1 (Always) 1 (Always) Đặt 2 trên máy chủ chuyên chạy PostgreSQL để chống cấp phát khống; đặt 1 cho Redis/Python/Qdrant để cho phép fork() Copy-on-Write.
vm.max_map_count 262144 262144 1048576 Tăng số lượng vùng nhớ mmap (VMA) tối đa cho các phân đoạn HNSW của Vector Database Milvus và Qdrant.
# file: /etc/sysctl.d/99-linux612-memory-tiering.conf
# Áp dụng cấu hình: sudo sysctl --system
# Mặc định kích hoạt Hồ sơ 1 (High-Performance Database / Storage Server trên Linux 6.12 LTS)

# 1. Kiểm soát tỷ lệ hoán đổi (0-200): Đặt = 1 khi dùng NVMe Swap; đổi thành = 150 nếu chỉ dùng zram zstd
vm.swappiness = 1

# 2. Ưu tiên bảo tồn bộ đệm siêu dữ liệu tệp dentry/inode trong vùng nhớ SReclaimable Slab
vm.vfs_cache_pressure = 50

# 3. Làm mượt luồng ghi đĩa nền (Writeback Smoothing) chống hiện tượng nghẽn I/O đột ngột
vm.dirty_background_ratio = 3
vm.dirty_ratio = 10
vm.dirty_expire_centisecs = 1500
vm.dirty_writeback_centisecs = 200

# 4. Phòng chống Synchronous Direct Reclaim (allocstall) bằng cách mở rộng khoảng đệm kswapd (200/10000 = 2%)
vm.min_free_kbytes = 524288
vm.watermark_scale_factor = 200
vm.watermark_boost_factor = 0

# 5. Cho phép số lượng vùng ánh xạ bộ nhớ (VMA) lớn phục vụ mmap của Database / Vector Engine
vm.max_map_count = 1048576

# 6. Tắt dịch chuyển trang NUMA tự động nếu đã ghim tiến trình DB bằng systemd NUMAPolicy
kernel.numa_balancing = 0

5. Giám sát Dung lượng Bộ nhớ Thời gian thực với sysstat (sar), vmstat và Bảo vệ Dịch vụ Trọng yếu

Khi tích hợp cảnh báo hạ tầng vào nền tảng giám sát hệ thống toàn diện với Prometheus và Grafana, quản trị viên cần kết hợp cả công cụ chẩn đoán dòng lệnh tại chỗ (sar, vmstat, /proc/vmstat) để phát hiện sớm hiện tượng cạn kiệt bộ nhớ trước khi sự cố xảy ra.

5.1. Đọc hiểu chỉ số Paging & Reclaim qua sar -r -B -W, vmstat 1 và eBPF Reclaim Telemetry

Thay vì chỉ nhìn vào phần trăm RAM đã dùng, hãy theo dõi 4 cột chỉ số vàng trong bộ công cụ sysstat (sar -r -B -W 1):

  1. pswpin/s và pswpout/s (sar -W hoặc cột si/so trong vmstat): Số lượng trang bộ nhớ bị hoán đổi vào/ra khỏi vùng Swap mỗi giây. Nếu si và so liên tục khác 0 trên máy chủ dùng Swap đĩa vật lý, hệ thống đang bị Swap Thrashing.
  2. pgscank/s so với pgscand/s (sar -B):
    • pgscank/s: Số trang được tiến trình nền kswapd quét bất đồng bộ (hoàn toàn bình thường, không làm chậm ứng dụng).
    • pgscand/s (allocstall trong /proc/vmstat): Số trang bị quét bởi Synchronous Direct Reclaim! Nếu pgscand/s > 0, nghĩa là tốc độ tiêu thụ RAM đã đâm thủng ngưỡng WMARK_MIN và các luồng xử lý Request của ứng dụng đang bị nhân đóng băng để tự dọn bộ nhớ. Bạn cần tăng ngay vm.watermark_scale_factor hoặc bổ sung RAM.
  3. majflt/s (Major Page Faults per second): Số lần tiến trình truy cập một trang bộ nhớ không có sẵn trong RAM mà buộc phải dừng lại chờ đọc từ đĩa cứng lên.
  4. %vmeff (Virtual Memory Efficiency): Tỷ lệ giữa số trang thu hồi thành công trên tổng số trang đã quét (pgsteal / pgscan). Khi %vmeff tụt xuống dưới 30%, nhân đang tốn rất nhiều CPU để quét bảng trang nhưng hầu hết các trang đều đang bận (nóng) không thể thu hồi được!

Dưới đây là kịch bản kiểm toán tự động đọc trực tiếp các bộ đếm nội tại từ /proc/meminfo, /proc/vmstat và numastat:

#!/usr/bin/env bash
# file: linux_memory_tier_capacity_audit.sh
# Chẩn đoán chuyên sâu dung lượng bộ nhớ ảo, MGLRU, Direct Reclaim & NUMA trên Linux 6.12 LTS
set -euo pipefail

echo "================ [1. PHÂN TÍCH /proc/meminfo (MiB)] ================"
awk '
  /^(MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Dirty|Writeback|AnonPages|Mapped|Shmem|Slab|SReclaimable|SUnreclaim|AnonHugePages):/ {
    gsub(":", "", $1);
    printf "%-18s : %10.2f MiBn", $1, $2 / 1024.0
  }
' /proc/meminfo

echo -e "n================ [2. BỘ ĐẾM THU HỒI TRANG (/proc/vmstat)] ================"
grep -E "^(pgsteal_kswapd|pgsteal_direct|pgscan_kswapd|pgscan_direct|allocstall_normal|allocstall_movable|pswpin|pswpout|thp_fault_alloc|thp_collapse_alloc)" /proc/vmstat

echo -e "n================ [3. THỐNG KÊ BẤT ĐỐI XỨNG ĐA SOCKET (numastat)] ================"
if command -v numastat >/dev/null 2>&1; then
  numastat -m | head -n 20
else
  grep -E "^Node [0-9]+, zone" -A 6 /proc/zoneinfo | head -n 28
fi

Và cách chạy chuỗi lệnh giám sát trực tiếp bằng sar và vmstat để đối chiếu hiệu năng thu hồi trang:

# Quan sát đồng thời: (-r) Dung lượng RAM, (-B) Paging & Direct Reclaim, (-W) Hoán đổi Swap mỗi 1 giây
sar -r -B -W 1 5

# Quan sát nhanh bằng vmstat theo đơn vị MiB (-SM): chú ý cột si, so (swap in/out) và wa (I/O wait)
vmstat -SM 1 5

5.2. Phân bổ hạn ngạch cgroups v2 (memory.high) và bảo vệ tiến trình cốt lõi với OOMScoreAdjust

Để ngăn chặn một dịch vụ phụ bị rò rỉ bộ nhớ ăn hết RAM của toàn máy chủ, hãy thiết lập ngưỡng tiết lưu mềm MemoryHigh= (kích hoạt thu hồi trang cục bộ trong cgroup của dịch vụ đó trước khi chạm trần cứng MemoryMax=) và gắn điểm miễn nhiễm OOMScoreAdjust=-900 cho các tiến trình sống còn (sshd, postgresql, kubelet, node_exporter) thông qua tệp ghi đè systemd drop-in. Nếu máy chủ của bạn đang gặp sự cố tiến trình bị giết với Exit Code 137 hoặc nghi ngờ rò rỉ bộ nhớ đang diễn ra, hãy chuyển ngay sang quy trình điều tra pháp y chi tiết tại bài viết chẩn đoán và xử lý sự cố OOM Killer trên Linux.

# file: /etc/systemd/system/postgresql.service.d/10-memory-protection.conf
# Áp dụng: sudo systemctl daemon-reload && sudo systemctl restart postgresql
[Service]
# Bảo vệ tiến trình cơ sở dữ liệu chính khỏi bị chọn làm mục tiêu khi hệ thống thiếu RAM
OOMScoreAdjust=-900

# Thiết lập ngưỡng tiết lưu mềm (MemoryHigh) và trần bảo vệ cứng (MemoryMax) trên cgroups v2
MemoryAccounting=true
MemoryHigh=48G
MemoryMax=54G
MemorySwapMax=0

6. Quy trình Kiểm toán Định kỳ Bộ nhớ Linux & Tài liệu Tham khảo Chuẩn Kỹ thuật

6.1. Checklist 8 bước rà soát cấu hình RAM/Swap trên máy chủ Linux Production

Bước Hạng mục Kiểm tra Lệnh Xác minh Nhanh Tiêu chuẩn Đạt (Pass Criteria)
1 Đệm MemAvailable awk '/MemAvailable/ {print $2/1024 " MiB"}' /proc/meminfo Luôn duy trì tối thiểu >= 15% tổng dung lượng RAM vật lý trong giờ cao điểm.
2 Trạng thái MGLRU cat /sys/kernel/mm/lru_gen/enabled Trả về 0x0007 kèm min_ttl_ms = 1000.
3 Chế độ THP cat /sys/kernel/mm/transparent_hugepage/enabled Đang chọn [madvise] (tuyệt đối không để [always] trên máy chủ DB/Redis).
4 Thiết bị nén zram zramctl --output-all Sử dụng thuật toán nén zstd với tỷ lệ nén thực tế Total /Compr >= 2.5x.
5 Độ trễ Direct Reclaim grep allocstall /proc/vmstat Bộ đếm allocstall_* (pgscand/s trong sar -B) giữ nguyên bằng 0.
6 Thông số swappiness sysctl vm.swappiness Bằng 1 cho máy chủ DB/Vector dùng đĩa; bằng 150 cho K8s Node dùng zram.
7 Cân bằng NUMA numastat -m Tỷ lệ numa_miss / numa_hit < 1% trên các máy chủ đa socket.
8 Bảo vệ Dịch vụ Lõi systemctl show -p OOMScoreAdjust sshd postgresql Các dịch vụ quản trị và cơ sở dữ liệu lõi có OOMScoreAdjust=-900.

6.2. Lịch trình kiểm toán dung lượng định kỳ và hoạch định nâng cấp phần cứng

Đội ngũ SRE cần tự động hóa việc thu thập biểu đồ xu hướng 30 ngày của tỷ lệ MemAvailable / MemTotal, tốc độ quét rate(node_vmstat_pgscan_kswapd[5m]) và tỷ lệ nén zram trên Grafana. Khi mức trung vị 7 ngày của MemAvailable giảm xuống dưới 12% tổng RAM hoặc xuất hiện các đỉnh node_vmstat_pgscan_direct > 0 lặp lại vào giờ cao điểm dù đã bật MGLRU, đó là tín hiệu định lượng chính xác để lên kế hoạch mở rộng ngang hoặc nâng cấp dung lượng DRAM/CXL cho cụm máy chủ.

6.3. Tài liệu tham khảo & Nguồn chuẩn kỹ thuật (Primary Technical References)

  1. The Linux Kernel Documentation — Documentation for /proc/sys/vm/ (swappiness, dirty_ratio, vfs_cache_pressure, watermark_scale_factor)
  2. The Linux Kernel Documentation — Multi-Generational LRU (MGLRU Admin Guide & /sys/kernel/mm/lru_gen/)
  3. The Linux Kernel Documentation — zram: Compressed RAM-based Block Devices
  4. The Linux Kernel Documentation — zswap: Compressed Swap Cache Architecture
  5. The Linux Kernel Documentation — Transparent Hugepage Support (THP Admin Guide)
  6. Red Hat Enterprise Linux 9 Documentation — Monitoring and Managing System Status and Performance (Memory & NUMA Optimization)