컨테이너에서 PostgreSQL 가속화

발행: (2026년 1월 20일 오전 08:45 GMT+9)
11 min read
원문: Dev.to

Source: Dev.to

문제

오래된 CI 머신에서 디스크가 느린 상태로 테스트 스위트를 실행했을 때 PostgreSQL이 주요 병목 현상으로 나타났습니다. 각 테스트 실행에 1시간 이상이 소요되었습니다.

원인? 테스트에서 수많은 데이터베이스 작업을 수행했으며, 각 테스트 후 데이터를 정리하기 위해 TRUNCATE 명령을 사용했습니다. 디스크 I/O가 느린 환경에서는 PostgreSQL이 대부분의 시간을 디스크에 데이터를 동기화하는 데 소비했는데, 이는 데이터 영속성이 필요 없는 일시적인 CI 환경에서는 전혀 필요 없는 작업이었습니다.

테스트 실행 중 top을 확인했을 때 다음과 같은 증거가 보였습니다:

242503 postgres  20   0  184592  49420  39944 R  81.7   0.3   0:15.66 postgres: postgres api_test 10.89.5.6(43216) TRUNCATE TABLE
  • PostgreSQL이 테이블을 TRUNCATE 하는 데 **CPU 81.7 %**를 사용하고 있었습니다.
  • 단일 TRUNCATE15초 이상 실행되었습니다.

디스크가 느린 머신에서는 PostgreSQL이 커널이 데이터를 물리적 저장소에 기록했음을 확인하기를 기다리고 있었으며, 실제로는 테스트 사이에 테이블을 비우는 작업만 필요했을 뿐이었습니다.

해결책 – 세 가지 간단한 PostgreSQL 튜닝

services:
  postgres:
    image: postgres:16.11-alpine
    environment:
      POSTGRES_INITDB_ARGS: "--nosync"
      POSTGRES_SHARED_BUFFERS: 256MB
    tmpfs:
      - /var/lib/postgresql/data:size=1g

1. --nosync 플래그

  • POSTGRES_INITDB_ARGS: "--nosync" 은 데이터베이스 초기화 중에 PostgreSQL이 fsync() 호출을 건너뛰도록 합니다.
  • CI 환경에서는 내구성을 신경 쓸 필요가 없으므로, 컨테이너가 중단되면 그냥 다시 시작하면 됩니다.
  • 이렇게 하면 데이터베이스 설정을 늦추던 비용이 많이 드는 디스크 동기화 작업이 사라집니다.

2. shared_buffers 증가

  • POSTGRES_SHARED_BUFFERS: 256MB (기본값 약 128 MB에서 증가) 로 PostgreSQL이 자주 접근하는 데이터를 캐시할 메모리를 더 많이 할당합니다.
  • 테스트에서 동일한 테이블에 반복적으로 접근할 때 유용합니다.

3. tmpfs에 데이터 디렉터리 마운트

tmpfs:
  - /var/lib/postgresql/data:size=1g
  • tmpfs는 PostgreSQL 데이터 디렉터리를 메모리 기반 파일 시스템으로 만들어 줍니다.
  • 모든 데이터베이스 작업이 RAM에서 이루어지므로 다음과 같이 속도가 크게 향상됩니다:
    • TRUNCATE 작업 – 테스트 간 즉시 정리
    • 인덱스 업데이트 – 디스크 탐색이 필요 없음
    • WAL (Write‑Ahead Log) 기록 – 순수 메모리 작업
    • 체크포인트 작업 – 디스크 플러시 대기 없음

1 GB 크기 제한은 대부분의 테스트용 데이터베이스에 충분히 관대합니다; 데이터 양에 따라 조정하세요.

결과

지표개선
전체 테스트 실행 시간~60 min~10 min6× faster
단일 테스트 (예: API::FilamentSupplierAssortmentsTest#test_create_validation_negative_price)25.5 s0.47 s≈ 55× faster
평균 테스트당 시간 (24 tests)27 s0.45 s≈ 60× faster

샘플 출력

tmpfs 최적화 전

API::FilamentSupplierAssortmentsTest#test_create_validation_negative_price = 25.536s
API::FilamentSupplierAssortmentsTest#test_list_with_a_single_assortment = 29.996s
API::FilamentSupplierAssortmentsTest#test_list_missing_token = 25.952s

tmpfs 최적화 후

API::FilamentSupplierAssortmentsTest#test_list_as_uber_curator = 0.474s
API::FilamentSupplierAssortmentsTest#test_list_as_assistant = 0.466s
API::FilamentSupplierAssortmentsTest#test_for_pressman_without_filament_supplier = 0.420s

왜 이것이 작동하는가

  • TRUNCATE – PostgreSQL이 빈 테이블 상태를 디스크에 동기화하고 있었습니다.
  • Database initialization – 각 CI 실행마다 데이터베이스를 재생성했습니다.
  • INSERT – 테스트 픽스처(사용자, 역할 등)를 생성합니다.
  • Transaction commits – 각 테스트는 롤백되는 트랜잭션 내에서 실행됩니다.
  • Frequent small writes – 테스트 실행 중에 발생하는 빈번한 작은 쓰기 작업입니다.

느린 디스크에서는 테스트 사용자를 생성하거나 테이블을 트렁케이트하는 것과 같은 간단한 작업조차 밀리초가 아니라 가 걸렸습니다. 위의 top 출력은 단일 TRUNCATE가 디스크 I/O를 기다리는 동안 81.7 % CPU를 사용하고 있음을 보여줍니다. 이를 수백 개의 테스트에 곱하면 몇 시간에 달하는 CI 실행 시간이 됩니다.

실용적인 가이드

  • Productionfsync를 활성화한 상태로 유지하고 내구성을 위해 보수적인 설정을 사용하세요.
  • CI – 데이터는 일시적이며, 속도가 내구성보다 더 중요합니다.
  • Profile 파이프라인 – 디스크 I/O가 병목임을 발견했으며, CPU나 메모리는 아닙니다.
  • tmpfs는 궁극적인 디스크‑I/O 제거 수단입니다 – 모든 것이 RAM에 있으면 디스크 병목이 사라집니다.
  • Memorytmpfs는 RAM을 사용합니다; CI 러너에 충분한 메모리가 있는지 확인하세요 (DB에 최소 1 GB 이상).

전체 PostgreSQL 서비스 구성

services:
  postgres:
    image: postgres:16.11-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: dbpgpassword
      POSTGRES_DB: api_test
      POSTGRES_INITDB_ARGS: "--nosync"
      POSTGRES_SHARED_BUFFERS: 256MB
    ports:
      - 5432
    tmpfs:
      - /var/lib/postgresql/data:size=1g

Note: tmpfs 필드는 Woodpecker CI 백엔드에서 공식적으로 지원됩니다(pipeline/backend/types/step.go 참고). 스키마 검증 경고가 발생한다면, 이는 오래된 문서 때문일 가능성이 높으며, 해당 기능은 기대대로 작동합니다.

요점

  • 작은 설정 조정만으로도 CI 속도에 엄청난 영향을 미칠 수 있습니다.
  • 임시 테스트 데이터베이스의 경우 내구성보다 속도에 최적화하는 것이 적절합니다.
  • tmpfs를 사용하면 디스크‑I/O 병목 현상이 사라져, 몇 시간 걸리던 테스트 실행이 몇 분 안에 끝납니다.

즐거운 테스트 되세요! 🚀

CI + tmpfs: 간단하고 효과적이며 코드 변경이 필요 없음

CI는 기본 Docker 지원을 통해 이를 매우 간단하게 만들어 줍니다 – tmpfs: 필드만 추가하면 끝입니다.
GitHub Actions, GitLab CI 또는 기타 플랫폼을 사용 중이라면 docker run--tmpfs 플래그를 사용하거나 커스텀 러너 설정과 같은 우회 방법이 필요할 수 있습니다.

TL;DR: 시도해봤습니다. tmpfs는 여전히 더 빠르고 또한 더 간단합니다.

Source:

공격적인 PostgreSQL 튜닝이 tmpfs와 매치될 수 있을까?

tmpfs로 얻은 극적인 개선을 보고 다음과 같이 생각해 보았습니다:

“PostgreSQL 설정을 공격적으로 튜닝하면 비슷한 성능을 낼 수 있을까?”

이는 tmpfs를 사용할 수 없거나 RAM이 제한된 환경에서 유용할 수 있습니다.

실험: 모든 내구성 기능 비활성화

services:
  postgres:
    command:
      - postgres
      - -c
      - fsync=off                 # 강제 디스크 동기화 건너뛰기
      - -c
      - synchronous_commit=off    # 비동기 WAL 쓰기
      - -c
      - wal_level=minimal         # 최소 WAL 오버헤드
      - -c
      - full_page_writes=off      # WAL 양 감소
      - -c
      - autovacuum=off            # 백그라운드 자동 청소 비활성화
      - -c
      - max_wal_size=1GB          # 체크포인트 횟수 감소
      - -c
      - shared_buffers=256MB      # 메모리 캐시 확대

이러한 공격적인 설정을 적용해도 tmpfs가 여전히 더 빠른 결과를 보였습니다.

기능 / 측면디스크 기반 (fsync=off 적용 시)tmpfs 기반
파일 시스템 오버헤드 – ext4/xfs 메타데이터 작업
디스크 탐색 – 기계적 지연 / 제한된 IOPS
커널 버퍼 캐시 – 메모리 복사
Docker overlay2 – 스토리지 드라이버 오버헤드
설정 복잡도 (7개 이상 옵션)
순수 RAM 작업 – 물리적 저장소 없음
디스크 I/O 제로
최대 성능 – RAM보다 빠른 것은 없음

보너스: 고려할 기타 PostgreSQL CI 최적화

여전히 더 빠른 속도 향상을 원한다면, 다음과 같은 조정을 시도해 보세요.

쿼리 로깅 비활성화

command:
  - postgres
  - -c
  - log_statement=none                # Don't log any statements
  - -c
  - log_min_duration_statement=-1    # Don't log slow queries

추가 조정

  • postgresql.conffsync=off – 동기식 쓰기를 비활성화합니다(--nosync와 유사). 임시이거나 영구적이지 않은 환경에서만 사용하세요.
  • work_mem 증가 – 각 쿼리에 더 많은 메모리를 할당하여 테스트에서 복잡한 작업을 빠르게 수행할 수 있습니다.
Back to Blog

관련 글

더 보기 »