Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
08f59d6
chore(license): Apache 2.0 헤더를 MIT로 통일
Jun 25, 2026
eabbd98
feat(ha): #220 자동 failover 안정화 — fencing·pod-readiness·promotion guard
Jun 25, 2026
df4aa52
feat(backup): #209 PITR restore 오프라인 오케스트레이션 완성
Jun 25, 2026
20c82af
test(e2e): fixture 독립화 + manager rollout 안정화
Jun 25, 2026
e602751
feat(pooler): PgBouncer stats_users 파라미터 허용
Jun 25, 2026
910ad01
docs: 프로젝트 분석 문서 + dev 환경 가이드 추가
Jun 25, 2026
dd2d8df
chore(dev): devcontainer 설정 + gitattributes
Jun 25, 2026
e9804f2
style(controller): 프로덕션 lint 지적 정리 (mapsloop·unparam)
Jun 25, 2026
d02fa34
docs: 작업 인수인계 문서(WORK_HANDOFF.ko.md) 추가
Jun 25, 2026
09b4f68
docs: 분석 문서 지도(DOCS_MAP) 신설 + 중복 정리
Jun 25, 2026
9cd7050
docs: 문서 작성 규칙을 에이전트 지침으로 고정 (AGENTS.md -> DOCS_MAP)
Jun 25, 2026
95c016b
docs: 용어집(GLOSSARY) 신설 + 각 문서 마지막 장 용어집 절 + 지침화
Jun 25, 2026
b85b9c3
chore(ha): operator-level failover 전용 lease 무효 배선 제거
Jun 26, 2026
488f922
feat(router): 교체 가능한 라우팅키 추출 전략(regex/parser/auto) + 라우터 갭 분석
Jun 26, 2026
d60e7d8
feat(router): CRD 토폴로지 소싱 + failover-aware 백엔드 + 샤드 장애 우아한 실패
Jun 26, 2026
dbf26d6
feat(router): 배포 가능한 라우터 컴포넌트(Dockerfile.router + config/router) + 핸드오프
Jun 26, 2026
ef4836a
perf(router): SQLShardExecutor 연결 풀링(shard별 *sql.DB 캐시)
Jun 26, 2026
830d937
feat(router): 백엔드 dial retry/backoff + per-backend circuit-breaker
Jun 26, 2026
ee48b49
feat(router): 읽기 쿼리 replica 라우팅 부품(StatusBackendResolver.ResolveRead …
Jun 26, 2026
3b6f0b3
feat(router): reference table 라우팅 (ShardRange.referenceTables + 라우터 해소)
Jun 26, 2026
b53dea6
fix(router): scatter merge 타입 인지 정렬 + LIMIT
Jun 26, 2026
cd5f516
feat(router): 쿼리 라우팅 결정 엔진 QueryRouter (E 증분)
Jun 26, 2026
db04bdb
docs(router): 백로그 배치 진행 상황 + 보류 항목 사유 기록
Jun 26, 2026
5dae032
feat(router): consistent-hash vindex (리밸런싱 친화적 샤딩)
Jun 26, 2026
7829033
docs(perf): 1차 로컬 실측 baseline (single-shard, kind)
Jun 26, 2026
fab05f6
docs(test): 라우터/샤딩 테스트 카탈로그 + DOCS_MAP 등록
Jun 26, 2026
f50cedd
fix(router): 라우팅 키 추출 정확성 보강 (모호 키 bail + dollar-quote)
Jun 26, 2026
8f51a0a
feat(router): 읽기 replica lag 임계(bounded staleness)
Jun 26, 2026
0f8759e
docs(router): 로직 보강 항목 백로그 기록 (P0 완료 3건 + P1 추적 6건)
Jun 26, 2026
cec61bb
perf(router): consistent-hash 링 fingerprint 캐시
Jun 26, 2026
59c310a
feat(router): scatter ORDER BY 다중컬럼/방향 + LIMIT pushdown
Jun 26, 2026
0dca370
feat(router): 회복력 보강 — breaker half-open + pool ConnMaxLifetime + 토폴로…
Jun 26, 2026
44b07a6
docs(router): 로직 보강 P1 6건 완료 표시 (테스트 미실행 단서)
Jun 26, 2026
f9f9e42
docs(router): ErrVindexUnsupported 주석 정정 (consistent-hash 구현됨)
Jun 26, 2026
f061e66
fix(router): scatter NULL 정렬을 PG 기본(NULLS LAST asc / FIRST desc)에 정합
Jun 27, 2026
066f9a0
docs(router): 보강 6건 + NULL정렬 검증 완료 표시
Jun 27, 2026
e4706a4
feat(router): PG wire 메시지 프레이머 — (E) 쿼리 인지 종단 토대
Jun 27, 2026
38a944e
feat(router): (E) query-mode 핸들러 — 첫 쿼리 인지 라우팅 결선
Jun 27, 2026
de639f7
docs(router): (E) query-mode PoC 결선 반영
Jun 27, 2026
adca2af
fix(router): query-mode 백엔드 핸드셰이크 소비 + Dockerfile 패키지 빌드 (라이브 검증)
Jun 27, 2026
74a6111
docs(router): (E) query-mode 라이브 PG end-to-end 검증 성공 기록
Jun 27, 2026
7342a0d
feat(router): (E) extended protocol(Parse) 라우팅 + per-query 로그
Jun 27, 2026
bf78fef
feat(router): query-mode 파라미터 라우팅(Bind 상관) — 단일라운드 extended 한정
Jun 27, 2026
5bbee2d
docs(router): (E) 파라미터 라우팅 진척 + lib/pq describe-first 한계 기록
Jun 27, 2026
c58d08e
docs: 분산 SQL 라우터 진척 최신화 + 앞으로의 개발 로드맵
Jun 27, 2026
ed3e878
feat(router): (E) scram-sha-256 백엔드 인증 대행 — 프로덕션 PG 동작
Jun 27, 2026
1e9cd13
docs(router): (E) scram 인증 대행 완료 반영 — query-mode 프로덕션 PG 동작
Jun 27, 2026
3ebdc0d
docs: 분산 라우터 현황 동기화 (RFC-0004 스펙·E2E 히스토리·CHANGELOG)
Jun 27, 2026
08f9904
feat(router): (E) describe-round 대행 — lib/pq 등 파라미터 쿼리 라우팅
Jun 27, 2026
48fb00c
docs(router): (E) describe-round 완료 반영 — 실 DB 드라이버 동작
Jun 27, 2026
895796c
feat(router): (E) 멀티샤드 scatter forwarding — 분산 읽기
Jun 27, 2026
b2abbc6
perf(router): scatter fan-out 병렬화 — 분산 읽기 확장
Jun 27, 2026
0bd8f1e
docs(router): scatter forwarding 완료 + 연결 고정(per-connection pinning) 한…
Jun 27, 2026
fa212b6
feat(pg-router): query-mode per-query 라우팅 (연결 고정 해소)
Jun 27, 2026
c3fc111
docs: per-query 라우팅 완료 반영 (연결 고정 한계 해소)
Jun 27, 2026
9f3f1a5
feat(pg-router): per-query extended protocol 라우팅 (prepared stmt 샤드별 l…
Jun 27, 2026
69a57ba
feat(router-bench): 분산 라우팅 처리량 실측 도구 + baseline 2차
Jun 27, 2026
a78ea4e
docs: per-query extended + 분산 수치 1차 완료 반영, 다음 우선순위 갱신
Jun 27, 2026
484040d
feat(router-bench): prepared-stmt 재사용 모드 + 라우터 오버헤드 정량화
Jun 27, 2026
76a71c9
docs(perf): 수평 스케일 단일 호스트 물리적 한계 실증 기록 (§3.0d)
Jun 27, 2026
a133d14
feat(pg-router): reference table·read-replica 프록시 결선
Jun 27, 2026
7d33266
feat(router): resharding 실데이터 이동 — hash-range 필터 copy + cutover delete
Jun 27, 2026
2b12c42
docs: reference/read-replica·resharding 데이터이동 완료 반영 + 우선순위 갱신
Jun 27, 2026
2af62e6
perf(pg-router): bufio 읽기 버퍼 + 단일 write — per-query 오버헤드 감소
Jun 27, 2026
5f7ff6c
docs: bufio 라우터 최적화 완료 반영 + 우선순위 갱신
Jun 27, 2026
e33d9d7
feat(controller): ShardSplitJob InitialCopy 실 결선 (K8s Job 기반 데이터 이동)
Jun 28, 2026
57f4e49
build: reshard-copy Dockerfile (InitialCopy Job 이미지)
Jun 28, 2026
19cf877
docs: ShardSplitJob InitialCopy K8s 결선 완료 반영
Jun 28, 2026
8cf60ff
feat(controller): ShardSplitJob Cleanup 결선 (source 이동분 삭제 Job)
Jun 28, 2026
ece86c9
docs: ShardSplitJob Cleanup 결선 완료 반영
Jun 28, 2026
39bd553
feat(resharding): Cutover write-block — 라우팅 전환 중 쓰기 차단(읽기 통과)
Jun 28, 2026
7684cdc
docs: Cutover write-block 완료 반영
Jun 28, 2026
419918c
feat(router-bench): 멀티 라우터 인스턴스 측정 (BENCH_ROUTERS)
Jun 28, 2026
0e007d2
feat(router): resharding 스키마 우선 복제 (target 테이블 자동 생성)
Jun 28, 2026
a601e0a
docs: 멀티 라우터 + resharding full e2e 성공 반영
Jun 28, 2026
0e9bb52
feat(resharding): CDC 증분 catch-up 빌딩블록 (논리복제) + wal_level=logical
Jun 28, 2026
7b5c205
docs: CDC 증분 catch-up 빌딩블록 완료 반영
Jun 28, 2026
50c16ab
feat(controller): CDCCatchup phase 결선 — online 무중단 resharding 모드
Jun 28, 2026
f24ae20
docs: CDCCatchup phase 컨트롤러 결선(online) 완료 반영
Jun 28, 2026
a025d96
feat(resharding): 운영화 마감 — 인덱스/PK 복제 + RESHARD_COPY_IMAGE manager env
Jun 28, 2026
9f530ed
docs: resharding 운영화(인덱스 복제·이미지 env) 완료 반영
Jun 28, 2026
f142bbb
docs: online 모드 full e2e 성공 반영
Jun 28, 2026
aab80da
feat(resharding): 외래키/체크 제약 복제 (스키마 충실도 완성)
Jun 28, 2026
67bcdb6
docs: 외래키/체크 제약 복제 완료 반영 + target 승격 RFC급 명시
Jun 28, 2026
457c96d
docs(adr): ADR-0029 resharding target 승격 정체성 전이 설계
Jun 28, 2026
c20ac22
feat(controller): ADR-0029 P-A — shard-id 명명 식별 label 토대 (additive)
Jun 28, 2026
0e6f043
fix(resharding): CreatePublication 멱등(존재 시 skip) — Job 재시도 안전
Jun 28, 2026
91ad6f3
docs(handoff): §6.7 2026-06-28 세션 요약 + 코드 리뷰 발견 + 다음 진입점
Jun 28, 2026
6d006ab
fix(sharding): harden reshard routing and abort cleanup
Jun 28, 2026
70a9394
fix(sharding): report target shard service endpoints
Jun 28, 2026
43fa8f7
feat(sharding): surface active named target shards
Jun 28, 2026
cd1833f
feat(sharding): decommission inactive ordinal sources
Jun 28, 2026
96ab4f8
feat(sharding): scale active named targets for HA
Jun 28, 2026
ca71422
feat(sharding): add reshard promote adopt phase
Jun 28, 2026
0149a42
fix(sharding): gate reshard promote on shardrange topology
Jun 28, 2026
052463d
fix(sharding): wait for ready target before promote adopt
Jun 28, 2026
dc75c0b
test(sharding): pin retained source resources after promote
Jun 28, 2026
8ba9e25
docs(sharding): settle named topology on shardrange
Jun 28, 2026
f45a490
chore(test): add windows go test wrapper
Jun 29, 2026
538ca1e
chore(test): enforce e2e resource cleanup policy
Jun 29, 2026
5075e80
docs(router): design cpu hpa autoscaling
Jun 29, 2026
c13354b
docs(router): plan cpu hpa implementation
Jun 29, 2026
4c0e06f
feat(router): build cpu hpa resources
Jun 29, 2026
9b84865
chore(test): allow windows go test executables
Jun 29, 2026
fad60b2
feat(router): reconcile cpu hpa
Jun 29, 2026
3969d68
fix(webhook): validate router autoscale bounds
Jun 29, 2026
5c3aa02
chore(api): regenerate router autoscale rbac
Jun 29, 2026
9aa0eef
chore(test): allow controller test executable
Jun 29, 2026
d13db0e
docs: sync roadmap/handoff/tests with current sharding+HPA implementa…
Jun 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .devcontainer/devcontainer-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"version": "2.5.9",
"resolved": "ghcr.io/devcontainers/features/common-utils@sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a",
"integrity": "sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a"
},
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"version": "2.17.0",
"resolved": "ghcr.io/devcontainers/features/docker-in-docker@sha256:25b9f05705ffba7dbe503230ac76081419306f8c8bc88e0ce78c4ecd99a0c78c",
"integrity": "sha256:25b9f05705ffba7dbe503230ac76081419306f8c8bc88e0ce78c4ecd99a0c78c"
},
"ghcr.io/devcontainers/features/git:1": {
"version": "1.3.5",
"resolved": "ghcr.io/devcontainers/features/git@sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251",
"integrity": "sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251"
}
}
}
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Kubebuilder DevContainer",
"image": "golang:1.25",
"image": "golang:1.26",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"moby": false,
Expand Down
33 changes: 33 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 모든 텍스트 파일의 기본 라인 엔딩: LF (Unix)
* text=auto eol=lf

# 명시적으로 LF 강제 (스크립트 실행 파일)
*.sh text eol=lf
*.bash text eol=lf

# Go 소스 코드
*.go text eol=lf

# 설정 / 마크다운 / 기타 텍스트
*.md text eol=lf
*.json text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
Makefile text eol=lf
Dockerfile* text eol=lf
*.toml text eol=lf
*.mod text eol=lf
*.sum text eol=lf
*.txt text eol=lf
*.env text eol=lf

# 바이너리 파일 (라인 엔딩 변환 금지)
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.zip binary
*.tar binary
*.gz binary
*.pdf binary
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Dockerfile.cross

# Test binary, built with `go test -c`
*.test
*.test.exe

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
Expand Down
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,14 @@ make bundle VERSION=X.Y.Z # OLM bundle 재생성
Apache 헤더 발견 시 보고 후 교체 (pg-router 정리 선례).
- 의존성 갱신: `renovate.json` (mongodb-operator 와 동일 기준 — k8s.io 그룹 / major 분리).
- CI (`.gitlab-ci.yml`): golang 이미지 버전은 `go.mod` 의 go directive 와 정합 유지.

### 문서 작성 (`docs/`)

`docs/` 의 한국어 분석/작업 문서는 [`docs/DOCS_MAP.ko.md`](docs/DOCS_MAP.ko.md) 의 지침을 **구속력 있는 규칙**으로 따른다. Claude / Codex / Cursor 등 어떤 에이전트나 작업자라도 `docs/` 를 편집할 때 동일하게 준수한다.

- **주제별 SSOT 준수**: 같은 사실(검증 수치·CRLF 조치·E2E 명령 등)을 여러 문서에 복제하지 않는다. 본문은 DOCS_MAP §3 이 지정한 출처 1곳에만 두고, 나머지는 링크한다.
- **계층 유지**: 요약은 `PROJECT_OVERVIEW`, 상세 동작은 `FEATURE_DEEP_DIVE`. 개요 문서에 상세를 복붙하지 않는다.
- **보강 위치**: 새 내용을 어디에 쓸지는 DOCS_MAP §5 를 따른다(기능→OVERVIEW/DEEP_DIVE, 테스트→TEST_ANALYSIS/E2E_REPORT, 작업→WORK_HANDOFF+메모리, 환경→dev-setup).
- **지도 갱신 의무**: 새 분석/작업 문서를 추가하면 DOCS_MAP §1 표와 §2 관계도에 한 줄 추가한다.
- 미완 여부·현재 상태는 문서 단정 대신 코드/`git log` 로 확인한다(과거 "미구현" 오진 선례).
- **용어집 유지**: 각 분석 문서는 **마지막 장에 "용어집" 절**을 둔다. 용어 정의 SSOT 는 [`docs/GLOSSARY.ko.md`](docs/GLOSSARY.ko.md) — 각 문서는 등장 용어만 **그 정의를 그대로 발췌**(어디서 보든 동일) + 전체 링크. 새 용어는 GLOSSARY 에 먼저 추가하고 발췌 측을 맞춘다.
23 changes: 23 additions & 0 deletions Dockerfile.reshard
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Build the reshard-copy-poc binary — the online-resharding InitialCopy data mover
# (ShardSplitJob InitialCopy phase runs this as a K8s Job; RFC 0003 / ROADMAP G4).
# Separate image from the operator manager so the copy Job pulls a minimal surface.
FROM golang:1.26.4@sha256:68cb6d68bed024785b69195b89af7ac7a444f27791435f98647edff595aa0479 AS builder
ARG TARGETOS
ARG TARGETARCH

WORKDIR /workspace
COPY go.mod go.mod
COPY go.sum go.sum
RUN go mod download

COPY . .

# CGO disabled → static distroless binary (lib/pq is pure Go).
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o reshard-copy ./cmd/reshard-copy-poc

FROM gcr.io/distroless/static:nonroot@sha256:e3f945647ffb95b5839c07038d64f9811adf17308b9121d8a2b87b6a22a80a39
WORKDIR /
COPY --from=builder /workspace/reshard-copy .
USER 65532:65532

ENTRYPOINT ["/reshard-copy"]
22 changes: 22 additions & 0 deletions Dockerfile.router
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Build the pg-router binary (the stateless query router — RFC 0004 / ROADMAP G3).
# Separate image from the operator manager so the router can scale independently.
FROM golang:1.26.4@sha256:68cb6d68bed024785b69195b89af7ac7a444f27791435f98647edff595aa0479 AS builder
ARG TARGETOS
ARG TARGETARCH

WORKDIR /workspace
COPY go.mod go.mod
COPY go.sum go.sum
RUN go mod download

COPY . .

# CGO disabled → static distroless binary (zero external runtime, minimal surface).
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o pg-router ./cmd/pg-router

FROM gcr.io/distroless/static:nonroot@sha256:e3f945647ffb95b5839c07038d64f9811adf17308b9121d8a2b87b6a22a80a39
WORKDIR /
COPY --from=builder /workspace/pg-router .
USER 65532:65532

ENTRYPOINT ["/pg-router"]
20 changes: 16 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,23 @@ setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist
# 빈 값(디폴트)은 전체 e2e 실행. CI 매트릭스가 본 변수를 채워 Pillar 단위 통과율을
# 추적한다(.github/workflows/ci.yml의 e2e job).
PILLAR ?=
KEEP ?=
E2E_LABEL_FILTER = $(if $(PILLAR),-ginkgo.label-filter=$(PILLAR),)

define run-e2e-and-cleanup
@status=0; \
KIND=$(KIND) KIND_CLUSTER=$(KIND_CLUSTER) $(1) || status=$$?; \
if [ "$(KEEP)" = "1" ]; then \
echo "KEEP=1 set; preserving Kind cluster '$(KIND_CLUSTER)'."; \
else \
$(MAKE) cleanup-test-e2e || { cleanup_status=$$?; if [ $$status -eq 0 ]; then status=$$cleanup_status; fi; }; \
fi; \
exit $$status
endef

.PHONY: test-e2e
test-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. Use PILLAR=p1 to narrow to one Pillar.
KIND=$(KIND) KIND_CLUSTER=$(KIND_CLUSTER) PILLAR=$(PILLAR) go test -tags=e2e ./test/e2e/ -v -ginkgo.v $(if $(PILLAR),-ginkgo.label-filter=$(PILLAR),)
$(MAKE) cleanup-test-e2e
$(call run-e2e-and-cleanup,PILLAR=$(PILLAR) go test -tags=e2e ./test/e2e/ -v -ginkgo.v $(E2E_LABEL_FILTER))

.PHONY: cleanup-test-e2e
cleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests
Expand All @@ -140,13 +152,13 @@ cleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests
# KEEP=1 시 cleanup skip (로컬 디버그).
.PHONY: test-e2e-pg
test-e2e-pg: setup-test-e2e manifests generate fmt vet ## Run RFC 0006 R1+R2 회귀 e2e (kind 의존, p1 라벨).
KIND=$(KIND) KIND_CLUSTER=$(KIND_CLUSTER) go test -tags=e2e ./test/e2e/ -timeout 30m -v -ginkgo.v -ginkgo.label-filter=p1
$(call run-e2e-and-cleanup,go test -tags=e2e ./test/e2e/ -timeout 30m -v -ginkgo.v -ginkgo.label-filter=p1)

# RFC 0006 R3 회귀 — primary kill → 새 primary auto-promote (RTO < 30s) → 옛 primary standby rejoin.
# replicas=1 (Pod 2 개) failover 라이프사이클. KEEP=1 시 cleanup skip.
.PHONY: test-e2e-failover
test-e2e-failover: setup-test-e2e manifests generate fmt vet ## RFC 0006 R3 회귀 e2e (kind 의존, p2 라벨, replicas=1 primary kill).
KIND=$(KIND) KIND_CLUSTER=$(KIND_CLUSTER) go test -tags=e2e ./test/e2e/ -timeout 30m -v -ginkgo.v -ginkgo.label-filter=p2
$(call run-e2e-and-cleanup,go test -tags=e2e ./test/e2e/ -timeout 30m -v -ginkgo.v -ginkgo.label-filter=p2)

.PHONY: lint
lint: golangci-lint ## Run golangci-lint linter
Expand Down
11 changes: 11 additions & 0 deletions api/v1alpha1/shardrange_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ type ShardRangeSpec struct {
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:MaxItems=1024
Ranges []ShardRangeEntry `json:"ranges"`

// ReferenceTables은 이 키스페이스에서 *모든 샤드에 복제*되는 reference 테이블 목록이다.
// 이 테이블만 참조하는 쿼리는 샤딩 키 없이 임의 샤드로 라우팅된다 (분산 조인 우회).
// +optional
ReferenceTables []string `json:"referenceTables,omitempty"`

// WriteBlocked은 true 면 라우터가 이 키스페이스로의 *쓰기*를 일시 거부한다(읽기는 통과).
// online resharding 의 Cutover 동안 라우팅 전환 중 쓰기 유실을 막는 write-block 신호로
// ShardSplitJob 컨트롤러가 설정/해제한다(Cutover=true, RoutingUpdate 완료 시 false).
// +optional
WriteBlocked bool `json:"writeBlocked,omitempty"`
}

// ShardRangeStatus은 reconciler 가 관찰한 ShardRange 상태이다.
Expand Down
13 changes: 11 additions & 2 deletions api/v1alpha1/shardsplitjob_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import (
// 본 CRD 는 *state machine 만 정의* — 실 step 구현은 internal/controller/
// shardsplit/ + internal/router/ 에 위임 (P-D §D.9.* 후속).

// ShardSplitJobPhase 는 7-step state machine 의 현재 phase 이다.
// +kubebuilder:validation:Enum=Pending;SnapshotWAL;Bootstrap;InitialCopy;CDCCatchup;Cutover;RoutingUpdate;Cleanup;Completed;Failed;Aborted
// ShardSplitJobPhase 는 resharding state machine 의 현재 phase 이다.
// +kubebuilder:validation:Enum=Pending;SnapshotWAL;Bootstrap;InitialCopy;CDCCatchup;Cutover;RoutingUpdate;Cleanup;Promote;Completed;Failed;Aborted
type ShardSplitJobPhase string

const (
Expand All @@ -38,6 +38,7 @@ const (
ShardSplitPhaseCutover ShardSplitJobPhase = "Cutover"
ShardSplitPhaseRoutingUpdate ShardSplitJobPhase = "RoutingUpdate"
ShardSplitPhaseCleanup ShardSplitJobPhase = "Cleanup"
ShardSplitPhasePromote ShardSplitJobPhase = "Promote"
ShardSplitPhaseCompleted ShardSplitJobPhase = "Completed"
ShardSplitPhaseFailed ShardSplitJobPhase = "Failed"
ShardSplitPhaseAborted ShardSplitJobPhase = "Aborted"
Expand Down Expand Up @@ -101,6 +102,14 @@ type ShardSplitJobSpec struct {
// +kubebuilder:default=false
// +optional
AllowForwardOnly bool `json:"allowForwardOnly,omitempty"`

// Online 은 true 면 CDC(논리복제) 기반 *무중단* 이동을 쓴다 — InitialCopy 의 정적 bulk
// 복사 대신 CDCCatchup phase 가 subscription(copy_data=true)으로 라이브 쓰기를 따라잡고,
// write-block 은 최종 drain 구간만 짧게 건다. false(기본)는 offline 모드(InitialCopy
// 범위복사 + cutover write-block) — 동시 쓰기 없는 유지보수 창에 단순·빠름.
// +kubebuilder:default=false
// +optional
Online bool `json:"online,omitempty"`
}

// ShardSplitTarget 는 split/merge 의 target shard 1건 정의.
Expand Down
8 changes: 4 additions & 4 deletions api/v1alpha1/shardsplitjob_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ import (
)

func TestShardSplitJob(t *testing.T) {
t.Run("Phase 전체 11 값 stringify", func(t *testing.T) {
t.Run("Phase 전체 12 값 stringify", func(t *testing.T) {
phases := []ShardSplitJobPhase{
ShardSplitPhasePending, ShardSplitPhaseSnapshotWAL,
ShardSplitPhaseBootstrap, ShardSplitPhaseInitialCopy,
ShardSplitPhaseCDCCatchup, ShardSplitPhaseCutover,
ShardSplitPhaseRoutingUpdate, ShardSplitPhaseCleanup,
ShardSplitPhaseCompleted, ShardSplitPhaseFailed,
ShardSplitPhasePromote, ShardSplitPhaseCompleted, ShardSplitPhaseFailed,
ShardSplitPhaseAborted,
}
if len(phases) != 11 {
t.Fatalf("phase count want=11 got=%d", len(phases))
if len(phases) != 12 {
t.Fatalf("phase count want=12 got=%d", len(phases))
}
for _, p := range phases {
if string(p) == "" {
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ spec:
maxItems: 1024
minItems: 1
type: array
referenceTables:
description: |-
ReferenceTables은 이 키스페이스에서 *모든 샤드에 복제*되는 reference 테이블 목록이다.
이 테이블만 참조하는 쿼리는 샤딩 키 없이 임의 샤드로 라우팅된다 (분산 조인 우회).
items:
type: string
type: array
vindex:
description: Vindex은 키 → 샤드 매핑 함수 정의이다.
properties:
Expand Down Expand Up @@ -145,6 +152,12 @@ spec:
required:
- type
type: object
writeBlocked:
description: |-
WriteBlocked은 true 면 라우터가 이 키스페이스로의 *쓰기*를 일시 거부한다(읽기는 통과).
online resharding 의 Cutover 동안 라우팅 전환 중 쓰기 유실을 막는 write-block 신호로
ShardSplitJob 컨트롤러가 설정/해제한다(Cutover=true, RoutingUpdate 완료 시 false).
type: boolean
required:
- cluster
- keyspace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ spec:
description: Keyspace 는 ShardRange 의 keyspace 식별자.
pattern: ^[a-z][a-z0-9_]{0,62}$
type: string
online:
default: false
description: |-
Online 은 true 면 CDC(논리복제) 기반 *무중단* 이동을 쓴다 — InitialCopy 의 정적 bulk
복사 대신 CDCCatchup phase 가 subscription(copy_data=true)으로 라이브 쓰기를 따라잡고,
write-block 은 최종 drain 구간만 짧게 건다. false(기본)는 offline 모드(InitialCopy
범위복사 + cutover write-block) — 동시 쓰기 없는 유지보수 창에 단순·빠름.
type: boolean
sources:
description: 'Sources 는 source shard ID 목록 (split: 1, merge: N).'
items:
Expand Down Expand Up @@ -272,6 +280,7 @@ spec:
- Cutover
- RoutingUpdate
- Cleanup
- Promote
- Completed
- Failed
- Aborted
Expand Down
17 changes: 9 additions & 8 deletions cmd/instance/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,11 @@ func main() {
dataDir := envOrDie("POSTGRES_DATA_DIR")
binDir := envOrDie("POSTGRES_BIN_DIR")
primaryEndpoint := os.Getenv("PRIMARY_ENDPOINT")
endpoint := instanceEndpoint(podName, cluster, int32(shardOrdinal), namespace)
serviceName := os.Getenv("POSTGRES_SERVICE_NAME")
if serviceName == "" {
serviceName = fmt.Sprintf("%s-shard-%d-headless", cluster, shardOrdinal)
}
endpoint := instanceEndpoint(podName, serviceName, namespace)
restartedPrimaryAsStandby, err := prepareRestartedPrimaryAsStandby(
dataDir, primaryEndpoint, binDir, podName, memberCount, logger,
)
Expand Down Expand Up @@ -297,7 +301,7 @@ func main() {
}()
logger.Info("Election started", "identity", elect.Identity(), "lease", leaseName)

startStatusReporterIfPossible(ctx, clientset, namespace, podName, cluster, int32(shardOrdinal), dataDir, elect, sup, logger)
startStatusReporterIfPossible(ctx, clientset, namespace, podName, endpoint, dataDir, elect, sup, logger)

select {
case <-ctx.Done():
Expand Down Expand Up @@ -519,9 +523,8 @@ func patchRejoinFailureStatus(
return patchPodAnnotation(ctx, clientset, namespace, podName, st)
}

func instanceEndpoint(podName, cluster string, shardOrdinal int32, namespace string) string {
return fmt.Sprintf("%s.%s-shard-%d-headless.%s.svc.cluster.local:5432",
podName, cluster, shardOrdinal, namespace)
func instanceEndpoint(podName, serviceName, namespace string) string {
return fmt.Sprintf("%s.%s.%s.svc.cluster.local:5432", podName, serviceName, namespace)
}

func delayElectionForRestartedPrimary(ctx context.Context, delay time.Duration, podName string, logger *slog.Logger) {
Expand All @@ -542,8 +545,7 @@ func delayElectionForRestartedPrimary(ctx context.Context, delay time.Duration,
func startStatusReporterIfPossible(
ctx context.Context,
clientset kubernetes.Interface,
namespace, podName, cluster string,
shardOrdinal int32,
namespace, podName, endpoint string,
dataDir string,
elect election.Election,
sup supervise.Supervisor,
Expand All @@ -552,7 +554,6 @@ func startStatusReporterIfPossible(
if clientset == nil {
return
}
endpoint := instanceEndpoint(podName, cluster, shardOrdinal, namespace)
go runStatusReporter(ctx, clientset, namespace, podName, endpoint, dataDir, elect, sup, logger)
}

Expand Down
8 changes: 8 additions & 0 deletions cmd/instance/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ func TestParsePodOrdinalOrDie_StatefulSetName(t *testing.T) {
}
}

func TestInstanceEndpoint_UsesProvidedServiceName(t *testing.T) {
got := instanceEndpoint("orders-rsd-t1-0", "orders-rsd-t1-headless", "ns1")
want := "orders-rsd-t1-0.orders-rsd-t1-headless.ns1.svc.cluster.local:5432"
if got != want {
t.Fatalf("endpoint = %q, want %q", got, want)
}
}

func TestPrepareRestartedPrimaryAsStandby_UsesCurrentPrimaryEndpointForAnyOrdinal(t *testing.T) {
dir := t.TempDir()
marker := filepath.Join(dir, supervise.RestartPrimaryAsStandbyMarker)
Expand Down
Loading