관리형 Postgres를 사용하는 Phoenix
이 가이드는 Phoenix 앱을 연결할 때 필요한 핵심적인 관리형 Postgres(MPG) 특유의 조정 사항을 설명해요. PgBouncer 연결 풀링 설정, 마이그레이션 실행, Oban과 MPG 사용, 문제 해결을 다룹니다. Phoenix나 Ecto 셋업 자체는 이미 되어 있다고 가정해요.
출처: 문서
본문
모든 언어에 적용되는 일반적인 연결 구성 — 연결 수명, 유휴 타임아웃, 프록시 재시작 동작, 문제 해결 — 은 클라이언트 연결하기를 참고해 보아요.
이 가이드는 Phoenix 앱을 연결할 때 필요한 핵심적인 관리형 Postgres (MPG) 특유의 조정 사항을 설명해요. 다음에 초점을 맞출게요:
- 연결 풀링 설정
- 마이그레이션 실행
- Oban을 MPG와 함께 사용하기
- 문제 해결 및 일반적인 문제
이 가이드는 Phoenix나 Ecto 셋업을 다루지 않아요. 이미 그것들이 준비되어 있다고 가정해요.
PgBouncer + Ecto 연결 풀링
Fly.io MPG는 연결 풀링에 PgBouncer를 사용해요. 기본적으로 MPG 클러스터는 Session 모드로 실행되지만, Ecto는 연결 풀링을 처리하는 방식 때문에 Transaction 모드를 요구해요.
PgBouncer를 구성하려면:
- 대시보드에서 MPG 클러스터를 열어요.
- Connect → Pooler settings로 가요.
- Pool mode를 Transaction으로 설정해요.
레포 구성도 업데이트해야 해요:
config :my_app, MyApp.Repo,
url: System.fetch_env!("DATABASE_URL"),
pool_size: 8,
timeout: 15_000, # query timeout
max_lifetime: 540_000..600_000, # max connection lifetime; the range staggers reconnects
prepare: :unnamed
prepare: :unnamed는 필수인데, 이름 붙은 prepared statements가 Transaction 모드의 PgBouncer와는 작동하지 않기 때문이에요.
마이그레이션 실행
대부분의 경우 Ecto와 함께 Transaction 모드를 사용해야 하지만, 마이그레이션은 특별한 경우예요. 마이그레이션은 advisory locks와 세션 고정(session stickiness)에 의존하는데, PgBouncer의 transaction pooling 모드는 이를 지원하지 않아요. 마이그레이션을 실행하려면 앱이 직접 데이터베이스 URL을 사용하도록 구성해야 해요.
시크릿을 업데이트해 DIRECT_DATABASE_URL을 추가해요
fly secrets set \
DATABASE_URL="postgresql://...@pgbouncer.YOUR_CLUSTER.flympg.net/fly-db" \
DIRECT_DATABASE_URL="postgresql://...@direct.YOUR_CLUSTER.flympg.net/fly-db"
fly.toml에서 릴리즈 명령을 업데이트해 마이그레이션 실행에 직접 연결을 사용해요:
[deploy]
release_command = "/bin/sh -lc 'DATABASE_URL=$DIRECT_DATABASE_URL bin/migrate'"
또는 마이그레이션을 수동으로 실행해요:
fly ssh console -C '/bin/sh -lc "DATABASE_URL=\"$DIRECT_DATABASE_URL\" bin/migrate"'
Oban을 MPG와 함께 사용하기
Job 큐 처리를 위해 Oban 라이브러리를 사용하고 있다면, Transaction 모드의 PgBouncer가 Oban이 잡 알림에 사용하는 LISTEN/NOTIFY를 지원하지 않기 때문에 몇 가지 조정이 필요해요. 몇 가지 옵션이 있어요:
1. Postgres가 아닌 notifier 사용
# Distributed Erlang notifier
config :my_app, Oban,
repo: MyApp.ObanRepo,
notifier: Oban.Notifiers.PG,
queues: [default: 10]
또는 Phoenix PubSub 사용:
config :my_app, Oban,
repo: MyApp.ObanRepo,
notifier: {Oban.Notifiers.Phoenix, pubsub: MyApp.PubSub},
queues: [default: 10]
2. 직접 연결에서 Oban 실행
Postgres LISTEN/NOTIFY를 사용해야 하는 요구사항이 있다면, PG Bouncer 대신 직접 데이터베이스 연결을 사용하도록 Oban을 설정할 수 있어요
config :my_app, Oban,
repo: MyApp.DirectRepo,
notifier: Oban.Notifiers.Postgres,
queues: [default: 10]
이것은 직접 DB 연결을 사용해요. 각 연결은 PgBouncer를 우회하는 Ecto 풀 항목이에요. 직접 연결은 제한적이므로 풀을 아주 작게 유지하세요.
3. 레거시 폴백 (Oban \\< 2.14)
이전 버전은 Repeater 플러그인이 필요했어요. Oban 2.14(2023)부터 폴링 폴백이 내장되어 있고 Repeater는 더 이상 사용되지 않아요.
문제 해결 및 일반적인 오류
문제 해결 체크리스트
- PgBouncer 풀 모드를 Transaction으로 설정해요.
- 모든 Repo에
prepare: :unnamed을 설정해요. - 앱 트래픽에는 풀링된 URL을, 마이그레이션과 다른 세션 범위 작업(advisory locks 등)에는 직접 URL을 사용해요.
- Basic 플랜에서 Ecto 풀을 알맞게 유지해요 (API repo 8, Oban repo 6). 대부분의 연결이 유휴 상태로 앉아 있다면 줄여요.
- 오류 타임스탬프 주변의 MPG 메트릭에서 재시작이나 메모리 압박을 확인해요.
일반적인 오류와 해결책
tcp recv (idle): closed또는tcp recv (idle): timeout— 이는 Fly 프록시나 PgBouncer가 유휴 연결을 닫을 때, 종종 일상적인 프록시 배포 중에 발생해요. Ecto가 자동으로 재연결하므로 일시적인 현상이에요. 빈도를 줄이려면 풀 크기를 낮춰 유휴로 앉아 있는 연결을 줄이세요. 왜 이런 일이 일어나는지와 연결 수명 및 유휴 타임아웃을 구성하는 방법에 대한 전체 설명은 클라이언트 측 연결 구성 — 문제 해결을 참고해요.- 로그인 시
FATAL 08P01 protocol_violation—prepare: :unnamed을 설정하고 PgBouncer가 Transaction 모드인지 확인해요. - Oban 잡이 실행되지 않음 — PgBouncer 뒤에서 Postgres가 아닌 notifier(PG 또는 Phoenix)를 사용하거나, Oban을 직접 Repo에서 실행해요. Oban ≥ 2.14에서는 Repeater를 추가하지 마세요 (PubSub를 사용할 수 없을 때 폴링 폴백이 자동이에요).
- 마이그레이션이 멈추거나 실패 — 마이그레이션을 PgBouncer를 통하지 말고 직접 데이터베이스 URL로 실행해요 (
release_command또는 일회용 SSH 명령으로). non-existing domain - :nxdomain- ipv6이 활성화되어 있는지 확인해요