PostgreSQL, 디스크 I/O 병목 현상, 3단계 캐싱 구조로 파헤치다!

by DD
4개월 전
조회수 38

PostgreSQL의 3단계 캐싱 구조(Shared Buffers, OS Page Cache, Disk)를 통해 디스크 I/O 과정 분석

인덱스 부재(Index Absence)로 인한 불필요한 디스크 읽기(Disk Read) 발생 및 성능 저하 문제점 지적

JSONB 컬럼(JSONB Column) 필터링을 위한 GIN 인덱스(GIN Index) 및 부분 인덱스(Partial Index) 제안

work_mem 설정(work_mem Configuration)과 디스크 스필(Disk Spill) 관계를 설명하며 메모리 관리 중요성 강조

PostgreSQL 캐싱 계층 구조 분석

PostgreSQL은 공유 버퍼(Shared Buffers), OS 페이지 캐시(OS Page Cache), 디스크(Disk)의 3단계 캐싱 구조를 가진다. 공유 버퍼는 PostgreSQL 프로세스 메모리에 위치하며, 페이지 요청 시 가장 먼저 확인하는 캐시이다. OS 페이지 캐시는 커널 레벨에서 관리되며, 공유 버퍼 미스 시 디스크 I/O를 줄여준다. 디스크는 가장 느린 계층으로, I/O 요청 시 IOPS 제한에 영향을 받는다.

인덱스 부재로 인한 성능 저하

계정 ID(account_id) 기반의 B-tree 인덱스(B-tree Index) 사용 시, JSONB 컬럼 필터링을 위해 모든 행을 디스크에서 읽어오는 문제가 발생한다. 특히, 필터링 조건에 부합하지 않는 행도 읽어오면서 불필요한 I/O가 발생하여 성능 저하를 야기한다. 인덱스 스캔(Index Scan) 후 필터링 과정에서 많은 디스크 읽기가 발생하며, 이는 IOPS 제한을 초과하는 주요 원인으로 작용한다.

JSONB 컬럼 필터링 최적화

JSONB 컬럼 필터링 성능 개선을 위해 GIN 인덱스(GIN Index)와 부분 인덱스(Partial Index)를 제안한다. GIN 인덱스는 JSONB 데이터 내의 값을 효율적으로 검색할 수 있게 해주며, 부분 인덱스는 특정 조건에 맞는 데이터만 인덱싱하여 불필요한 I/O를 줄인다. 이러한 인덱스 전략은 쿼리 성능(Query Performance)을 향상시키고, 디스크 I/O 부하를 감소시키는 데 기여할 수 있다.

work_mem 설정과 디스크 스필 문제

PostgreSQL의 work_mem 설정(work_mem Configuration)은 정렬, 해시 조인, 해시 집계와 같은 연산에 할당되는 메모리 크기를 결정한다. work_mem이 작으면 정렬 결과가 디스크로 스필(Disk Spill)되어 추가적인 I/O가 발생한다. 반면, work_mem이 크면 각 쿼리 연산이 더 많은 메모리를 사용하게 되어 공유 버퍼 또는 페이지 캐시를 밀어낼 수 있다. 따라서 적절한 work_mem 설정을 통해 메모리 사용량을 최적화해야 한다.

MVCC와 데이터 분산 문제

PostgreSQL의 MVCC(Multi-Version Concurrency Control)는 행 업데이트 시 기존 행을 삭제하고 새로운 행을 추가하는 방식으로 작동한다. 이로 인해 동일한 계정 ID(account_id)를 가진 행들이 물리적으로 테이블 전체에 분산될 수 있다. 인덱스를 사용하여 행을 가져올 때, 각 행이 서로 다른 페이지에 위치하여 랜덤 I/O(Random I/O)가 증가하고 성능 저하를 유발한다.

Three Cache Layers Between SELECT and disk