스키마 진화
스키마 진화 (Schema Evolution)
시간이 지남에 따라 스키마 진화가 발생해요. 비즈니스 요구가 진화하고 데이터 형식이나 구조를 변경해야 할 때, Pinot을 사용해 스키마를 최신 상태로 유지하세요. Pinot에서 스키마를 막 시작했다면 Pinot 테이블용 새 스키마를 만드는 방법을 참고하세요.
이 튜토리얼에서는 스키마에 새 컬럼을 추가하고, 업데이트된 스키마로 데이터를 로드하고, 업데이트된 스키마를 테스트하기 위한 쿼리를 실행하고, 데이터를 백필하는 방법을 배워요.
참고: Pinot은 스키마에 컬럼 추가만 지원해요. 컬럼을 삭제하거나 컬럼 이름이나 데이터 타입을 변경하려면 새 테이블을 만들어야 해요.
출처: 문서
본문
사전 요구 사항 (Prerequisites)
시작하기 전에 Pinot 클러스터가 실행 중이고 baseballStats 테이블(Quickstart 옵션으로 Pinot 클러스터를 설정할 때 생성됨)이 있어야 해요. 자세한 내용은 Quickstart로 Pinot 실행 및 클러스터 설정 방법을 참고하세요.
스키마에 새 컬럼 추가 (Add a new column to your schema)
-
controller API를 사용해 기존 스키마를 가져오세요:
$ curl localhost:9000/schemas/baseballStats > baseballStats.schema -
baseballStats.schema파일을 편집해 스키마 끝에 새 컬럼을 포함시키세요. 예를 들어 여기서는dataType이INT이고defaultNullValue가1인yearsOfExperience라는 새 컬럼을 추가해요.
baseballStats.schema:
{
"schemaName" : "baseballStats",
"dimensionFieldSpecs" : [ {
...
}, {
"name" : "yearsOfExperience",
"dataType" : "INT",
"defaultNullValue": 1
} ]
}
- 다음 명령으로 스키마를 업데이트하세요:
pinot-admin.sh
bin/pinot-admin.sh AddSchema -schemaFile baseballStats.schema -exec
curl
$ curl -F [email protected] localhost:9000/schemas
테이블 세그먼트 리로드 (Reload table segments)
스키마에 새 컬럼을 추가한 후 테이블 세그먼트를 리로드해, 완료된 세그먼트가 새 필드를 노출하도록 하고 실시간 consumer가 새 consuming 세그먼트에서 스키마를 가져오도록 하세요.
- (실시간 테이블)
pinot.server.instance.reload.consumingSegment를 기본값true로 유지해(Server 구성) 일관성 모드가 허용할 때 reload가 consuming 세그먼트에 대해 강제 커밋을 요청하게 하세요. 서버는 현재 mutable 세그먼트를 비동기로 봉인하고 최신 스키마/테이블 구성을 가진 새 consumer를 시작해요.POST /tables/{tableName}/forceCommit을 명시적으로 호출하고 폴링할 수도 있어요. force commit API를 참고하세요. - 새
baseballStats컬럼이 완료된 세그먼트에 나타나도록 하려면 테이블을 리로드하세요 — 상태 폴링 시 아래 샘플reloadJobId를 자신의 것으로 교체하세요:
명령
$ curl -X POST localhost:9000/segments/baseballStats/reload
응답
{"baseballStats_OFFLINE":{"reloadJobId":"c3989a04-9fd1-46af-85e8-00f484759ef2","reloadJobMetaZKStorageStatus":"SUCCESS","numMessagesSent":"3"}}
이것은 테이블의 세그먼트를 호스팅하는 각 서버에서 리로드 연산을 트리거해요. API 응답에는 reloadJobId가 있어, 세그먼트 리로드 상태 API로 리로드 연산 상태를 모니터링할 수 있어요.
참고: 세그먼트 리로드는 진행 중인(in-flight) 쿼리에 영향을 주지 않아요. 기존 세그먼트가 진행 중인 쿼리를 서비스하지 않는 경우에만 새 세그먼트가 기존 세그먼트를 대체하도록 리로드돼요.
명령
$ curl -X GET localhost:9000/segments/segmentReloadStatus/c3989a04-9fd1-46af-85e8-00f484759ef2
응답
{
"estimatedTimeRemainingInMinutes": 0,
"timeElapsedInMinutes": 0.17655,
"totalServersQueried": 3,
"successCount": 12,
"totalSegmentCount": 12,
"totalServerCallsFailed": 0,
"metadata": {
"jobId": "c3989a04-9fd1-46af-85e8-00f484759ef2",
"messageCount": "3",
"submissionTimeMs": "1661753088066",
"jobType": "RELOAD_ALL_SEGMENTS",
"tableName": "baseballStats_OFFLINE"
}
}
참고:
- 실시간 consuming 세그먼트의 경우
pinot.server.instance.reload.consumingSegment가 true이면 리로드는 강제 커밋으로 수행돼요: 현재 consuming 세그먼트가 불변으로 커밋되고, 업데이트된 테이블 구성과 스키마로 새 consuming 세그먼트가 시작돼요.- 모든 컬럼 추가에
pauseConsumption이 필요한 것은 아니에요. 일반적인 기본값 전용 컬럼은 보통 스키마 업데이트 + 리로드/forceCommit이 필요해요. 수집 transform 변경은 consumer가 이전 transform 계획을 유지하지 않도록 일시 중지 경계나 즉시 forceCommit이 더 안전해요. 스키마 진화 결정 테이블을 참고하세요.
- Upsert와 dedup은 테이블 수준(세그먼트 간) 메타데이터를 서버 테이블 데이터 매니저 안에 유지해요. 허용된 partial-upsert 전략 변경은 제어된 서버 재시작이 필요하며 소급 적용되지 않아요. 핵심 identity와 ordering 설정은 불변이므로, reload나 재시작에 의존하는 대신 새 테이블을 만들고 재수집하세요. full-upsert 테이블에 null 기본값 컬럼 추가는 위의 reload/forceCommit 경로를 따르지만, partial-upsert 테이블과 순서 어긋난 처리가 구성된 upsert 테이블은 consuming-segment 일관성 모드가 허용하지 않으면 강제 커밋을 제한해요.
- 어떤 경우, 예를 들어 변환 함수 평가가 실패하거나 리로드되는 세그먼트의 일부가 아닌 컬럼을 참조하면 리로드가 변환을 성공적으로 적용하지 못할 수 있어요. 리로드 상태 API는 새 컬럼 쿼리가 실패하는 동안에도 성공을 보고할 수 있어요 — 서버 리로드 로그를 확인하세요.
데이터 쿼리 및 백필 (Query and backfill data)
- 세그먼트를 리로드한 후 다음을 실행해 새 컬럼을 쿼리하세요:
명령
$ bin/pinot-admin.sh PostQuery \
-queryType sql \
-brokerPort 8000 \
-query "select playerID, yearsOfExperience from baseballStats limit 10" 2>/dev/null
응답
Executing command: PostQuery -brokerHost 192.168.86.234 -brokerPort 8000 -queryType sql -query select playerID, yearsOfExperience from baseballStats limit 10
Result: {"resultTable":{"dataSchema":{"columnNames":["playerID","yearsOfExperience"],"columnDataTypes":["STRING","INT"]},"rows":[["aardsda01",1],["aardsda01",1],["aardsda01",1],["aardsda01",1],["aardsda01",1],["aardsda01",1],["aardsda01",1],["aaronha01",1],["aaronha01",1],["aaronha01",1]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numConsumingSegmentsQueried":0,"numDocsScanned":10,"numEntriesScannedInFilter":0,"numEntriesScannedPostFilter":20,"numGroupsLimitReached":false,"totalDocs":97889,"timeUsedMs":3,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0}
- 보시다시피 쿼리는 새로 추가된 컬럼에 대해
defaultNullValue를 반환해요. 이 컬럼을 실제 값으로 채우려면 과거 날짜에 대한 배치 수집 작업을 다시 실행해 데이터를 백필하세요.
경고: 백필링은 실시간 테이블에서 동작하지 않아요. 같은 상대를 사용하는 오프라인 테이블을 추가해 실시간 테이블을 하이브리드 테이블로 변환한 다음, 오프라인 테이블을 백필해 새로 추가된 컬럼 값을 채울 수 있어요. 자세한 내용은 하이브리드 테이블을 참고하세요.