icon

안동민 개발노트

11장 : I/O 시스템

디스크 스케줄링과 I/O 최적화


하드 디스크는 기계 장치입니다. 헤드가 물리적으로 이동해야 데이터를 읽을 수 있습니다. 여러 프로세스가 동시에 디스크 I/O를 요청하면, 요청을 어떤 순서로 처리하느냐에 따라 성능이 크게 달라집니다.


디스크의 물리적 구조

하드 디스크(HDD)는 회전하는 플래터(Platter) 위에 헤드(Head)가 데이터를 읽고 씁니다.

  • 트랙(Track): 플래터 위의 동심원. 바깥쪽 트랙이 0번입니다.
  • 섹터(Sector): 트랙을 나눈 최소 단위 (전통 512바이트, 현대 4KB/Advanced Format)
  • 실린더(Cylinder): 같은 반지름의 트랙들을 수직으로 모은 것. 헤드 이동 없이 접근 가능합니다.

디스크 접근 시간은 세 요소로 구성됩니다.

구성 요소의미일반적인 시간
탐색 시간(Seek Time)헤드를 원하는 트랙으로 이동3~15ms
회전 지연(Rotational Latency)원하는 섹터가 헤드 아래로 회전2~6ms (7200RPM 약 4.2ms)
전송 시간(Transfer Time)데이터를 실제로 읽기0.01~0.1ms

탐색 시간이 가장 크므로, 디스크 스케줄링의 목표는 헤드 이동 거리를 최소화하는 것입니다.


디스크 스케줄링 알고리즘

요청 큐: 트랙 [98, 183, 37, 122, 14, 124, 65, 67], 현재 헤드 위치: 53.

FCFS (First-Come, First-Served)

요청이 도착한 순서대로 처리합니다. 53→98→183→37→122→14→124→65→67.

헤드 이동 거리: 45+85+146+85+108+110+59+2 = 640 트랙. 헤드가 디스크 전체를 왔다 갔다 합니다.

SSTF (Shortest Seek Time First)

현재 헤드 위치에서 가장 가까운 요청을 먼저 처리합니다. 53→65→67→37→14→98→122→124→183.

헤드 이동: 12+2+30+23+84+24+2+59 = 236 트랙. FCFS보다 크게 개선됩니다.

단점: 멀리 있는 요청이 계속 뒤로 밀려 기아(Starvation)가 발생할 수 있습니다. SJF 스케줄링과 같은 문제입니다.

SCAN (엘리베이터 알고리즘)

헤드가 한쪽 끝에서 다른 쪽 끝으로 이동하면서 경로 상의 모든 요청을 처리하고, 끝에 도달하면 방향을 바꿉니다.

53으로 시작, 왼쪽으로 진행: 53→37→14→0(끝)→65→67→98→122→124→183.

기아가 발생하지 않습니다. 하지만 끝에서 방향을 바꾸면 바로 지나온 영역은 요청이 비어 있으므로 비효율적일 수 있습니다.

C-SCAN (Circular SCAN)

한 방향으로만 요청을 처리하고, 끝에 도달하면 반대쪽 끝으로 즉시 점프합니다. 모든 트랙에 균등한 대기 시간을 제공합니다.

53 오른쪽으로: 53→65→67→98→122→124→183→(끝)→0(점프)→14→37.

LOOK / C-LOOK

SCAN/C-SCAN의 실용적 변형입니다. 디스크의 물리적 끝까지 가지 않고, 마지막 요청이 있는 위치에서 방향을 전환합니다. 불필요한 이동을 줄입니다.

disk_scheduling.py
def sstf(requests, head):
    """SSTF 스케줄링 시뮬레이션"""
    pending = list(requests)
    order = []
    total_movement = 0
    current = head

    while pending:
        # 가장 가까운 요청 찾기
        closest = min(pending, key=lambda x: abs(x - current))
        total_movement += abs(closest - current)
        current = closest
        order.append(closest)
        pending.remove(closest)

    return order, total_movement

def c_look(requests, head):
    """C-LOOK 스케줄링 시뮬레이션"""
    left = sorted([r for r in requests if r < head])
    right = sorted([r for r in requests if r >= head])
    order = right + left  # 오른쪽 끝까지 → 왼쪽 처음으로 점프
    total = 0
    current = head
    for r in order:
        total += abs(r - current)
        current = r
    return order, total

requests = [98, 183, 37, 122, 14, 124, 65, 67]
head = 53

for name, func in [("SSTF", sstf), ("C-LOOK", c_look)]:
    order, movement = func(requests, head)
    print(f"{name}: 이동={movement}, 순서={order}")

알고리즘 비교

알고리즘이동 거리기아공평성실무 사용
FCFS최대없음공평거의 안 씀
SSTF최소에 가까움있음불공평드물게
SCAN중간없음양 끝 불리기반 알고리즘
C-SCAN중간없음균등변형 사용
C-LOOK좋음없음균등널리 사용

SSD의 등장

SSD(Solid State Drive)는 반도체(NAND 플래시) 기반의 저장 장치로, 기계적 움직임이 없습니다. 탐색 시간과 회전 지연이 0입니다.

특성HDDSATA SSDNVMe SSD
순차 읽기~200 MB/s~550 MB/s~3,500 MB/s
순차 쓰기~200 MB/s~520 MB/s~3,000 MB/s
랜덤 읽기 (IOPS)~100~90,000~500,000
랜덤 쓰기 (IOPS)~100~80,000~400,000
지연 시간3~15ms~0.1ms~0.02ms

SSD에서는 전통적인 디스크 스케줄링의 의미가 크게 줄어듭니다. 어떤 위치를 읽든 접근 시간이 거의 동일하기 때문입니다.

NVMe SSD는 PCIe 버스에 직접 연결되어 SATA 인터페이스의 병목을 제거합니다. 큐 깊이도 SATA의 32에서 NVMe의 65,535로 늘어나 다중 I/O 병렬 처리가 가능합니다.


Linux I/O 스케줄러

Linux에서 I/O 요청은 커널의 I/O 스케줄러를 거칩니다.

noop (none): 스케줄링 없이 순서대로 처리합니다. SSD에 적합합니다. 스케줄링 오버헤드가 필요 없기 때문입니다.

deadline (mq-deadline): 각 요청에 만료 시간(읽기 500ms, 쓰기 5초)을 부여합니다. 기아를 방지하면서도 탐색 시간을 최적화합니다. 범용적으로 우수합니다.

CFQ (Completely Fair Queuing): 프로세스별로 공평하게 I/O 대역폭을 분배합니다. 데스크탑 환경에서 반응성을 유지합니다. 커널 5.0에서 제거되었습니다.

BFQ (Budget Fair Queueing): CFQ의 후계자. 프로세스별 I/O 예산을 관리합니다. 인터랙티브 작업(텍스트 편집기)이 백그라운드 작업(대용량 복사)에 의해 느려지지 않게 합니다.

kyber: 지연 시간 기반 스케줄러. 읽기/쓰기의 목표 지연 시간을 설정하고, 큐 깊이를 자동 조절합니다. 고성능 NVMe에 적합합니다.

io_scheduler_tuning.sh
# 현재 스케줄러 확인
cat /sys/block/sda/queue/scheduler
# [mq-deadline] kyber bfq none

# NVMe SSD에 none 설정
echo "none" > /sys/block/nvme0n1/queue/scheduler

# HDD에 mq-deadline 설정
echo "mq-deadline" > /sys/block/sda/queue/scheduler

# I/O 성능 모니터링
iostat -xz 1 5
# %util: 장치 사용률, await: 평균 응답 시간
# await이 높으면 I/O 병목

I/O 성능 최적화 실무

Zero-Copy

전통적인 파일 전송(파일 → 네트워크)은 4번의 데이터 복사가 발생합니다. 디스크→커널 버퍼→사용자 버퍼→커널 소켓 버퍼→NIC. Zero-Copy(sendfile() 시스템 콜)는 사용자 공간 복사를 제거하여 2번으로 줄입니다. Nginx, Kafka 등 고성능 서버가 사용합니다.

I/O 배치와 병합

I/O 스케줄러는 인접한 블록에 대한 개별 요청을 병합(Merge)합니다. 4KB 읽기 4번을 16KB 읽기 1번으로 합칩니다. 애플리케이션 레벨에서도 작은 쓰기를 모아서 큰 쓰기로 배치하면 성능이 크게 향상됩니다.

ionice

프로세스의 I/O 우선순위를 설정합니다. 백업 작업은 낮은 우선순위로, 데이터베이스는 높은 우선순위로 설정하면 서로 간섭이 줄어듭니다.

ionice_example.sh
# Idle 클래스로 백업 실행 (다른 I/O가 없을 때만 실행)
ionice -c 3 rsync -a /data /backup

# Best-effort 클래스의 높은 우선순위로 DB 관련 I/O
ionice -c 2 -n 0 mysqld

다음 장에서는 OS가 자원을 보호하고 시스템의 보안을 유지하는 방법을 다루겠습니다.

목차