연결 풀 뒤에서 비동기 드라이버를 쓸 때 만난 것들
트랜잭션 모드 풀러 · IPv6 전용 엔드포인트 · URL 인코딩된 비밀번호. 데이터베이스에 붙는 것만으로 하루를 쓴 원인들을 정리했습니다.
관리 시스템 백엔드는 파이썬 비동기 프레임워크에, 데이터베이스는 관리형 PostgreSQL을 씁니다. 연결은 트랜잭션 모드 풀러를 거칩니다. 이 조합에서 마주친 문제들이 하나같이 "연결이 안 된다" 또는 "가끔 안 된다" 형태로 나타나서, 원인을 좁히는 데 시간이 걸렸습니다.
1. 준비된 구문 캐시를 꺼야 한다
비동기 드라이버는 성능을 위해 쿼리를 서버에 "준비된 구문"으로 등록해 두고 재사용합니다. 그런데 트랜잭션 모드 풀러는 트랜잭션이 끝나면 물리 연결을 다른 세션에 넘깁니다. 다음에 같은 이름으로 구문을 찾으면 그 연결에는 없습니다.
증상이 고약합니다. 항상 실패하지 않고, 부하가 있을 때만 간헐적으로 실패합니다. 연결이 재사용되는 타이밍에 달려 있으니까요.
python
connect_args = {
"statement_cache_size": 0, # 풀러가 트랜잭션 모드라 준비된 구문을 재사용할 수 없다
...
}이 값은 환경변수로 빼지 않고 코드에 박아 두고, 옆에 이유를 적었습니다. 환경에 따라 바꿀 값이 아니라 이 연결 방식에서는 항상 0이어야 하는 값이기 때문입니다. 이유가 없으면 다음에 "성능 최적화"로 되돌아옵니다.
2. 직접 연결 주소는 IPv6 전용이었다
덤프를 뜨려고 풀러가 아닌 직접 연결 주소를 썼는데 이름 해석 자체가 실패했습니다. 확인해 보니 그 호스트에는 AAAA 레코드만 있고 A 레코드가 없었습니다. IPv4 회선에서는 접속이 아니라 DNS 단계에서 끝납니다.
해결은 세션 모드 풀러였습니다. 호스트는 같고 포트만 다르며, IPv4 주소가 있습니다. 정리하면 용도별로 이렇게 나뉩니다.
- 앱 런타임 — 트랜잭션 모드 풀러. 짧은 요청이 많은 웹 서버에 맞습니다.
- 덤프 · 복원 · 콘솔 접속 — 세션 모드 풀러. 준비된 구문 제약이 없습니다.
- 직접 연결 — IPv4 환경에서는 쓸 수 없습니다.
3. 비밀번호를 두 번 다르게 다뤄야 한다
접속 문자열에 담긴 비밀번호에 특수문자가 있으면 URL 인코딩을 해야 합니다. 그런데 비동기 드라이버는 접속 URL을 파싱하며 자동으로 디코딩하고, 명령줄 도구가 쓰는 클라이언트 라이브러리는 환경변수 값을 그대로 씁니다.
그래서 같은 비밀번호를 앱에는 인코딩된 채로, 명령줄 도구에는 디코딩해서 넘겨야 합니다. 이걸 모르고 인코딩된 값을 그대로 넘기면 "비밀번호가 틀렸다"는 응답을 받습니다. 값은 맞는데요.
4. 설정에 없는 키를 넣으면 앱이 아예 안 뜬다
설정 라이브러리가 기본적으로 "선언되지 않은 키는 거부"로 동작합니다. 환경변수 파일에 새 키를 추가하고 설정 클래스에 필드를 선언하지 않으면, 그 키를 쓰지 않아도 기동 단계에서 검증 오류로 죽습니다.
엄격한 기본값이 옳다고 생각합니다. 오타 난 환경변수를 조용히 무시하는 것보다 낫습니다. 다만 "환경변수를 추가하면 설정 클래스도 함께 고친다"를 규칙으로 적어 두지 않으면 배포할 때마다 같은 곳에서 멈춥니다.
공통점
네 가지 모두 라이브러리가 잘못한 것이 아니라, 각각의 기본값이 서로 다른 전제 위에 서 있었던 문제였습니다. 드라이버는 연결이 고정된다고 보고, 풀러는 연결을 돌려쓴다고 보고, 설정 라이브러리는 실수를 막는 편을 택합니다.
연결 문제를 디버깅할 때는 코드보다 먼저 “이 계층은 무엇을 전제하고 있는가”를 확인하는 편이 빠릅니다.