PGKeeper: Postgres에 꼭 필요했던 보안관 만들기 | Figma 블로그 (새 탭에서 열림)
Figma는 데이터베이스 트래픽과 제품 규모가 커지면서 기존 PgBouncer의 확장성·부하 제어·연결 관리 한계에 부딪혔고, 이를 대체할 자체 서비스 PGKeeper를 구축했다. PGKeeper는 DBProxy와 PostgreSQL 사이에서 연결 풀링과 트래픽 관리를 담당하며, 과부하로부터 데이터베이스와 연결을 보호하는 것을 목표로 한다. 글은 Figma의 데이터베이스 구조와 PgBouncer를 교체하게 된 이유, 그리고 PGKeeper를 직접 개발한 배경을 설명한다. ## Figma의 PostgreSQL 데이터베이스 구조 - PostgreSQL은 Figma의 OLTP 시스템 기반이다. - 데이터 증가에 대응하기 위해 데이터베이스를 수평·수직으로 확장하고 여러 PostgreSQL 인스턴스를 운영했다. - 애플리케이션이 샤딩 구조를 직접 알 필요 없도록 **DBProxy**라는 요청 라우팅 계층을 구축했다. - DBProxy는 쿼리를 분석해 적절한 PostgreSQL 인스턴스를 선택하고, 필요하면 여러 인스턴스를 대상으로 쿼리를 재작성한다. - DBProxy와 PostgreSQL 사이에는 연결 풀러가 위치한다. - 각 PostgreSQL 머신에는 전용 연결 풀러 복제본 그룹이 배치되어, 여러 풀러가 하나의 데이터베이스에 연결되는 n-to-1 구조를 이룬다. ## PgBouncer가 한계에 도달한 이유 - **확장성 부족** - PgBouncer는 단일 스레드 아키텍처라 수직 확장에 한계가 있었다. - 복제본을 늘려 수평 확장했지만 트래픽 분배가 치우치면서 성능 저하가 발생했다. - **정교한 부하 관리 부재** - 중요한 트래픽을 낮은 우선순위의 문제성 트래픽보다 먼저 처리할 수 없었다. - 백프레셔가 없어 급격한 트래픽 증가를 우아하게 제어하기 어려웠다. - 대기 시간에 기반해 작업을 버리는 CoDel(Controlled Delay) 같은 부하 완화 알고리즘도 지원하지 않았다. - **연결 폭증과 연결 churn 위험** - PostgreSQL 연결은 많은 자원을 사용한다. - 연결이 빠르게 생성되거나 과도하게 재생성되면 데이터베이스 안정성이 크게 떨어진다. - 장애 이후 무제한으로 연결을 다시 만들면 복구 과정 자체가 PostgreSQL을 압박할 수 있다. - 이로 인해 연결 churn과 연쇄적인 과부하가 장시간 지속될 수 있다. - **확장성과 운영 제어의 한계** - 트래픽 유형별 admission control, 공정한 자원 분배 등 세밀한 정책이 필요했다. - 심층적인 관측성, 기능 플래그 기반의 안전한 롤아웃도 요구됐다. - PgBouncer에 작은 수정 사항을 유지하는 것만으로도 부담이 컸기 때문에, 대규모 확장은 장기적인 유지보수 비용을 초래할 가능성이 컸다. ## DBProxy에 연결 풀링을 통합하지 않은 이유 - PostgreSQL 인스턴스 하나의 연결 풀은 대략 100개 규모로 운영됐다. - 반면 DBProxy는 수백 개의 상태 비저장 복제본으로 구성됐다. - 각 DBProxy가 독립적으로 연결 풀을 관리하면: - 전체 PostgreSQL 연결 제한을 초과할 수 있고, - 복제본 간 복잡한 조정이 필요하며, - 고정된 소규모 연결 풀을 수백 개 인스턴스에 분산하기 어렵다. - 따라서 연결 풀링은 DBProxy 내부가 아니라 별도의 중앙화된 계층에서 담당해야 했다. ## PGCat 대신 자체 서비스를 만든 배경 - Figma는 PgBouncer의 단일 스레드 한계를 해결하기 위해 멀티스레드 PostgreSQL 프록시인 PGCat도 검토했다. - 그러나 필요한 기능을 추가하려면 PGCat의 핵심 실행 경로를 크게 수정해야 했다. - 관측성, 기능 플래그, admission control을 upstream에 반영하기도 쉽지 않았다. - 결국 PGCat을 포크해 장기적으로 유지해야 할 가능성이 높았으므로, Figma는 요구사항에 맞는 Go 기반 자체 서비스 **PGKeeper**를 개발했다. ## PGKeeper의 역할 - PGKeeper는 DBProxy와 PostgreSQL 사이에 위치하는 Go 서비스다. - 주요 목적은 다음과 같다. - 데이터베이스를 과도하거나 잘못된 트래픽으로부터 보호 - PostgreSQL 연결의 급격한 생성과 churn 방지 - 트래픽 우선순위와 자원 사용을 세밀하게 제어 - 대규모 환경에 맞는 확장성과 운영 도구 제공 - 이름은 골키퍼처럼 위험한 트래픽이 시스템을 압도하지 못하도록 막고, 연결 자체도 보호한다는 의미에서 붙여졌다. Figma의 사례는 단순히 연결 풀러를 교체한 것이 아니라, 데이터베이스 앞단에서 트래픽을 선별하고 과부하를 제어하는 별도 인프라 계층이 필요해졌음을 보여준다. PostgreSQL 연결 수가 제한적이고 프록시 복제본이 많은 환경에서는 풀링을 애플리케이션 프록시에 분산하기보다, 독립적인 계층으로 관리하는 편이 적합하다.