pgmux 프로젝트
“내 손으로 만드는 데이터베이스 프록시”
애플리케이션과 DB 사이에 위치하여 커넥션 풀링, 읽기/쓰기 자동 분산, 반복 쿼리 캐싱을 수행하는 프록시를 Go로 직접 구현하는 프로젝트입니다.
🎯 프로젝트 배경 및 목표
🔍 기술적 호기심
- PostgreSQL wire protocol을 직접 다뤄보고 싶다
- 커넥션 풀링, 쿼리 라우팅이 내부적으로 어떻게 동작하는지 이해하고 싶다
- PgBouncer 같은 프록시가 어떤 원리로 만들어지는지 궁금하다
📚 학습 목표
- Go 동시성 프로그래밍 (goroutine, channel, sync.Mutex)
- PostgreSQL wire protocol (바이트 레벨 메시지 파싱)
- 커넥션 풀링 설계 (acquire/release, 헬스체크, 타임아웃)
- LRU 캐시 구현 및 테이블 기반 무효화
🚀 핵심 구현 기능
✅ 구현 완료
프록시 통합 (Pool + Router + Cache)
- SCRAM-SHA-256 / MD5 백엔드 인증 직접 구현
- Extended Query Protocol (Parse/Bind/Execute/Sync) 지원
- Prepared Statement reader 라우팅 (Parse 메시지 SQL 추출)
- Reader Pool의 DialFunc으로 PG-aware 커넥션 풀링
- 양방향 auth relay (클라이언트↔백엔드 인증 중계)
- E2E 테스트: Docker PG 클러스터 + lib/pq 드라이버
Prometheus 메트릭
- 쿼리 라우팅 카운터/히스토그램 (writer/reader별)
- 캐시 히트/미스/무효화 카운터, 항목 수 게이지
- 커넥션 풀 open/idle/acquire 메트릭
/metricsHTTP 엔드포인트 (Prometheus scrape)
Transaction Pooling
- PgBouncer transaction 모드와 동일한 커넥션 다중화
- 쿼리/트랜잭션 단위 Writer 커넥션 Acquire/Release
DISCARD ALL로 세션 상태 완전 초기화- Extended Query Protocol 트랜잭션 경계 추적
TLS Termination + Front-end Auth
- SSLRequest 핸들링 및 TLS 연결 업그레이드 (
crypto/tls) - 프록시 자체 MD5 인증 (백엔드 의존 없이 접근 제어)
- YAML 기반 사용자 목록 관리
Circuit Breaker + Rate Limiting
- Circuit Breaker: Closed → Open → Half-Open 상태 머신
- Rolling Window 기반 에러율 측정 및 자동 트립
- Token Bucket Rate Limiter (burst 허용 + 평균 rate 제어)
- Writer/Reader 독립 Circuit Breaker
Zero-Downtime Reload
- SIGHUP 시그널 +
POST /admin/reloadHTTP API - Reader Pool 핫스왑 (기존 유지, 추가/삭제)
- RoundRobin Balancer 원자적 백엔드 갱신
- Rate Limiter 동적 재설정
LSN 기반 Causal Consistency
- 쓰기 후 Writer의
pg_current_wal_lsn()추적 - Reader별
pg_last_wal_replay_lsn()주기적 폴링 - LSN-aware 로드밸런싱 (복제 완료된 Reader에만 라우팅)
- Prometheus
pgmux_reader_lsn_lag_bytes메트릭
AST 기반 쿼리 파서
- pg_query_go (PostgreSQL 실제 C 파서 바인딩)
- CTE 내부 write 감지, DDL 20+ 노드 타입 정확 분류
- 파싱 실패 시 문자열 파서 자동 fallback
쿼리 방화벽 (Query Firewall)
- AST 분석으로 위험 쿼리 사전 차단
- DELETE/UPDATE without WHERE, DROP TABLE, TRUNCATE 차단
- fail-open 전략 + Prometheus 차단 메트릭
시맨틱 캐시 키
- pg_query Parse+Deparse로 구조적 동일 쿼리에 같은 캐시 키 생성
- 공백/대소문자 정규화, 리터럴 값 보존
Audit Logging & Slow Query Tracker
- 비동기 채널 기반 감사 로그 (쿼리 경로 비블로킹)
- Slow Query 임계값 초과 시 구조화 로그 + Webhook 알림 (Slack 호환)
- 동일 쿼리 패턴 중복 알림 방지 (rate limiting)
Serverless Data API
POST /v1/query— HTTP REST → PG Wire Protocol → JSON 응답- RowDescription OID 기반 타입 매핑 (int, bool, float, text 등)
- 기존 R/W 라우팅, 캐싱, 방화벽, Rate Limiting 투명 적용
- API Key Bearer 인증
Helm Chart & Docker
- Multi-stage Dockerfile (CGO 빌드 지원)
- Helm Chart: Deployment, Service, ConfigMap, HPA, PDB, ServiceMonitor
- Kubernetes 원커맨드 배포
OpenTelemetry 분산 추적
- TracerProvider 초기화 (OTLP gRPC / stdout exporter)
- Simple Query / Extended Query 경로 Span 계측
- Data API
traceparentHTTP 헤더 전파 enabled: false시 noop (성능 영향 없음)
설정 파일 자동 리로드 (fsnotify)
- 부모 디렉토리 감시로 K8s ConfigMap symlink swap 지원
- 1초 디바운싱으로 연속 이벤트 병합
- 기존 SIGHUP 리로드 경로 재사용
Admin API
/admin/health— 백엔드 헬스 상태 조회/admin/stats— 풀, 캐시 통계 JSON/admin/config— 현재 설정 (비밀번호 마스킹)/admin/cache/flush[/{table}]— 전체/테이블별 캐시 비우기/admin/reload— 무중단 설정 리로드
멀티 인스턴스 스케일링
- Redis Pub/Sub 기반 캐시 무효화 브로드캐스트
- 쓰기 시 로컬 캐시 무효화 + 다른 인스턴스에 publish
- mode: “local” (단일) / “pubsub” (멀티) 설정 가능
커넥션 풀링
- min/max 커넥션 수 관리
- idle_timeout, max_lifetime 만료 자동 폐기
- connection_timeout 대기 큐
- 주기적 헬스체크 및 min_connections 보충
R/W 쿼리 자동 분산
- SQL 키워드 기반 Read/Write 분류
- 힌트 주석 강제 라우팅 (
/* route:writer */) - 트랜잭션 내 모든 쿼리 Writer 고정
- read_after_write_delay 타이머 또는 LSN 기반 Causal Consistency
- Reader 라운드로빈 로드밸런싱 + 장애 자동 감지/복구
반복 쿼리 캐싱
- LRU 캐시 (container/list 기반)
- TTL 만료 + max_entries 제한
- max_result_size 초과 시 캐싱 스킵
- 쓰기 시 테이블별 캐시 자동 무효화 (역인덱스)
🛠️ 기술 스택
백엔드
Go # 고루틴 기반 동시성
PostgreSQL Wire Protocol # 바이트 레벨 프로토콜 직접 구현
YAML # 설정 파일
모니터링
Prometheus # 메트릭 수집 + /metrics 엔드포인트
테스트 환경
Docker Compose # PG Primary + Replica 2대
Go testing # 단위 테스트 + 통합 테스트 + 벤치마크
🏗️ 시스템 아키텍처
┌─────────────────┐
│ Application │
│ (psql, app) │
└────────┬────────┘
│ PG Wire Protocol
▼
┌──────────────────────────────────────────┐
│ pgmux │
│ │
│ ┌───────────┐ ┌──────────────────────┐ │
│ │ TLS Term. │ │ Front-end Auth │ │
│ │ (SSL/TLS) │ │ (MD5 Challenge) │ │
│ └─────┬─────┘ └──────────┬───────────┘ │
│ │ │ │
│ ┌─────▼────────────────────▼───────────┐ │
│ │ Rate Limiter (Token Bucket) │ │
│ └─────┬────────────────────────────────┘ │
│ │ │
│ ┌─────▼─────┐ ┌────────────────┐ │
│ │ Query │ │ Tx Pooling │ │
│ │ Parser │ │ (Acquire/ │ │
│ └─────┬─────┘ │ Release) │ │
│ │ └────────────────┘ │
│ ┌─────▼────────────────────┐ │
│ │ Router (R/W + Session) │ │
│ └─────┬───────────┬───────┘ │
│ │ │ │
│ ┌─────▼─────┐ ┌──▼──────────────┐ │
│ │ Cache │ │ Round Robin │ │
│ │ (LRU) │ │ Load Balancer │ │
│ └───────────┘ └──────────────────┘ │
│ │ │
│ ┌─────▼──────────────────────────┐ │
│ │ Circuit Breaker (per backend) │ │
│ └─────┬──────────────┬──────────┘ │
└────────┼──────────────┼──────────────────┘
│ │
┌────▼────┐ ┌────▼────┐
│ Writer │ │ Readers │
│(Primary)│ │(Replica)│
└─────────┘ └─────────┘
📊 벤치마크 결과 (Apple M4 Pro)
| 항목 | 성능 | 메모리 |
|---|---|---|
| Cache Key 생성 | 15 ns/op | 0 alloc |
| Cache Get (Hit) | 36 ns/op | 0 alloc |
| Cache Get (Miss) | 6 ns/op | 0 alloc |
| RoundRobin Next | 1.7 ns/op | 0 alloc |
| Query Classify | 1,222 ns/op | 45 alloc |
| ClassifyAST SELECT | ~7 µs/op | - |
| ClassifyAST Complex JOIN | ~32 µs/op | - |
| SemanticCacheKey | ~2 µs/op | - |
| CheckFirewall | ~5.5 µs/op | - |
📝 개발 블로그 시리즈
프로젝트 개발 과정에서 배운 내용을 기록하고 있습니다.
- PG Wire Protocol 이해
- 커넥션 풀링 직접 구현
- 읽기/쓰기 자동 분산
- 쿼리 캐싱과 무효화
- 통합, E2E 테스트, 회고
- Prometheus 메트릭, Prepared Statement 라우팅, Admin API
- QA 버그 수정과 멀티 인스턴스 스케일링
- 보안 취약점 심화 수정
- Transaction Pooling
- TLS Termination과 프록시 인증
- Circuit Breaker와 Rate Limiting
- 무중단 설정 리로드
- LSN 기반 Causal Consistency
- AST 파서와 쿼리 방화벽
- 보안 QA와 취약점 수정
- Audit Logging, Helm Chart, Serverless Data API
- Channel Blocking과 Connection Poisoning 버그 수정
- OpenTelemetry 분산 추적과 설정 자동 리로드
- Writer-Only 모드와 진입장벽 낮추기
🔗 관련 링크
- GitHub Repository: 프로젝트 소스코드
- 개발 블로그: 개발 블로그 시리즈