I/O 작업이 지나가는 관문, I/O 스케줄러
아래 내용은 DevOps와 SE를 위한 리눅스 커널 이 야기 11장 I/O 작업이 지나가는 관문, I/O 스케줄러를 읽고 정리한 내용입니다.
사용자가 발생시키는 읽기, 쓰기와 같은 I/O 작업은 가상 파일 시스템, 로컬 파일 시스템 등의 경로를 거친 후 블록 디바이스로 전달되기 전에 I/O 스케줄러를 거치게 된다. I/O 스케줄러는 상대적으로 접근 속도가 느린 디스크에 대한 성능을 최대화하기 위해 구현된 커널의 일부분이며 모든 I/O 작업은 이 I/O 스케줄러를 통해서 블록 디바이스에 전달된다.
HDD와 SSD
- HDD의 경우 헤드의 움직임을 최소화하고 한번 움직일 때 최대한 처리해야 I/O 성능이 극대화될 수 있다. (HDD는 디스크 암에 연결된 헤드가 데이터가 있는 곳으로 이동해야 해당 데이터를 읽을 수 있다.)
- 반면에 SSD는 장치를 움직이지 않고 전기적 신호를 이용해서 접근하기 때문에 임의로 특정 섹터에 접근할 때 소요되는 시간은 모두 동일하다.
디스크와 관련된 작업은 시간이 오래 소요되기 때문에 커널은 I/O 스케줄러를 통해서 조금이라도 성능을 극대화하려 한다. 성능 극대화 작업을 위해서 병합과 정렬이라는 두 가지 방법을 사용한다.
병합과 정렬
- 병합은 여러 개의 요청을 하나로 합치는 것, 각각의 요청들을 하나의 큰 요청으로 합쳐서 넘겨주면 헤드의 움직임을 최소화할 수 있다.
- 정 렬이란 여러 개의 요청을 섹터 순서대로 재배치하는 것을 의미, 정렬될 경우 먼저 발생한 요청이 더 늦게 처리되는 문제가 생길 수도 있음
정렬되지 않을 경우, HDD에서 3번, 5번, 1번 섹터 순으로 데이터를 읽는다면 헤드는 3번에서 5번으로 이동 후에 다시 1번으로 이동한다. 만약 정렬될 경우 1번, 3번, 5번 순으로 읽기 때문에 1번에서 3번으로 이동 후 5번으로 이동하여 데이터를 읽는다. 즉, 정렬되지 않을 경우의 이동 거리보다 정렬된 경우의 헤드의 이동 거리가 더 짧기 때문에 정렬을 통해 헤드의 움직임을 최소화할 수 있다.
SSD는 오히려 정렬하는 데 CPU 리소스를 쓸데없이 사용하여 성능이 더 나빠질 수 있어 HDD와는 조금 다른 I/O 스케줄러를 사용한다.
I/O 스케줄러
[ec2-user@ip-172-31-35-50 queue]$ pwd
/sys/block/xvda/queue
# []표시 현재 설정되어 있는 I/O 스케줄러: mq-deadline
[ec2-user@ip-172-31-35-50 queue]$ cat scheduler
[mq-deadline] kyber bfq none
# I/O 스케줄러 변경 및 iosched 디렉터리 확인
[root@ip-172-31-35-50 queue]$ echo bfq > ./scheduler
[root@ip-172-31-35-50 queue]$ cat scheduler
mq-deadline kyber [bfq] none
[root@ip-172-31-35-50 queue]$ ls ./iosched/
back_seek_max fifo_expire_async low_latency slice_idle strict_guarantees
back_seek_penalty fifo_expire_sync max_budget slice_idle_us timeout_sync
# I/O 스케줄러 변경 및 iosched 디렉터리 확인
[root@ip-172-31-35-50 queue]$ echo mq-deadline > ./scheduler
[root@ip-172-31-35-50 queue]$ cat scheduler
[mq-deadline] kyber bfq none
[root@ip-172-31-35-50 queue]$ ls ./iosched/
fifo_batch front_merges read_expire write_expire writes_starved
I/O 스케줄러를 바꾸는 것뿐 아니라 iosched 디렉터리에 있는 파일들의 값을 변경하여 같은 I/O 스케줄러를 사용하더라도 조금 더 워크로드에 최적화할 수 있다.
현재 위 서버(xvda)에서는 본문(sda)과는 다른 스케줄러 종류를 가지고 있지만 본문에서 소개하고 있는 3가지 I/O 스케줄러는 cfq I/O 스케줄러
, deadline I/O 스케줄러
, noop I/O 스케줄러
가 있다.
- cfq I/O 스케줄러: (Completely Fair Queueing의 약자) 프로세스들이 발생시키는 I/O 요청들이 모든 프로세스에서 공정하게 실행, 일부 I/O를 많이 일으키거나 더 빨리 처리되어야 하는 I/O를 가진 프로세스들의 경우에 자신의 차례가 될 때까지 기다려야 하기 때문에 I/O 요청에 대한 성능이 낮아질 가능성이 있다.
- deadline I/O 스케줄러: deadline은 그 이름에서 유추할 수 있듯이 I/O 요청별로 완료되어야 하는 deadline을 가지고 있는 I/O 스케줄러이다. 예를 들어 읽기 요청은 500ms 안에 완료되어야 하고 쓰기 요청은 5s 안에 완료되어야 하는 등의 요청별 deadline이 주어지며, 가능한 한 해당 deadline을 넘기지 않도록 동작한다.
- deadline I/O 스케줄러는 쓰기 요청보다 읽기 요청을 더 많이 처리하도록 설계되었으며, batch와 같은 형태로 한번에 다수의 I/O 요청을 처리하고 있음을 알 수 있 다.
- noop I/O 스케줄러: 병합 작업만 하는 I/O 스케줄러, 플래시 디스크는 어느 섹터에 어디서 접근하든 소요되는 시간이 같기 때문에 굳이 정렬을 해서 헤더의 움직임을 최소화할 필요가 없다. 오히려 정렬하는 데 시간을 뺏겨서 성능이 더 안 좋아질 수 있기 때문에 플래시 디스크의 경우는 noop I/O 스케줄러 사용을 권고하고 있으며 이 스케쥴러는 튜닝 가능한 값도 존재하지 않는다.
cfq vs deadline
I/O를 일으키는 프로세스가 여러 개인 상황에서는 cfq와 deadline은 차이가 있다. cfq는 다수의 프로세스들의 요청을 공평하게 분배해서 처리하는 한편, deadline은 요청이 어떤 프로세스로부터 발생했느냐가 아니라 언제 발생했느냐를 기준으로 처리한다.
추가로 본문에서는 fio라는 명령어의 다양한 옵션을 활용해서 실제 서버와 유사한 워크로드를 만들어 CPU 코어 개수를 늘려가며 순차 방식에서의 읽기와 쓰기 성능 그리고 임의 읽기와 임의 쓰기 성능을 비교한다. 이때 비교할 스케쥴러는 cfq와 deadline이다. 분문에서 테스트 결과는 cfq와 deadline 모두 순차 읽기와 쓰기는 성능이 비슷했으나 임의 읽기 쓰기에서는 deadline이 cfq보다 우수한 성능을 보여준다.
I/O 워크로드
다수의 프로세스가 동등하게 I/O 요청을 일으키는 경우라면 cfq I/O 스케줄러가 조금 더 좋은 성능을 보여줄 수 있다. 여기서 좋은 성능이란 더 빠른 응답 속도보다는 모든 프로세스가 비슷한 수준으로 I/O 요청을 처리하는 것을 의미한다. 주로 동영상 스트리밍이나 인코딩을 처리하는 서버에 유리한 환경이며 다수의 프로세스가 다수의 사용자로부터 받은 스트리밍 혹은 인코딩 요청을 처리하게 되는데 그 프로세스들이 균등하게 I/O 요청을 처리할 수 있다면 특정 사용자의 요청이 빨리 처리되거나 하는 불균등 현상을 줄일 수 있다.
반대로 DB 서버와 같이 특정 프로세스가 많은 양의 I/O 요청을 일으키는 경우에는 deadline I/O 스케줄러가 더 효율적이다. 타임 슬라이스에 따라서 특정 프로세스의 I/O 요청이 처리되지 않는 idle 타임이 존재하지 않고 I/O 요청의 발생 시간을 기준으로 처리되기 때문이다.
웹서버에서 I/O를 일으키는 프로세스들을 가장 효과적으로 파악할 수 있는 명령어인 iotop(iotop -P
)으로 다수의 프로세스가 I/O 요청을 일으키는 것을 확인하였고 웹 서버의 경우 사용자의 요청을 처리하는 프로세스들이 모두 공평하게 I/O를 사용하도록 보장해주면 전체적인 성능에 도움이 되기 때문에 cfq I/O 스케줄러를 사용하는 것이 좋다고 한다.
iotop -P
를 사용하는 이유는 cfq는 쓰레드가 아닌 프로세스 별로 큐를 할당하기 때문에 -P 옵션을 붙였다.
또한 perf-tools의 iosnoop이란 툴을 사용해서 순차 읽기 테스트 시 명령 결과를 보았는데 순차 I/O에서는 블록이 순차적으로 증가하였고 LATms도 짧았지만 임의 읽기시에 블록의 수가 앞뒤로 큰 차이를 보였으며 LATms도 크게 증가한 모습을 보였다. 이 결과로 임의 읽기/쓰기가 많아지면 헤드의 움직임이 많아지기 때문에 이에 따라 요청 처리 시간도 길어진다는 것을 알 수 있었다.