시스템의 문제점을 분석하고 확인하기 위해서는 먼저 현재 시스템 구성정보를 확인할 수 있어야 한다.
여기서 말하는 정보는 현재 사용중인 커널 버전과 부팅 시 사용한 커널, 그리고 하드웨어인 CPU 와 메모리는 어떤 것들을 사용하는지에 대한 정보들이다.
이 정보들을 바탕으로 특정 커널 버그와 특정 하드웨어 문제를 해결할 수 있다.
uname -a
옵션을 통해서 커널 정보를 확인할 수 있다.
ubuntu@ip-172-31-12-59:~$ uname -a
Linux ip-172-31-12-59 5.11.0-1022-aws #23~20.04.1-Ubuntu SMP Mon Nov 15 14:03:19 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
dmesg | grep -i kernel | more
를 통해 커널이 부팅할 때 나오는 디버그 메시지로 운영 중에 발생하는 메시지를 볼 수 있다.****
그리고 커널이 하드웨어를 인식하고 드라이버를 올리는 과정과 부팅시 적용된 커널 파라미터를 확인하는게 가능하다.
ubuntu@ip-172-31-12-59:~$ sudo dmesg | grep -i kernel | more
[ 0.000000] KERNEL supported cpus:
[ 0.000000] Netfront and the Xen platform PCI driver have been compiled for this kernel: unplug emulated NICs.
[ 0.000000] Blkfront and the Xen platform PCI driver have been compiled for this kernel: unplug emulated disks.
in your root= kernel command line option
[ 0.038067] Booting paravirtualized kernel on Xen HVM
[ 0.038915] Kernel command line: BOOT_IMAGE=/boot/vmlinuz-5.11.0-1022-aws root=PARTUUID=24ca9e81-01 ro console=tty1 console=ttyS0 nvme_core.io_timeout=4294967295 p
anic=-1 // (1)
[ 0.041653] Memory: 978736K/1048180K available (16393K kernel code, 3488K rwdata, 10372K rodata, 2688K init, 5968K bss, 69184K reserved, 0K cma-reserved) // (2)
[ 0.041824] Kernel/User page tables isolation: enabled
[ 0.882078] DMA: preallocated 128 KiB GFP_KERNEL pool for atomic allocations
[ 0.885991] DMA: preallocated 128 KiB GFP_KERNEL|GFP_DMA pool for atomic allocations
[ 0.889978] DMA: preallocated 128 KiB GFP_KERNEL|GFP_DMA32 pool for atomic allocations
[ 2.205409] Loaded X.509 cert 'Build time autogenerated kernel key: ecb0788dc66cd3f3e8014485463fe429aa2b1eba'
[ 2.220262] Loaded X.509 cert 'Canonical Ltd. Kernel Module Signing: 88f752e560a1e0737e31163a466ad7b70a850c19'
[ 2.373456] Freeing unused kernel image (initmem) memory: 2688K
[ 2.381969] Write protecting the kernel read-only data: 30720k
[ 2.386692] Freeing unused kernel image (text/rodata gap) memory: 2036K
[ 2.391819] Freeing unused kernel image (rodata/data gap) memory: 1916K
[ 3.307520] systemd[1]: Listening on udev Kernel Socket.
[ 3.346216] systemd[1]: Mounting Kernel Debug File System...
[ 3.545140] systemd[1]: Mounting Kernel Trace File System...
[ 3.611301] systemd[1]: Starting Create list of static device nodes for the current kernel...
[ 3.701278] systemd[1]: Starting Load Kernel Module drm...
[ 3.793261] systemd[1]: Starting Load Kernel Modules...
[22531.000664] audit: type=1400 audit(1646338108.352:40): apparmor="STATUS" operation="profile_replace" info="same as current profile, skipping" profile="unconfined"
name="snap.lxd.check-kernel" pid=23760 comm="apparmor_parser"
[22554.722873] audit: type=1400 audit(1646338132.073:72): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="snap.lxd.check-kernel" pid=24330 co
mm="apparmor_parser"
리눅스에서는 dmidecode
를 통해서 하드웨어 정보를 확인할 수 있다. dmidecode
만을 이용하면 너무 많은 정보가 출력되므로 키워드와 함께 사용할 것이 권장된다.
먼저 BIOS
를 살펴보는 명령어다.
dmidecode -t bios
명령어를 통해서 bios 정보를 볼 수 있다. 주로 특정 BIOS 버전에 문제가 있다는 보고를 보고서 확인할 때 사용한다.
ubuntu@ip-172-31-12-59:~$ sudo dmidecode -t bios
# dmidecode 3.2
Getting SMBIOS data from sysfs.
SMBIOS 2.7 present.
Handle 0x0000, DMI type 0, 24 bytes
BIOS Information
Vendor: Xen
Version: 4.11.amazon
Release Date: 08/24/2006
Address: 0xE8000
Runtime Size: 96 kB
ROM Size: 64 kB
Characteristics:
PCI is supported
EDD is supported
Targeted content distribution is supported
BIOS Revision: 4.11
다음은 system
키워드다.
dmidecode -t system
명령을 통해 주로 모델명을 확인하는 용도로 사용한다. 모델명을 통해서 해당 장비가 어느 정도의 성능을 내는지 확인할 수 있다.
ubuntu@ip-172-31-12-59:~$ sudo dmidecode -t system
# dmidecode 3.2
Getting SMBIOS data from sysfs.
SMBIOS 2.7 present.
Handle 0x0100, DMI type 1, 27 bytes
System Information
Manufacturer: Xen
Product Name: HVM domU
Version: 4.11.amazon
Serial Number: ec28691a-0746-fbe7-e8c5-f8a4125a824e
UUID: ec28691a-0746-fbe7-e8c5-f8a4125a824e
Wake-up Type: Power Switch
SKU Number: Not Specified
Family: Not Specified
Handle 0x2000, DMI type 32, 11 bytes
System Boot Information
Status: No errors detected
다음은 processor
키워드다.
dmidecode -t processor
명령을 통해서 장비의 CPU 정보를 확인하는게 가능하다.
ubuntu@ip-172-31-12-59:~$ sudo dmidecode -t processor
# dmidecode 3.2
Getting SMBIOS data from sysfs.
SMBIOS 2.7 present.
Handle 0x0401, DMI type 4, 35 bytes
Processor Information
Socket Designation: CPU 1
Type: Central Processor
Family: Other
Manufacturer: Intel
ID: F2 06 03 00 FF FB 8B 17
Version: Not Specified
Voltage: Unknown
External Clock: Unknown
Max Speed: 2400 MHz
Current Speed: 2400 MHz
Status: Populated, Enabled
Upgrade: Other
L1 Cache Handle: Not Provided
L2 Cache Handle: Not Provided
L3 Cache Handle: Not Provided
Serial Number: Not Specified
Asset Tag: Not Specified
Part Number: Not Specified
다음 명령어를 통해서도 확인이 가능하다. /proc/cpuinfo
ubuntu@ip-172-31-12-59:~$ cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 63
model name : Intel(R) Xeon(R) CPU E5-2676 v3 @ 2.40GHz
stepping : 2
microcode : 0x46
cpu MHz : 2399.971
cache size : 30720 KB
physical id : 0
siblings : 1
core id : 0
cpu cores : 1
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm cpuid_fault invpcid_single pti fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt
bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit
bogomips : 4800.04
clflush size : 64
cache_alignment : 64
address sizes : 46 bits physical, 48 bits virtual
power management:
그리고 lscpu
명령을 통해서도 조회가 가능한데 이 경우에는 NUMA 아키텍처와 관련된 내용까지도 알 수 있다.
dmidecode
에서 memory
키워드로 메모리 정보를 확인할 수 있다.
ubuntu@ip-172-31-12-59:~$ sudo dmidecode -t memory
# dmidecode 3.2
Getting SMBIOS data from sysfs.
SMBIOS 2.7 present.
Handle 0x1000, DMI type 16, 19 bytes
Physical Memory Array
Location: Other
Use: System Memory
Error Correction Type: Multi-bit ECC
Maximum Capacity: 1 GB
Error Information Handle: Not Provided
Number Of Devices: 1
Handle 0x1100, DMI type 17, 34 bytes
Memory Device
Array Handle: 0x1000
Error Information Handle: 0x0000
Total Width: 64 bits
Data Width: 64 bits
Size: 1024 MB
Form Factor: DIMM
Set: None
Locator: DIMM 0
Bank Locator: Not Specified
Type: RAM
Type Detail: None
Speed: Unknown
Manufacturer: Not Specified
Serial Number: Not Specified
Asset Tag: Not Specified
Part Number: Not Specified
Rank: Unknown
Configured Memory Speed: Unknown
df -h
명령을 통해서 디스크 정보를 확인하는게 가능하다.
ubuntu@ip-172-31-12-59:~$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/root 30G 3.7G 26G 13% /
devtmpfs 479M 0 479M 0% /dev
tmpfs 485M 0 485M 0% /dev/shm
tmpfs 97M 932K 96M 1% /run
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 485M 0 485M 0% /sys/fs/cgroup
/dev/loop0 25M 25M 0 100% /snap/amazon-ssm-agent/4046
/dev/loop1 56M 56M 0 100% /snap/core18/2253
/dev/loop3 68M 68M 0 100% /snap/lxd/21835
/dev/loop2 62M 62M 0 100% /snap/core20/1242
tmpfs 97M 0 97M 0% /run/user/1000
/dev/loop5 56M 56M 0 100% /snap/core18/2284
/dev/loop6 62M 62M 0 100% /snap/core20/1361
/dev/loop7 44M 44M 0 100% /snap/snapd/14978
/dev/loop8 27M 27M 0 100% /snap/amazon-ssm-agent/5163
/dev/loop9 68M 68M 0 100% /snap/lxd/22526
/dev/sda
,/dev/hda
, /dev/vda
등 다양할 수 있다.ethtool eth0
명령을 통해서 연결 상태를 확인하는게 가능하다.
ubuntu@ip-172-31-12-59:~$ ethtool eth0
Settings for eth0:
Cannot get wake-on-lan settings: Operation not permitted
Link detected: yes
Supoorted link modes
Advertised link modes
그리고 ethtool -g eth0
명령을 통해 Ring Buffer 의 크기를 확인할 수 있고 최적화를 할 수 있다.
Ring Buffer 는 케이블을 통해서 들어온 패킷이 가장 먼저 복사되는 곳으로 네트워크 카드에 있는 버퍼 공간이다.
이 영역이 작다면 네트워크 성능 저하를 일으킬 수 있다.
ethtool -G eth0 {영역} {값}
(ethtool -G eth0 rx 255
) 이런 식으로 G 옵션을 통해서 값을 설정할 수 있다.
ethtool -i eth0
옵션을 통해 네트워크 카드가 어떤 커널 드라이버를 사용하는지, 버전이 몇인지를 알 수 있다.
리눅스에서는 시스템의 상태를 살펴볼 수 있는 다양하 명령들이 있는데 그 중에선 top
이 전반적으로 가장 빠르게 확인할 수 있다.
여기서는 top
으로 시스템의 상태를 전반적으로 파악하는 방법과 특히 프로세스와 관련된 값들의 의미가 어떤게 있는지 알아보겠다.
top
명령을 입력하면 다음과 같은 정보를 볼 수 있다.
옵션 없이 입력하면 주어진 Interval (기본 3초) 간격으로 화면을 갱신하면서 보여준다.
-b
옵션을 사용하면 된다.나오는 정보는 다음과 같다.
top - 15:54:16 up 7 days, 2:01, 1 user, load average: 0.00, 0.00, 0.00
Tasks: 115 total, 1 running, 114 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.0 us, 0.0 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.3 st
MiB Mem : 968.9 total, 135.8 free, 371.4 used, 461.8 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 444.3 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 168808 11184 6776 S 0.0 1.1 0:18.18 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
PR
: 프로세스의 실행 우선순위.NI
: PR 에 부여할 가산점. NI 와 PR 을 더해서 실제 프로세스의 우선순위가 결정된다.VIRT
: 프로세스가 사용하는 Virtual Memory 의 전체 용량을 가리킨다.RES
: 현재 프로세스가 사용하고 있는 실제 메모리의 양을 가리킨다.SHR
: 다른 프로세스와 공유하고 있는 메모리를 나타낸다. 리눅스의 경우에 공통으로 사용하는 라이브러리 같은게 있는데 이게 이 메모리의 영역이 될 수 있다.S
: 프로세스의 상태를 나타낸다. CPU 를 사용하고 있는 중인지, I/O 를 기다리고 있는지, 아니면 아무 작업도 하지 않는 유휴 상태인지 나타낸다.S
: sleeping. S 는 D 와 유사하지만 시그널을 주면 언제든지 실행할 수 있다.D
: Uninterruptible sleep. 디스크 혹은 네트워크 I/O 가 걸려있는 상태를 말한다.R
: running. 실행 중인 프로세스로 실제로 CPU 를 먹고 있는 상태다.T
: traced or stopped. trace 등으로 프로세스의 시스템 콜을 추적하고 있는 상태다.Z
: zombie. 좀비 프로세스로 부모 프로세스에 의해서 회수되지 못한 자식 프로세스가 남겨져 있는 상태다. 좀비 프로세스의 문제는 적은 리소스를 메모리에서 계속 가지고 있는 문제와 PID 를 점유하고 있는 문제가 있다.****malloc() 과 같은 메모리 할당을 호출하는 시스템 콜을 필요로 하는 함수를 호출한다면 RES 가 높아질 것이라고 생각하지만 그렇지 않다. VIRT 만 올라간다.
실제로 메모리에 쓰기 작업이 일어나는 시점에 메모리가 할당되고 RES 가 올라갈 것이다.
가상의 메모리만 처음에 할당 받고 실제로 쓰기 작업을 했을 때 Page Fault 가 일어난다. 그리고 그제서야 메모리를 할당한다. 이 일련의 과정을 Memory Commit 이라고 한다.
Memory Commit 이 필요한 이유는 COW (Copy-On-Write) 라는 기법을 위해서 필요하다.
그리고 VIRT 도 한도 끝도 없이 올라가는게 아니라 실제 물리 메모리와 관련이 있다. 실제로 사용하고 있는 물리 메모리가 없다면 Swap 을 사용하게 되거나 OOM 으로 프로세스를 죽이는 방법으로 메모리를 확보하게 된다.
Load Average
의 정의가 뭘까?
이는 man proc 을 통해서 확인할 수 있다.
The first three fields in this file are load average figures giving the number of jobs in the run queue (state R) or waiting for disk I/O (state D) average over 1, 5, and 15 minutes
즉 run queue 에 있는 CPU 를 받기 위해 대기중인 프로세스의 수와 I/O 작업이 끝나기를 기다리고 있는 프로세스의 수를 합한 값을 1분 5분 15분 단위로 나타낸 것을 말한다.
이 값은 프로세스의 수를 세는 것이기 때문에 CPU Core 의 수가 몇 개냐에 따라 각각 의미하는 바가 다르다. 상대적이다.
예로 Load Average 가 2 이고 run queue 에 두 개의 프로세스가 대기중이며 CPU 코어가 하나다. 이 경우에는 한 개의 프로세스는 작업을 처리할 수 없다.
하지만 이 경우에 CPU 코어의 수를 늘린다면 같은 Load Average 값이더라도 모든 프로세스를 다 실행할 수 있다. 이 경우에는 문제가 되지 않는다.
그리고 같은 Load Average 값이더라도 CPU 를 원하는 프로세스인지 I/O 를 원하는 프로세스인지는 알 지 못한다. 두 값을 합한 것이니까.
그렇다면 I/O 를 원하는지 CPU 를 원하는지 알려면 어떻게 해야할까? 이는 vmstat
으로 알 수 있다.
vmstat
을 사용할 때 출력되는 r
값과 b
값에 주목해서 구별하면 된다.
r
은 실행되기를 기다리고 있거나 현재 실행하고 있는 프로세스의 개수륾 말하는 거며 b
는 I/O 를 위해 대기열에 있는 프로세스의 개수를 말한다.
이렇게 CPU 가 병목인지 I/O 가 병목인지 알았다면 이제 각 문제를 해결하면 된다.
먼저 사용자 프로그램이 병목인지, 시스템 프로그램이 병목인지 확인하자. 이는 top
이나 sar
명령으로 확인이 가능하다.
그리고 ps
명령으로 어떤 프로세스가 CPU 를 얼마나 차지하고 있는지 확인하는게 가능하다.
이렇게 프로세스를 찾은 후에는 좁혀서 분석해야한다. 주로 로그를 심어놨다면 로그를 통해서 확인하는게 가능하다.
또 다른 방법이라면 strace
나 oprofile
로 병목지점을 좁혀볼 수 있다.
strace
는 프로세스가 호출한 시스템 콜을 추적하는게 가능하다.oprofile
는 어떤 함수가 cpu 를 많이 사용하는지 추적하는게 가능하다.I/O 가 병목인 경우에는 입출력이 많아서 병목인지, 스왑영역을 사용해서 병목인지 알아야 한다.
sar
이나 vmstat
으로 스왑을 사용하는지 살펴보자.
스왑영역을 쓰고 있다면 특정 프로세스가 메모리를 독차지 하고 있을 수 있으므로 ps
명령어로 찾아보자.
메모리를 많이 쓰고 있는 사용자 프로세스가 있다면 메모리 누수가 있는지 확인해봐야 한다. 그렇지 않다면 메모리를 증설해야 할 수도 있다.
입출력이 빈번하게 발생하고 있다면 캐시서버를 통해서 입출력 자체를 줄이거나 알고리즘 개선으로 줄이는 방법이 있을 수 있고 블라킹 되지 않게 리액티브 프로그래밍을 이용하거나 코루틴을 적용해볼 수 있다.
메모리가 부족하다면 프로세스는 더 이상 작업을 할 수 없고 이는 시스템 장애나 응답 속도가 느려지는 현상을 일으킬 수 있다.
그렇기 때문에 메모리가 어떻게 사용되고 있는지를 파악하는 것은 CPU 사용률과 Load Average 만큼 중요한 포인트다.
여기서는 메모리 상태를 전체적으로 빠르게 살펴 볼 수 있는 free
명령어와 free
만으로 살펴볼 수 없는 정보는 어떻게 볼 수 있는지 살펴보겠다.
다음은 free -m
명령의 출력 결과다.
ubuntu@ip-172-31-12-59:~$ free -m
total used free shared buff/cache available
Mem: 968 371 135 0 462 443
Swap: 0 0 0
-m
: 이 옵션을 통해서 출력되는 메모리 양은 MB 단위로 출력한다. -b
를 주면 byte 단위, -k
를 주면 KB 단위 -g
를 주면 GB 단위로 표시된다.total
: 현재 시스템에 설치되어있는 전체 메모리양을 의미한다.used
: 시스템에서 사용하고 있는 메모리양을 의미한다.free
: 시스템에서 아직 사용하고 있지 않은 메모리 양을 의미한다. 그러므로 애플리케이션이 사용할 수도, 커널이 사용할 수도 있다.shared
: 프로세스 사이에 공유하고 있는 메모리양이다.buffred
: 버퍼 용도로 사용하고 있는 메모리 영역을 말하며 시스템의 성능 향상을 위해서 커널에서 사용하고 있는 영역이다.cached
: 페이지 캐시라고 불리는 캐시 영역에 있는 메모리양을 의미한다. 페이지 캐시는 디스크 블록을 캐싱해놓음으로써 I/O 작업의 성능향상을 위해서 사용된다.-/+ buffers/cached
가 출력되는 경우도 있는데 이는 메모리 사용량에서 bufferd 와 cached 를 제외하고, 사용하고 있는 영역과 사용하지 않는 영역을 표시해준다.Swap
: Swap 의 전체 영역과 실제로 사용하고 있는 영역 그리고 사용하지 않은 영역을 표시해준다.****커널이 블록 디바이스라고 불리는 디스크에서 데이터를 읽어올 때는 상당히 느리다. 그래서 이를 통해 시스템 부하가 일어나기도 한다.
그래서 커널은 상대적으로 느린 디스크 영역을 좀 더 빠르게 하기 위해서 캐싱 영역으로 한번 읽은 파일은 Page cache 인 cached 영역에 저장하고 여기서 읽어 오도록 한다.
파일의 내용이 아닌 파일 시스템을 관리하기 위한 메타 데이터 (super block, inode block) 을 읽어올 때는 Buffer 영역을 사용한다.
그리고 이전에 메모리에서 buffer 과 cached 영역을 제외하고 보여주는 부분이 있는데 이 이유는 이 영역의 경우에는 프로세스가 메모리를 더 필요로 하는 경우에 언제든지 해제될 수 있는 영역이기 때문이다.****
free
명령은 전체 시스템이 사용하고 있는 메모리와 가용 메모리를 빠르게 읽을 수 있지만 시스템의 메모리 전체 사용량을 알려주지는 않는다.
자세하게 보고 싶다면 /proc/meminfo
파일을 읽으면 된다.
ubuntu@ip-172-31-12-59:~$ cat /proc/meminfo
MemTotal: 992204 kB
MemFree: 138264 kB
MemAvailable: 454700 kB
Buffers: 37596 kB
Cached: 384688 kB
SwapCached: 0 kB
Active: 310688 kB
Inactive: 384112 kB
Active(anon): 1868 kB
Inactive(anon): 274412 kB
Active(file): 308820 kB
Inactive(file): 109700 kB
Unevictable: 23064 kB
Mlocked: 18528 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 16 kB
Writeback: 0 kB
AnonPages: 295616 kB
Mapped: 68360 kB
Shmem: 956 kB
KReclaimable: 51116 kB
Slab: 94424 kB
SReclaimable: 51116 kB
SUnreclaim: 43308 kB
KernelStack: 3712 kB
PageTables: 3976 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 496100 kB
Committed_AS: 1668184 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 19556 kB
VmallocChunk: 0 kB
Percpu: 13632 kB
HardwareCorrupted: 0 kB
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
ShmemPmdMapped: 0 kB
FileHugePages: 0 kB
FilePmdMapped: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 0 kB
DirectMap4k: 110592 kB
여기서는 전부 보지말고 중요한 부분만 살펴보자.
SwapCached
: swap 으로 빠진 메모리 영역 중 다시 메모리로 돌아온 영역을 말한다. swap 으로 한번 메모리가 빠졌어도 메모리가 확보되면 다시 돌아올 수 있다. 그리고 커널은 한번 swap 이 되면 다시 swap 이 될 가능성이 많아서 swap 영역에 있는 메모리는 메모리로 돌아올 때도 삭제되지는 않는다. 이를 통해 I/O 를 조금이나마 줄일 수 있기 떄문에.Active(anon)
: anon 은 anonymous 로 Page cache 영역을 제외한 메모리 영역을 말한다. active 는 최근까지 메모리를 사용하고 있어서 swap 영역으로 넘어가지 않는 영역을 뜻한다.inactive(anon)
: swap 영역으로 넘어갈 수 있는 anon 영역을 말한다.Active(file)
: buffers 와 cached 영역을 포함한 영역이고 active 이므로 최근에 참조해서 swap 영역으로 넘어가지 않는다.inactive(file)
: swap 영역으로 넘어갈 수 있는 buffers 와 cached 영역을 말한다.Dirty
: I/O 성능 향상을 위해 커널이 캐시 목적으로 사용하는 영역으로 그 중 쓰기 작업의 영역을 말한다. 커널은 I/O 작업으로 쓸 때 바로 블록 디바이스로 명령을 내리지 않고 이 영역에 모아서 한 번에 쓴다./proc/meminfo 에서 아직 설명하지 않은 영역이 있는데 slab 영역들이다.
이 영역들은 커널이 내부적으로 사용하는 영역을 뜻한다.
주로 I/O 작업을 더 빠르게 하기 위해서 inode cache, dentry cache 등에 사용하거나 네트워크 소켓을 위한 메모리 영역을 확보하는 작업들을 한다.
inode_cache 는 파일의 inode 에 대한 정보를 지정해두는 캐시라면 dentry 는 디렉터리의 계층 관계를 저장해두는 캐시다.
즉 dentry 는 ls 명령으로 디렉터리를 살펴보기만 해도 값이 증가한다. 만약 파일에 자주 접근하고 디렉터리의 생성/삭제가 빈번한 시스템이라면 Slab 메모리가 늘어날 수 있다.
/proc/meminfo
에 있는 영역 중 slab
과 관련된 영역은 다음과 같다.
Slab
: 메모리 영역 중 커널이 직접 사용하고 있는 영역을 말한다.SReclaimable
: Slab 영역 중 캐시용도로 사용하고 있는 영역이며 메모리 부족 현상이 일어나면 프로세스에게 할당될 수 있는 영역이다.SUnreclaim
: 커널이 현재 사용중인 영역을 말하며 해제할 수 없는 영역이다.그리고 추가로 말하는건데 Slab 메모리는 buffers/cached 영역에 포함되지 않고 free 명령으로 조회했을 때 used 에 포함된다.
여기서는 메모리가 부족하다는 걸 어떻게 알 수 있는지, 메모리가 부족하면 어떤 일이 일어나는지 그리고 부족할 때 어떻게 커널이 대처하는지 알아보자.
swap 영역은 물리 메모리가 부족한 경우를 대비해서 만들어 놓은 영역이다.
메모리가 모자라기 시작하면 프로세스는 더 이상 연산을 위한 공간을 얻을 수 없으므로 장애를 일으킬 수 있는데
이를 방지하기 위해서 비상용으로 디스크 공간에 확보해놓은 메모리 공간이 swap 영역이다.
swap 영역은 물리 메모리가 아니라 디스크 영역이기 때문에 메모리에 비해서 현저히 접근 속도가 떨어진다.
그래서 swap 영역을 사용하고 있다면 시스템 성능이 급격하게 떨어져있다고 생각하면 된다.
리눅스에서 사용중인 swap 영역은 free
명령을 통해서 볼 수 있다.
ubuntu@ip-172-31-12-59:~$ free -k
total used free shared buff/cache available
Mem: 992204 380928 137760 956 473516 454308
Swap: 0 0 0
Swap
이 스왑영역을 말한다.스왑 영역을 볼때는 쓰고 있느냐 안쓰고 있느냐가 중요하다. 그리고 스왑 영역을 사용하고 있다면 어떤 프로세스에서 메모리를 그렇게 많이 사용되고 있는지를 찾아야한다.
만약 서비스 용도의 프로세스가 아니라 관리 용도의 프로세스에서 메모리 누수가 발생해서 swap 이 일어나고 있다면 이를 죽여서 해결할 수 있다. 즉 누가 swap 을 쓰고 있느냐도 중요하다.
해당 프로세스가 swap 을 쓰고 있는지 알아보는 방법은 /proc/{PID}
디렉토리에 있는 smaps
파일을 읽어보면 알 수 있다.
출력해보면 다음과 같은 정보들을 볼 수 있다.
$ cat /proc/19325/smaps
...
7f68aa146000-7f68aa147000 r--p 00000000 103:01 3454 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
Size: 4 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 4 kB
Pss: 0 kB
Shared_Clean: 4 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 4 kB
Anonymous: 0 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
FilePmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
커널이 메모리를 할당하는 과정을 간단하게 살펴보자.
커널은 버디 시스템을 통해서 프로세스에 메모리를 할당한다.
버디 시스템은 물리 메모리를 연속된 메모리 영역으로 관리하는데 예를 들면 연속 1 개의 페이지 크기별 버디, 연속 2 개의 페이지 크기별 버디, 연속 4 개의 페이지 크기별 버디, 연속 8 개의 페이지 크기별 버디 이런식으로 관리한다.
그러므로 8KB 메모리를 요청하면 연속 1 개 짜리를 두 개 주는게 아니라 연속 2 개 짜리 하나를 준다.
이런 방식을 사용하는 이유는 연속된 메모리를 구분해서 전달하므로 메모리 단편화 현상을 그나마 막아준다.
버디 시스템의 현재 상황은 /proc/buddyinfo
에서 볼 수 있다.
ubuntu@ip-192-168-111-19:~$ cat /proc/buddyinfo
Node 0, zone DMA 1 0 0 1 2 1 1 1 0 1 3
Node 0, zone DMA32 1296 838 470 182 133 70 35 14 14 6 42
Node 0, zone Normal 238 391 186 76 39 10 3 1 3 1 0
이번엔 커널이 메모리를 해제하고 다시 할당하는 재할당하는 과정을 보자. 이 경우는 크게 두 가지 케이스가 있다.
1) Page cache, Buffer cache, inode cache, dentry cache 같은 캐시 공간을 해제하고 다시 할당하는 것. (커널은 그냥 메모리를 안쓰고 남겨두는 걸 좋아하지 않는다.)
2) Swap 을 위한 재할당. 캐시용도로 사용하고 있는 메모리를 제외하고 프로세스가 사용중인 메모리는 임의로 커널이 해제할 순 없다. 하지만 메모리가 가득 차있는 상황이라면 사용하는 메모리 중 inactive 영역에 있는 메모리는 swap 영역으로 내리고 요청한 메모리를 올리는 과정을 한다.****
메모리가 부족해서 swap 영역을 쓰고 있다면 메모리를 증설해야 한다고 생각할 수 있지만, 메모리가 누수되고 있는 문제일 수도 있다.
메모리가 누수되고 있는 경우는 메모리의 사용량이 시간이 지남에 따라 선형적으로 증가하는 경우다.
요청이 끝나면 메모리를 해제해야 하는데 그러지 못해서 메모리가 요청을 받을 때마다 선형적으로 증가해서 이런 추세를 가질 수 있다.
이 경우에는 pmap
등의 명령을 이용해서 해당 프로세스가 사용하는 힙 메모리 영역 변화 과정을 보거나 gdb
같은 도구를 이용해서 디버깅을 해보면 메모리 덤프를 생성해 실제 어떤 데이터들이 메모리에 있는지 확인하고 어떤 로직에서 문제가 있을지 예측할 수 있다.
만약 요청이 폭증해서 이로인한 메모리의 사용량도 폭증해서 swap 을 사용하게 되는 경우도 있는데 이 경우에는 안정적인 서비스를 위해서 해당 트래픽을 처리할 수 있을만큼 메모리를 증설해서 swap 영역을 사용하지 않도록 하자. swap 영역을 사용하는 순간 응답 속도가 많이 느려질 수 있다.
여기서는 NUMA 아키텍처가 뭐고, NUMA 아키텍처가 메모리 할당에 어떤 영향을 주는지 알아보겠다.****
NUMA 는 Non-Uniform Memory Access 의 약어다. 번역하자면 불균형 메모리 접근이라는 뜻이며 멀티 프로세서 환경에서 적용되는 메모리 접근 방식을 말한다.
NUMA 아키텍처의 반대는 UMA (Uniform Memory Access) 로 모든 프로세서가 공용 BUS 를 통해서 메모리에 접근하는 방식이다.
UMA 아키텍처의 문제점은 버스를 동시에 이용하지 못하는 단점이 있다. 예로 0 번 소켓에 있는 CPU 가 메모리에 접근하는 동안은 1 번 소켓에 있는 CPU 는 메모리에 접근하지 못한다. 즉 싱글 CPU 에 적합한 구조. (TMI: CPU 안에 코어가 있다.)
NUMA 는 CPU 마다 로컬 메모리가 서로 붙어 있는 관계로 이뤄져있다. 그래서 한 CPU 가 자신의 로컬 메모리에 접근하는 동안 다른 CPU 도 자신에게 붙어 있는 로컬 메모리에 접근이 가능하다.
주의할 점은 Remote 에 있는 메모리에 접근할 땐 성능이 떨어질 수 있어서 로컬 메모리를 최대한으로 쓰도록 하는게 중요하다.****
ubuntu@ip-192-168-111-182:~$ numactl --show
policy: default
preferred node: current
physcpubind: 0 1
cpubind: 0
nodebind: 0
다음은 -H
옵션이다. 이건 노드에 있는 메모리 정보를 확인할 때 사용할 수 있다.
ubuntu@ip-192-168-111-182:~$ numactl -H
available: 1 nodes (0) # (1)
node 0 cpus: 0 1 # (2)
node 0 size: 3866 MB
node 0 free: 1759 MB
node distances: # (3)
node 0
0: 10
다음은 NUMA 환경에서 메모리의 상태를 자세하게 확인할 때 사용하는 명령어인 numastat
이다.
주로 노드의 메모리 균형을 확인하고 한쪽에 메모리가 가득차서 스왑을 하는지 확인하기 위해서 사용한다.
ubuntu@ip-192-168-111-182:~$ numastat -cm
Per-node system memory usage (in MBs):
Token Node not in hash table.
Token Node not in hash table.
Token Node not in hash table.
Token Node not in hash table.
Token Node not in hash table.
Node 0 Total
------ -----
MemTotal 3867 3867
MemFree 1761 1761
MemUsed 2106 2106
Active 777 777
Inactive 1074 1074
Active(anon) 1 1
Inactive(anon) 77 77
Active(file) 776 776
Inactive(file) 997 997
Unevictable 22 22
Mlocked 18 18
Dirty 0 0
Writeback 0 0
FilePages 1779 1779
Mapped 102 102
AnonPages 94 94
Shmem 1 1
KernelStack 2 2
PageTables 2 2
NFS_Unstable 0 0
Bounce 0 0
WritebackTmp 0 0
Slab 195 195
SReclaimable 148 148
SUnreclaim 46 46
AnonHugePages 0 0
HugePages_Total 0 0
HugePages_Free 0 0
HugePages_Surp 0 0
이번에는 프로세스가 어떤 메모리 할당 정책으로 실행되었는지를 보자. /proc/{PID}/numa_maps
에서는 현재 동작 중인 프로세스의 메모리 할당 정책과 관련된 정보가 기록된다.
$ sudo cat /proc/92432/numa_maps
558244dbf000 default file=/usr/sbin/sshd mapped=11 mapmax=3 N0=11 kernelpagesize_kB=4
558244dca000 default file=/usr/sbin/sshd mapped=127 mapmax=3 N0=127 kernelpagesize_kB=4
558244e49000 default file=/usr/sbin/sshd mapped=39 mapmax=3 N0=39 kernelpagesize_kB=4
리눅스에선 numad 를 통해서 NUMA 메모리 할당 정책을 직접 설정하지 않고도 메모리 지역성을 높일 수 있는 방법을 제공해준다.
numad 는 백그라운드 데몬과 같은 형태로 시스템에 늘 상주해있으면서 프로세스들의 메모리 할당 과정을 지켜보고 프로세스가 필요로 하는 메모리 크기가 노드보다 작다면 한쪽 노드에서만 메모리를 할당하도록 해서 메모리 지역성을 높여 최적화를 해준다.
다수의 프로세스를 사용하는 경우에 default 정책이라면 메모리가 여러 곳으로 흩어지면서 성능이 다소 떨어질 수 있다. 하지만 numad 를 실행시킨다면 하나의 프로세스가 필요로 하는 메모리를 하나의 노드에서만 할당하도록 해준다.
이 경우에 조심해야 할 것은 하나의 노드에만 집중적으로 하기 때문에 메모리 불균형이 생겨서 스왑을 사용하는 경우다. ****
예를 들면 Process 1 이 Node B 의 메모리를 모두 차지하고 있는데 Process 2 의 경우는 NUMA 정책을 interleaved 으로 해서 스왑이 발생하는 경우.
numad 말고도 NUMA 아키텍처에서 메모리 할당에 영향을 줄 수 있는 커널 파라미터가 있는데 이건 vm.zone_reclaim_mode
이다.
vm.zone_reclaim_mode
에 대해서 이야기 하기 전에 zone 이 무엇인지 먼저 살펴보자.
커널은 메모리를 사용 용도에 따라 zone 이라 불리는 영역으로 구분해서 관리한다.
zone 에 대한 정보는 /proc/buddyinfo
에서 살펴볼 수 있다. (buddyinfo 는 메모리 할당 과정에서 이전에 살펴봤다.)
ubuntu@ip-192-168-111-182:~$ cat /proc/buddyinfo
Node 0, zone DMA 1 0 0 1 2 1 1 1 0 1 3
Node 1, zone DMA32 3428 2965 1761 328 115 13 5 1 4 22 393
Node 0, zone Normal 1888 1077 510 103 18 17 10 3 6 0 0
이제 vm.zone_reclaim_mode
에 대해서 보자. 이 값은 총 4 개의 값을 가질 수 있지만 실제적으로 중요한 값은 0 과 1 이다.
0 을 이용하면 page cache 와 같은 재할당이 가능한 메모리들도 재할당 하지않고 다른 노드에 있는 메모리를 할당 받아서 사용하므로 I/O 가 중요한 프로세스의 경우에는 vm.zone_reclaim_mode 를 0 으로 설정해서 사용하는 것이 더 좋을 수 있다.
반대로 page cache 보다 로컬 메모리의 접근이 더 유리할 때는 vm.zone_reclaim_mode 를 1 로 할당해서 가능한 동일한 노드에서 메모리를 할당 받을 수 있게 해주는 것이 좋다.
이번에는 지금까지의 내용을 바탕으로 NUMA 의 메모리 할당 정책을 어떻게 사용하는게 최적화를 할 수 있을지를 알아보자.
이것은 정답이 없는 문제이기도 해서 그냥 이런 이유로 사용할 수 있다 정도만을 알아두자.
NUMA 시스템에서 워크로드를 확인할 때 고려해봐야 할 것이 두 개가 있다.
표로 나타내면 다음과 같다.
스레드 개수 | 메모리 크기 |
---|---|
싱글 스레드 | 메모리가 노드 하나를 넘지 않음 |
멀티 스레드 | 메모리가 노드 하나를 넘지 않음 |
싱글 스레드 | 메모리가 노드 하나를 넘음 |
멀티 스레드 | 메모리가 노드 하나를 넘음 |
먼저 싱글 스레드 + 메모리가 노드 하나를 넘지 않는 경우를 보자. 이 경우는 흔하진 않을 건데 하나의 코어만 받으면 되므로 바인딩을 통해서 로컬 메모리 엑세스를 하도록 하는게 좋다. 그리고 vm.zone_reclaim_mode 도 1 로 둬서 가능한 로컬 메모리를 향하도록 하자.
두 번째는 멀티 스레드 + 메모리가 노드 하나를 넘지 않는 경우인데 이 경우에도 cpunodebind 모드를 이용해서 여러 개의 코어에 프로세스를 바인딩 하도록 해서 로컬 메모리 엑세스를 하도록 하자.
이 경우에는 CPU Usage 에 대한 모니터링이 필요한데 특정 노드에 위치함으로써 CPU 를 독차지 할 수 있다. 모니터링 할 땐 그러므로 전체 CPU 가 아닌 개별 CPU 를 봐야한다. vm.zone_reclaim_mode 도 가능한 1 로 둬서 로컬 메모리를 사용하도록 하자.
세 번째는 싱글 스레드이고 메모리가 노드 하나를 넘는 경우인데 이 경우 부터는 어쩔 수 없이 리모트 메모리 엑세스가 일어난다. 하지만 이를 최소화 하기 위해서는 로컬 메모리 사용 비중을 높이기 위해서 동일한 cpu 에 계속해서 바인딩 하도록 하는게 좋다. 그래서 첫 번째와 마찬가지로 cpunodebind 정책을 쓰자. 그리고 어짜피 노드 하나의 메모리를 넘을 것이니 계속해서 재할당 하는 것보다는 그냥 골고루 메모리를 할당 받는게 나을수 있으니 vm.zone_reclaim_mode 는 0 으로 하는게 나을 수 있다.
네 번째는 멀티 스레드에 메모리가 노드 하나를 넘는 경우인데 실제로 이 경우가 가장 많을 수 있다. 어짜피 리모트 메모리 엑세스도 일어나고 한쪽으로 CPU 를 할당하는 것도 그렇게 좋지 않다. 그러므로 그냥 interleave 모드로 골고루 받는게 가장 나을 수 있다. 이 경우에도 vm.zone_reclaim_mode 는 0 으로 하는게 더 나아보인다.
엄청나네요..