Chapter 8. 저장 장치
HDD의 동작 방식
HDD는 플래터platter라고 불리는 자기 장치에 데이터를 자기 정보로 기록하는 저장 장치이다. 데이터는 섹터sector라는 단위(512바이트 or 4KiB)로 읽고 쓴다. 섹터는 동심원 모양으로 원의 중심부터 바깥 방향으로 분할되어 있다.
플래터의 섹터 데이터를 읽으려면, 먼저 스윙 암을 움직여서 자기 헤드를 플래터의 위로 이동시키고, 그 다음 플래터를 회전시켜 자기 헤드를 목적 섹터의 바로 위에 오도록 한다. HDD로의 데이터 전송 흐름은 다음과 같다.
디바이스 드라이버가 데이터를 HDD에 전달한다. (섹터 번호, 섹터 개수, 읽기 쓰기 등)
스윙 암을 이동시키고 플래터를 회전시켜 자기 헤드를 섹터 위에 위치시킨다.
데이터를 읽거나 쓴다.
2, 3번의 경우 기계적 처리이기 때문에, 다른 전기적 처리에 비해 레이턴시가 길다. 즉 레이턴시 중 대부분이 기계적 처리에 걸리는 시간이다.
HDD의 성능 특성
HDD는 연속된 여러 섹터 데이터를 읽을 때, 플래터를 회전하는 것만으로 한 번에 읽을 수 있다. 한 번에 읽을 수 있는 양은 HDD마다 제한이 있다. 이런 특성때문에 파일시스템은 각 파일의 데이터를 최대한 연속된 영역에 배치되도록 한다. 프로그램의 레이턴시를 줄이려면 다음 사항을 생각해야 한다.
파일 내의 데이터를 연속(혹은 가깝게)으로 배치한다.
연속된 영역에는 한 번에 접근한다. (여러번 나눠서 하면 레이턴시 증가)
파일은 큰 사이즈로 시퀀셜sequential하게 접근한다.
HDD 테스트
사용하지 않는 파티션에서 테스트할 것!!
umount
명령어로 마운트를 해제해야 테스트할 수 있었다. 마운트된 상태로 테스트하면device or resource busy
에러가 발생한다.
HDD 테스트 프로그램
측정 내용
I/O 사이즈에 따른 성능 변화
시퀀셜 접근 vs 랜덤 접근
테스트 프로그램 사양
지정된 파티션의 처음부터 1GiB까지의 영역에 총 64MiB의 I/O를 요청한다.
파라미터
파일명
커널의 I/O 지원 기능 사용 여부(on / off)
읽기 쓰기(r / w)
접근 패턴(seq / rand)
1회당 I/O 사이즈[KiB]
빌드
O_DIRECT
를 사용하기 위해_GNU_SOURCE
매크로를 지정하여 컴파일한다. (소스코드에 정의해도 됨)$ gcc -o output/io src/io.c -D _GNU_SOURCE
아쉽게도 현재 사용 가능한 HDD가 없어서 테스트를 해보지는 못했다. 책 저자의 실험 결과를 바탕으로 정리했다. 직접 해볼 때는 4장에서처럼 log로 남긴 뒤 plot으로 그려보면 좋을 것 같다.
HDD 시퀀셜 접근 테스트
커널의 I/O 지원 기능을 끈 상태로 시퀀셜 접근의 r, w 속도를 측정해본다.
$ sudo su root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 off r seq $i ; done root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 off w seq $i ; done
읽기 쓰기 모두 1회당 I/O 사이즈가 커질수록 스루풋 성능이 향상된다. 하지만 HDD가 한 번에 접근할 수 있는 데이터량의 한계 이상부터는 스루풋이 비슷한데, 이때의 스루풋이 이 HDD의 최대 성능이다.
HDD 랜덤 접근 테스트
커널의 I/O 지원 기능을 끈 상태로 랜덤 접근의 r, w 속도를 측정해본다.
$ sudo su root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 off r rand $i ; done root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 off w rand $i ; done
랜덤 접근의 성능은 시퀀셜 접근보다 전체적으로 떨어진다. 특히 I/O 사이즈가 작을 때 랜덤 접근의 성능이 처참하고, I/O 사이즈가 커질수록 스루풋 성능이 올라간다. I/O 사이즈가 커지면 프로그램의 접근 대기 시간이 줄어들기 때문이다. 그래도 시퀀셜 접근에 비하면 느리다.
블록 장치 계층
리눅스에서는 HDD나 SSD처럼 랜덤 접근이 가능하며 일정 단위로 접근 가능한 장치를 블록 장치라고 분류한다. 이 블록 장치에는 디바이스 파일을 통해 직접 접근하거나 파일시스템을 통해 간접 접근할 수 있다. (대부분은 간접 접근)
블록 장치들에는 공통되는 처리가 많기 때문에, 이를 각 디바이스 드라이버에서 구현하지 않고 커널의 블록 장치 계층에서 처리하여 디바이스 드라이버로 넘긴다.
블록 장치 계층에는 I/O 스케줄러와 미리 읽기 기능이 있다.
I/O 스케줄러
I/O 스케줄러의 역할
병합(merge) : 연속된 섹터에 대한 I/O 요청을 하나로 모은다.
정렬(sort) : 불연속적인 섹터에 대한 I/O 요청을 섹터 번호 순서대로 정리한다.
I/O 스케줄러는 블록 장치에 대한 접근 요청을 일정 기간동안 모아서, 위의 가공을 한 뒤 디바이스 드라이버에 I/O 요청을 한다. 이러면 I/O 성능이 좋아진다. 정렬 후 병합하는 경우도 있다.
이 I/O 스케줄러 덕분에, 블록 장치의 성능 특성에 대해 자세히 알지 못한 채로 애플리케이션을 개발해도 어느 정도의 I/O 성능은 보장된다.
미리 읽기read-ahead
프로세스에서 데이터에 접근할 때 공간적 국소성Spatial locality이라는 특징이 존재하므로, 이를 이용한 미리 읽기라는 기능이 있다. 특정 영역에 I/O 요청을 하면, 그 바로 뒤의 연속된 영역을 미리 읽어두는 기능이다. 해당 요청 직후에 예측대로 연속된 영역에 접근하면, 이미 데이터 읽기가 되어 있으므로 저장 장치에서의 읽기를 생략할 수 있고, 이를 통해 성능이 향상된다. 예측대로 접근하지 않았을 경우 단순히 읽었던 데이터를 버린다. (명령어 파이프라인과 비슷한 느낌)
커널의 I/O 지원 기능 테스트 - HDD 시퀀셜 접근
커널의 I/O 지원 기능을 켠 상태로 시퀀셜 접근의 r, w 속도를 측정해본다.
$ sudo su root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 on r seq $i ; done root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 on w seq $i ; done
I/O 지원 기능을 껐을 때에 비해 스루풋 성능이 엄청나게 향상되었다. I/O 사이즈가 작을 때부터 거의 HDD의 한계까지 성능이 나오는데, 미리 읽기 덕분이다.
io 프로그램 실행 중에 다른 터미널로
iostat -x
명령어를 실행하면 미리 읽기에 대한 정보를 관찰할 수 있다.$ iostat -x -p 지정파티션 1 # 1초마다 관찰
$ iostat -x -p nvme0n1p5 1 Linux 5.13.13-surface (ubun2-Surface-Pro-7) 2021년 09월 25일 _x86_64_ (8 CPU) avg-cpu: %user %nice %system %iowait %steal %idle 4.59 0.01 1.06 0.05 0.00 94.28 Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz aqu-sz %util nvme0n1p5 4.49 214.47 1.43 24.21 0.24 47.77 7.50 224.07 6.86 47.76 1.67 29.87 0.00 0.00 0.00 0.00 0.00 0.00 0.01 0.77
rkB/s 필드가 초당 읽은 용량을 의미한다.
지원 기능을 끈 상태와 켠 상태 두 가지를 비교해보면, 껐을 때는 여러 번에 걸쳐 읽어오고 켰을 때는 한번에 많이 불러오는 것을 알 수 있다. 즉 미리 읽기를 통해 데이터를 메모리에 미리 불러와서 스루풋 성능을 높이는 것이다.
rrqm/s 필드가 읽기 처리에 대한 병합 처리량이다. 읽기에 I/O 스케줄러가 동작하는 경우는 여러 프로세스로부터 병렬로 읽기를 하거나, 비동기 I/O 등이므로, 이 테스트 프로그램에서는 병합 기능이 동작하지 않는다.
wrqm/s 필드가 쓰기 처리에 대한 병합 처리량이다. 자잘한 I/O 쓰기 요청을 병합하여 일정 사이즈 이상이 되면 HDD의 실제 I/O가 수행되어 쓰기 처리를 고속화한다.
커널의 I/O 지원 기능 테스트 - HDD 랜덤 접근
커널의 I/O 지원 기능을 켠 상태로 랜덤 접근의 r, w 속도를 측정해본다.
$ sudo su root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 on r rand $i ; done root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 on w rand $i ; done
지원 기능을 켜도 I/O 사이즈가 작을 때는 스루풋 성능이 처참하다.
읽기의 경우 미리 읽기를 해도 랜덤 접근으로 연속된 섹터가 아닌 다른 데이터에 대해 읽기 요청을 하므로 미리 읽기한 데이터가 버려지는 것이다.
쓰기의 경우 I/O 사이즈가 작을 때 I/O 스케줄러의 효과가 있기는 하다. 즉 랜덤 접근 중에 우연히 접근 영역이 연속된 I/O 요청에 대해 병합이 발생한다.
SSD의 동작 방식
SSD는 HDD와 달리 전기적으로 데이터에 접근하기 때문에, 스윙 암이나 플래터 등 기계적인 장치가 움직이는 시간이 없다. 즉 전체적으로 성능이 HDD보다 훨씬 빠르며, 랜덤 접근 성능도 좋다.
이 테스트는 sd카드에서 해보려했으나, 쓰기 테스트에서 1분이 넘게 걸리는 것을 보고 제대로 측정이 안된다고 판단하여 책의 내용을 보고 정리하였다.
SSD 테스트
SSD 시퀀셜 접근 테스트
커널의 I/O 지원 기능을 끈 상태로 시퀀셜 접근의 r, w 속도를 측정해본다.
$ sudo su root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 off r seq $i ; done root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 off w seq $i ; done
HDD에 비하면 모두 몇 배는 빠르다.
SSD 랜덤 접근 테스트
커널의 I/O 지원 기능을 끈 상태로 랜덤 접근의 r, w 속도를 측정해본다.
$ sudo su root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 off r rand $i ; done root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 off w rand $i ; done
랜덤 접근의 경우 SSD의 스루풋 성능이 HDD보다 훨씬 좋다. 시퀀셜 접근에 비해서는 성능이 낮으나, HDD에서의 경우보다는 그 차이가 적다. 또한 I/O 사이즈가 커지면 시퀀셜 접근과 랜덤 접근의 성능이 비슷해진다.
커널의 I/O 지원 기능 테스트 - SSD 시퀀셜 접근
커널의 I/O 지원 기능을 켠 상태로 시퀀셜 접근의 r, w 속도를 측정해본다.
$ sudo su root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 on r seq $i ; done root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 on w seq $i ; done
HDD의 경우와 마찬가지로 I/O 사이즈가 작을 때도 스루풋 성능이 한계에 접근한다. 읽기 성능은 미리 읽기의 효과이고, 쓰기 성능은 I/O 스케줄러가 수행하는 병합 처리의 효과이다.
쓰기에서 I/O 사이즈를 크게 하면 I/O 지원 기능을 껐을 때보다 스루풋 성능이 낮아지는데, 이는 I/O 스케줄러의 오버헤드가 커지기 때문에 발생한다. HDD의 경우에는 기계적 처리의 소요 시간이 이 오버헤드보다 커서 발견하지 못했던 문제점인데, SSD는 전기적 처리로 인한 고속화로 이런 문제점이 보이게 된다.
커널의 I/O 지원 기능 테스트 - SSD 랜덤 접근
커널의 I/O 지원 기능을 켠 상태로 랜덤 접근의 r, w 속도를 측정해본다.
$ sudo su root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 on r rand $i ; done root# for i in 4 8 16 32 64 128 256 512 1024 2048 4096 ; for> do time output/io 지정파티션 on w rand $i ; done
시퀀셜 접근에 비해서는 당연히 느리지만, 어느정도 I/O 사이즈가 커지면 시퀀셜 접근을 할 때와 성능이 비슷해진다.
I/O 지원 기능을 껐을 때와 비교하면, 읽기는 비슷하고(HDD에서의 이유와 같음 - 미리 읽기가 소용없어서) 쓰기는 오히려 성능이 떨어진다. 이는 I/O 스케줄러의 오버헤드를 무시할 수 없고, SSD에서는 정렬의 효과가 별로 없기 때문이다.
정리
사용자가 블록 장치의 원리에 대해 깊게 생각하지 않아도, 커널의 지원 기능 덕분에 어느 정도의 접근 최적화는 이루어진다. 하지만 모든 경우에서 최적의 성능이 나오는 것은 아니며, 바로 위에서처럼 SSD의 경우 특정 상황에서는 I/O 스케줄러의 오버헤드때문에 성능이 오히려 저하되는 경우가 발생할 수 있다.
앞에서도 적었듯 프로그램을 개발할 때에는 다음 사항을 최대한 지키는게 여러모로 좋다.
파일 안의 데이터가 연속되거나 가깝도록 배치한다.
연속된 영역에 대한 접근은 되도록 한 번에 처리한다.
파일에는 최대한 큰 사이즈로 시퀀셜하게 접근한다.
참고
소스 코드
'Linux > Linux Structure' 카테고리의 다른 글
실습과 그림으로 배우는 리눅스 구조 학습 내용 (0) | 2021.09.26 |
---|---|
Chapter 7. 파일시스템 (0) | 2021.09.26 |
Chapter 6. 메모리 계층 (0) | 2021.09.26 |
Chapter 5. 메모리 관리 (3) | 2021.09.26 |
Chapter 4. 프로세스 스케줄러 (0) | 2021.09.26 |