세그먼트
세그먼트 (Segment)
Apache Pinot의 세그먼트 구성 요소를 살펴보는 페이지예요. 테이블 데이터를 청크로 나누어 효율적으로 저장하고 쿼리하는 세그먼트의 개념과, 배치/스트리밍 방식으로 세그먼트를 생성하는 방법을 다룹니다.
출처: Segment
본문
Pinot 테이블은 세그먼트라고 하는 하나 이상의 독립적인 샤드에 저장돼요. 작은 테이블은 단일 세그먼트에 담길 수 있지만, Pinot는 테이블이 무제한의 세그먼트로 커질 수 있게 해요. 세그먼트를 생성하는 과정은 다양해요 (수집 참조). 세그먼트는 테이블 데이터의 시간 기반 파티션이며, 스토리지와 컴퓨팅 모두에서 필요에 따라 수평 확장되는 Pinot 서버에 저장돼요.
Pinot는 데이터를 세그먼트(관계형 데이터베이스의 샤드/파티션과 유사)로 알려진 더 작은 청크로 나눠서 이를 달성해요. 세그먼트는 시간 기반 파티션으로 볼 수 있어요.
세그먼트는 몇 개의 행을 가진 테이블 데이터의 청크를 나타내는 수평 샤드예요. 세그먼트는 테이블의 모든 칼럼에 대한 데이터를 저장해요. 각 세그먼트는 데이터를 칼럼형(columnar) 방식으로, 칼럼에 대한 딕셔너리와 인덱스와 함께 포장해요. 세그먼트는 칼럼형 형식으로 배치되어 쿼리 서빙을 위해 메모리에 직접 매핑될 수 있어요.
칼럼은 단일 또는 다중 값일 수 있으며 다음 유형이 지원돼요: STRING, BOOLEAN, INT, LONG, FLOAT, DOUBLE, BIG_DECIMAL, TIMESTAMP, BYTES.
칼럼은 스키마에서 **metric 또는 dimension(또는 특히 time dimension)**으로 선언될 수 있어요. 칼럼은 기본 null 값을 가질 수 있어요. 예를 들어 정수 칼럼의 기본 null 값은 0일 수 있어요. bytes 칼럼의 기본값은 스키마에 추가되기 전에 hex 인코딩되어야 해요.
Pinot는 딕셔너리 인코딩을 사용해 값을 딕셔너리 ID로 저장해요. 칼럼은 "no-dictionary" 칼럼으로 구성될 수 있는데, 이 경우 원시 값이 저장돼요. 딕셔너리 ID는 효율적인 저장을 위해 최소 비트 수로 인코딩돼요 (예: 카디널리티가 3인 칼럼은 각 딕셔너리 ID에 2비트만 사용).
각 칼럼에 포워드 인덱스가 빌드되고 메모리 사용을 효율적으로 만드는 압축이 적용돼요. 또한 모든 칼럼 집합에 inverted 인덱스를 선택적으로 구성할 수 있어요. inverted 인덱스는 더 많은 저장 공간을 차지하지만 쿼리 성능을 향상시켜요. Star-Tree 인덱스 같은 특수 인덱스도 지원돼요. 자세한 내용은 Indexing을 참조하세요.
세그먼트 생성 (Creating a segment)
테이블이 구성되면 데이터를 로드할 수 있어요. 데이터 로드는 원시 데이터에서 pinot 세그먼트를 생성하고 이를 pinot 클러스터에 푸시하는 것을 포함해요. 데이터는 배치 모드 또는 스트리밍 모드로 로드할 수 있어요. 자세한 내용은 수집 개요 페이지를 참조하세요.
배치 로드
사전 요구 사항
다음은 독립 실행형 스크립트를 통해 세그먼트를 생성하고 Pinot에 푸시하는 지침이에요. 프로덕션 설정에서는 Hadoop이나 Spark 같은 프레임워크를 사용해야 해요. 데이터 수집 작업 설정에 대한 자세한 내용은 데이터 가져오기를 참조하세요.
Job Spec YAML
세그먼트를 생성하려면 먼저 job spec YAML 파일을 만들어야 해요. 이 파일은 데이터 형식, 입력 데이터 위치, pinot 클러스터 좌표에 관한 모든 정보를 포함해요. controller가 테이블 설정과 스키마를 가져오기 위해 RUNNING 상태라고 가정해요. 그렇지 않으면 spec이 해당 위치를 가리키도록 구성해야 해요. 전체 구성은 Ingestion Job Spec을 참조하세요.
executionFrameworkSpec:
name: 'standalone'
segmentGenerationJobRunnerClassName: 'org.apache.pinot.plugin.ingestion.batch.standalone.SegmentGenerationJobRunner'
segmentTarPushJobRunnerClassName: 'org.apache.pinot.plugin.ingestion.batch.standalone.SegmentTarPushJobRunner'
segmentUriPushJobRunnerClassName: 'org.apache.pinot.plugin.ingestion.batch.standalone.SegmentUriPushJobRunner'
jobType: SegmentCreationAndTarPush
inputDirURI: 'examples/batch/baseballStats/rawdata'
includeFileNamePattern: 'glob:**/*.csv'
excludeFileNamePattern: 'glob:**/*.tmp'
outputDirURI: 'examples/batch/baseballStats/segments'
overwriteOutput: true
pinotFSSpecs:
- scheme: file
className: org.apache.pinot.spi.filesystem.LocalPinotFS
recordReaderSpec:
dataFormat: 'csv'
className: 'org.apache.pinot.plugin.inputformat.csv.CSVRecordReader'
configClassName: 'org.apache.pinot.plugin.inputformat.csv.CSVRecordReaderConfig'
configs:
tableSpec:
tableName: 'baseballStats'
schemaURI: 'http://localhost:9000/tables/baseballStats/schema'
tableConfigURI: 'http://localhost:9000/tables/baseballStats'
segmentNameGeneratorSpec:
pinotClusterSpecs:
- controllerURI: 'http://localhost:9000'
pushJobSpec:
pushParallelism: 2
pushAttempts: 2
pushRetryIntervalMillis: 1000
세그먼트 생성 및 푸시
세그먼트를 한 번에 생성하고 푸시하려면 다음을 사용하세요:
Docker:
docker run \
--network=pinot-demo \
--name pinot-data-ingestion-job \
${PINOT_IMAGE} LaunchDataIngestionJob \
-jobSpecFile examples/docker/ingestion-job-specs/airlineStats.yaml
샘플 콘솔 출력
SegmentGenerationJobSpec:
!!org.apache.pinot.spi.ingestion.batch.spec.SegmentGenerationJobSpec
excludeFileNamePattern: null
executionFrameworkSpec: {extraConfigs: null, name: standalone, segmentGenerationJobRunnerClassName: org.apache.pinot.plugin.ingestion.batch.standalone.SegmentGenerationJobRunner,
segmentTarPushJobRunnerClassName: org.apache.pinot.plugin.ingestion.batch.standalone.SegmentTarPushJobRunner,
segmentUriPushJobRunnerClassName: org.apache.pinot.plugin.ingestion.batch.standalone.SegmentUriPushJobRunner}
includeFileNamePattern: glob:**/*.avro
inputDirURI: examples/batch/airlineStats/rawdata
jobType: SegmentCreationAndTarPush
outputDirURI: examples/batch/airlineStats/segments
overwriteOutput: true
pinotClusterSpecs:
- {controllerURI: 'http://pinot-controller:9000'}
pinotFSSpecs:
- {className: org.apache.pinot.spi.filesystem.LocalPinotFS, configs: null, scheme: file}
pushJobSpec: {pushAttempts: 2, pushParallelism: 1, pushRetryIntervalMillis: 1000,
segmentUriPrefix: null, segmentUriSuffix: null}
recordReaderSpec: {className: org.apache.pinot.plugin.inputformat.avro.AvroRecordReader,
configClassName: null, configs: null, dataFormat: avro}
segmentNameGeneratorSpec: null
tableSpec: {schemaURI: 'http://pinot-controller:9000/tables/airlineStats/schema',
tableConfigURI: 'http://pinot-controller:9000/tables/airlineStats', tableName: airlineStats}
Trying to create instance for class org.apache.pinot.plugin.ingestion.batch.standalone.SegmentGenerationJobRunner
Initializing PinotFS for scheme file, classname org.apache.pinot.spi.filesystem.LocalPinotFS
Finished building StatsCollector!
Collected stats for 403 documents
Created dictionary for INT column: FlightNum with cardinality: 386, range: 14 to 7389
Using fixed bytes value dictionary for column: Origin, size: 294
Created dictionary for STRING column: Origin with cardinality: 98, max length in bytes: 3, range: ABQ to VPS
Created dictionary for INT column: Quarter with cardinality: 1, range: 1 to 1
Created dictionary for INT column: LateAircraftDelay with cardinality: 50, range: -2147483648 to 303
......
......
Pushing segment: airlineStats_OFFLINE_16085_16085_29 to location: http://pinot-controller:9000 for table airlineStats
Sending request: http://pinot-controller:9000/v2/segments?tableName=airlineStats to controller: a413b0013806, version: Unknown
Response for pushing table airlineStats segment airlineStats_OFFLINE_16085_16085_29 to location http://pinot-controller:9000 - 200: {"status":"Successfully uploaded segment: airlineStats_OFFLINE_16085_16085_29 of table: airlineStats"}
Pushing segment: airlineStats_OFFLINE_16084_16084_30 to location: http://pinot-controller:9000 for table airlineStats
Sending request: http://pinot-controller:9000/v2/segments?tableName=airlineStats to controller: a413b0013806, version: Unknown
Response for pushing table airlineStats segment airlineStats_OFFLINE_16084_16084_30 to location http://pinot-controller:9000 - 200: {"status":"Successfully uploaded segment: airlineStats_OFFLINE_16084_16084_30 of table: airlineStats"}
런처 스크립트 사용:
bin/pinot-admin.sh LaunchDataIngestionJob \
-jobSpecFile examples/batch/airlineStats/ingestionJobSpec.yaml
대안으로 jobType을 SegmentCreation 또는 SegmenTarPush로 변경해 세그먼트를 생성하고 푸시를 따로 수행할 수도 있어요.
Ingestion Job Spec 템플릿
Ingestion job spec은 Groovy 문법으로 템플릿을 지원해요.
이것은 수집 작업 템플릿 파일 하나를 생성하고 매일 업데이트되는 추가 파라미터로 매일 스케줄하려는 경우에 편리해요.
예를 들어 날짜를 나타내는 파라미터로 inputDirURI를 설정해 수집 작업이 특정 날짜의 데이터만 처리하도록 할 수 있어요. 입력과 출력 디렉터리의 날짜를 템플릿화하는 예제는 아래와 같아요.
inputDirURI: 'examples/batch/airlineStats/rawdata/${year}/${month}/${day}'
outputDirURI: 'examples/batch/airlineStats/segments/${year}/${month}/${day}'
수집 작업을 시작할 때 ${year}, ${month}, ${day}에 대한 값을 포함한 인자를 전달할 수 있어요: -values $param=value1 $param2=value2...
Docker:
docker run \
--network=pinot-demo \
--name pinot-data-ingestion-job \
${PINOT_IMAGE} LaunchDataIngestionJob \
-jobSpecFile examples/docker/ingestion-job-specs/airlineStats.yaml
-values year=2014 month=01 day=03
이 수집 작업은 2014-01-03 날짜에 대한 세그먼트만 생성해요.
스트리밍 로드
사전 요구 사항
다음은 샘플 데이터를 스트림에 게시하는 방법의 예시예요. 데이터가 실시간 스트림에서 사용 가능해지자마자 실시간 서버가 소비하기 시작해요.
Kafka
Docker:
아래 명령을 실행해 JSON 데이터를 Kafka 토픽 flights-realtime으로 스트리밍하세요:
docker run \
--network pinot-demo \
--name=loading-airlineStats-data-to-kafka \
${PINOT_IMAGE} StreamAvroIntoKafka \
-avroFile examples/stream/airlineStats/sample_data/airlineStats_data.avro \
-kafkaTopic flights-realtime -kafkaBrokerList kafka:9092 -zkAddress pinot-zookeeper:2181/kafka
런처 스크립트 사용:
아래 명령을 실행해 JSON 데이터를 Kafka 토픽 flights-realtime으로 스트리밍하세요:
bin/pinot-admin.sh StreamAvroIntoKafka \
-avroFile examples/stream/airlineStats/sample_data/airlineStats_data.avro \
-kafkaTopic flights-realtime -kafkaBrokerList localhost:19092 -zkAddress localhost:2181/kafka