프로젝션

프로젝션 (Projections)

프로젝션은 datasource의 세그먼트에 미리 계산·저장되는 일종의 집계예요. 자주 쓰는 쿼리 형태에 맞춰 프로젝션을 만들어 두면 처리해야 할 행 수를 줄여 쿼리를 빨라지게 할 수 있어요. 다만 아직 실험적(experimental) 기능이라 프로덕션 사용은 권장하지 않아요.

출처: 문서

본문

:::admonish 참고

프로젝션은 실험적 기능이에요. 프로덕션 사용은 권장하지 않아요.


프로젝션은 datasource의 일부로 세그먼트에 계산·저장되는 일종의 집계예요. 롤업(rollup)으로 행을 미리 집계하면 특정 세분성(granularity)을 기준으로 하고 원본 행은 더 이상 사용할 수 없게 돼요. 반면 프로젝션은 원본 차원에 영향을 주지 않아요. 프로젝션은 datasource의 일부로 남고 쿼리할 수 있어요.

미리 집계된 데이터는 프로젝션과 일치하는 어떤 형태(shape)든 처리해야 할 행 수를 줄여 쿼리를 빠르게 할 수 있어요. 따라서 자주 쓰는 쿼리에 대해 프로젝션을 만들 것을 권장해요. 예를 들어 datasource에 다음과 같은 프로젝션을 정의한다고 해요:

"projections": [
  {
    "type": "aggregate",
    "name": "channel_page_hourly_distinct_user_added_deleted",
    "groupingColumns": [
      { "type": "long", "name": "__gran" },
      { "type": "string", "name": "channel" },
      { "type": "string", "name": "page" }
    ],
    "virtualColumns": [
      {
        "type": "expression",
        "expression": "timestamp_floor(__time, 'PT1H')",
        "name": "__gran",
        "outputType": "LONG"
      }
    ],
    "aggregators": [
      { "type": "HLLSketchBuild", "name": "distinct_users", "fieldName": "user" },
      { "type": "longSum", "name": "sum_added", "fieldName": "added" },
      { "type": "longSum", "name": "sum_deleted", "fieldName": "deleted" }
    ]
  }
]

집계된 차원을 같은 방식으로 그룹화하는 쿼리는 이 프로젝션을 사용해요. 예:

SELECT
  TIME_FLOOR(__time, 'PT1H') AS __gran,
  channel,
  page,
  APPROX_COUNT_DISTINCT_DS(user) AS distinct_users,
  SUM(added) AS sum_added,
  SUM(deleted) AS sum_deleted
FROM your_datasource
GROUP BY
  TIME_FLOOR(__time, 'PT1H'),
  channel,
  page

프로젝션 만들기 (Create a projection)

프로젝션은 인제스트의 일부로 만들거나, 기존 datasource에 수동으로 추가할 수 있어요.

다음과 같은 방법으로 프로젝션을 만들 수 있어요:

  • datasource의 인제스트 스펙 또는 쿼리에서
  • 기존 datasource의 카탈로그(catalog)에서
  • 기존 datasource의 컴팩션(compaction) 스펙에서

datasource의 컬럼에 더해, 프로젝션은 세 가지 구성 요소를 가져요:

  • 가상 컬럼 (Virtual columns, spec.projections.virtualColumns) — datasource의 여러 기존 컬럼으로 구성돼요. 프로젝션은 datasource의 기존 컬럼이나 이 블록에서 정의된 가상 컬럼을 참조할 수 있어요.
  • 그룹화 컬럼 (Grouping columns, spec.projections.groupingColumns) — 프로젝션을 정렬하는 데 사용돼요. 반드시 datasource에 이미 존재하거나 인제스트 스펙의 virtualColumns에 정의되어 있어야 해요. 그룹화 컬럼을 정의하는 순서가 프로젝션 안의 정렬 순서를 결정해요. 정렬 순서는 항상 오름차순이에요.
  • 집계자 (Aggregators, spec.projections.aggregators) — 프로젝션을 만들 컬럼과 그 컬럼에 사용할 집계자를 정의해요. 컬럼은 datasource에 이미 존재하거나 virtualColumns에 정의되어 있어야 해요.

만드는 모든 프로젝션 차원은 datasource의 일부가 된다는 점에 유의하세요. datasource에서 프로젝션을 제거하려면 데이터를 다시 인제스트해야 해요. 또는 쿼리 context 파라미터를 사용해 특정 쿼리에 대해 프로젝션을 사용하지 않게 할 수도 있어요.

제한사항 (Limitations)

프로젝션을 만들 때 다음 제한사항을 염두에 두세요:

  • 프로젝션에 타입이 float인 소스 컬럼이 포함되면 프로젝션에서 doubleSum 같은 double 집계를 사용해야 해요.
  • 프로젝션이 사용되려면 프로젝션의 집계자가 쿼리의 집계자와 일치해야 해요. 예를 들어 cardinality 집계자의 출력 타입은 인제스트 시간(string)과 쿼리 시간(long)이 달라요.
  • 프로젝션의 소스 컬럼은 프로젝션의 영향을 받지 않으므로 저장 요구사항이 늘어날 수 있어요.

인제스트의 일부로 (As part of your ingestion)

인제스트 시간에 프로젝션을 만들려면 인제스트 스펙 에서 projectionsSpec 블록을 사용하세요.

SQL 기반 인제스트에 대한 프로젝션을 만들려면 druid-catalog 확장도 로드해야 해요.

프로젝션 수동 추가 (Manually add a projection)

기존 datasource에 프로젝션을 정의할 수 있어요. druid-catalog 확장을 권장하지만, 컴팩션 스펙에서 프로젝션을 정의할 수도 있어요.

다음 API 호출은 프로젝션을 정의하는 properties.projections 블록이 포함된 페이로드를 담고 있어요:

{
  "type": "datasource",
  "columns": [],
  "properties": {
    "segmentGranularity": "PT1H",
    "projections": [
      {
        "spec": {
          "name": "channel_page_hourly_distinct_user_added_deleted",
          "type": "aggregate",
          "virtualColumns": [
            {
              "type": "expression",
              "name": "__gran",
              "expression": "timestamp_floor(__time, 'PT1H')",
              "outputType": "LONG"
            }
          ],
          "groupingColumns": [
            { "type": "long", "name": "__gran", "multiValueHandling": "SORTED_ARRAY", "createBitmapIndex": false },
            { "type": "string", "name": "channel", "multiValueHandling": "SORTED_ARRAY", "createBitmapIndex": true },
            { "type": "string", "name": "page", "multiValueHandling": "SORTED_ARRAY", "createBitmapIndex": true }
          ],
          "aggregators": [
            {
              "type": "HLLSketchBuild",
              "name": "distinct_users",
              "fieldName": "user",
              "lgK": 12,
              "tgtHllType": "HLL_4"
            },
            { "type": "longSum", "name": "sum_added", "fieldName": "added" },
            { "type": "longSum", "name": "sum_deleted", "fieldName": "deleted" }
          ]
        }
      }
    ]
  }
}

이 예시에서 Druid는 지정된 집계자와 소스 차원에 따라 distinct_user, sum_added, sum_deleted 차원으로 데이터를 집계해요. 이 집계들은 groupingColumns에 정의한 컬럼으로 그룹화돼요.

프로젝션 사용하기 (Use a projection)

쿼리가 정의한 프로젝션과 일치하면 Druid는 자동으로 그 프로젝션을 사용해요. 프로젝션의 기본 동작을 재정의하려면 다음 쿼리 context 파라미터를 사용할 수 있어요:

  • useProjection : 정의한 프로젝션의 이름. 쿼리 엔진은 반드시 이 특정 프로젝션을 사용해야 해요. 프로젝션이 쿼리와 일치하지 않으면 쿼리는 실패해요.
  • forceProjections : true 또는 false로 설정. 쿼리 엔진이 프로젝션을 사용하도록 강제해요. 쿼리와 일치하는 프로젝션이 없으면 쿼리는 실패해요. 기본값은 false 인데, 이는 Druid가 쿼리와 일치하는 프로젝션이 있으면 그것을 사용한다는 뜻이에요. 없으면 평소처럼 쿼리를 처리해요.
  • noProjections : true 또는 false로 설정. 쿼리 엔진은 어떤 프로젝션도 사용하지 않아요.

컴팩션 (Compaction)

프로젝션이 포함된 datasource에 컴팩션을 사용하려면 스펙 타입을 catalog로 설정해야 해요. (spec.type: catalog):

  • Coordinator duties
  • Supervisors
{
  "type": "catalog",
  "dataSource": YOUR_DATASOURCE,
  "engine": "native",
  "skipOffsetFromLatest": "PT0H",
  "taskPriority": 25,
  "inputSegmentSizeBytes": 100000000000000,
  "taskContext": null
}
{
  "type": "autocompact",
  "spec": {
    "type": "catalog",
    "dataSource": YOUR_DATASOURCE,
    "engine": "native",
    "skipOffsetFromLatest": "PT0H",
    "taskPriority": 25,
    "inputSegmentSizeBytes": 100000000000000,
    "taskContext": null
  },
  "suspended": true
}

더 알아보기 (Learn more)