3.6 자체 호스팅 한계와 문제 해결
compose 실습을 마치면 한 가지 질문이 생깁니다. 이 구성을 사내 서버에 올려 개발 환경으로 쓸 수 있는가. 이 장은 upstream이 공개한 영역과 공개하지 않은 영역의 경계를 정리합니다. 또한 실습 중에 만난 오류와 그 해석을 모아 둡니다.
공개된 것과 공개되지 않은 것
| 계층 | 공개 여부 | 실습에서의 대체물 |
|---|---|---|
| compute (패치된 PostgreSQL) | PostgreSQL License (PostgreSQL 소스와 그 파생) | 이미지 그대로 사용 |
| compute (neon 확장, compute_ctl) | Apache 2.0 | 이미지 그대로 사용 |
| pageserver, safekeeper, storage_broker | Apache 2.0 | 이미지 그대로 사용 |
| storage controller (tenant 배치, shard, 재배치) | 저장소에 포함되나 compose에는 없음 | control_plane_emergency_mode=true로 pageserver 단독 동작 |
| control plane (프로젝트, branch 이름, 사용자, API, 콘솔) | 비공개 | pageserver API 직접 호출, .branches 파일 |
proxy (접속 라우팅, 인증, -pooler 엔드포인트) | Apache 2.0. 소스는 저장소 proxy/, 바이너리는 이미지에 포함 | 사용하지 않음, compute 포트 직접 노출 |
| autoscaling (autoscaler-agent, NeonVM, vm-monitor) | 별도 저장소(neondatabase/autoscaling), Kubernetes 전용 | 없음 |
| scale to zero | control plane과 proxy가 협력 | 없음 |
upstream compose README의 표현을 그대로 옮기면 이 구성은 "docker 이미지 테스트용이며 사용 가능한 시스템을 배포하려는 것이 아니다"입니다. storage controller가 없는 이유도 적혀 있습니다. controller는 실행 중인 compute를 재설정할 방법이 필요합니다. 하지만 compose에는 그런 장치가 없습니다.
따라서 자체 호스팅으로 얻는 것은 "branch가 있는 PostgreSQL 저장 엔진"입니다. 얻지 못하는 것은 "서비스"입니다. branch 이름, 사용자 관리, 접속 라우팅, 자동 정지와 재기동, 다중 pageserver 배치는 모두 직접 만들어야 합니다. 커뮤니티 포럼의 "Can Neon be self-hosted?" 스레드와 이후 해설 글도 같은 결론을 반복합니다.
운영 관점에서 빠진 것
- 인증: 실습 pageserver는 인증 없이 9898 포트를 열어 둡니다. compute 로그에
Storage auth token not set이 찍히는 것이 그 표시입니다. 실제 배치에서는 pageserver와 safekeeper의 JWT 인증을 켜야 합니다. 또한 compute spec에 토큰을 넣어야 합니다. - 다중 pageserver와 shard: 큰 tenant를 여러 pageserver로 나누는 sharding은 storage controller가 담당합니다. compose는 pageserver 하나에 모든 tenant를 둡니다.
- pageserver 장애 복구: pageserver가 죽으면 다른 pageserver가 S3의 layer와 safekeeper의 WAL로 tenant를 attach해야 합니다. 하지만 그 attach 결정을 내리는 주체가 없습니다.
- 백업: MinIO가 유일한 장기 저장소입니다. 어느 구성 요소를 잃는지에 따라 남는 범위가 다릅니다. MinIO를 잃으면 이미 업로드된 과거 layer까지 사라집니다. 이때 pageserver 로컬 디스크에 남은 layer와 safekeeper의 WAL 보존 구간만 재료로 남습니다. pageserver 로컬 디스크만 잃으면 MinIO의 layer로 다시 attach합니다. safekeeper 과반을 잃으면
remote_consistent_lsn이후 WAL을 잃습니다. safekeeper의 WAL 보존 기간도 무기한이 아닙니다. pageserver가 처리한 구간부터 삭제됩니다. - 업그레이드: 이미지 태그를 올릴 때는 upstream 릴리스 노트에서 pageserver 저장 포맷 호환성을 확인해야 합니다. compute의 PostgreSQL major 버전은 timeline을 새로 만들어도 바뀌지 않습니다. 자식 timeline은 ancestor의
pg_version을 상속합니다. 요청에 넣은 값은 무시됩니다. 새 major 버전으로 옮기려면 해당 버전으로 tenant나 프로젝트를 새로 만듭니다. 이후 dump/restore 또는 논리 복제로 데이터를 이관합니다. Neon 클라우드 서비스도 같은 방식을 안내합니다.
실습 중 만난 메시지와 해석
compute 로그의 BatchSpanProcessor.ExportError
ERROR name="BatchSpanProcessor.ExportError" error="Operation failed: reqwest::Error { kind: Request, url: Url { scheme: "http", ... } }"
5초마다 반복됩니다. compute_ctl이 OpenTelemetry trace를 내보내려 하지만 collector가 없어 발생하는 오류입니다. 동작에는 영향이 없습니다. 로그를 줄이려면 compute spec이나 환경변수에서 OTLP 엔드포인트를 비활성화해야 합니다. 이 노트에서는 그대로 두었습니다.
Storage auth token not set
INFO start_compute:prepare_pgdata:get_basebackup{lsn=0/0}: Storage auth token not set
pageserver 인증이 꺼져 있음을 알리는 메시지입니다. 실습에서는 정상입니다.
/shell/compute.sh: no such file or directory
compute 컨테이너가 시작 직후 종료되면서 이 메시지가 나오면 bind mount가 비어 있는 것입니다. Docker Desktop이나 Colima 같은 VM 기반 Docker는 파일 공유 대상 디렉터리가 정해져 있습니다. 따라서 그 밖의 경로(이 노트에서는 /private/tmp 아래)를 마운트하면 빈 디렉터리가 연결됩니다. 홈 디렉터리 아래로 옮기면 해결됩니다.
HTTP 406 Timed out while waiting for WAL record
ancestor_start_lsn이 부모의 last_record_lsn보다 앞선 경우입니다. 3.3에서 다뤘습니다. 재시도해도 결과는 같습니다. LSN을 다시 확인합니다.
HTTP 412 Cannot delete timeline which has child timelines
자식 branch가 있는 timeline을 삭제하려고 할 때 발생합니다. 자식을 먼저 지우거나 detach_ancestor로 독립시킵니다.
두 compute가 서로 재시작한다
두 compute가 같은 TIMELINE_ID를 받았을 때 나타납니다. safekeeper의 election은 timeline마다 proposer 하나만 허용합니다. 새로 연결된 proposer는 quorum에 max(term)+1을 제안해 기존 proposer를 fence합니다. 따라서 특정 쪽이 항상 격리되는 것이 아니라 더 높은 term을 얻은 쪽이 이깁니다. 격리된 compute_ctl이 재시작해 다시 election을 시도하면 두 compute가 번갈아 격리되는 상태가 반복됩니다. .env에 TENANT_ID와 TIMELINE_ID, BRANCH_TIMELINE_ID가 모두 있는지 확인합니다. 또한 show neon.timeline_id가 compute마다 다른지 확인합니다.
physical size가 오래 0으로 남는다
오류가 아닙니다. WAL은 checkpoint_distance(256 MB)에 이르거나 checkpoint_timeout(기본 10분)이 지나기 전까지 in-memory layer에 있습니다. 3.4를 참고합니다.
디스크와 이미지 크기
| 항목 | 크기 |
|---|---|
| ghcr.io/neondatabase/neon:latest | 6.98 GB |
| ghcr.io/neondatabase/compute-node-v17:latest | 1.75 GB |
| compose 기동 후 MinIO 데이터(50만 건 실습 후) | 약 630 MB |
| pageserver 로컬 layer | 약 315 MB |
docker compose down -v를 잊으면 MinIO 볼륨이 남습니다. 이미지 자체는 docker image rm으로 따로 지워야 합니다.
그래도 자체 호스팅을 검토한다면
- 목적이 "branch 학습과 데모"라면 compose로 충분합니다. 이 Part의 실습이 그 범위입니다.
- 목적이 "개발팀용 branch 있는 PostgreSQL"이라면 Part V의 DBLab Engine이나 ZFS 기반 thin clone이 운영 부담이 적습니다. PostgreSQL 바이너리를 바꾸지 않아도 됩니다. 또한 백업과 모니터링 도구를 그대로 쓸 수 있습니다.
- 목적이 "Kubernetes 위의 branch 플랫폼"이라면 Xata OSS가 control plane까지 공개된 선택지 중 하나입니다. 대신 CloudNativePG와 OpenEBS 운영이 전제됩니다.
- Neon 저장 엔진 자체를 운영하려면 storage controller, 인증, 백업 정책, 모니터링을 직접 구성해야 합니다. 이 작업은 PostgreSQL 클러스터 하나를 운영하는 것과 성격이 다릅니다.
연습 문제
- openapi 명세에서
/v1/tenant/{tenant_shard_id}/location_config의mode값들을 찾아AttachedSingle,AttachedMulti,Secondary가 storage controller의 어떤 시나리오를 위한 것인지 정리합니다. - pageserver를
docker compose kill pageserver로 강제 종료하고 다시 올렸을 때 compute1이 자동 복구되는지, 복구 시간은 얼마인지 측정합니다. - MinIO 대신 실제 S3 호환 스토리지(예: 사내 object storage)를
pageserver.toml의remote_storage에 지정하려면 어떤 값이 필요한지 정리합니다.