PostgreSQL 성능 최적화를 위한 3가지 핵심 전략: '8월의 저주' 극복기

XORuby Atlanta 2025 - Scaling PostgreSQL Beyond Query Optimization by Alex Yarotsky

작성자
jeff
발행일
2026년 01월 06일

핵심 요약

  • 1 PostgreSQL의 기본 설정을 최적화하여 메모리 캐싱 효율을 높이고 쿼리 플래너의 성능을 개선해야 합니다.
  • 2 Autovacuum 설정을 정밀하게 조정하여 불필요한 데이터(dead tuples) 축적을 방지하고 데이터베이스 블로트를 효과적으로 관리해야 합니다.
  • 3 장기 실행 트랜잭션을 엄격히 관리하고 타임아웃을 설정하여 애플리케이션 지연 및 치명적인 트랜잭션 ID 랩어라운드 위험을 예방해야 합니다.

도입

본 강연은 Hubstaff에서 6년간 겪었던 '8월의 저주' 사례를 통해 PostgreSQL 성능 저하의 근본적인 원인과 해결책을 제시합니다. 매년 8월마다 데이터베이스 CPU 부하가 급증하고 쿼리 속도가 현저히 느려지는 현상이 반복되었으나, 기존 SQL 최적화만으로는 해결할 수 없었습니다. 이는 단순히 효율적인 쿼리 작성이나 `EXPLAIN ANALYZE` 활용을 넘어, PostgreSQL의 심층적인 설정과 동작 방식에 대한 이해가 필수적임을 시사합니다.

Hubstaff는 ‘8월의 저주’를 극복하는 과정에서 80/20 법칙에 따라 최소한의 노력으로 최대의 효과를 얻을 수 있는 세 가지 핵심 교훈을 얻었습니다. 이 교훈들은 모든 규모의 PostgreSQL 환경에 적용 가능하며 즉각적인 성능 개선을 약속합니다.

1. PostgreSQL 기본 설정 변경

PostgreSQL은 다양한 환경에서 작동하도록 설계되어 기본 설정이 최적화되어 있지 않습니다. 다음 세 가지 설정을 조정하여 성능을 크게 향상시킬 수 있습니다.

  • shared_buffers: 데이터 캐싱을 위해 PostgreSQL에 할당할 메모리 양을 지정합니다. 총 메모리의 25-40%를 할당하는 것이 좋습니다. 읽기 집약적인 워크로드에는 40%까지 늘릴 수 있습니다.

  • effective_cache_size: 쿼리 플래너에게 시스템에 사용 가능한 총 메모리 양에 대한 힌트를 제공합니다. 총 메모리의 50-75%로 설정하면 플래너가 메모리를 더 많이 활용하는 계획을 세우는 데 도움이 됩니다.

  • random_page_cost: 디스크에서 임의 페이지를 읽는 비용을 나타냅니다. 기본값 4.0은 구식 회전 디스크에 적합하며, SSD 환경에서는 1.5-2.0으로 낮추어 인덱스 스캔을 더 선호하도록 유도해야 합니다. 이를 통해 쿼리 속도가 획기적으로 빨라질 수 있습니다.

2. Autovacuum 튜닝

PostgreSQL의 MVCC(다중 버전 동시성 제어) 모델은 업데이트 및 삭제 시 데이터를 물리적으로 제거하지 않고 ‘dead tuples’로 표시합니다. 이 dead tuples는 공간을 차지하고 인덱스 효율성을 저하시키며 ‘블로트(bloat)’ 현상을 유발합니다. Autovacuum은 이러한 dead tuples를 정리하는 자동화된 프로세스입니다. 그러나 대규모 테이블에서는 Autovacuum이 지연될 수 있으므로 다음 설정을 조정해야 합니다.

  • autovacuum_vacuum_scale_factor: 테이블 크기 대비 dead tuples의 비율이 얼마가 되어야 vacuum이 시작될지 결정합니다. 기본값 0.2(20%)는 너무 높으므로 0.03-0.05(3-5%) 범위로 낮추어 블로트 축적을 조기에 방지해야 합니다.

  • autovacuum_analyze_scale_factor: 테이블 변경 비율이 얼마가 되어야 analyze가 실행될지 결정합니다. 기본값 0.1(10%)은 쿼리 플랜이 최신 상태로 유지되기에는 너무 드물므로 0.05(5%) 이하로 설정하여 쿼리 플래너가 최적의 실행 계획을 수립하도록 돕습니다.

3. 장기 실행 트랜잭션 방지

트랜잭션 내에서 HTTP 호출, 과도한 Ruby 로직 처리, 장시간 데이터베이스 락 유지와 같은 느린 작업을 수행하면 심각한 문제가 발생합니다.

  • 성능 저하 및 DDL 블로킹: 애플리케이션 속도가 느려지고, 단순한 SELECT 쿼리라도 ALTER TABLE과 같은 DDL(데이터 정의 언어) 작업을 블로킹하여 무정지 배포를 방해할 수 있습니다.

  • Autovacuum 방해: 장기 실행 트랜잭션은 Autovacuum이 dead tuples를 정리하는 것을 막아 블로트를 가속화합니다.

  • Transaction ID Wraparound 위험: PostgreSQL은 모든 트랜잭션에 32비트 XID(Transaction ID)를 할당합니다. 40억 개의 XID가 모두 사용되고 20억 개의 unvacuued dead tuples가 축적되면 PostgreSQL은 더 이상 쓰기 작업을 허용하지 않는 치명적인 ‘Transaction ID Wraparound’ 상태에 빠질 수 있습니다.

이를 방지하기 위해 다음 설정을 적용해야 합니다.

  • statement_timeout: 개별 SQL 문에 대한 실행 시간 제한을 설정합니다. 이상적으로는 5초 미만으로 설정하여 느린 쿼리를 조기에 차단합니다.

  • idle_in_transaction_session_timeout: 트랜잭션이 열려 있지만 아무 작업도 수행하지 않는 ‘유휴’ 상태일 때 자동으로 트랜잭션을 종료하는 시간 제한을 설정합니다. 이를 통해 장기 유휴 트랜잭션으로 인한 문제를 방지합니다.

결론

이 세 가지 PostgreSQL 최적화 전략은 Hubstaff가 겪었던 '8월의 저주'와 같은 심각한 데이터베이스 문제를 해결하는 데 결정적인 역할을 했습니다. `shared_buffers`, `effective_cache_size`, `random_page_cost`와 같은 기본 설정 변경, `autovacuum_vacuum_scale_factor`, `autovacuum_analyze_scale_factor`와 같은 Autovacuum 튜닝, 그리고 `statement_timeout`, `idle_in_transaction_session_timeout` 설정을 통한 장기 실행 트랜잭션 관리는 모든 개발자가 반드시 숙지하고 적용해야 할 사항입니다. 이러한 설정들은 최소한의 노력으로 최대의 효과를 가져오며, 데이터베이스 성능을 안정적으로 유지하고 잠재적인 재앙을 예방하는 데 필수적입니다.

댓글 0

로그인이 필요합니다

댓글을 작성하거나 대화에 참여하려면 로그인이 필요합니다.

로그인 하러 가기

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!