DevOps와 SE를 위한 리눅스 커널 이야기 10장 요약입니다.
1. dirty page란
•
아래와 같이 커널은 PageCache를 이용해서 디스크에 있는 파일의 내용을 메모리에 잠시 저장하고, 필요할 때마다 메모리에 접근해서 사용
Dirty Page
읽은 파일이 디스크에 업데이트 되지 않고 page cache 내 특정 공간에만 업데이트 되어 있는 경우가 있는데 이때 그 특정 공간을 dirty page라고 함
Page Writeback (Dirty Page 동기화 과정)
순서
1.
PageCache b == Disk b
•
맨 처음 pagecache에 있는 데이터와 디스크에 있는 데이터는 동일한 데이터를 유지
2.
PageCache d != Disk b
•
b 파일이 d로 변경
•
이때 커널은 변경된 내용을 디스크에 바로 저장하지 않고 매핑된 page cache의 내용을 변경
•
그리고 해당 페이지는 ditry page가 됨
3.
PageCache d == Disk d
•
그 후 일정한 조건이 되면 커널은 다양한 방법으로 dirty page의 내용을 실제 디스크에 쓰게 되며 이런 일련의 과정을 dirty page 동기화라고 함
의의
•
dirty page가 생성될 때마다 디스크에 쓰게 되면 상당량의 쓰기 I/O를 일으켜서 성능을 저하시킬 수 있음
•
따라서 커널은 몇가지 조건을 만들어서 해당 조건을 만족시키면 dirty page로 디스크를 동기화 (=page writeback)
•
보통 flush라는 단어가 들어간 커널 스레드(pdflush, flush, bdflush 등)가 이 작업을 진행
•
I/O가 많이 발생하는 서버에서는 dirty page를 언제 얼마나 동기화 시키느냐가 중요한 성능 튜닝의 요소
2. dirty page 관련 커널 파라미터
> sysctl -a | grep -i dirty
vm.dirty_background_ratio = 10
vm.dirty_background_bytes = 0
vm.dirty_ratio = 20
vm.dirty_bytes = 0
vm.dirty_writeback_centisecs = 500
vm.dirty_expire_centisecs = 1000
Bash
복사
(1) vm.dirty_background_ratio
dirty page의 내용을 백그라운드로 동기화할 때 그 기준이 되는 비율
•
ex) 10 -> dirty page가 전체 메모리의 10/100일 때 실행
•
vm.dirty_ratio보다 크면 vm.dirty_ratio의 절반값으로 수정된다.
(2) vm.dirty_background_bytes
dirty page의 내용을 백그라운드로 동기화할 때 그 기준이 되는 절대적인 bytes 값
•
0보다 높게 설정할 경우 vm.dirty_background_ratio 값은 무시된다.
(3) vm.dirty_ratio
dirty page에 대한 싱크 작업을 background로 하지 않고 해당 프로세스의 쓰기 I/O를 블락시킨 후 수행하는 싱크 작업의 기준 비율. dirty page에 대한 일종의 hard limit
•
ex) 20 -> dirty page가 전체 메모리의 20/100일 때 실행
•
최소값은 5이며 5보다 작은 값으로 해도 강제로 5로 재설정 된다.
(4) vm.dirty_bytes
dirty page에 대한 싱크 작업을 background로 하지 않고 해당 프로세스의 쓰기 I/O를 블락시킨 후 수행하는 싱크 작업의 기준이 되는 절대적인 bytes 값
•
0보다 높게 설정할 경우 vm.dirty_ratio 값은 무시된다.
(5) vm.dirty_writeback_centisecs
flush 데몬이 background에서 깨어나는 기준이 되는 값. 이 값에 해당하는 시간을 주기로 캐시를 체크해서 dirty page들을 디스크에 flush함
•
ex) 500 -> 5초
•
0으로 설정할 경우 flush 데몬 스레드를 주기적으로 실행하지 않는다.
(6) vm.dirty_expire_centisecs
vm.dirty_writeback_centisecs 값에 의해 깨어난 flush 커널 스레드가 디스크에 싱크시킬 dirty page의 기준을 찾을 때 기준이 되는 값
•
ex) 1000 -> 10초
유의사항
•
위의 6개의 값들은 완전히 독립적으로 동작하지 않는다.
•
ex)
◦
flush 커널 스레드가 자주 실행될 경우 (vm.dirty_writeback_centisecs, vm.dirty_expire_centisecs 값이 작을 경우)
◦
vm.dirty_background_ratio에 설정된 기준만큼 dirty page가 캐시에 존재하기도 전에 비워질 수 있음
3. dirty page 동기화 방식 종류
(1) 백그라운드 동기화
dirty page의 생성량이 일정 수준을 넘으면 백그라운드로 flush 커널 스레드가 동작하면서 동기화하는 방식
•
vm.dirty_background_ratio와 vm.dirty_ratio를 통해서 조절 가능 (명령어를 통해서 명시적으로 이루어지지 않는 동기화)
(2) 주기적 동기화
일정 시간을 주기로 flush 커널 스레드가 깨어나서 특정 조건에 해당하는 dirty page를 동기화하는 방식
•
vm.dirty_writeback_centisec, vm.dirty_expire_centisecs를 통해서 조절 가능
(3) 명시적 동기화
sync, fsync 등의 명령어를 통해 명시적으로 동기화시키는 방식
•
ex) sync 명령어를 직접 사용하여 dirty page를 비울 수 있다
4. dirty page 동기화 예제
(1) 백그라운드 동기화 예제
•
(참고) 기본값
sudo sysctl -w vm.dirty_writeback_centisecs=500
sudo sysctl -w vm.dirty_background_ratio=10
Bash
복사
•
설정 변경
sudo sysctl -w vm.dirty_writeback_centisecs=0 #주기적 동기화 off
sudo sysctl -w vm.dirty_background_ratio=1 #백그라운드 동기화 기준 1/100
Bash
복사
•
초당 1MB 쓰기작업을 일으키는 test_dirtypage.c 작성
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#define MEGABYTE 1024*1024
int main(){
int output_fd;
char message[MEGABYTE] = "";
char file_name[] = "./test.dump";
int count = 0;
output_fd = open(filename, O_CREAT | O_C_RDWR | O_TRUNC);
for(;;){
count++;
write(output_fd, message, MEGABYTE);
printf("Write File - Current Size : %d KB\n", count*1024);
sleep(1);
}
return 0;
}
C
복사
•
컴파일 및 실행
# 컴파일
> gcc -o test test_dirtypage.c #컴파일
# 실행
> ./test
Write File - Current Size : 1024 KB
Write File - Current Size : 2048 KB
Write File - Current Size : 3072 KB
# ...
Bash
복사
•
dirty page 모니터링
> cat show_dirty.sh
#!/bin/bash
while true
do
cat /proc/meminfo | grep -i dirty
sleep 1
done
> ./show_dirty.sh
# ...
Dirty: 83100 kB
Dirty: 84044 kB
Dirty: 85068 kB
Dirty: 72 kB
# ...
Bash
복사
•
전체 시스템 메모리 8GB 중에서 1/100수준인 80MB 수준에서 dirty page 동기화가 진행된다.
(2) 주기적 동기화 예제
•
(참고) 기본값
sudo sysctl -w vm.dirty_background_ratio=10
sudo sysctl -w vm.dirty_ratio=20
sudo sysctl -w vm.dirty_writeback_centisecs=500
sudo sysctl -w vm.dirty_expire_centisecs=3000
Bash
복사
•
설정 변경
sudo sysctl -w vm.dirty_background_ratio=20 #20%로 상향 조정
sudo sysctl -w vm.dirty_ratio=40 #40%로 상향 조정
sudo sysctl -w vm.dirty_writeback_centisecs=500 #5초마다 flush 커널 스레드 깨움
sudo sysctl -w vm.dirty_expire_centisecs=1000 #10초가 넘은 dirty page 동기화
Bash
복사
•
실행
# 실행
> ./test
Write File - Current Size : 1024 KB
Write File - Current Size : 2048 KB
Write File - Current Size : 3072 KB
# ...
Bash
복사
•
dirty page 모니터링
> ./show_dirty.sh
Dirty: 24 kB
Dirty: 1080 kB
Dirty: 2252 kB
Dirty: 3316 kB
Dirty: 4340 kB
Dirty: 5236 kB
Dirty: 6268 kB
Dirty: 7292 kB
Dirty: 8452 kB
Dirty: 9476 kB
Dirty: 60 kB
# ...
Bash
복사
•
10초가 넘어가는 10~15MB 사이의 구간에서 5초 주기로 깨어나는 flush 커널 스레드가 작동한다.
•
다시 말해서 생성된지 10초가 넘은 시점에서 dirty page를 비운다. (dirty page 동기화)
(3) 동작방식 비교: 백그라운드 동기화 vs. 주기적 동기화
•
wb_do_writeback() 함수는 백그라운드와 주기적 동기화 두가지 방식에서 모두 호출된다.
•
vm.dirty_background_ratio로 백그라운드 동기화 스레드를 깨우든 vm.dirty_writeback_centisecs로 주기적 동기화 스레드를 깨우거든 background ratio에 맞게 dirty page를 동기화하는 것뿐 아니라 생성된 지 특정 시간 이상이 된 dirty page 동기화도 함께 진행한다는 것을 알 수 있다.
•
다시 말해서 백그라운드 동기화와 주기적인 동기화는 flush 커널 스레드를 깨우는 기준은 다르지만 실제로 실행하는 작업은 동일하다.
4. dirty page 설정과 I/O 패턴 분석 및 적용
Case 1 vs. Case 2.
구분 | dirty_background_ratio | dirty_radio | dirty_writeback_centisecs | dirty_expire_centisecs | 비고 |
Case 1 | 10 (=10%) | 20 (=20%) | 500 (=5초) | 3000 (=30초) | Case 2에 비해 '덜 자주' 동기화 |
Case 2 | 1 (=1%) | 20 (=20%) | 500 (=5초) | 1000 (=10초) | Case 1에 비해 '자주' 동기화 |
•
공통
◦
vm.dirty_background_bytes=0, vm.dirty_bytes=0 (bytes로 설정하는 절대값 기준 동기화는 off)
◦
총 시스템 메모리: 8GB
◦
iostat -x로 IO 사용량(util) 조회하여 비교
Case 1
•
Dirty Page 변화: 0 kb -> 800MB -> 0 kb
•
I/O 사용량(util): 0% -> 100% -> 0%
•
flush 커널스레드가 깨어나는 조건이 더 길어지는 대신에 한 번에 동기화해야하는 양이 더 많기 때문
Case 2
•
Dirty Page 변화: 0kb -> 80MB (Case 1번과 비교하여 1/10의 수준) -> 0kb
•
I/O 사용량(util): 0% -> 98% -> 0% (최대 98%로 Case 1보다는 작아졌다)
•
자주 flush 커널스레드가 깨어나지만 한 번에 동기화해야하는 양이 Case 1에 비해 적기 때문
분석
flush 커널
•
자주 깨어나면 io util(%)이 비교적 적지만 flush 커널 스레드가 자주 깨어나는 단점이 있음
•
flush 커널 스레드가 너무 자주 깨어나면 스케줄링에 대한 오버헤드가 발생
•
멀티 스레드 환경의 애플리케이션의 경우 불필요하게 자주 깨어나는 flush 커널 스레드에 cpu 리소스를 빼앗길 수 있기 때문에 성능 저하가 발생할 수 있음
•
늦게 깨우면 flush 커널 스레드는 자주 깨어나진 않지만 io util(%)가 높아지는 단점이 있음
적용
•
초당 1MB의 dirty page를 생성하는 애플리케이션이 서로 다른 두개의 시스템에서 동작하고 있다고 가정
초당 10MB의 쓰기작업을 견딜 수 있는 A 시스템 vs. 초당 100MB의 쓰기 작업을 견딜 수 있는 B 시스템
•
A 시스템은 10MB단위로 dirty page를 동기화할 수 있도록 설정하는 것 이 전체적인 성능에 도움이 된다
•
B 시스템은 디스크의 성능이 좋기 때문에 굳이 10MB 수준에서 flush 커널 스레드를 깨울 필요가 없다
•
결론적으로 같은 애플리케이션을 사용하더라도 운영하고 있는 시스템에 따라 dirty page 동기화는 다른 전략을 취해야 한다
5. 요약
•
vm.dirty_ratio의 최소값은 5이다. 5보다 작은 값으로 해도 강제로 5로 재설정된다.
•
vm.dirty_background_ratio가 vm.dirty_ratio보다 크면 강제로 절반값으로 수정된다.
•
vm.dirty_background_bytes, vm.dirty_bytes 값이 설정되어 있다면 각각 vm.dirty_background_ratio, vm.dirty_ratio 값은 무시된다.
•
vm.dirty_writeback_centisecs가 0이면 주기적인 동기화를 실행하지 않는다.
•
vm.dirty_ratio에 설정한 값 이상으로 dirty page가 생성되면 성능 저하가 발생한다.
•
dirty page를 너무 빨리 동기화시키면 flush 커널 스레드가 너무 자주 깨어나게 되며, 너무 늦게 동기화시키면 동기해야할 dirty page가 많아서 vm.dirty_ratio에 도달할 가능성이 커지게 된다. 따라서 워크로드와 시스템 구성에 맞게 dirty page 동기화 수준을 설정해주어야한다.