인덱스 구성

인덱스 구성 (Configure Indexes)

Pinot 테이블에 인덱스를 적용하는 방법을 배우는 가이드예요. Apache Kafka에서 데이터 수집 가이드를 따라 했다고 가정해요.

출처: Configure Indexes

본문

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 값을 데이터베이스에 존재하는 값으로 바꿔야 해요.

더 알아보기 (Learn more)