Câu hỏi "có nên tắt swap không" được tranh cãi nhiều hơn là được đo. Bài này đo.
Cách đo
Container giới hạn 512 MB bộ nhớ, chạy một chương trình cấp một vùng nhớ rồi duyệt qua toàn bộ vùng đó nhiều vòng. Đổi hai thứ: kích thước vùng nhớ, và có cho phép swap hay không.
docker run --rm -m 512m --memory-swap 512m ... # khong swap
docker run --rm -m 512m --memory-swap 3g ... # 2,5 GB swap
Kết quả
| Vùng nhớ | Không swap | Có swap 2,5 GB |
|---|---|---|
| 200 MB | 0,01 µs/trang | 0,01 µs/trang |
| 480 MB | 0,01 µs/trang | 0,01 µs/trang |
| 600 MB | bị giết (137) | 4,82 – 10,65 µs/trang |
| 900 MB | bị giết (137) | 8,48 – 10,12 µs/trang |
| 1.400 MB | bị giết (137) | 9,91 rồi 41,21 µs/trang |
Mã thoát 137 là 128 + 9, nghĩa là bị SIGKILL — OOM killer.
Điều đáng nhớ: không có bậc trung gian
Vùng 480 MB nằm gọn trong giới hạn 512 MB: 0,01 µs mỗi trang. Vùng 600 MB vượt ra ngoài chút xíu: 4,82 µs.
Gần 500 lần, chỉ vì vượt giới hạn 17%.
Đây là điều tôi muốn nhấn mạnh, vì trực giác thường hình dung swap là một cái dốc thoai thoải: vượt một ít thì chậm một ít. Thực tế nó là một vách đá. Lý do đơn giản — mỗi trang bị đẩy ra swap phải đọc lại từ ổ đĩa khi cần, và một lần đọc ổ đĩa đắt gấp hàng nghìn lần một lần truy cập RAM (phần 12 đã đo: 2,2 µs so với 34,6 µs cho 4 KB, và swap còn đắt hơn vì hoàn toàn ngẫu nhiên).
Nói lại lần thứ ba theo cách khác: nếu chương trình của bạn đang chạm tới swap, nó không đang "hơi chậm". Nó đang chậm ở một bậc độ lớn khác.
Dòng 1.400 MB cho thấy chuyện còn xấu thêm: vòng 1 mất 9,91 µs mỗi trang, vòng 2 lên 41,21 µs. Càng nhiều dữ liệu phải luân chuyển, tỷ lệ trang nằm trong RAM càng thấp, và vòng lặp bắt đầu ăn thịt chính nó.
Cái bẫy thật của swap
So hai cột với nhau:
Không swap — tiến trình bị giết ngay. Xấu, nhưng: bạn biết ngay, hệ thống giám sát báo ngay, container được khởi động lại, dịch vụ hồi phục trong vài giây.
Có swap — tiến trình sống. Kiểm tra sức khoẻ vẫn trả về 200. systemctl status vẫn hiện active (running). Bảng điều khiển vẫn xanh. Chỉ có điều mỗi yêu cầu mất vài giây thay vì vài mili giây, hàng đợi dồn lại, và các dịch vụ gọi tới nó bắt đầu hết thời gian chờ.
Không có ngưỡng cảnh báo nào bắt được trạng thái đó, vì mọi chỉ số nhị phân đều báo "ổn".
Đó là lý do thực sự khiến Kubernetes mặc định yêu cầu tắt swap trên node: một pod chết được lên lịch lại; một pod chậm 500 lần thì kéo cả cụm xuống theo và không cơ chế tự chữa nào nhận ra.
Vậy tắt swap luôn?
Không. Swap có một việc mà nó làm rất tốt, và đó là việc khác hẳn.
Trên máy chủ nào cũng có bộ nhớ đã cấp nhưng không bao giờ chạm tới: dữ liệu khởi tạo của thư viện, mã của tính năng chưa ai dùng, vùng đệm cấp sẵn. Swap đẩy những trang đó ra đĩa và trả RAM lại cho bộ đệm trang — đúng nơi RAM sinh lợi.
Việc đó xảy ra êm và không ai thấy. Trong bảng trên, dòng 480 MB có swap chạy đúng bằng tốc độ không swap: swap có mặt nhưng không được dùng tới.
Phân biệt hai chế độ bằng vm.swappiness:
cat /proc/sys/vm/swappiness # mac dinh 60
sysctl -w vm.swappiness=10 # chi swap khi that su can
sysctl -w vm.swappiness=1 # gan nhu chi khi sap OOM
sysctl -w vm.swappiness=0 # chi khi khong con cach nao khac
swappiness không phải phần trăm và không phải "bao nhiêu thì swap". Nó là trọng số nhân khi nhân cân nhắc nên thu hồi trang tệp hay trang ẩn danh. Giá trị thấp nghĩa là ưu tiên vứt bộ đệm trang trước.
Khuyến nghị thực dụng: giữ swap, đặt swappiness thấp (1–10), và đặt cảnh báo trên chính lượng swap đang dùng.
# canh bao khi swap dung vuot 5%
free -m | awk '/Swap:/ {if ($2>0 && $3/$2 > 0.05) print "SWAP:", $3, "/", $2, "MB"}'
Chỉ số tốt hơn: áp lực bộ nhớ
Nhân từ 4.20 có PSI — Pressure Stall Information — đo trực tiếp thời gian tiến trình bị đình trệ vì thiếu bộ nhớ:
cat /proc/pressure/memory
some avg10=0.00 avg60=0.00 avg300=0.00 total=0
full avg10=0.00 avg60=0.00 avg300=0.00 total=0
some là phần trăm thời gian có ít nhất một tiến trình bị đình trệ; full là phần trăm thời gian mọi tiến trình đều đình trệ. full khác 0 nghĩa là máy đang không làm được gì hữu ích.
Đây là chỉ số cảnh báo đúng cho trạng thái "sống mà như chết" ở trên, vì nó đo cái đang thực sự xảy ra chứ không đo một con số tuyệt đối. Cùng có ở mức cgroup: /sys/fs/cgroup/<ten>/memory.pressure.
Thử ba mươi giây
Xem máy bạn có đang âm thầm swap không:
free -m | awk '/Swap:/{printf "swap: %d / %d MB (%.1f%%)\n", $3, $2, ($2>0? $3*100/$2 : 0)}'
echo "swappiness: $(cat /proc/sys/vm/swappiness)"
echo "--- ap luc bo nho ---"; cat /proc/pressure/memory 2>/dev/null || echo "nhan khong ho tro PSI"
echo "--- 10 tien trinh swap nhieu nhat ---"
for p in $(ls /proc | grep -E '^[0-9]+$'); do
s=$(awk '/^VmSwap:/{print $2}' /proc/$p/status 2>/dev/null)
[ "${s:-0}" -gt 0 ] && printf "%8d KB %s\n" "$s" "$(tr -d '\0' < /proc/$p/cmdline | cut -c1-50)"
done 2>/dev/null | sort -rn | head -10
Cột VmSwap khác 0 với một tiến trình đang phục vụ yêu cầu là dấu hiệu cần xử lý — không phải vì con số lớn, mà vì mỗi lần chạm vào những trang đó sẽ trả cái giá 500 lần ở bảng trên.
Phần sau: OOM killer — đo cách nhân chọn nạn nhân, và vì sao nó thường chọn sai.