“Process này tốn bao nhiêu RAM?” — câu hỏi tưởng đơn giản. Trên Linux, tùy bạn nhìn VSS/VSZ, RSS, hay USS, số có thể chênh vài lần, và cộng RSS của mọi process thường lớn hơn RAM máy (vì shared bị đếm chồng).

Bài này là mental model khi debug container OOM, pod memory, hay so sánh worker Go / JVM / Python — không phải thay man page đầy đủ.

Demo đếm trên 2 process giả (shared lib + SHM): VSS / RSS / USS / PSS + SHM.

Ba (thật ra bốn) con số

  • VSS / VSZ — Virtual Set Size: tổng address space ảo đã map (kể cả chưa fault vào RAM, file map, reservation…). ps VSZ, top VIRT.
  • RSS — Resident Set Size: page đang nằm trong RAM, kể cả page shared (đếm full cho mỗi process). ps RSS, top RES.
  • USS — Unique Set Size: page chỉ process này map; kill process thì “trả” phần này. smem / smaps.
  • PSS — Proportional Set Size: unique + (shared ÷ số process đang share). Cộng PSS mọi process ≈ phân bổ “công bằng” hơn. smem, smaps_rollup.

Quan hệ thô (không phải định lý tuyệt đối trên mọi kernel config):

VSS  ≥  RSS  ≥  PSS  ≥  USS  ≥  0
// RSS có thể > “fair share” vì shared đếm full
// Σ RSS (mọi process) thường > RAM dùng thật
// Σ PSS ≈ hữu ích hơn khi ước lượng “ai đang giữ bao nhiêu”

VSS — ảo, dễ “to đùng”

Mỗi process có không gian địa chỉ ảo. Map thư viện, map file, mmap lớn, heap, stack, vùng reserve… đều làm VSS tăng, kể cả khi phần lớn page chưa fault vào RAM (demand paging).

  • JVM / large heap config, Tensor buffer map, elastic mapping → VSS hàng GB trong khi RSS chỉ vài trăm MB là chuyện thường.
  • Đừng dùng VSS để kết luận “hết RAM”. Nó trả lời: “address space rộng cỡ nào?”, không phải “đang ngồi bao nhiêu DRAM”.
ps -o pid,vsz,rss,comm -p $PID
# VSZ, RSS đơn vị KB (ps cổ điển)

RSS — đang resident, nhưng shared bị đếm chồng

RSS = các page có frame trong RAM thuộc mapping của process. Gồm:

  • Anonymous private (heap, stack, brk…) — thường “của riêng”
  • File-backed đã fault (code libc.so, file mmap…) — nhiều process dùng chung page vật lý
  • Shared memory (POSIX/System V SHM, mmap MAP_SHARED) — cùng một vùng RAM, mỗi process map vào RSS của mình

Hệ quả vận hành:

2 worker, mỗi RSS = 200MB
  trong đó ~80MB là libc + lib chung (cùng page)
  + 50MB SHM dùng chung cả hai

Cộng 200+200 = 400MB  ← ảo giác
Thực trên máy có thể ~200 - 80 - 50 + … ≈ thấp hơn nhiều

OOM killer / cgroup memory vẫn có quy tắc riêng (RSS, cache, shmem accounting…). Điểm cần nhớ: cộng cột RES trên top không ra “RAM còn lại”.

USS — “nếu kill process này, được lại bao nhiêu?”

USS chỉ đếm page private / unique: không share với ai. Kill process → phần đó free (trừ khi còn ref khác ngoài model đơn giản này).

  • So sánh “worker này phình heap không” → USS (hoặc private trong smaps) sát hơn RSS.
  • 10 process fork + copy-on-write: lúc đầu RSS mỗi đứa có thể cao (share page), USS thấp; sau khi ghi đè page, USS tăng (COW break).
# smem (python) — cột USS PSS RSS
smem -p $PID
smem -r  # sort RSS
# hoặc tự cộng từ smaps:
# Private_Clean + Private_Dirty ≈ hướng USS

PSS — chia shared “công bằng”

Page private: tính full. Page đang được N process map: mỗi process chịu size/N.

PSS ≈ USS + Σ (shared_page_size / n_sharers)

Muốn trả lời “trên máy này, service X ‘đáng’ bao nhiêu RAM nếu phân bổ chi phí lib chung” → PSS. Muốn “kill X được bao nhiêu” → USS. Muốn “kernel đang giữ resident cho mapping của X” → RSS.

SHM — shared memory, chỗ RSS hay đánh lừa

SHM (System V shmget/shmat, POSIX /dev/shm, mmap(..., MAP_SHARED), đôi khi cả tmpfs) là vùng RAM (hoặc tmpfs) nhiều process cùng map.

  • Mỗi process attach: vùng đó vào RSS (và VSS) của process đó.
  • USS của mỗi process không ôm trọn SHM (vì không unique) — trừ khi chỉ còn một process map.
  • PSS: mỗi bên chịu 1/N kích thước segment.
  • Trên hệ thống: shmem hay hiện trong free / MemAvailable accounting khác anonymous heap — IPC lớn (Redis không, nhưng app tự SHM, DB segment, ML shared weight map…) cần nhìn riêng.
ipcs -m              # System V shm segments
ls -lh /dev/shm      # POSIX shm objects (tmpfs)
# trong smaps: mapping có path /dev/shm/... hoặc SYSV...
# cgroup v2: memory.stat → shmem, file, anon…

Docker/K8s: limit memory container tính theo cgroup (thường gồm anon + file cache + shmem theo rule version). Process RSS cao vì map SHM lớn có thể đẩy cgroup chạm limit dù “logic app” tưởng chỉ dùng ít heap — đọc memory.stat / metrics runtime, đừng chỉ nhìn một số ps.

Nhìn số trên máy

# nhanh
ps -o pid,vsz,rss,comm -p $PID
cat /proc/$PID/status | egrep 'VmSize|VmRSS|VmData|RssAnon|RssFile|RssShmem'

# chi tiết từng mapping
less /proc/$PID/smaps
cat /proc/$PID/smaps_rollup   # kernel mới: Pss, Rss, … gộp

# tổng quan “fair”
smem -t   # tổng USS/PSS/RSS

Gợi ý đọc status:

  • VmSize ≈ hướng VSS
  • VmRSS ≈ RSS
  • RssAnon / RssFile / RssShmem — tách anonymous vs file vs shmem (hữu ích hơn một số RSS)

Khi nào dùng metric nào

  • OOM / “máy hết RAM?” — nhìn host MemAvailable, cgroup memory.current + memory.stat, không cộng VSZ.
  • Process leak heap? — USS hoặc RssAnon theo thời gian (RSS có thể nhiễu vì cache file).
  • N replica cùng image? — đừng × N × RSS; lib shared + image layer page cache được share (PSS / cgroup chung node).
  • SHM / mmap shared lớn? — kiểm tra RssShmem, /dev/shm, ipcs; nhớ RSS per-process đếm chồng.
  • Capacity planning “mỗi pod thêm bao nhiêu?” — gần USS (private) + phần shmem/cache riêng policy; PSS nếu muốn amortize lib.

Liên hệ app (Go / JVM / Python / Nest…)

  • Runtime allocator (Go GC, JVM heap, Python arena) chủ yếu đẩy anonymous / USS khi sống lâu + fragment.
  • Native lib, model weights mmap, extension module → file-backed RSS share được giữa process.
  • Worker prefork (Gunicorn, cũ PHP-FPM…): COW — fork xong RSS trông “đắt”, USS mới lộ sau khi request ghi page.

Đọc thêm

Heap managed (JVM/Go/V8/Python) còn lớp garbage collector — RSS process ≠ “heap live set”.

Một câu để nhớ

VSS = bản đồ ảo. RSS = đang ở RAM (shared đếm full mỗi process). USS = phần riêng, kill là mất. PSS = chia shared cho công bằng. SHM nằm ở shared — làm RSS cộng dồn sai nếu không để ý.

Debug memory: chọn đúng câu hỏi trước, rồi mới chọn cột số. Cộng nhầm RSS/VSS là cách nhanh nhất để “tối ưu” một vấn đề không tồn tại — hoặc bỏ lỡ leak anonymous thật.