# free -m
total used free shared buff/cache available
Mem: 7941 5626 361 166 2403 2314
Swap: 0 0 0
- Mem
- total: 시스템에 설치되어 있는 전체 메모리양
- used: 사용하고 있는 메모리 양
- free: 아직 사용하고 있지 않은 메모리양
- shared: 프로세스 사이에 공유하고 있는 메모리 양
- buff/cache: 버퍼,캐시 용도로 사용하고 있는 메모리 양 (프로세스가 사용하는게 아니라 커널에서 사용)
- swap
4.2 buffered, cached 영역
커널은 디스크에서 데이터를 읽거나 사용자의 데이터를 디스크에 저장하는데 디스크에 읽고 쓰면 느리기 때문에 메모리에 읽어둔 내용을 캐싱 영역으로 사용하여 저장해둔다. 디스크까지 안가고 메모리에서 처리하기 때문에 좀 더 빠르게 처리가 가능하다.
캐싱 영역을 buffers, cached라고 부른다.
buffers는 buffer cache를 의미하는데 super block, inode block처럼 파일의 내용이 아닌 파일 시스템을 관리하기 위한 메타 데이터를 읽어올 때는 _get_blk()와 같은 내부 함수를 통해 블록 디바이스와 직접 통신한다. 이 때 가져온 특정 블록의 내용을 buffer cache 영역에 저장해둔다.
cached는 page cache를 의미하는데 커널이 블록 디바이스에서 데이터를 읽을 때 데이터가 위치한 특정 블록의 주소를 넘겨주고, 블록 디바이스는 해당 블록 주소의 데이터를 커널에 전달한다. 이 때 커널이 bio 구조체를 만들고 page cache 용도로 할당한 메모리 영역을 연결해준다.
가용영역은 cache된 영역을 제외하고 보여준다. 그 이유는 메모리가 여유가 있을 때는 커널의 I/O 속도 향상을 위해 메모리를 cache 영역으로 많이 사용하지만 메모리가 부족한 경우에는 프로세스가 사용할 메모리를 위해 커널에서 메모리를 자동으로 반환하게 된다. 그러므로 free 명령에서도 해당 영역을 제외한 영역을 실제 사용 가능한 영역으로 계산하게 된다.
4.3 /proc/meminfo 읽기
# cat /proc/meminfo
MemTotal: 8131712 kB
MemFree: 357264 kB
MemAvailable: 2358020 kB
Buffers: 4696 kB
Cached: 2326208 kB
SwapCached: 0 kB
Active: 6373232 kB
Inactive: 1029032 kB
Active(anon): 5225160 kB
Inactive(anon): 16892 kB
Active(file): 1148072 kB
Inactive(file): 1012140 kB
Unevictable: 4000 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Zswap: 0 kB
Zswapped: 0 kB
Dirty: 4 kB
Writeback: 0 kB
AnonPages: 5063016 kB
Mapped: 161232 kB
Shmem: 170692 kB
KReclaimable: 130700 kB
Slab: 213272 kB
SReclaimable: 130700 kB
SUnreclaim: 82572 kB
KernelStack: 4332 kB
PageTables: 13084 kB
SecPageTables: 0 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 4065856 kB
Committed_AS: 5807104 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 22592 kB
VmallocChunk: 0 kB
Percpu: 2224 kB
HardwareCorrupted: 0 kB
AnonHugePages: 4800512 kB
ShmemHugePages: 0 kB
ShmemPmdMapped: 0 kB
FileHugePages: 0 kB
FilePmdMapped: 0 kB
CmaTotal: 0 kB
CmaFree: 0 kB
Unaccepted: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 0 kB
DirectMap4k: 151404 kB
DirectMap2M: 5091328 kB
DirectMap1G: 5242880 kB
free 명령으로 볼 수 있는것보다 많은 정보를 볼 수 있다.
- SwapCached: swap으로 빠진 메모리 영역 중 다시 메모리로 돌아온 영역을 의미한다. 메모리가 부족하면 커널은 프로세스 주소 공간 중 swap 영역으로 이동시킬 수 있는 메모리를 선택해서 swap 영역으로 이동시킨다. 이 과정에서 I/O가 일어나기 때문에 성능 저하가 발생한다. 이후에 메모리가 확보되어서 swap 영역으로 빠졌던 영역이 메모리로 다시 돌아가게 되어도 커널은 swap 영역에서 메모리 내용을 삭제하지 않는다. 추후에 재발할 가능성을 두고 나중에 재발시 swap 영역에 올라와있는 메모리 내용 그대로 사용하게 된다.
- Active(anon): page cache 영역을 제외한 메모리 영역을 의미. 주로 프로세스들이 사용하는 메모리 영역을 지칭할 때 많이 사용된다. 비교적 최근에 참조되어 swap 영역으로 이동하지 않을 메모리를 의미.
- Inactive(anon): 2번과 같은 영역을 의미하지만, 비교적 참조된지 오래되어 swap 영역으로 이동될 수 있는 메모리 영역을 의미한다.
- Active(file): buffers 와 cached와 같이 커널이 I/O 향상 목적으로 사용하던 영역이 여기 속한다. Active이기 때문에 swap 영역으로 이동되지 않을 메모리를 의미
- Inactive(file): 위와 동일하지만 Inactive이기 때문에 swap 영역으로 이동되지 않을 메모리를 의미
- Dirty: 커널이 I/O 향상을 목적으로 사용하는 캐시, 쓰기 작업이 이루어져서 실제 블록 디바이스의 블록에 씌어져야 할 영역을 의미한다. 커널은 I/O 쓰기 요청이 발생했을 대 바로 블록 디바이스에 쓰지 않고 모아두었다가 한번에 쓰는데 dirty 메모리는 이 때 사용된다.
Active랑 Inactive를 나누는 기준을 뭘까? 메모리 영역마다 LRU list로 구성이 되어 있다. 기본적으로 프로세스가 메모리 할당을 요청하면 active 리스트에 연결이 되는데 그 후 메모리 할당이 실패하거나 메모리가 부족하면 kswapd 혹은 커널 내부에서 try_to_free_pages()함수를 통해서 LRU 리스트를 확인한다. Active에 있는 페이지가 Inactive로 이동은 메모리가 부족하고 해제할 메모리를 찾아야 하는 순간이
와야 한다. vm.min_free_kbytes는 시스템에서 유지해야하는 최소한의 free 메모리의 양인데 이걸 만족시키기 위해 kswapd 데몬이 열심히 일하면서 active 영역에 있는 페이지 중 오래된 페이지를 우선적으로 Inactive로 옮긴 후 메모리를 해제한다.
4.4 slab 메모리 영역
slab 메모리는 커널에 내부적으로 사용하는 영역이다. 커널도 프로세스기 때문에 메모리가 필요하다.
Slab: 213272 kB
SReclaimable: 130700 kB
SUnreclaim: 82572 kB
- slab: 메모리 영역 중 커널이 직접 사용하는 영역을 slab 영역이라고 한다. 이 영역에는 dentry cache, inode cache 등 커널이 사용하는 메모리가 포함된다.
- SReclaimable: Slab 영역 중 재사용될 수 있는 영역이다. 캐시 용도로 사용하는 메모리들이 주로 여기 포함된다. 메모리 부족이 발생하면 해제해서 프로세스에도 사용할 수 있는 영역.
- SUnreclaim: slab 영역 중 재사용될 수 없는 영역이다. 커널이 현재 사용중인 영역이며, 해제해서 다른 용도로 사용할 수 없다.
slaptop을 사용하면 현재 시스템에서 사용중인 slab의 정보를 확인할 수 있다.
# slabtop -o
Active / Total Objects (% used) : 692503 / 712135 (97.2%)
Active / Total Slabs (% used) : 25525 / 25525 (100.0%)
Active / Total Caches (% used) : 170 / 264 (64.4%)
Active / Total Size (% used) : 196538.50K / 206315.98K (95.3%)
Minimum / Average / Maximum Object : 0.01K / 0.29K / 8.00K
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
251559 251559 100% 0.19K 11979 21 47916K dentry
64832 64788 99% 0.06K 1013 64 4052K lsm_inode_cache
35760 34875 97% 1.06K 1192 30 38144K xfs_inode
33280 32294 97% 0.02K 130 256 520K kmalloc-16
29792 29479 98% 0.12K 931 32 3724K kernfs_node_cache
29632 29632 100% 0.06K 463 64 1852K kmalloc-rcl-64
29504 27914 94% 0.06K 461 64 1844K kmalloc-64
27048 26792 99% 0.57K 966 28 15456K radix_tree_node
커널에서 I/O 작업을 조금이라도 빨리 하기 위해 inode cache, dentry cache 등을 사용하거나, 네트워크 소켓을 위한 메모리 영역을 확보하는 작업들은 커널에서 하게 되는데 이 과정에서 메모리가 필요하다. 메모리를 할당해주는 버디 시스템은 4KB의 페이지 단위로 메모리를 할당하는데 커널의 입장에서는 이렇게 큰 메모리를 할당받을 필요가 없고, 할당받은 메모리보다 사용하는 메모리가 적어 메모리 단편화 현상이 발생할 수 있다. 그렇기 때문에 커널이 사용하려는 메모리 영역은 좀 더 작고 효율적으로 사용할 수 있어야 하고, 이를 충족시키기 위해 slab 할당자를 통해서 원하는 메모리 영역을 확보한다. 페이지 크기의 기본인 4KB의 영역을 할당 받은 후에 각각의 캐시 크기(inode cache, dentry cache 등)에 맞게 영역을 나눠서 사용한다.
- dentry cache: 디렉터리의 계층 관계를 저장해 두는 캐시
- inode cache: 파일의 inode에 대한 정보를 저장해두는 캐시
slab 영역은 free 명령어에서 used로 계산이 되는데 프로세스들이 사용하는 메모리 영역을 모두 더하고도 used와 맞지 않을 경우에는 slab 메모리에서 누수가 발생하는 경우가 있다.
4.5 Case Study - slab 메모리 누수
메모리 점유율이 높아지는 원인이 프로세스가 아닌 slab 메모리에 있다면 drop cache를 이용하여 캐시 영역 강제 플러싱하는 방법이 있다.
echo 2 > /proc/sys/vm/drop_cached
echo 로 3을 넣어주면 page cache까지 날릴 수 있음. 2는 dentry cache만 날리는 방법이다.
slab cache를 플러싱했는데도 동일하게 slab 메모리가 증가하면 일정 시간 간격으로 slabtop을 기록하는 방법으로 어디에서 slab 메모리를 사용하는지 확인할 수 있다.
회사 내에서 발생했던 메모리 증가 문제
특정 k8s 클러스터에서 메모리 사용량이 높아 모니터링 알람이 지속적으로 발생하였다. top으로 프로세스의 메모리 사용량을 확인하였을 때 많지 않은 수준이었고 slaptop으로 메모리 확인시에 kmalloc-4096 이쪽에서 메모리 사용량이 높은 것으로 확인을 하였다.
kmalloc-4096은 4096 바이트의 메모리 블록을 할당 및 관리하기 위한 slab cache이다. 아래의 경우에 사용하게 된다.
- 네트워크 패킷 버퍼 (SKB, Socket Buffer)
- 파일 시스템의 I/O 버퍼
- 기타 드라이버 및 서브시스템에서 4KB 크기 메모리가 필요한 경우
가장 처음 의심했던 거는 fluentd pod에서 Elasticsearch쪽과 통신하지 못하면서 여러번 재시도를 하고 있었는데 (5번 시도 후에는 pod 재시작을 하게 되는데 pod가 재시작된 횟수가 1035번이었나.. 그랬다.) 이 때 socket을 여러개 생성하면서 발생한게 아닌가라는 생각이 들었다. 근데 lsof 명령어 이용해서 fluentd 관련해서 열려있는 것들이 있나 확인해봤을 때는 그렇게 많은 수는 아니었다.
내가 관리하던 서버는 아니라 더 자세히는 파악하지 못하였지만.. 팀원분께서 cache 클리어 한 후에도 메모리 사용량 동일하다고 하셨고 재기동한 이후에는 같은 문제 재발하고 있지는 않은듯 하다.
이후에 이런 문제가 발생하면 어디에서 slab 캐시를 사용하고 있는지 자세히 확인해보고 싶다.