스키마 진화

스키마 진화 (Schema Evolution)

칼럼을 추가하고, 세그먼트를 리로드하고, 언제 새 테이블이 더 깨끗한 경로인지 결정해서 Pinot 스키마를 안전하게 진화시키는 페이지예요.

출처: Schema Evolution

본문

Pinot 스키마 진화는 의도적으로 좁게 설계됐어요. 안전한 경로는 칼럼을 추가하고, 영향을 받는 세그먼트를 리로드하고, 테이블 유형과 데이터 흐름이 지원할 때만 백필(backfill)하는 거예요. 변경이 그보다 더 침습적이라면 기존 테이블을 억지로 늘리는 대신 새 테이블을 만드세요.

안전한 것 (What is safe)

추가적(additive) 스키마 변경이 일반적인 경로예요. 수집 흐름과 세그먼트 리로드 동작을 이해하고 있다면 전체 테이블을 다시 쓰지 않고 새 칼럼을 도입할 수 있어요.

안전하지 않은 것 (What is not safe)

칼럼 이름 변경, 칼럼 삭제, 칼럼 타입 변경은 작은 스키마 트윅이 아니에요. 이런 것은 테이블 재설계 작업으로 취급하세요.

일반적인 흐름 (Typical flow)

  1. 새 칼럼을 스키마에 추가해요 (이전 행이 기본값을 보여줘야 할 때 적절한 defaultNullValue와 함께).
  2. 새 필드가 변환, 인덱스, upsert 전략 항목이 필요하면 테이블 설정 또는 수집 설정을 업데이트해요.
  3. 새 소비 세그먼트가 업데이트된 스키마로 시작하도록 보장해요. 명시적 경계에는 forceCommit을 호출하고 상태를 폴링하세요. 변환 변경에는 아래의 일시 중지 절차를 선호하세요.
  4. 그 경계 이후 완료된 세그먼트를 리로드해서 이전 계획으로 커밋된 세그먼트도 새 칼럼 메타데이터를 노출하게 하세요 (백필하지 않으면 defaultNullValue로 채워짐).
  5. 기본값 대신 실제 값이 필요한 사용 사례라면 과거 데이터를 백필해요 (오프라인/하이브리드 경로).

실시간 소비 세그먼트: 일시 중지, 리로드, 강제 커밋 시점 (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)

스키마 설계가 배치와 스트림 파이프라인에 어떻게 영향을 주는지 보려면 수집 페이지들을 읽으세요.

더 알아보기 (Learn more)