본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 비동기 I/O 환경에서 Disk I/O 병목을 해결하는 3가지 실무 방법과 차이점

by Papa Martino V 2026. 3. 28.
728x90

파일 시스템(Disk I/O)
파일 시스템 (Disk I/O)

 

1. 도입: 왜 비동기(Async)인데 디스크에서 막힐까?

파이썬의 asyncio는 네트워크 통신(Socket I/O)에서는 혁명적인 성능을 보여줍니다. 하지만 많은 개발자가 간과하는 사실이 있습니다. 현대의 대부분의 운영체제는 파일 시스템(Disk I/O)에 대한 진정한 비동기 시스템 콜을 지원하지 않거나, 지원하더라도 파이썬 표준 라이브러리 수준에서 구현이 까다롭다는 점입니다. 네트워크 I/O는 데이터가 올 때까지 기다리는 동안 루프가 다른 일을 할 수 있지만, 일반적인 파일 읽기/쓰기는 커널 레벨에서 블로킹(Blocking)이 발생하여 이벤트 루프 전체를 멈추게 만듭니다. 본 글에서는 이러한 병목 현상을 근본적으로 해결하기 위한 아키텍처 설계와 실전 코드를 제안합니다.


2. Disk I/O 병목 해결을 위한 3가지 핵심 전략 비교

비동기 루프 안에서 블로킹 함수를 실행할 때 발생하는 문제와 이를 보완하는 기법들의 차이를 명확히 이해해야 합니다.

전략별 아키텍처 및 성능 비교

해결 전략 작동 원리 장점 단점
ThreadPoolExecutor 별도의 스레드에서 블로킹 I/O 수행 구현이 가장 쉽고 범용적임 스레드 생성 및 컨텍스트 스위칭 비용
aiofiles (Task Delegation) 스레드 풀 기반의 비동기 인터페이스 기존 asyncio 문법과 호환성 최상 내부적으로는 결국 스레드를 소모함
Direct I/O & OS Buffer OS 레벨의 버퍼 및 mmap 활용 물리적 I/O 횟수 자체를 감소시킴 데이터 일관성 관리가 복잡함

3. 실전 해결책: run_in_executor 활용 방법

가장 검증된 방법은 이벤트 루프가 디스크 작업을 기다리지 않도록 run_in_executor를 사용하여 해당 작업을 작업자 스레드로 넘기는 것입니다.

[Sample Example] 비블로킹 파일 기록 구현


import asyncio
import time
from concurrent.futures import ThreadPoolExecutor

# 블로킹 방식의 무거운 디스크 쓰기 함수
def heavy_disk_write(filename, data):
    with open(filename, 'a') as f:
        # 대량의 데이터를 기록하여 물리적 I/O 유발
        f.write(data * 1000000)
    return f"{filename} 쓰기 완료"

async def main():
    loop = asyncio.get_running_loop()
    # 1. 전용 스레드 풀 생성 (CPU 코어 수 고려)
    executor = ThreadPoolExecutor(max_workers=4)

    print("메인 루프: 디스크 작업을 예약합니다.")
    
    # 2. run_in_executor를 통해 병목 구간 외주화
    task1 = loop.run_in_executor(executor, heavy_disk_write, 'log_1.txt', 'A')
    task2 = loop.run_in_executor(executor, heavy_disk_write, 'log_2.txt', 'B')

    # 3. 다른 비동기 작업 수행 (루프가 멈추지 않음)
    for i in range(3):
        print(f"메인 루프: 다른 작업 수행 중... {i}")
        await asyncio.sleep(0.5)

    results = await asyncio.gather(task1, task2)
    print(results)

if __name__ == "__main__":
    asyncio.run(main())
        

4. 고수준의 대안: aiofiles와 하드웨어 최적화

직접 스레드 풀을 관리하기 번거롭다면 aiofiles 라이브러리를 권장합니다. 하지만 소프트웨어적 최적화 이전에 Linux의 io_uring과 같은 최신 커널 기능을 지원하는 환경인지 확인하는 것이 중요합니다. 2026년 기준, 최신 파이썬 버전은 커널의 비동기 기능을 더 밀접하게 활용하여 컨텍스트 스위칭 없이도 I/O를 처리하는 방향으로 진화하고 있습니다.

병목 현상 모니터링 팁 1가지

프로그램 실행 중 iostat 명령어를 통해 %util 값이 100%에 근접한다면, 코드의 문제가 아니라 물리적 디스크 대역폭의 한계입니다. 이 경우 로깅 레벨을 조정하거나 인메모리 버퍼(Redis 등)를 앞단에 두는 방법으로 해결해야 합니다.


5. 결론 및 요약

비동기 파이썬 환경에서 디스크 I/O 병목은 피할 수 없는 숙제입니다. 핵심은 이벤트 루프가 직접 파일을 만지게 하지 않는 것입니다. run_in_executor를 사용한 스레드 분리나 aiofiles를 통한 추상화는 서비스의 응답성을 유지하는 데 결정적인 역할을 합니다.


내용 출처 및 참고 자료

  • Python Documentation: asyncio - Event Loop (Executing code in thread or process pools)
  • Linux Kernel Archive: The Rise of io_uring for Asynchronous I/O
  • Real Python: Speed Up Your Python Program With Concurrency
  • High Performance Python 2nd Edition (O'Reilly Media)
728x90