인덱스 구성
인덱스 구성 (Configure Indexes)
Pinot 테이블에 인덱스를 적용하는 방법을 배우는 가이드예요. Apache Kafka에서 데이터 수집 가이드를 따라 했다고 가정해요.
본문
Pinot은 쿼리 성능을 최적화하는 데 쓸 수 있는 여러 종류의 인덱스를 지원해요. 이 가이드에서는 Apache Kafka에서 데이터 수집 가이드에서 설정한 events 테이블에 인덱스를 추가하는 방법을 배워요.
왜 인덱스가 필요할까?
Pinot 세그먼트의 컬럼에 인덱스가 적용되지 않으면 쿼리 엔진이 모든 document를 스캔하며 쿼리에 주어진 필터 조건을 충족하는지 확인해야 해요. 스캔할 document가 많으면 이는 느린 과정일 수 있어요.
인덱스가 적용되면 쿼리 엔진이 필터 조건을 충족하는 document를 더 빨리 찾을 수 있어, 쿼리 실행 시간을 줄여요.
Pinot이 지원하는 인덱스
기본적으로 Pinot은 모든 컬럼에 대한 포워드 인덱스(forward index)를 만들어요. 포워드 인덱스는 일반적으로 document를 삽입 순서로 저장해요.
하지만 세그먼트를 플러시하기 전에 Pinot은 모든 컬럼을 한 번 훑어 데이터가 정렬되었는지 확인해요. 데이터가 정렬되어 있으면 Pinot은 그 컬럼에 포워드 인덱스 대신 정렬(포워드) 인덱스를 만들어요.
실시간 테이블에서는 컬럼 중 하나가 정렬되어야 한다고 Pinot에 명시적으로 알릴 수도 있어요. 자세한 내용은 Sorted Index Documentation 참고.
세그먼트 내 document를 필터링하기 위해 Pinot은 다음 인덱싱 기법을 지원해요:
- 역방향 인덱스 (Inverted index): 정확한 일치 조회에 사용.
- 범위 인덱스 (Range index): 범위 쿼리에 사용.
- 텍스트 인덱스 (Text index): 구절, 용어, 부울, 접두사, 또는 정규식 쿼리에 사용.
- 지리공간 인덱스 (Geospatial index): H3 기반, 육각 계층 격자. 다른 점에서 일정 거리 내에 존재하는 점을 찾는 데 사용.
- JSON 인덱스 (JSON index): JSON 문서의 컬럼 쿼리에 사용.
- Star-Tree 인덱스: 여러 컬럼에 걸쳐 결과를 사전 집계.
events 테이블 보기
데이터에 이 인덱싱 기법들을 어떻게 적용하는지 보자. 정리하면 events 테이블은 다음 필드가 있어요:
| 날짜/시간 필드 (Date Time Fields) | 차원 필드 (Dimensions Fields) | 메트릭 필드 (Metric Fields) |
|---|---|---|
ts |
uuid |
count |
ts와 uuid 컬럼에서 필터링하는 쿼리를 쓰고 싶을 수 있으니, 이 두 컬럼에 인덱스를 구성하고 싶을 거예요.
Kafka 토픽으로 수집하는 데이터가 모두 타임스탬프로 암묵적으로 정렬되어 있으므로, ts 컬럼은 이미 정렬 인덱스를 갖고 있어요. 즉 이 컬럼에서 필터링하는 모든 쿼리는 이미 최적화되어 있어요.
그러면 uuid 컬럼이 남아요.
역방향 인덱스 추가
uuid 컬럼에 역방향 인덱스를 추가해서 그 컬럼에서 필터링하는 쿼리가 더 빨리 반환되게 할 거예요. 다음 줄을 추가해야 해요:
"invertedIndexColumns": ["uuid"]
tableIndexConfig 섹션에 추가해요.
다음을 복사하세요:
/tmp/pinot/table-config-stream.json
{
"tableName": "events",
"tableType": "REALTIME",
"segmentsConfig": {
"timeColumnName": "ts",
"schemaName": "events",
"replicasPerPartition": "1"
},
"tenants": {},
"tableIndexConfig": {
"invertedIndexColumns": ["uuid"],
"loadMode": "MMAP",
"streamConfigs": {
"streamType": "kafka",
"stream.kafka.topic.name": "events",
"stream.kafka.decoder.class.name": "org.apache.pinot.plugin.inputformat.json.JSONMessageDecoder",
"stream.kafka.consumer.factory.class.name": "org.apache.pinot.plugin.stream.kafka30.KafkaConsumerFactory",
"stream.kafka.broker.list": "kafka:9092",
"realtime.segment.flush.threshold.rows": "0",
"realtime.segment.flush.threshold.time": "24h",
"realtime.segment.flush.threshold.segment.size": "50M",
"stream.kafka.consumer.prop.auto.offset.reset": "smallest"
}
},
"metadata": {
"customConfigs": {}
}
}
localhost:9000/#/tenants/table/events_REALTIME로 이동해 Edit Table을 클릭하고, 다음 테이블 설정을 붙여 넣은 다음 Save를 클릭해요.
완료 후 Reload All Segments를 클릭하고 Yes를 클릭해 인덱싱 변경을 모든 세그먼트에 적용해요.
인덱스 적용 확인
Pinot의 REST API를 조회해 인덱스가 모든 세그먼트에 적용되었는지 확인할 수 있어요. Swagger 문서는 localhost:9000/help에서 찾을 수 있어요.
다음 쿼리는 uuid 컬럼에 정의된 인덱스를 반환해요:
curl -X GET "http://localhost:9000/segments/events/metadata?columns=uuid" \
-H "accept: application/json" 2>/dev/null |
jq '.[] | [.segmentName, .indexes]'
출력
관심 있는 필드를 추출하는 데 jq 명령줄 JSON 프로세서를 사용하고 있어요.
[
"events__0__1__20220214T1106Z",
{
"uuid": {
"bloom-filter": "NO",
"dictionary": "YES",
"forward-index": "YES",
"inverted-index": "YES",
"null-value-vector-reader": "NO",
"range-index": "NO",
"json-index": "NO"
}
}
]
[
"events__0__0__20220214T1053Z",
{
"uuid": {
"bloom-filter": "NO",
"dictionary": "YES",
"forward-index": "YES",
"inverted-index": "YES",
"null-value-vector-reader": "NO",
"range-index": "NO",
"json-index": "NO"
}
}
]
inverted-index 속성을 보면 인덱스가 적용된 것을 확인할 수 있어요.
쿼리
이제 uuid 컬럼에서 필터링하는 쿼리를 실행할 수 있어요:
SELECT *
FROM events
WHERE uuid = 'f4a4f'
LIMIT 10
UUID는 우리 스크립트로 무작위 생성되므로 실제 uuid 값을 데이터베이스에 존재하는 값으로 바꿔야 해요.