본문으로 건너뛰기
9.5 callbacks와 역할 전파

9.5 callbacks와 역할 전파

앞 절들의 라우팅 도구는 클러스터 상태를 밖에서 폴링하거나 watch했다. 방향을 뒤집은 통로도 있다. Patroni가 스크립트를 실행해 자기 상태 변화를 밖으로 알리는 callbacks다. VIP 이동, DNS 갱신, pooler 재설정처럼 “역할이 바뀌었으니 무언가를 고쳐야 하는” 작업을 붙이는 자리이며, 동시에 실행 계약을 오해하면 사고가 나는 자리이기도 하다.

5종 콜백

postgresql.callbacks 아래에 다섯 가지를 등록한다.

콜백실행 시점
on_startpostgres가 시작될 때
on_stoppostgres가 정지될 때
on_restart역할 변화 없이 postgres가 재시작될 때
on_role_changepostgres가 promote 또는 demote될 때
on_reload설정 reload가 트리거될 때

스크립트에는 action, role, cluster name 세 인자가 전달된다.

postgresql:
  callbacks:
    on_role_change: /etc/patroni/on_role_change.sh
    on_stop: /etc/patroni/on_role_change.sh
#!/bin/bash
ACTION="$1"    # 어떤 콜백으로 호출됐는지
ROLE="$2"      # 이 노드의 role
CLUSTER="$3"   # cluster 이름

case "$ACTION" in
  on_role_change|on_stop)
    # role에 따라 VIP/DNS/pooler 갱신
    ;;
esac

같은 스크립트를 여러 콜백에 걸어 두고 첫 인자로 분기하는 패턴이 흔하다. 저장소는 이런 콜백 스크립트의 공식 예시로 scripts/aws.py를 제공한다.

비동기 실행이라는 계약

콜백의 실행 계약이 이 절에서 가장 중요한 사실이다. Patroni 내부의 CallbackExecutor는 콜백을 “call it without waiting for it to finish”, 즉 완료를 기다리지 않고 실행한다. promote와 demote는 콜백이 끝나기를 기다리지 않고 진행되며, 콜백이 성공했는지조차 Patroni는 확인하지 않는다.

VIP나 DNS 이동을 on_role_change에 맡길 때 이 계약이 함정이 된다. promote는 콜백 완료 전에 끝나므로, 새 primary가 쓰기를 받기 시작한 시점에 VIP는 아직 구 노드에 있을 수 있다. 콜백이 실패해도 Patroni는 재시도하지 않고, 연달아 role이 바뀔 때 이전 콜백 프로세스를 어떻게 처리하는지도 문서화되어 있지 않다. PostgreSQL 시작이 실패한 경우 콜백이 호출되지 않은 사례도 이슈(patroni/patroni#1418)로 보고된 바 있다. 라우팅의 정확성이 콜백 완료에 의존하는 설계라면, 그 공백과 실패를 감지하는 모니터링을 함께 두어야 한다.

on_role_change 활용 패턴

계약의 한계를 이해한 위에서라면 on_role_change는 역할 전파의 표준 지점이다. 자주 쓰이는 패턴은 세 갈래다.

VIP 갱신은 promote된 노드에서 인터페이스에 IP를 추가하고 demote된 노드에서 제거하는 스크립트를 거는 방식이다. 9.3의 vip-manager가 DCS watch로 같은 일을 하는 것과 대비되는데, 콜백 방식은 데몬이 하나 줄어드는 대신 위의 비동기 계약과 실패 미감지를 떠안는다. DNS 갱신은 클라우드나 내부 DNS의 레코드 변경 API를 호출하는 패턴으로, DNS TTL만큼의 전파 지연이 추가로 얹힌다. pooler 갱신은 PgBouncer 대상 주소를 다시 쓰고 reload하는 패턴이다.

공식 문서에 있는 실제 예시는 백업 도구 쪽에 있다. Barman 통합의 patroni_barman config-switch는 promote 시점에 Barman 설정을 새 primary 기준으로 전환하는 명령으로, 공식 문서는 이를 on_role_change에 걸도록 안내한다.

postgresql:
    callbacks:
        on_role_change: >
            patroni_barman --api-url YOUR_API_URL config-switch
                --barman-server YOUR_BARMAN_SERVER_NAME
                --barman-model YOUR_BARMAN_MODEL_NAME
                --switch-when promoted

pre_promote와 before_stop: 콜백과 다른 계약

이름이 비슷해 혼동하기 쉽지만, postgresql.pre_promotepostgresql.before_stop은 콜백이 아니며 계약이 다르다.

pre_promote는 fencing 스크립트다. failover 중에 leader lock을 획득한 뒤, replica를 promote하기 전에 실행된다. 스크립트가 non-zero로 종료하면 Patroni는 promote를 하지 않고 DCS에서 leader key를 제거한다. 즉 exit code가 failover의 진행 자체를 좌우한다. 구 primary를 확실히 격리(fencing)했는지 확인하고, 실패하면 승격을 포기시켜 split brain을 막는 용도다.

before_stop은 postgres 정지 직전에 실행되며, 콜백과 달리 동기로 실행되어 완료될 때까지 shutdown을 블로킹한다. 다만 return code는 이후 shutdown 진행에 영향을 주지 않는다.

구분callbackspre_promotebefore_stop
실행 방식비동기동기동기 (shutdown 블로킹)
exit code의 효과없음non-zero면 promote 중단없음
시점상태 변화에 대한 통지leader lock 획득 후, promote 전postgres 정지 직전

failover 한 번의 흐름 위에 놓으면 순서가 분명해진다.

    flowchart TD
  A["leader lock 획득"] --> B["pre_promote 실행"]
  B -->|exit 0| C["promote"]
  B -->|exit != 0| D["promote 포기"]
  D --> E["leader key 제거"]
  E --> F["leader race 재개"]
  C --> G["on_role_change<br/>비동기 실행"]
  
postgresql:
  pre_promote: /etc/patroni/fencing.sh
  before_stop: /etc/patroni/before_stop.sh
  callbacks:
    on_role_change: /etc/patroni/on_role_change.sh

split brain 방지 관점의 fencing 논의는 Part II의 split brain 절과 이어진다.

정리

  • 콜백은 5종(on_start, on_stop, on_restart, on_role_change, on_reload)이고 action, role, cluster name을 인자로 받는다.
  • 콜백은 비동기다. promote/demote를 블로킹하지 않고 완료도 보장되지 않으므로, VIP/DNS를 콜백으로 옮기는 설계는 그 공백을 전제해야 한다.
  • pre_promote(non-zero exit면 promote 중단 + leader key 제거)와 before_stop(동기, shutdown 블로킹)은 콜백과 계약이 다른 별도 스크립트다.