예측: 파일 시스템 I/O의 지연시간을 질의 중간에 측정해 본다면, 느린 파일 시스템이 질의 속도를 느리게 하는 원인임을 알 수 있을 것이다
테스트: 파일 시스템 지연시간을 질의 지연시간으로 나누어 보니 전체 시간의 5%가 안됨
분석: 파일 시스템이나 디스크가 원인은 아님 → 새로운 가설을 세우고 추가적인 실험을 지속적으로 진행
1-4. 지연시간 분석
어떤 연산을 완료하기까지 걸린 시간을 분석
그 후, 근본원인을 식별할 수 있을 때까지 더 작은 요소로 나눠서 파악 ex)전체 지연시간(2s) → A 지연시간 (1.5) + B 지연시간 (0.5) → C 지연시간(1) + D 지연시간(0.5) + B 지연시간(0.5) ※ C+D=A
소프트웨어 스택의 각 단계를 조사 → 어플케이션에서 운영체제, 라이브러리, 시스템콜, 커널, 드라이버
ex) MySQL 질의 지연시간
CPU 시간 vs CPU 외 시간
CPU 외 시간의 주 대기시간? → 파일 I/O 시간
파일 I/O 시간 → 디스크 I/O? 락 획득 시간?
디스크 I/O 시간 → 데이터 전송 시간? 임의 접근 시간?
1-5. 리눅스 성능 분석
이 외에도 다양한 접근 방법이 존재
드릴다운 분석: 문제를 좀 더 높은 수준으로 보고, 관심 대상을 깊이 파고드는 방식
이벤트 추적: 통계적 정보가 아닌 개별적으로 이벤트를 분석하여 문제를 해결 ex) 네트워크 이슈 tcpdump 이용
워크로드 특성 평가: 시스템에 가해진 입력 내용에 초점. ex) 타임시리즈 데이터베이스 성능이슈 → 조회 워크로드 패턴을 조정하여 해결
다양한 리눅스 성능 관측 도구가 있으며, 목적에 맞게 적절하게 사용하는 것이 중요
최근에는 유저 스페이스가 아니라 커널 스페이스에서 동작하여 적은 오버헤드를 발생시키는 eBPF 기반 도구들이 추세 ex) DataDog도 eBPF를 사용
2. CPU 관련 주요 개념 및 분석
CPU는 모든 소프트웨어 실행을 담당 → 성능 분석 시 가장 먼저 봐야하는 대상
CPU는 모든 실행이 필요한 프로세스가 사용
CPU 코어 개수보다 Process 개수가 많을 경우 대기열에 들어가서 자신의 순서를 기다려야 함 → 프로세스 스케줄러가 순서를 정함
2-1. 프로세스 스케줄러
2-1-1. O(1) 스케줄러
여기서 O는 시간 복잡도를 의미한다.
리눅스 2.4까지는 하나의 큐를 사용하는 방식이었다
이는 큐가 존재하고 프로세서가 있을 때, 모든 프로세스 목록을 탐색해 가장 우선순위가 높은 프로세스를 실행하는 방식으로 시간 복잡도가 O(N)이었다. 많은 프로세스가 실행되는 시스템에서 성능 저하의 문제로 2.6부터는 멀티큐 방식을 사용하는 O(1) 스케줄러가 등장하였다.
O(1) 스케줄러는 Active 큐와 Expired 큐를 가지고 있다.
액티브 큐의 정해진 시간 (time slice) 동안 프로세스가 실행이 되고 남은 작업은 만료 큐로 이동이 된다.
액티브 큐 수행 완료 후에는 만료 큐를 액티브 큐로 변경하고 다시 정해진 시간에 따라 프로세스가 실행이 된다.
우선순위는 140까지 있으며 빠른 탐색을 위해 bitmap을 사용하고 있다. bit가 1이면 존재, 0이면 존재하지 않는 것으로 해당 우선순위에 프로세스가 있는지 알 수 있다.
각 우선순위에는 동일한 우선순위를 가지는 Task가 배치되며 라운드 로빈 방식으로 실행이 된다.
우선순위가 높은 Task를 찾기 위해 bitmap으로 최대 140까지 탐색을 해야하므로 항상 상수 시간만큼 시간이 소요된다. 그래서 O(1) 시간 복잡도를 가진다.
2-1-2. CFS 스케줄러
리눅스 커널 2.6.23 버전부터는 CFS 스케줄러를 사용하게 된다.
가상 런타임이 가장 적은 프로세스를 정해진 시간(time slice)동안 실행하는 것이 기본 개념이다.
여기서 가상 런타임은 실행시간에 비례해서 커지기 때문에 프로세스가 CPU를 오랫동안 기다리면 높은 우선순위가 부여된다. 즉, 프로세스가 CPU를 기다리는 시간을 계산하여 동적으로 우선순위를 부여한다.
CFS는 가상 런타임이 낮은 프로세스를 찾기 위해 레드블랙트리(red-black tree)알고리즘을 사용합니다.
우선순위가 높은 프로세스를 먼저 실행시키고 더 많은 타임 슬라이스를 부여한다 - 0~99 : RT(실시간) 프로세스 (실시간 프로세스는 숫자가 클수록 우선순위가 높고 일반 프로세스보다 높음) - 100~139 : 일반 프로세스 (-20 ~ +19 까지의 값을 가지며 기본값은 0입니다. 이 값은 커널 공간에서 100~139 사이 값으로 변환되어 관리)
각 프로세스의 타임 슬라이스는 위 공식으로 계산된다. 스케줄링 레이턴시란 CFS 런큐에서 실행 대기 상태로 기다리는 프로세스들의 타임 슬라이스를 합한 값이다. 위 식에 의해서 각 프로세스는 load weight 비율만큼의 타임 슬라이스를 얻게 된다. 프로세스가 많아지면 CFS 런큐 전체 가중치 합이 많아지게 되므로 타임 슬라이스가 0에 가까워지는 것이 아니냐할 수도 있지만 최소 타임 슬라이스 값이 정해져있다.
가중치는 우선순위에 따라 부여됨
예시)
문서 편집기와 동영상 인코더 두 가지 작업이 있는 시스템
문서 편집기는 대부분의 시간을 사용자 키 입력을 기다리는데 사용 → I/O 중심 프로세스
동영상 인코더는 데이터 로드와 저장을 제외하고는 대부분의 시간을 프로세서를 사용 → CPU 중심
매뉴얼한 해결 방법: 문서 편집기에 더 높은 우선순위 및 긴 타임슬라이스 할당 하지만, 리눅스에서는 일반적으로 사용자 프로세스에 동일한 우선순위를 부여한다.
CFS 스케줄러 정책에서는 가중치와 이전 실행시간을 고려해 다음 실행 시간을 산정하여 공평하게 실행 시간을 나눈다. → 가중치(=우선순위)가 같아도 이전 실행시간이 다름 → 동영상 인코더는 쉬지 않고 CPU를 사용했기 때문에 큰 vruntime → 문서 편집기는 대부분의 시간을 I/O 요청을 대기하고 있었기 때문에 작은 vruntime → 문서 편집기의 반응성을 유지할 수 있음
2-2. CPU 주요 개념
2-2-1. 클럭 속도
CPU는 클럭 속도에 따라 명령을 실행
전력 소모를 위해 클럭 속도를 조정할 수 있음
CPU의 전력은 P-states(performance states)와 C-state(operating states) 2가지 상태값으로 제어가 된다
P-state는 CPU 사용량이 적을때 CPU 코어의 주파수를 떨어뜨려서 적은 전압으로 CPU를 동작할 수 있게 하기 위해 사용되는 상태인데, 미리 정의된 CPU 코어 주파수와 전압의 셋팅 값을 가지고, 바쁠때는 최대 주파수로, 한가할 때는 낮은 주파수로 동작하게 한다
C-state는 CPU 내부 컴포넌트의 사용을 중단시켜서 전력 소모를 줄이는데 사용된다.
C-states 레벨에 따라 코어 내부 클럭이 중단되거나 L1/L2 캐시 Flush 및 Off 시키는 등의 동작이 발생하고, 레벨이 높아질수록 많은 컴포넌트들이 꺼짐으로써 전력 소모량은 줄지만, 그에 비례해서 정상 상태(C0)로 복귀하는데 더 많은 리소스와 시간이 소요되는 문제가 있다
2-2-2. 하이퍼쓰레딩
각 물리코어에 둘 이상의 스레드를 실행하도록 지원하는 기술
더 많은 작업을 병렬로 수행 가능 (최대 30% 성능 개선)
단, 프로세서 구조에 최적화되지 않은 코드의 경우 성능이 떨어질 수 있음
2-2-3. CPU의 한계
물리적인 한계와 전력 효율 등의 원인으로 클럭 속도 증가의 한계에 있음 → 수직적 확장이 아닌 수평적 확장으로 해당 한계 극복
2-2-4. 스케줄링 클래스
리얼타임 프로세스
실시간 부하를 위해 고정된 높은 우선순위 제공
스케줄링 정책: 라운드로빈, 선입선출(FIFO)
일반 프로세스
CFS 스케줄러에 적용할 우선순위를 조정
Completely Fair 지만 우선순위에 따라 실행 시간을 더 할당 받을 수 있음
스케줄링 정책: 일반(NORMAL), 일괄(BATCH)
우선순위 정보 확인 ps -el
사용자 프로세스는 일반 클래스, 대부분 동일한 우선순위 (NICE 0)
2-2-5. 사용률
특정 기간동안 CPU 인스턴스가 작업을 수행한 전체 시간의 백분율 → 어떤 CPU가 프로세스를 실행하거나 인터럽트를 처리하는데 걸리는 시간을 측정 * 인터럽트: 하드웨어가 프로세서에 신호를 보내서 적합한 처리를 하도록 요청 (폴링 X) (예, 키보드 입력, 디스크에서 데이터 읽기, 네트워크 패킷 도착)
높은 CPU 사용률이 꼭 문제가 되지는 않음 → 낮은 CPU 사용률은 자원 낭비
2-2-6. 사용자 시간 / 커널 시간
사용자 공간 코드를 실행하는데 CPU가 소비한 시간 → 사용자 시간
커널 공간 코드를 실행하는데 CPU가 소비한 시간 → 커널 시간
계산 중심 애플리케이션은 사용자/커널 시간은 99/1 비율에 가까움
I/O 중심 애플리케이션은 커널 시간의 비중이 높음 (예, 웹 서버는 70/30)
2-3. CPU 성능 분석 도구
2-3-1. 기본 설치
top : 프로세스/스레드별로 CPU 사용 정보를 확인
가장 많이 실행 중인 프로세스를 표시 → CPU 사용률 내림차순
시스템 전체 요약 내용 표시 중 CPU 관련 내용
us : 사용자 모드에서 실행한 시간
sy : 커널 모드에서 실행 시간
nice : 우선순위가 조정된 사용자 프로세스 실행시간
id : idle 시간
wa : I/O 완료를 기다리며 아무것도 실행하지 않은 시간
hi : 하드웨어 인터럽트 처리에 사용된 시간
si : 소프트웨어 인터럽트 처리에 사용된 시간 트랩 또는 예외 → 예, 존재하지 않은 메모리 주소 접근, 0 으로 나누기
st : 하이퍼바이저에 의해 빼앗긴 시간
단점
오버헤드가 높음 (/proc의 모든 프로세스 노드에 대해 open,read,close 수행)
단기간만 짧게 실행되는 프로세스는 표시 안될 수 있음 (기본 3초, d키 눌러서 delay 조정 가능)
ps : 프로세스 상태 확인
uptime : 부하 평균 표시
시스템 부하 평균을 표시하는 도구
CPU 자원의 1분, 5분, 15분 평균 부하를 표시 → 15분 동안의 평균 부하의 경향을 볼 수 있음 (top는 정보가 계속 갱신 → 현재 상태 확인) * 부하 : 실행중 / 인터럽트불가능 상태의 프로세스의 평균 개수
단점
인터럽트 불가능 상태(예, disk I/O 기다림)를 포함 → CPU 수요와 디스크 부하를 구분하기 힘듦
vmstat : 가상 메모리 통계 표시 (시스템 전체 CPU 사용률 평균을 포함)
가상 메모리 통계 표시하는 도구
시스템 전체 CPU 사용률을 같이 표시
uptime 대비 인터럽트 불가능 상태의 프로세스 정보도 포함
r: 실행/대기 중인 프로세스 개수, b: 인터럽트 불가능 상태의 프로세스 개수
us: 사용자 시간, sy : 시스템(커널)시간, id: 유휴, wa: I/O 대기, st: 하이퍼바이저가 뺏어간 시간
단점
프로세스 전체 평균만을 표시 → mpstat 은 CPU별 통계를 표시
pidstat : 프로세스/스레드별로 CPU 사용을 구분해 표시
프로세스 / 스레드별로 CPU 사용을 구분해 사용률을 표시 ← uptime, vmstat은 cpu별 정보표시
XFS, EXT4의 경우 성능차이가 크지 않음 → 다양한 워크로드에 이미 최적화 → 일반적이지 않은 I/O 바운드 워크로드를 제외하고는 , 파일시스템이 파일 I/O 성능 관련한 지배적인 요소는 아닌 경우가 많음 → 하지만 , 커널 버전에 따른 성능 이슈는 한번 확인 필요, 여전히 관련 commit 은 진행 됨
4-1-1. 참고) FIO (flexible I/O tester)
I/O 퍼포먼스 벤치마크
sudo apt install -y fio
사용 예시 1) 1 MB 크기 연속 쓰기 sudo fio --name=write_test \ --filename=/dev/xvdb --filesize=100G \ --time_based --ramp_time=2s --runtime=1m \ --ioengine=libaio --direct=1 --verify=0 --randrepeat=0 \ --bs=1M --iodepth=64 --rw=write --numjobs=10 --offset_increment=10G
페이지 캐시의 크기는 동적으로 변함 → 가용 메모리가 있는 경우 증가 → 가용 메모리가 없는 경우 감소
페이지에 읽으려는 데이터가 있는 경우를 캐시 히트, 없는 경우를 캐시 미스라고 칭함 → 캐시 히트 비율에 따라 성능 크게 영향
4-3. 버퍼 캐시 (쓰기 성능 개선)
최대한 디스크 I/O가 적게 일어나도록 캐시 사용
지연 쓰기 (비동기 쓰기)
쓰기 동작은 페이지 캐시에 수행하고 즉시 디스크에 내용을 갱신하지 않음 → dirty bit 셋팅
주기적으로 더티 페이지를 디스크에 저장하는데 pdflush 데몬이 수행함 → 더티 페이지가 일정 비율 도달 시 (vm.dirty_ratio 로 지정 가능) → 동기적으로 쓰기 페이지 flush작업이 진행 → 쓰기 성능 급격히 저하 발생 → 즉, 쓰기 요청이 급격히 증가할 경우, 동기적으로 쓰기 작업이 이루어지고 성능 저하 발생할 수 있음
Buffer Cache가 따로 존재하지는 않고 Page Cache일부를 나누어서 사용한다 → 쓰기 요청이 많이 발생하는 상황에서, 읽기 요청에 사용되는 공간이 적어지면 성능 저하 발생 → pdflush로 쓰기 캐시 공간을 제한 가능함
4-4. 동기적인 쓰기 동작
만약 캐시를 사용하지 않고 디스크에 곧바로 저장하고 싶다면 fsync( ) 함수를 사용하면 된다
fsync(int fd) : 특정 파일에 대한 모든 지연된 쓰기 버퍼 내용을 동기적으로 디스크에 저장
파일 오픈 시 동기화 옵션 사용
O_SYNC 모드 : write요청 + fsync요청
O_DIRECT : 버퍼 캐시를 사용하지 않고 바로 디스크에 쓰기 수행
4-5. 파일시스템 관련 주요 성능 지표
처리량 : 초당 전송되는 데이터 양
IOPS (Input/Output Operation Per Second) : 초당 IO 요청 개수
읽기 / 쓰기 비율 : 페이지 캐시를 서로 공유, 페이지 캐시 크기가 작을 수록 캐시 미스가 많아짐
동기적 쓰기 비율 : 지연쓰기와의 성능차이가 큼 (디스크를 접근 유/무 차이)
임의적 접근 / 순차적접근 :
4-6. 파일 시스템 분석 도구
free
여유 및 사용중인 메모리 정보 확인
출력 내용
total: 전체 메모리
used: 사용중인 메모리 (= total - free - buffers - cache)
free: 여유 메모리
shared : 호환성을 위해 컬럼 유지 (의미x)
buffer/cache : 버퍼와 캐시를 합친 공간 -w 옵션 사용하면 두 공간 분리표기
available : 새로운 앱 시작에 사용가능한 메모리 공간 (= free + page cache 일부 공간)
top
메모리 요약 정보를 포함하고 있음
캐시 적중 비율은 ftrace같은 도구로 커널 정보를 추가로 모니터링 하거나 eBPF 기반 도구를 사용
5. 디스크 관련 주요 개념 및 분석
디스크 종류에는 크게 HDD와 SSD가 있다
SSD가 모든 성능면에서 우세
하지만 가격적인 부분 때문에 HDD를 사용할 수도 있다
HDD는 랜덤 읽기/쓰기에서는 성능이 크게 떨어지므로 연속적인 읽기/쓰기에서는 활용 가능 ex) 대규모 스트리밍 데이터 저장
그냥 SSD 쓰는게...ㅎ
5-1. 블록 I/O 계층
고정된 크기의 데이터 묶음을 임의 접근
데이터 묶음을 블록이라고 부름
SSD, HDD, USB Memory 등
파일 시스템을 통해 마운트 하여 사용
블록장치는 섹터 단위(보통 512B) 로 접근
파일시스템은 블록 단위(예, 4KB)로 접근
5-2. I/O 스케줄러
블록 I/O 성능을 향상 시키기 위해 블록 I/O 요청을 병합하고 정렬하는 역할 수행
지연된 블록 I/O 요청들에게 디스크 I/O 자원을 골고루 분배
스케줄러 타입
deadline : 각 I/O 요청의 시작 시간을 보장
cfq : 완전히 공정한 대기열. 프로세스간 I/O를 고정하게 할당
noop : 요청을 정렬하지 않음
none : (멀티큐) 요청을 정렬하지 않음
mq-deadline : (멀티큐) 각 I/O 요청의 시작 시간을 보장
클라우드 환경에서는 워크로드가 I/O 바운드한 앱이 아닌 경우, 어떤 스케줄러를 쓰던 극적인 효과는 보기 힘듦
5-3. 디스크 성능 분석 도구
iostat
CPU 통계와 디바이스나 파티션에 대한 I/O 통계 내용을 표시
설치 : sudo apt install sysstat
표시 내용
tps : 디바이스에 요청된 데이터 전송 요청 횟수
read/s, wrtn/s : 초당 데이터 읽기 / 쓰기 양
read, wrtn : 전체 읽기/쓰기 양
dscd/s : unmapped된 블록 수 (예, 디스크 일부 공간을 메모리에 맵핑 했던 내용을 해제한 블록 수)
표시 항목 확장 : iostat -x
r_await, w_await : 읽기/쓰기 요청의 평균 응답 시간 (큐에서 대기한 시간도 포함)
rrqm/s, wrqm/s : 대기열에 들어가 병합된 읽기/쓰기 요청 횟수
pidstat
-d 옵션을 사용하면 Disk I/O 통계 정보도 출력됨
표시 내용
rd/s : 초당 읽은 크기
wd/s : 초당 쓴 크기
ccwr/s : 초당 취소한 쓰기 크기 (예, 디스크에 버퍼 캐시 내용을 플러시 하기전에 덮어 쓴 경우)
iodelay: 블록 I/O를 대기한 시간 (동기적인 블록 I/O를 대기한 시간, 스왑 인 시간을 포함)
iotop
top 명령과 유사하게, io에 대한 top 정보를 출력
설치 : sudo apt install iotop
1초 단위로 수집된 정보를 표시
배치 모드, -b 를 사용 sudo iotop -bod5 (배치 모드로, I/O를 진행중인 프로세스만, 5초 단위로 출력)
표시 내용: 프로세스 별 읽기/쓰기 처리량
5-4. 디스크 성능 튜닝
ionice
I/O 스케줄링 클래스(-c)와 우선순위(-n)를 조정
설정 방법
ionice [-c class] [-n level] [-t] –p PID
ionice [-c class] [-n level] [-t] –p PGID
ionice [-c class] [-n level] [-t] –p UID
ex) ionice -c 3 -p 1623
클래스 번호
0 (none): 클래스 지정 하지 않음
1 (realtime): 실시간. 가장 높은 우선순위를 제공
2 (best effort): 최선을 다하도록 설정
3 (idle): 디스크가 유휴 상태인 경우에만 I/O를 요청 (예, 관리 도구의 운영 부하를 최소화 목적으로 사용)
우선순위
0-7에서 지정
0이 가능 높은 우선순위
ex) 높은 I/O 우선순위를 가지는 프로세스 실행 : ionice -c 2 -n 0 COMMAND
I/O 스케줄러
스케줄러 종류: [noop, cfq], [none, mq-deadline] 등
none : 처리 순서를 변경하지 않음. 최소 오버헤드. 빠른 SSD 디바이스에 적합
mq-deadline
멀티 큐를 지원하는 deadline 스케줄러. 요청에 대해 보장된 대기 시간을 제공하려고 시도