PgBouncer transaction 모드에서 search_path를 믿기 어려운 이유

앱이 접속 직후 SET search_path를 한 번 실행하고 이후 모든 요청이 같은 schema를 본다고 가정하면 transaction pooling과 충돌할 수 있습니다. 클라이언트 연결의 수명과 backend 연결의 할당 수명이 다르기 때문입니다. 이를 곧바로 “다른 사용자의 schema가 반드시 새어 나온다”는 관찰로 확대해서는 안 됩니다.

두 트랜잭션 사이에는 같은 backend를 기대하지 않는다

가상 사례로 클라이언트 A의 첫 트랜잭션이 서버 연결 1을 쓰고, 다음 트랜잭션이 연결 2를 받을 수 있습니다. 첫 연결에만 남긴 세션 상태를 두 번째 연결이 갖고 있다는 보장은 없습니다. PgBouncer 기능표의 session·transaction 차이는 앱이 이 계약에 협조해야 함을 보여 줍니다.

BEGIN;
SET LOCAL search_path = app_schema, pg_catalog;
SELECT current_schema();
ROLLBACK;

자신의 검토된 schema 이름을 사용하는 트랜잭션 범위 예입니다. PostgreSQL SET LOCAL은 해당 트랜잭션이 끝날 때까지의 설정입니다. 실제 업무 쿼리도 같은 트랜잭션 안에 있어야 하며, 임의의 사용자 입력을 schema 식별자로 연결하지 않습니다. 정적 쿼리라면 명시적인 schema-qualified 테이블 이름도 검토할 수 있습니다.

prepared statement 문제와 섞지 않기

transaction 모드에서 prepared statement가 전부 불가능하다는 설명은 현재 기능과 맞지 않습니다. 프로토콜 수준의 named prepared statement와 SQL PREPARE는 다르며, 지원 여부는 PgBouncer 버전·max_prepared_statements·드라이버 동작을 함께 봐야 합니다. 이 기능이 search_path 같은 임의 세션 상태의 안전성을 대신 보장하지는 않습니다.

검증할 때는 스테이징의 서로 다른 두 클라이언트에서 반복 트랜잭션의 schema 선택을 확인합니다. 한 번 성공한 SELECT만으로 판단하지 말고 transaction 경계·오류 후 rollback·backend 재사용까지 관찰하세요. 여기의 SQL은 문서 기반 예이며 운영 풀에서 실행하지 않았습니다.