스키마 진화
스키마 진화 (Schema Evolution)
칼럼을 추가하고, 세그먼트를 리로드하고, 언제 새 테이블이 더 깨끗한 경로인지 결정해서 Pinot 스키마를 안전하게 진화시키는 페이지예요.
출처: Schema Evolution
본문
Pinot 스키마 진화는 의도적으로 좁게 설계됐어요. 안전한 경로는 칼럼을 추가하고, 영향을 받는 세그먼트를 리로드하고, 테이블 유형과 데이터 흐름이 지원할 때만 백필(backfill)하는 거예요. 변경이 그보다 더 침습적이라면 기존 테이블을 억지로 늘리는 대신 새 테이블을 만드세요.
안전한 것 (What is safe)
추가적(additive) 스키마 변경이 일반적인 경로예요. 수집 흐름과 세그먼트 리로드 동작을 이해하고 있다면 전체 테이블을 다시 쓰지 않고 새 칼럼을 도입할 수 있어요.
안전하지 않은 것 (What is not safe)
칼럼 이름 변경, 칼럼 삭제, 칼럼 타입 변경은 작은 스키마 트윅이 아니에요. 이런 것은 테이블 재설계 작업으로 취급하세요.
일반적인 흐름 (Typical flow)
- 새 칼럼을 스키마에 추가해요 (이전 행이 기본값을 보여줘야 할 때 적절한
defaultNullValue와 함께). - 새 필드가 변환, 인덱스, upsert 전략 항목이 필요하면 테이블 설정 또는 수집 설정을 업데이트해요.
- 새 소비 세그먼트가 업데이트된 스키마로 시작하도록 보장해요. 명시적 경계에는 forceCommit을 호출하고 상태를 폴링하세요. 변환 변경에는 아래의 일시 중지 절차를 선호하세요.
- 그 경계 이후 완료된 세그먼트를 리로드해서 이전 계획으로 커밋된 세그먼트도 새 칼럼 메타데이터를 노출하게 하세요 (백필하지 않으면
defaultNullValue로 채워짐). - 기본값 대신 실제 값이 필요한 사용 사례라면 과거 데이터를 백필해요 (오프라인/하이브리드 경로).
실시간 소비 세그먼트: 일시 중지, 리로드, 강제 커밋 시점 (Realtime consuming segments: when to pause, reload, or force commit)
이전 문서는 때때로 "칼럼을 추가할 때 항상 소비를 일시 중지하라"고 말했어요. 그보다 더 강한 요구예요. 소비 세그먼트는 변경 가능한 세그먼트가 시작됐을 당시 유효했던 스키마와 테이블 설정으로 빌드돼요. 스키마를 변경할 때 그 변경 가능한 세그먼트에 이미 인덱싱된 행은 제자리에서 다시 쓰여지지 않아요. 실용적인 목표는:
- 완료된(불변) 세그먼트 — 새 칼럼이 나타나도록 리로드 (일반적으로
defaultNullValue로). - 진행 중인 소비 세그먼트 — 커밋하고 최신 스키마/설정을 로드하는 새 소비자로 시작.
- 변환 / 파생 칼럼 — 변경 가능한 세그먼트가 시작됐을 때 로드한 변환 계획으로 계속하는 것을 막기 위해 이전 소비자를 중지.
소비 세그먼트의 서버 리로드는 pinot.server.instance.reload.consumingSegment가 true(기본값)일 때 강제 커밋을 요청해요. 소비자가 비동기로 seal되고 교체 소비자가 최신 스키마와 테이블 설정으로 시작해요. 리로드 작업 완료는 새 소비자가 이미 ONLINE이 되었다는 하드 경계가 아니에요. 명시적 경계를 원하면 POST /tables/{tableName}/forceCommit을 호출하고 상태를 폴링하세요. force commit API를 참조하세요.
결정 테이블 (기존 테이블에 칼럼 추가) (Decision table)
| 테이블 상황 | 스키마만 업데이트하고 기다리면 생기는 위험 | 권장 단계 |
|---|---|---|
| OFFLINE만 | 스트림 소비자에는 없음 | 스키마 업데이트 → 세그먼트 리로드 → 기본값이 아닌 값이 필요하면 백필/재푸시. |
실시간, 일반 칼럼 (스트림에 있거나 defaultNullValue로만 채워짐; 새 변환 없음) |
현재 소비 세그먼트가 커밋될 때까지 이전 스키마를 유지; 그 변경 가능한 세그먼트에 대한 쿼리는 칼럼을 생략하거나 가상 기본값을 사용할 수 있음 | 1) 스키마 업데이트 2) POST /segments/{table}/reload (활성화되면 소비 리로드가 강제 커밋 요청) 또는 POST /tables/{table}/forceCommit 후 폴링 3) 물리적 칼럼 메타데이터나 새 인덱스가 필요하면 완료된 세그먼트 리로드 4) 히스토리를 위한 선택적 오프라인 백필 |
수집 변환이 있는 실시간 (새 칼럼이 transformConfigs / Groovy / built-in으로 채워지거나 변환 로직이 변경됨) |
높음: 활성 소비자가 이전 변환 계획을 계속 사용하므로 세그먼트에 잘못되거나 누락된 파생 값이 포함될 수 있음 | 1) 일시 중지 및 완료 대기 선호 → 스키마 + 테이블 설정 적용 → 완료된 세그먼트 리로드 → 재개 (깨끗한 경계), 또는 2) 스키마/설정 적용 → forceCommit 및 폴링 → 이전 계획으로 커밋된 세그먼트 리로드 3) 이전 소비자가 이미 인덱싱한 행이 제자리에서 수리된다고 가정하지 말 것 |
| 실시간 전체 upsert | 추가 칼럼에 대해 일반 RT와 같은 소비자 경계 문제; 표준 전체 upsert 메타데이터는 정상 force commit 동안 유효하게 유지됨 | 추가 스키마 변경에는 일반 RT 단계를 따름. mode, primary/comparison 칼럼, hash 함수, delete/out-of-order 필드 같은 불변 upsert 설정에 대해서는 재시작을 마이그레이션 경로로 취급하지 말 것; 새 테이블을 만들고 다시 수집할 것. |
| 실시간 부분 upsert | 위와 같은 칼럼/변환 문제, 그리고 기본 RESTRICTED 모드에서 force commit / reload-consuming이 차단되거나 건너뛰어짐 (복제본이 승자 선택에서 분기할 수 있기 때문) |
1) 추가 스키마/설정 업데이트 2) 일시 중지, force commit, reload-consuming 전에 Helix 클러스터 설정 pinot.server.consuming.segment.consistency.mode=PROTECTED 설정 (controller 클러스터 설정으로 — pinot-server.conf가 아님; Consuming Segment Consistency Mode 참조) 3) 유지보수 창을 사용하고 upsert 결과 검증 4) 허용된 partialUpsertStrategies / defaultPartialUpsertStrategy 변경은 통제된 서버 재시작이 필요하고 소급 적용되지 않음 5) 불변 코어 upsert 설정은 새 테이블과 재수집 필요 |
| 하이브리드 (OFFLINE + REALTIME) | 양쪽이 업데이트될 때까지 오프라인과 실시간이 불일치할 수 있음 | 스키마를 한 번 진화 → 오프라인 세그먼트 리로드 (및 백필) → 실시간 쪽은 이 테이블의 해당 행대로 처리 |
잘못된 값이 나타날 수 있는 이유 (Why incorrect values can appear)
- 소비 세그먼트의 변경 가능한 인덱스는 시작 시점에 고정된 스키마/변환 파이프라인으로 생성돼요. 세그먼트 중간에 칼럼이나 변환을 추가해도 그 변경 가능한 세그먼트의 이전 행을 소급 재계산하지 않아요.
- 완료된 세그먼트는 리로드(기본값) 또는 재빌드/백필(실제 값)을 통해서만 새 칼럼을 얻어요.
- Upsert / dedup은 서버 테이블 데이터 매니저 내부에 크로스 세그먼트 상태를 유지해요. 허용된 부분 upsert 전략 변경은 통제된 서버 재시작이 필요하고 소급 적용되지 않아요. 코어 upsert/dedup 정체성과 정렬 설정은 불변이므로 재시작에 의존하는 대신 새 테이블을 사용하고 다시 수집하세요.
최소 실시간 런북 (추가 칼럼) (Minimal realtime runbook)
평범한 추가 칼럼의 경우:
# 1) 업데이트된 스키마 업로드
curl -F [email protected] http://localhost:9000/schemas
# 2) 변환/인덱스가 변경됐으면 테이블 설정도 PATCH/PUT
# 3) 명시적 소비자 경계를 위해 force commit하고 폴링
curl -X POST "http://localhost:9000/tables/myTable/forceCommit"
curl -X GET "http://localhost:9000/tables/forceCommitStatus/<forceCommitJobId>"
# numberOfSegmentsYetToBeCommitted == 0이 될 때까지 대기
# 4) 물리 메타데이터/인덱스가 필요하면 완료된 세그먼트를 리로드.
# 교체 소비자를 강제 커밋하지 않도록 세그먼트별 리로드를 사용.
curl -X POST "http://localhost:9000/segments/myTable/<committedSegmentName>/reload"
변환 변경에는 깨끗한 일시 중지 경계를 사용하세요:
# 1) 일시 중지; pauseConsumption이 현재 소비자를 force commit
curl -X POST "http://localhost:9000/tables/myTable/pauseConsumption"
curl -X GET "http://localhost:9000/tables/myTable/pauseStatus"
# 소비 세그먼트가 커밋될 때까지 대기
# 2) 스키마와 테이블 설정 업로드
# 3) 테이블이 일시 중지된 동안 완료된 세그먼트 리로드
curl -X POST "http://localhost:9000/segments/myTable/reload?type=REALTIME"
# 4) 새 스키마/설정으로 빌드된 소비자로 재개
curl -X POST "http://localhost:9000/tables/myTable/resumeConsumption"
참고 자료 (Reference material)
단계별 오프라인 퀵스타트 워크스루: Schema Evolution 튜토리얼.
운영 API: Segment reload, Force commit, 스트림 수집 일시 중지.
이 페이지에서 다룬 내용 (What this page covered)
- 추가적 스키마 진화 경로와 새 테이블이 더 안전한 경우.
- 실시간 소비자에 대해 일시 중지, 리로드, forceCommit이 필요한 시점.
- Upsert별 제한 (설정 변경 vs 스키마 추가).
다음 단계 (Next step)
스키마 설계가 배치와 스트림 파이프라인에 어떻게 영향을 주는지 보려면 수집 페이지들을 읽으세요.