PostgreSQL 확장 커넥션 풀

PostgreSQL 확장 커넥션 풀 (PostgreSQL Extension Connection Pool)

PostgreSQL 서버는 들어오는 클라이언트 커넥션마다 백엔드 프로세스를 하나씩 띄워요. 이 모델은 postgres 확장에서 성능 문제를 일으킬 수 있는 두 가지 특성으로 이어져요.

  • 새 커넥션을 여는 것이 비교적 비싸요.
  • 동시에 열어 놓는 커넥션 수가 너무 많으면 안 돼요.

이 문제를 해결하기 위해 확장은 인메모리 커넥션 풀을 사용해요. 풀을 쓰면 제한된 수의 커넥션을 사용 후에도 (유휴 상태로) 열어 두고, 이후 쿼리에 재사용할 수 있어요.

출처: 문서

본문

병렬 스캔

DuckDB의 멀티스레드 쿼리 처리 때문에 postgres 확장은 단일 PostgreSQL 테이블을 스캔하는 쿼리에서도 여러 커넥션을 열 수 있어요. 단일 테이블 스캔에 대해 병렬로 실행되는 쿼리 수는 현재 DB 인스턴스의 워커 스레드 수(threads 전역 설정 옵션)만큼 높아질 수 있어요.

threads 파라미터를 너무 높게 설정하면(DuckDB 쪽에서 다른 쿼리를 로컬 처리하는 데 필요할 수 있어서요) 과도하게 많은 PostgreSQL 커넥션을 열려는 시도로 이어질 수 있어요. 이를 막기 위해 (같은 테이블 스캔을 위한) 이런 병렬 쿼리는 커넥션 풀에 허용된 최대 커넥션 수(기본 최대 32)로 추가로 제한돼요.

커넥션 풀이 모든 커넥션 슬롯이 차 있을 때 추가 커넥션을 열도록 구성돼 있어도(force 획득 모드), 추가 병렬 쿼리는 항상 try 모드를 사용해요. 그들을 위한 커넥션을 구할 수 없으면(풀의 최대 커넥션 수 초과) 그 워커 스레드는 이 테이블 스캔 처리에 더 이상 참여하지 않고, 스캔은 풀에서 커넥션을 획득할 수 있었던 워커 스레드들로 진행돼요.

자세한 내용은 [postgres_configure_pool]({% link docs/current/core_extensions/postgres/functions.md %}#postgres_configure_pool) 함수 설명을 참고하세요.

커넥션 프록시

커넥션 풀은 DuckDB가 PostgreSQL 서버에 직접 연결하고 새 커넥션마다 새 백엔드 프로세스가 시작될 때 가장 효과적이에요. DuckDB와 PostgreSQL 사이에 중간 투명 프록시(예: PgBouncer)가 있으면 새 커넥션을 여는 것은 전체 백엔드 프로세스가 아니라 새 네트워킹 소켓만 여는 것뿐이에요.

따라서 커넥션 프록시가 있는 배포 환경에서는 유휴 타임아웃을 더 낮은 값으로 설정하거나(pg_pool_idle_timeout_millis 설정 옵션 또는 [postgres_configure_pool]({% link docs/current/core_extensions/postgres/functions.md %}#postgres_configure_pool) 함수 사용), 풀링을 완전히 비활성화하는(pg_pool_max_connections = 0 설정) 게 더 나을 수 있어요.

Reaper 스레드

postgres 확장은 백그라운드 스레드(소위 "reaper thread") 실행을 지원하는데, 주기적으로 풀의 유휴 커넥션을 스캔해 idle_timeout_millismax_lifetime_millis 값을 초과한 것들을 닫아요.

커넥션 풀마다 별도 스레드가 실행되며, pg_pool_enable_reaper_thread 설정 옵션으로 켜고 끌 수 있어요.

reaper 스레드가 실행 중이 아닐 때도 커넥션의 idle_timeout_millismax_lifetime_millis 값은 확인될 수 있지만, 커넥션을 풀에서 꺼내거나 풀에 반환할 때만 확인돼요.

스레드 로컬 캐시

풀의 유휴 커넥션은 어떤 호출자 스레드든(DuckDB 내부 워커 스레드든 새 클라이언트 스레드든) 사용할 수 있어요. 어떤 경우에는 같은 스레드에서 오는 이후 쿼리가 첫 쿼리와 같은 커넥션에서 실행되도록 보장하는 것이 유리할 수 있어요.

이를 지원하기 위해 pg_pool_enable_thread_local_cache 설정 옵션을 사용할 수 있어요. 이 옵션은 유휴 커넥션이 모든 스레드가 공유하는 메인 캐시 대신 스레드 로컬(그리고 스레드 전용) 캐시에 반환되도록 해요.

Warning 스레드 로컬 커넥션은 reaper 스레드가 확인하지도 정리하지도 않아요. 캐시된 커넥션은 다른 스레드가 사용할 수 없어도 여전히 풀의 자리를 차지하므로 "pool starvation"(풀 기아)을 일으킬 수 있어요. 스레드 로컬 캐시는 주의해서 사용하세요.

설정 옵션

커넥션 풀을 구성하는 데 다음 전역 설정 옵션을 사용할 수 있어요. 이 옵션들은 옵션이 설정된 부착되는 데이터베이스에만 유효해요. 이미 부착된 데이터베이스의 커넥션 풀 설정을 바꾸려면 대신 [postgres_configure_pool]({% link docs/current/core_extensions/postgres/functions.md %}#postgres_configure_pool) 함수를 사용할 수 있어요.

  • pg_pool_acquire_mode (VARCHAR, 기본: 'force'): 풀에서 커넥션을 획득하는 방식. 'force'(항상 연결, 풀 한도 무시), 'wait'(가능할 때까지 블로킹), 'try'(불가능하면 즉시 실패).
  • pg_pool_max_connections (UBIGINT, 기본: 4 <= cpu_count * 1.5 <= 32): 부착된 Postgres 데이터베이스 각각에 대해 커넥션 풀에 캐시할 수 있는 최대 커넥션 수. 이 수는 병렬 스캔을 사용할 때 일시적으로 초과될 수 있어요.
  • pg_pool_wait_timeout_millis (UBIGINT, 기본: 30000): 사용 가능한 커넥션이 모두 차 있는 풀에서 커넥션을 획득할 때 기다리는 최대 밀리초 수.
  • pg_pool_enable_thread_local_cache (BOOLEAN, 기본: FALSE): 스레드 로컬 캐시에서 커넥션 캐싱을 활성화할지 여부. 이런 커넥션은 스레드에 고정되어 다른 스레드가 사용할 수 없지만, 여전히 풀의 자리를 차지해요.
  • pg_pool_max_lifetime_millis (UBIGINT, 기본: 0 미적용): 커넥션을 열어 둘 수 있는 최대 밀리초 수. 이 값은 커넥션을 풀에서 꺼내고 풀에 반환할 때 확인돼요. 커넥션 풀 reaper 스레드가 활성화되면(pg_pool_enable_reaper_thread 옵션) 이 값이 백그라운드에서 주기적으로 확인돼요.
  • pg_pool_idle_timeout_millis (UBIGINT, 기본: 60000): 커넥션을 풀에서 유휴 상태로 유지할 수 있는 최대 밀리초 수. 이 값은 커넥션을 풀에서 꺼낼 때 확인돼요. 커넥션 풀 reaper 스레드가 활성화되면(pg_pool_enable_reaper_thread 옵션) 이 값이 백그라운드에서 주기적으로 확인돼요.
  • pg_pool_enable_reaper_thread (기본: TRUE): 커넥션 풀 reaper 스레드를 활성화할지 여부. 이 스레드는 풀을 주기적으로 스캔해 max_lifetime_millisidle_timeout_millis를 확인하고 지정된 값을 초과한 커넥션을 닫아요. max_lifetime_millisidle_timeout_millis 중 하나는 이 옵션이 효과를 가지려면 0이 아닌 값으로 설정해야 해요.
  • pg_pool_health_check_query (VARCHAR, 기본: SELECT 1): 커넥션이 정상인지 확인하는 데 사용하는 쿼리. 이 옵션을 빈 문자열로 설정하면 헬스 체크가 비활성화돼요.

더 알아보기 (Learn more)