prepared statement, PgBouncer, Prisma | 풀러만 이미 있다고 나오면?
prepared statement s0 already exists는 6543 트랜잭션 풀러가 이름 있는 준비된 문장을 거절한 줄입니다. pgbouncer=true는 런타임 주소에만 두고 마이그레이션은 5432 직접 주소로 나눕니다. 연결 실패와 유니크 제약과 문장을 가릅니다. Prisma, Supabase, 백엔드, 한국 프리뷰. 2026년 9월 슈퍼베이스 문서.
prepared statement "s0" already exists는 표가 없어서가 아니라, 이름 있는 준비된 문장을 트랜잭션 풀러가 거절한 줄입니다. 6543 주소에는 pgbouncer=true를 붙이고, 마이그레이션은 5432 직접 주소로 빼세요.
로컬 5432는 조용한데 슈퍼베이스 풀러만 빨간 경우가 많습니다. 비밀번호를 다시 적어도 문장 이름이 풀러 뒤에서 부딪히면 그대로입니다.
슈퍼베이스는 2026-09-24 안내에서 수파바이저 트랜잭션 모드가 준비된 문장을 받지 않는다고 적습니다. 연결이 안 되는 오류, 풀에 자리가 없는 대기와는 문장이 다릅니다.
6543에서만 이미 있다고 나온다
직접 포트는 되는데, 풀러 포트만 이 문장입니다.
프리즈마는 쿼리를 준비된 문장으로 만들어 이름을 붙입니다. 포스트그레스는 그 이름을 세션(연결) 안에 둡니다. 트랜잭션 모드 풀러는 트랜잭션이 끝나면 서버 연결을 다른 클라이언트에게 넘깁니다. 다음 요청이 같은 이름 s0을 다시 만들면, 이전 세션의 문장이 아직 남아 already exists가 납니다. 포스트그레스 SQLSTATE는 42P05입니다.
로컬 포스트그레스나 슈퍼베이스 직접 연결(호스트 db, 포트 5432)은 풀러가 중간에 없어 이 충돌이 없습니다. 버셀처럼 함수가 짧게 붙었다 떨어지는 환경에서 6543을 쓰면 재현이 잘 됩니다. 한국에서 프리뷰만 가입이 실패하고 로컬은 초록인 자리가 여기인 경우가 있습니다.
직접 5432는 조용하고, 트랜잭션 풀러 6543에서만 준비된 문장 이름이 부딪힌다
세션 모드와 트랜잭션 모드
세션 모드는 준비된 문장을 견디고, 트랜잭션 모드는 기본으로 못 견딥니다.
연결
준비된 문장
프리즈마에서
직접 5432
세션에 그대로 둠
마이그레이션, 오래 켜 둔 서버
수파바이저 세션 모드
지원한다고 슈퍼베이스가 안내
연결 수가 풀 한도 안일 때
수파바이저 트랜잭션 6543
지원하지 않음
pgbouncer=true로 끄기
자체 피지바운서 1.21 미만
트랜잭션 모드에서 이름 충돌
pgbouncer=true
피지바운서 1.21 이상
max_prepared_statements가 0보다 크면 풀러가 다룸
그 값을 켠 뒤에 재시도
프리즈마 문서는 클라이언트가 피지바운서와 안정적으로 붙으려면 풀러가 트랜잭션 모드여야 한다고 적습니다. 그 모드에서 1.21.0 미만은 pgbouncer=true가 필요하고, 1.21.0부터는 max_prepared_statements를 0보다 크게 두면 준비된 문장을 풀러가 중계할 수 있다고 설명합니다. 슈퍼베이스가 호스팅하는 수파바이저 6543은 2026-09-24 안내대로 아직 준비된 문장을 받지 않으니, 그 주소는 끄는 쪽이 맞습니다. 유료 플랜의 전용 피지바운서는 프로젝트 설정에서 모드를 따로 확인하세요.
pgbouncer=true가 끄는 것
이 플래그는 풀 크기가 아니라 준비된 문장을 끕니다.
슈퍼베이스 프리즈마 트러블슈팅은 연결 문자열 끝에 pgbouncer=true를 붙이면 프리즈마가 준비된 문장을 만들지 않는다고 적습니다. 예시 호스트는 리전 풀러이고 포트는 6543입니다. 사용자 이름은 db사용자.프로젝트ref 형태인 풀러 계정을 그대로 씁니다. 비밀번호만 직접 연결과 같다고 사용자 이름을 짧은 쪽으로 바꾸면 이번 문장 전에 인증이 실패합니다.
자리 부족으로 기다리는 오류와는 약이 다릅니다. connection_limit을 올려도 이름 s0이 이미 있다는 문장은 남습니다. 반대로 플래그만 붙이고 함수마다 클라이언트를 새로 만들면 연결 수가 다시 터집니다. 한 프로세스에 클라이언트를 하나 두고, 6543에만 플래그를 다는 짝이 맞습니다.
앱은 6543, 플래그는 그 주소에만
# 런타임. 풀러 사용자 형식은 슈퍼베이스 화면의 문자열을 복사
DATABASE_URL="postgres://USER.PROJECT:PASSWORD@aws-0-REGION.pooler.supabase.com:6543/postgres?pgbouncer=true"
# 마이그레이션. 직접 호스트, 포트 5432, pgbouncer 플래그 없음
DIRECT_URL="postgres://USER:PASSWORD@db.PROJECT.supabase.co:5432/postgres"
마이그레이션은 직접 주소로
migrate는 풀러 주소에 붙이면 같은 s0 문장을 냅니다.
프리즈마는 마이그레이션을 피지바운서 경유로 돌리면 prepared statement "s0" already exists가 날 수 있다고 적습니다. 스키마에 directUrl을 두고, 그 값만 직접 5432로 줍니다. 앱의 url은 풀러와 플래그를 유지합니다. 버셀 프리뷰에도 두 변수를 같이 넣어야, 빌드 중 migrate deploy가 풀러로 새지 않습니다.
직접 연결은 IPv6이거나, 프로젝트에 IPv4 추가 기능이 있어야 열릴 수 있습니다. 슈퍼베이스 풀링 문서는 직접 연결에 풀러 오버헤드가 없고, 서버리스의 짧은 연결에는 서버 쪽 풀러가 필요하다고 구분합니다. 오래 켜 둔 가상머신 하나면 앱 풀만으로 충분한 경우도 있습니다. 함수가 여러 개로 늘어나면 6543이 다시 필요합니다.
슈퍼베이스의 준비된 문장 끄기 안내를 그대로 옮기면 이렇습니다. 드리즐의 postgres.js는 prepare: false입니다. node-postgres는 쿼리 객체에 name을 넣지 않습니다. asyncpg는 statement_cache_size=0입니다. 프리즈마만 연결 주소의 pgbouncer=true입니다. 스택을 바꿔도 6543에 이름을 남기면 같은 42P05가 납니다.
알파인 이미지에서 쿼리 엔진 바이너리가 없는 오류, 유니크 제약 P2002, 호스트에 아예 못 가는 연결과는 로그 한 줄이 다릅니다. 엔진 파일이 없으면 쿼리 문장 전에 프로세스가 멈추고, 유니크는 행이 중복일 때입니다. 이번 문장은 연결은 열린 뒤, 준비된 문장 이름에서 멈춥니다.
플래그를 직접 주소에 붙이지 않기 | pgbouncer=true는 6543 런타임 주소에만 둡니다. 직접 주소까지 끄면 마이그레이션이 기대하는 준비된 문장 동작과 어긋날 수 있습니다. 두 환경 변수를 화면에서 한 번 더 읽으세요.