PostgreSQL 장애의 숨겨진 원인: 파일 디스크립터(File Descriptor) 고갈 이해하기
대규모 트래픽을 처리하는 PostgreSQL 기반 시스템에서 갑작스러운 장애가 발생했을 때, 많은 운영자들이 문제의 원인을 데이터베이스 자체의 쿼리 최적화나 설정 문제로 오해하는 경우가 많습니다. 하지만 실제로는 데이터베이스가 아닌, 그 아래 계층인 운영체제(OS)의 자원 한계에 도달했기 때문인 경우가 빈번합니다. 특히 '파일 디스크립터(File Descriptor, FD)' 고갈은 PostgreSQL 시스템의 안정성을 위협하는 가장 흔하면서도 간과하기 쉬운 문제입니다.
파일 디스크립터란 무엇이며 왜 중요한가요?
리눅스(Linux)와 같은 OS 환경에서 파일 디스크립터는 시스템이 사용하는 거의 모든 자원(Resource)을 추상화하여 관리하는 일종의 '핸들'입니다. TCP 소켓 연결, 열려 있는 테이블 파일, 인덱스 파일, WAL(Write-Ahead Log) 세그먼트, 임시 파일 등 데이터베이스가 처리하는 모든 연결과 파일 I/O 작업이 FD를 사용합니다. 즉, FD는 시스템의 자원 사용량을 측정하는 핵심 지표입니다.
PostgreSQL은 프로세스 기반(Process-based)으로 작동합니다. 클라이언트가 연결할 때마다 새로운 OS 프로세스(백엔드)가 생성되며, 이 백엔드 프로세스는 연결 소켓, 접근하는 테이블/인덱스 파일, WAL 세그먼트, 임시 파일 등 각기 다른 FD를 할당받습니다. 따라서 연결 수가 늘어날수록, 그리고 각 연결이 많은 자원을 사용하게 될수록 FD 사용량은 기하급수적으로 증가하게 됩니다.
FD 고갈의 메커니즘: 단순한 연결 증가 이상의 위험성
FD 고갈은 단순히 최대 연결 수(max_connections)에 도달하는 것 이상의 복합적인 메커니즘으로 발생합니다. 이 문제를 이해하는 것이 시스템 안정화의 첫걸음입니다.
1. 시스템 및 프로세스별 한계 (System Limits)
- 시스템 전체 한계 (fs.file-max): OS 커널이 시스템 전체에서 허용하는 최대 FD 개수입니다. 이 한계에 도달하면 어떤 프로세스가 새로운 파일을 열려고 해도 실패합니다.
- 프로세스별 한계 (ulimit -n): 개별 프로세스가 가질 수 있는 최대 FD 개수입니다.
이 두 가지 한계가 모두 존재하며, 장애 진단 시 둘 다 확인해야 합니다. 시스템 전체 한계에 근접했다는 것은 시스템 전체가 위험에 처했다는 신호입니다.
2. 연결 폭증과 자원 누적 (Connection Accumulation)
가장 위험한 시나리오는 애플리케이션이 커넥션 풀러(Connection Pooler) 없이 연결을 무분별하게 열고 닫지 않을 때 발생합니다. 특히 장시간 트랜잭션 상태(idle in transaction)로 대기하는 백엔드 프로세스가 많아지면, 이들이 자원을 점유한 채 OS 리소스를 누적시키게 됩니다. 여기에 배치 작업(Batch Job)이 추가로 대량의 연결을 열면, 시스템은 안전 임계치를 넘어설 수 있습니다.
3. 락 경합과 FD 점유 시간 증가 (Lock Contention)
단순히 연결 수가 많다는 것만으로는 장애가 발생하지 않을 수 있습니다. 문제는 백엔드 프로세스들이 공유 자원(예: LWLock:BufferContent, LWLock:WALInsert)을 두고 경쟁(Contention)할 때 발생합니다. 백엔드들이 락을 기다리며 대기하는 시간이 길어지면, 해당 프로세스들은 FD를 평소보다 훨씬 오랫동안 점유하게 됩니다. 이로 인해 시스템은 정적인 계산보다 훨씬 낮은 동시성 수준에서 FD 고갈 임계치(fs.file-max)를 초과하게 됩니다.
근본적인 해결책: 커넥션 풀러(PgBouncer) 도입의 중요성
FD 고갈의 근본적인 원인은 '너무 많은 백엔드 프로세스'입니다. 단순히 OS의 파일 디스크립터 한계를 높이는 것(sysctl -w fs.file-max=...)은 임시방편일 뿐, 근본적인 문제를 해결하지 못합니다. 마치 물이 넘치기 직전에 댐의 높이를 올리는 것과 같습니다.
가장 효과적이고 구조적인 해결책은 PgBouncer와 같은 커넥션 풀러를 애플리케이션과 PostgreSQL 사이에 배치하는 것입니다. PgBouncer는 다음과 같은 역할을 수행합니다:
- 커넥션 다중화 (Multiplexing): 애플리케이션 측에서 수천 개의 연결을 시도하더라도, PgBouncer가 이를 받아 실제 PostgreSQL 백엔드 프로세스(Backend)의 작은 풀(Pool)로 효율적으로 분배합니다.
- 트랜잭션 기반 관리: 트랜잭션 모드(Transaction Mode)에서 사용될 경우, 트랜잭션이 끝나는 즉시 백엔드 프로세스를 풀로 반납합니다.
이 구조를 통해 PostgreSQL이 수천 개의 백엔드 프로세스를 유지할 필요가 없어지며, 시스템의 FD 사용량이 획기적으로 줄어들어 안정성이 극대화됩니다. PgBouncer를 도입한 후에는 PostgreSQL의 max_connections 설정을 500~1,000개 수준으로 낮추는 것이 권장됩니다.
시스템 안정성 확보를 위한 모니터링 전략
사고가 발생한 후에만 진단하는 것이 아니라, 사전에 위험 징후를 포착하는 것이 중요합니다. 다음 두 가지 지표에 대한 알림(Alert) 설정을 최우선으로 고려해야 합니다.
1. 파일 디스크립터 사용률 모니터링
가장 중요한 지표는 시스템 전체의 FD 사용량입니다. cat /proc/sys/fs/file-nr 명령을 통해 현재 할당된 FD 개수와 시스템 최대 한계를 주기적으로 확인해야 합니다. 이 수치가 최대 한계의 50~60%를 초과하기 시작하면 경고를 발생시켜야 합니다.
2. PostgreSQL 활동 상태 모니터링
pg_stat_activity 뷰를 사용하여 현재 활성 연결 수와 대기 중인 연결 수를 모니터링해야 합니다. 특히 배치 작업이 실행되는 시간대에는 평소보다 높은 연결 수 증가 추이를 감지하고, 연결이 'idle' 상태로 장기간 머무르는 프로세스가 있는지 확인하는 것이 중요합니다.
결론적으로, PostgreSQL의 안정적인 운영은 단순히 DB 튜닝만으로는 완성되지 않습니다. OS 레벨의 자원 관리와 아키텍처적인 접근(커넥션 풀링)을 통해 시스템의 취약점을 보완하는 통합적인 관점이 필요합니다. PgBouncer 도입은 선택이 아닌, 대규모 트래픽 환경에서 필수적인 인프라 개선 작업입니다.
참고 자료 및 출처
- 제목: File Descriptors: The OS Limit That Takes Down PostgreSQL
출처: Planet PostgreSQL
URL: https://postgr.es/p/9lb
원문 출처 및 참고 자료
이 글은 아래 원문 자료를 바탕으로 작성되었습니다.
- 제목: warda bibi: File Descriptors: The OS Limit That Takes Down PostgreSQL
출처: Planet PostgreSQL
URL: https://postgr.es/p/9lb - 제목: Stefan Fercot: Does pgBackRest work with pg_tde?
출처: Planet PostgreSQL
URL: https://postgr.es/p/9la