I/O Bound 란?
시스템의 전체 성능이 CPU 연산 속도가 아니라 I/O(입출력) 작업 속도에 의해 결정되는 상태를 말한다.
여기서 I/O는 디스크 접근, 네트워크 통신, 데이터베이스 쿼리, 파일 읽기/쓰기, 시스템 콜 대기 등 CPU 외부장치와 데이터를 주고받는 모든 작업을 포함한다.
즉, CPU가 데이터를 더 처리할 여유가 있음에도 불구하고, I/O 완료를 기다리느라 놀고 있는 시간(iowait)이 많아지는 상황이 I/O Bound 상태이다.
대표 증상
- 평균 CPU 사용률은 낮은데(또는 코어가 남는데), iowait 시간이 높다
- RPS/Throughput이 낮고 레이턴시가 길며, burst에서 급격히 악화(큐가 쌓임)
- 동시성(concurrency)을 늘리면 어느 정도까지는 개선되지만, 임계점을 넘으면 오히려 타임아웃, 재시도 폭증
- 디스크/네트워크/DB 모니터링에서 대기열 길이(queue depth) 증가, 디스크 서비스 시간(svc_t) 상승
전형적 원인 유형
- 디스크 I/O 지연: 작은 랜덤 I/O 다발, sync flush, 로그/스냅샷 경쟁, 파일 시스템 저널링 부담
- 네트워크 I/O 지연: RTT 높음, Nagle/Delayed ACK 영향, 재전송·혼잡, 외부 API 지연
- DB/스토리지 병목: 인덱스 부재, 풀 스캔, 락 경합, Connection Pool 부족, 캐시 미스
- 서비스 체인: Downstream 서비스가 느리거나 재시도 폭탄으로 전파적 지연
- 시스템 콜 대기: read()/write()/accept()/connect() 블로킹, 동기 DNS 조회
개선 전략
( 요약 : 비동기 처리, 캐싱, 배치, I/O 장치 성능 개선, 데이터 접근 최적화 )
- 디스크/파일 I/O
- 캐싱·버퍼링: 핫 데이터 메모리 캐시, 페이지 캐시 확인.
- 배치/병합: 작은 I/O를 묶어 쓰기 합치기(write coalescing).
- 비동기 I/O: aio/io_uring, 파이프라인 처리.
- 데이터 구조 최적화: 순차 접근 유도(LSM-tree, Append-only 로그), 압축/분할.
- 스토리지 계층: SSD/NVMe, RAID 레이아웃, 파일시스템 마운트 옵션 점검.
- 네트워크/외부 API
- 커넥션 재사용(HTTP keep-alive), 풀링 크기 조정.
- 타임아웃/재시도/백오프 표준화, 서킷 브레이커·버크헤드로 격리.
- 배치·압축(gRPC, HTTP/2), 헤더 최소화, Nagle 비활성화(TCP_NODELAY) 상황별 적용.
- 근접 배치: 리전/존 근접, CDN/에지 캐시.
- 데이터베이스
- 적절한 인덱스와 쿼리 플랜 점검(Explain).
- N+1 제거(조인/프리패치/집계), 읽기·쓰기 경로 분리(read replica).
- Connection Pool: 최소/최대, 대기 시간 모니터링.
- 캐시 계층: Redis/멤캐시, TTL/무효화 전략 설계.
- 애플리케이션 아키텍처
- 비동기/논블로킹로 I/O 대기 중 CPU를 해방:
- Python: asyncio, aiohttp, uvloop
- Node.js: 기본 이벤트 루프 활용, Promise/Stream 제대로 사용
- JVM: Netty, Loom(가상 스레드), reactive stack
- 파이프라이닝/프로듀서-컨슈머: 큐 기반 버퍼링, 백프레셔 적용.
- 사전 계산/프리페치: 오프라인 ETL, warm-up, 결과 캐시.
- 배치 전송과 Scatter-Gather 패턴으로 왕복 수(RTT)를 줄이기.
- 비동기/논블로킹로 I/O 대기 중 CPU를 해방:
- 운영 레버
- 동시성 조절: 워커 수/세마포어/풀 크기를 점진적으로 늘려 한계점 관찰(포화 이전 지점이 최적).
- 스로틀링과 큐 제한으로 폭주 방지.
- 리소스 격리: 핫패스/백그라운드 작업 분리, 다른 noisy neighbor와의 경쟁 최소화.
동기 vs 비동기 I/O 예시
CPU 바운드라면 비동기화로 큰 이득이 없지만, I/O 대기 시간이 지배적인 경우 동시성 확대만으로도 체감 성능이 크게 개선된다
# 동기식: 파일/네트워크 읽기 동안 스레드가 놀게 된다.
for url in urls:
resp = requests.get(url, timeout=2)
process(resp.text)
# 비동기식: I/O 대기 중에도 다른 코루틴이 실행되어 CPU 유휴를 줄인다.
import asyncio, aiohttp
async def fetch(session, url):
async with session.get(url, timeout=2) as r:
return await r.text()
async def main(urls):
async with aiohttp.ClientSession() as s:
texts = await asyncio.gather(*(fetch(s, u) for u in urls))
for t in texts: process(t)
asyncio.run(main(urls))
'Backend' 카테고리의 다른 글
| 카카오 로그인 과정, OAuth 동작 방식 (0) | 2024.06.17 |
|---|---|
| FastAPI 동작 구조, 비동기 매커니즘 (1) | 2024.06.15 |
| [Study] 보이저엑스 백엔드 개발자 기술 질문 (1) | 2024.05.29 |