File System과 IO

3줄 요약

  • File System과 I/O는 로그, 업로드, 임시 파일, classpath resource, heap dump 같은 백엔드 작업의 실제 비용을 만든다.
  • File descriptor는 파일뿐 아니라 socket, pipe, eventfd 같은 OS 자원을 가리키므로 고갈되면 네트워크 장애처럼 보일 수 있다.
  • Disk I/O 병목은 CPU 사용률보다 iowait, latency, queue, file descriptor, 로그 쓰기 패턴으로 확인해야 한다.

핵심 정리

  • File descriptor는 process가 열린 파일과 socket을 참조하는 번호이며 OS limit의 영향을 받는다.
  • Buffered I/O는 system call 횟수를 줄이지만 flush와 sync 시점이 지연과 데이터 유실 가능성을 만든다.
  • 로그는 단순 문자열 출력이 아니라 파일 쓰기, rotation, 압축, disk quota, stdout 수집 비용을 가진다.
  • Java File I/O는 try-with-resources, stream close, temp file 정리, multipart 설정과 연결된다.
  • I/O 병목은 애플리케이션 thread가 WAITING이 아니라 RUNNABLE 또는 native 상태로 보일 수 있어 OS 지표가 필요하다.

헷갈리는 지점

  • 파일을 닫지 않아도 GC가 처리한다고 생각하기 쉽다.
    • File descriptor는 OS 자원이므로 close를 명시적으로 보장해야 한다.
    • GC timing에 의존하면 고부하에서 descriptor가 먼저 고갈될 수 있다.
  • 로그는 성능에 영향이 작다고 보기 쉽다.
    • 동기 로그, 큰 stack trace, 과도한 debug 로그는 disk와 CPU를 동시에 압박한다.
    • 로그 수집기가 느리면 stdout 쓰기도 지연될 수 있다.
  • Disk가 느리면 CPU만 낮게 나온다고 단정하기 쉽다.
    • I/O wait와 thread 상태, load average가 함께 변할 수 있다.
    • Application에서는 전체 응답 지연으로만 보일 수 있다.

확인 질문

  • File descriptor 고갈이 API 장애로 보이는 이유는 무엇인가?
    • Socket도 file descriptor를 사용하므로 새 연결이나 파일 열기가 실패할 수 있기 때문이다.
  • Buffered I/O를 쓰는 이유는 무엇인가?
    • 작은 write마다 system call과 disk 반영을 하지 않고 묶어서 처리해 비용을 줄이기 위해서다.
  • 로그 파일 쓰기를 운영에서 조심해야 하는 이유는 무엇인가?
    • 로그량, rotation, 압축, 수집 지연이 disk I/O와 애플리케이션 지연을 만들 수 있기 때문이다.