brunch

You can make anything
by writing

C.S.Lewis

by doz Nov 16. 2020

3. swap 메모리 충격 완충제

swap은 이전 'free' 명령어에 대하여 작성한 글에서도 잠깐 언급했듯이, 메모리가 부족할 경우를 대비하여 만들어놓은 영역이다. 메모리가 부족할 경우 발생할 수 있는 전체 시스템 응답 불가 상태를 방지할 수 있도록 하는 일종의 '비상용' 메모리 공간으로 볼 수 있겠다. 또, 다른 용도로는 메모리의 내용을 디스크에 저장하기 위해서도 사용된다.

좋은 점만 있을까?


swap은 비상용으로 디스크의 일부분을 메모리처럼 사용하기 위하여 만들어 놓은 공간이므로 물리 메모리가 아니다. 그러므로 매우 느린 접근과 처리 속도를 보여주며, swap 사용은 자연스레 성능저하로 이어지게 된다.


root@desktop:~# free -h

                     total        used        free      shared  buff/cache   available

Mem:           3.7G        171M        3.0G         59M        502M        3.2G

Swap:          2.0G          68M        1.9G


전체 2.0G의 스왑 공간 중 현재 68M의 스왑 공간을 사용하고 있다. 매우 적은 수치이므로 무시할 수도 있으나, swap 공간을 사용한다는 자체가 메모리 관련된 문제가 있을 수도 있다는 의미로 받아드려야 한다.


그렇다면 swap이 발생하는 프로세스는 어떻게 확인할 수 있을까?

swap이 발생하는 프로세스를 확인하는 방법으로는 대표적으로 3가지가 있다.


1. /proc/<PID>/smaps 를 확인한다.

    smaps 파일은 현재 PID의 프로세스가 사용중인 메모리 영역 주소, swap 크기 등을 확인할 수 있다. 특히 프로세스의 메모리 영역별로 출력되므로 자세한 내용을 확인 가능하다.

root@localhost:~$ cat /proc/19286/smaps

561d89213000-561d89239000 r-xp 00000000 08:01 1310791                    /bin/less

Size:                152 kB

Rss:                 152 kB

Pss:                 152 kB

Shared_Clean:          0 kB

Shared_Dirty:          0 kB

Private_Clean:       152 kB

Private_Dirty:         0 kB

Referenced:          152 kB

Anonymous:             0 kB

Swap:                  0 kB

SwapPss:               0 kB

   위 내용은 일부분이며, 보는 것과 같이 152kb의 메모리 크기를 사용하고 있으며, swap은 없다.


2. /proc/<PID>/status 를 확인한다.

root@localhost:~$ cat /proc/19286/status

Name:pager

Umask:0022

State:T (stopped)

Tgid:19286

Ngid:0

Pid:19286

PPid:19273

VmPeak:   10936 kB

VmSize:   10936 kB

VmLck:       0 kB

VmPin:       0 kB

VmHWM:     844 kB

VmRSS:     844 kB

RssAnon:     104 kB

RssFile:     740 kB

RssShmem:       0 kB

VmData:     216 kB

VmSwap:       0 kB

    smaps 은 너무 많은 내용이 출력되므로, 간편하게 보자고 한다면 status를 사용하면 된다. VmSwap 영역을 확인하면 된다.


3. smem 툴을 다운로드 받아 실행한다.

    /proc/<PID>/smaps | status 를 통해 swap 프로세스를 추적하는 것은 쉬운 일이 아니다. 모든 PID를 순회하면서 확인하는 작업이 필요하기 때문이다. 이런 번거로운 작업을 없앨 수 있는 툴이 smem 이라는 툴이다. smem 툴은 /proc/<PID> 내용을 바탕으로 프로세스 별 Swap 상황을 알 수 있다.

[centos@ip-172-31-27-12 ~]$ smem -t

  PID User     Command                         Swap      USS      PSS      RSS 

 6855 centos   tail -f main.log                   0      108      137      668 

24300 centos   tail -f main.log                   0      108      137      668 

13981 centos   tail -f main.log                   0      116      145      672 

30311 centos   vmstat 1                           0      416      458     1520 

 8152 centos   -bash                              0      476      777     2136 

28130 centos   -bash                              0      484      786     2148 

24274 centos   -bash                              0      552      855     2220 

 8220 centos   python /usr/bin/smem -t            0     5492     5992     7320 

-------------------------------------------------------------------------------

   12 1                                           0  2488716  2522189  2562980 


이처럼 3가지 방법을 통해 swap 영역을 사용하고 있는 프로세스를 확인할 수 있다. 만약 swap을 사용하고 있으며, 가용 영역이 부족하다면 메모리 부족으로 인한 swap으로 확인할 수 있다. 하지만 swap을 사용하고 있으나, 가용 영역이 충분한 상황이라면 어떨까? 이는 갑작스럽게 메모리 사용이 증가했던 순간이 있었으며, 그때 사용된 swap 영역이 남아있는 것으로 보인다.


메모리 할당은 어떻게 이루어지는 걸까?


커널이 메모리를 할당하는 과정은 buddy system을 이해하면 된다. buddy system은 추후 다룰 NUMA 에서도 나오게 되는데 다음과 같은 /proc/buddyinfo 를 통해 확인할 수 있다.

[centos@ip-172-31-27-12 ~]$ cat /proc/buddyinfo

Node 0, zone      DMA          1          0      0          1          2         1          1      0      1      1      3 

Node 0, zone    DMA32  10256   5350    752    342    207        5          2      0      0      0      0 

Node 0, zone   Normal  20375  16755   5068   2235    268     84     28      1      0      0      0


우선 상단 DMA, DMA32, Normal 등은 추후에 다루도록 한다. 위의 내용을 알기 위해 우선 buddy system에 대해서 설명을 해보자.


buddy system은 뭘까?

커널이 메모리를 할당하는 과정이 buddy system에 의해 이루어진다고 한다. 그렇다면 buddy system은 무엇일까? 좀더 자세한 설명은 아래 그림을 참고해라. 

출처 : https://woodz.tistory.com/57

리눅스 커널은 물리 메모리를 page단위로 관리한다. page는 4KB가 최소 할당단위가 된다(8KB, 2MB 등 크기는 설정 가능하다). 


자, 이제 그림을 기준으로 설명을 해보자. order 는 연속적인 page의 묶음을 나타낸다고 볼 수 있다. 수학적으로는 2의 지수승(0~10)를 오더라고 말할 수 있다.

order가 0이라면 2^0개의 4kb 페이지가 하나의 블록이 된다. 만일 order가 4라면, 2^4개의 page 들(64kb)이 하나의 블록이 되는 것이다. 만약 프로세스가 8kb 요청을 하면 order 1 을 통해 연속된 4kb 페이지 두개를 제공하게 된다. 이런 방식으로 메모리의 외부 단편화를 막을 수 있으며(내부 단편화는 발생한다) 프로세스 요청에 더 빠른 응답이 가능하도록 한다.


간단하게 생각해보자. 만일 연속된 공간을 제공하는 것이 아닌, 요청 할 때마다 4kb를 이곳 저곳에서 주게 된다면 페이지 테이블을 자주 수정하게 된다. 자주 수정하면 메모리 엑세스 타임이 늘어나고 느려지게 된다.


즉, 버디 시스템은 연속된 공간을 사용하여 연속된 메모리를 제공할 수 있도록 하는데 목적이 있다. 



다시 돌아와서 그렇다면 buddyinfo의 결과는 어떻게 해석해야 하는걸까?

                              order      0        1        2        3            4        5            6    7        8       9    10

Node 0, zone      DMA          1          0      0          1          2         1          1      0      1      1      3 

Node 0, zone    DMA32  10256   5350    752    342    207        5          2      0      0      0      0 

Node 0, zone   Normal  20375  16755   5068   2235    268     84     28      1      0      0      0


상단에 빨간색으로 한 줄을 추가해 보았다. 이제 좀 감이 올 수도 있다. 각각의 숫자들은 좌측부터 order가 1, 2, 3, 4, ... 10 을 나타낸다고 보면 된다. order 1이 4kb이므로 좌측부터 4kb, 8kb, 16kb , ... 2048kb, 4096kb 를 나타낸다. 해당 숫자 만큼의 여유 블록이 미리 할당 되어 있다. 이후 메모리 요청이 오면 적절하게 연속된 페이지를 제공하게 되며 숫자는 -1이 되게 된다.


이런식으로 커널은 메모리를 효율적으로 페이지 단위로 분할 한 다음, buddy 알고리즘을 통해 단편화를 최소화 시키며 빠른 응답을 가능하도록 메모리를 제공한다. 



메모리 재할당은 언제 일어날까?


특정 프로세스로 부터 메모리 할당 요청이 들어왔을 때, 메모리가 부족하다면 공간을 할당하기 위하여 어떻게 할지를 결정하기 위한 vm.swappiness 커널 파라미터 값을 확인하게 된다. 

vm.swappiness는 어떤 수치를 나타낼까?

vm.swapiness

0~100의 범위로 낮을 수록 cache 상대적으로 더 많이 비우게 되며, 100에 가까울 수록 swap을 상대적으로 더 많이 하게 된다. 즉, swappiness 의 값을 통해서 page cache를 free 할 것인지, 다른 프로세스가 사용 중인 inactive(file):page cache 메모리 영역을 swap 공간으로 옮김으로써 swapping 할 것인지 결정 한다. swapping을 하게 될 경우 커널 데몬인 kswapd를 통해 메모리 반환이 일어나게 된다. kswapd는 프로세스가 사용하는 메모리 영역 중에 Inactive LRU list에 있는 tail 부분의 영역을 선택하여 swap 영역으로 이동시킨다. 


그러면 0이면 swap이 비활성화 되나요 ? 


아니다. 0 일 경우에도, cache를 싹싹 비우더라도 더이상 비울 cache가 없다면 swap을 하게 된다. 다만 1로 설정했을 때 보다는 좀더 많은 cache를 해지하게 된다. 하지만 이렇게 될 경우 당연히 커널이 I/O 성능 향상을 위해 저장한 캐시가 날라가게 되므로 많은 부하가 있을 수 있다. 그러므로 0은 되도록 안하는것이 좋다.


cache에는 종류가 있는데 어떤 cache를 사용하는건가요?


아마 앞서 swapiness를 본 사람은 이런 생각을 해봤을 수도 있다. "cache는 종류가 많은데 어떤 cache를 말하는거야?" 커널은 바로 이러한 고민을 해결해 줄 또 하나의 커널 파라미터를 갖고 있다. 바로 vfs_cache_pressure 이라는 파라미터 이다. 앞서 'free..알고 쓰시나요?' 글에서도 작성했듯이 cache는 종류가 여러가지다. 

커널이 사용하는 cache 종류

이러한 종류 중에서도 크게는 Page cache+Buffer cache / Dentry cache+Inode cache(slabs) 로 구분할 수 있다. 이 중 Buffer cache는 매우 작으므로 무시하며 Page cache / slabs로 구분할 수 있겠다(vfs_cache_pressure는 page cache <> dentry+inode 를 다룬다). 이러한 두가지 바운더리에서 어느것을 선택할 지는 vfs_cache_pressure를 통해서 확인 가능하다.

주로 vfs_cache_pressure는 dentry cache, inode cache로 사용된 메모리를 반환(reclaim)하는 수준을 지정한다. 근데 왜 Page cache가 언급되는걸까? slabs가 반환되지 않으면 Page cache가 그 만큼 더 반환되기 때문이다.


vfs_cache_pressure를 좀 더 파보자


vfs_cache_pressure는 기본값이 100이며 0 부터 10000이상도 가능하다. 


0 으로 설정 할 경우 dentry, inode cache를 반환하지 않는다.

100 이하로 설정 할 경우 dentry, inode cache의 사용하지 않은 캐시들 중 적은 양을 반환한다.

100 으로 설정 할 경우 dentry, inode cache에서 사용하지 않은 캐시들을 그대로 반환 한다.

100 초과할 경우 dentry, inode cache에서 사용하지 않은 캐시는 물론, 사용하는 객체들 마저 반환할 수 있다.


커널 파라미터를 정리해보면..


swappiness 를 통해 메모리 부족이 발생 되면 이를 cache를 비움으로써 해결할 것인지, 혹은 swapping을 통해서 해결할 지 결정하게 된다. cache를 비우는데 있어서는 vfs_cache_pressure 의 파라미터가 dentry cache + inode cache 의 캐시 영역을 얼마큼 반환할 지 결정하게 된다. 만일 100 이하로써 적은 수치라면 그만큼의 page cache가 더 반환되게 된다.



메모리 누수가 발생한다면


자, 이제 어쩌면 이 글의 핵심이 될 수도 있을 것 같다. swap 영역이 사용된다는 것은 메모리의 문제가 있을 수 있다는 것으로 표현 했었다. 만일 현재 swap 영역이 사용 중이며, 현재 메모리 가용량(free)이 충분하다면 이는 가끔 메모리가 집중적으로 많이 쓰인다는 것으로 판단할 수 있다. 이럴 때는 간단히 물리 메모리의 양을 높임으로써 해결 할 수 있다. 그런데 만약 swap 사용 영역이 계속해서 늘어나고 있으며, 현재 가용량이 없거나 현저히 낮다면 어떻게 해야할까? 그건 메모리 누수를 의심해 볼 수 있다. 이때 가장 좋은 방법은 gdb를 통한 메모리 덤프를 하는 것이다. 


절차는 다음과 같다. 우선 메모리 릭이 의심되는 프로세스의 PID를 확인한다. 간단하게 top 명령어를 통해 확인하면 된다. 이후 다음과 같이 명령어를 수행한다.

cat /proc/`pidof 이름`/smaps



여러 메모리가 있겠지만 많은 점유율을 나타내는 메모리 번지수를 확인한다. 상단 참조. 지금 현재 해당 프로세스는 계속해서 Size가 늘어나고 있는 상황이다. 번지수는 08692000-09066000로써 pid와 번지수를 확인했으니 gdb를 동작시켜보자.


gdb -p `pidfo 이름`

dump memory ./mem_dump 0x08692000 0x09066000


 ctrl+z 눌러서 나온다.

이후 파일을 보면 mem_dump 라는 파일이 생겨있음을 확인할 수 있다. 

도대체 어떤 내용이 담겨있을까?


각종 프로세스명과 라이브러리 내용이 보인다. 해당 프로그램은 프로세스와 라이브러리를 수집하는 프로세스로써, 위와 같이 메모리에 계속적으로 누적시킴으로써 메모리 점유율이 높아지는 것임을 알 수 있다. (사실 테스트용이다)


마치며


swap은 elastic search를 사용하면서 비활성화를 해야하는 등으로 몇번 접한 적이 있다. 사실 그때까지 대충 넘기기 마련이였는데 이번 기회를 통해 제대로 공부한 것 같아서 간지러운 곳을 긁은 느낌이 든다. 역시 메모리 부분은 정말 끝이 없는 것 같다.

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari