업서트의 세그먼트 컴팩션

업서트의 세그먼트 컴팩션 (Segment Compaction on Upserts)

업서트가 활성화된 실시간 테이블에서 세그먼트 컴팩션을 사용하는 페이지예요.

출처: Segment Compaction on Upserts

본문

세그먼트 컴팩션 개요

세그먼트를 컴팩션하면 완료된 세그먼트를 레코드의 최신 버전만 포함하는 컴팩션된 세그먼트로 교체해요. Pinot 실시간 테이블에서 업서트를 사용하는 방법에 대한 자세한 내용은 스트림 수집 + Upsert 참고.

Pinot 업서트 기능은 불변 세그먼트에 수집된 레코드의 모든 버전을 저장해요. 이전 버전은 쿼리되지 않지만 계속 스토리지 오버헤드를 추가해요. 쿼리 결과에 더 이상 사용되지 않는 이전 레코드를 제거하고 스토리지 공간을 회수하려면 Pinot 세그먼트를 주기적으로 컴팩션해야 해요. 세그먼트 컴팩션은 새 minion 태스크로 수행돼요. Pinot 태스크를 주기적으로 스케줄링하려면 Minion 문서 참고.

실시간 테이블의 업서트 세그먼트 컴팩션

업서트 세그먼트를 컴팩션하려면 다음 단계를 완료해요:

  1. 태스크 스케줄링이 활성화되어 있고 minion이 사용 가능한지 확인.
  2. 테이블 설정에 다음을 추가. 이 설정들(schedule 제외)이 어떤 세그먼트를 컴팩션할지 결정해요.
"task": {
  "taskTypeConfigsMap": {
    "UpsertCompactionTask": {
      "schedule": "0 */5 * ? * *",
      "bufferTimePeriod": "7d",
      "invalidRecordsThresholdPercent": "30",
      "invalidRecordsThresholdCount": "100000",
      "tableMaxNumTasks": "100",
      "validDocIdsType": "SNAPSHOT"
    }
  }
}
  • bufferTimePeriod: 세그먼트가 완료되면 즉시 컴팩션하려면 "0d"로 설정. 컴팩션을 지연하려면(위 설정처럼 7일("7d") 지연) 세그먼트 완료 후 컴팩션을 지연할 일 수를 지정.
  • invalidRecordsThresholdPercent (선택) 완료된 세그먼트에서 허용되는 이전 레코드를 세그먼트 전체 레코드 수의 백분율로 제한. 위 예시에서는 세그먼트 레코드의 30%가 오래되면 완료된 세그먼트가 컴팩션 대상으로 선택될 수 있음.
  • invalidRecordsThresholdCount (선택) 완료된 세그먼트에서 허용되는 이전 레코드를 레코드 수로 제한. 위 예시에서 세그먼트가 100K보다 많은 레코드를 포함하면 컴팩션 대상으로 선택될 수 있음.
  • tableMaxNumTasks (선택) 스케줄링할 수 있는 태스크 수를 제한.
  • validDocIdsType (선택) 데이터 컴팩션 실행 시 가져올 validDocIds의 출처를 지정. 유효 값은 SNAPSHOT, SNAPSHOT_WITH_DELETE, IN_MEMORY, IN_MEMORY_WITH_DELETE예요. upsertConfig.deleteRecordColumn이 구성되어 있어도 SNAPSHOT이 기본값으로 유지되며, Pinot은 설정된 값을 있는 그대로 존중해요.
    • SNAPSHOT: 기본 validDocIds 유형. Pinot 세그먼트에서 validDocIds 스냅샷을 로드. 이 유형에는 upsertConfig.snapshot이 DISABLE이 아니어야 함.
    • SNAPSHOT_WITH_DELETE: Pinot 세그먼트에서 delete-aware queryableDocIds 스냅샷을 로드. upsertConfig.snapshot이 DISABLE이 아니어야 하고 upsertConfig.deleteRecordColumn이 설정되어야 함.
    • IN_MEMORY: 실시간 서버의 메모리에서 validDocIds 비트맵을 로드.
    • IN_MEMORY_WITH_DELETE: 실시간 서버의 메모리에서 delete-aware queryableDocIds 비트맵을 로드. 이 유형에는 upsertConfig.deleteRecordColumn이 설정되어야 함.

{% hint style="warning" %} 두 인메모리 유형을 사용할 때, 서버가 재시작되면 서버가 시작 전에 수집했던 데이터를 다시 수집한 후에 업서트 뷰가 다시 일관성 있게 됩니다. 인메모리 비트맵은 서버가 소비 중인 세그먼트(커밋 전)에 데이터를 수집할 때 업데이트되기 때문에, 서버가 데이터를 소비하는 동안 재시작되면 이전에 수집한 데이터를 따라잡은 후에 업서트 뷰가 다시 일관성 있게 됩니다. 반면 비트맵 스냅샷은 세그먼트를 커밋한 후에만 찍히므로 서버 재시작 시 더 일관성 있을 수 있지만, 서버가 데이터 수집 중 재시작되면 결국 일관성을 얻게 됩니다. {% endhint %}

{% hint style="info" %} 세그먼트 컴팩션은 비용이 큰 연산이므로, invalidRecordsThresholdPercent과 invalidRecordsThresholdCount를 너무 낮게(1 근처) 설정하는 것을 권장하지 않습니다. 기본적으로 위 설정은 모두 0이며, 즉 임계값이 적용되지 않습니다. {% endhint %}

예시

다음 예시는 24M 레코드와 각각 100번 중복된 240K 고유 키를 가진 데이터셋을 포함해요. 데이터 수집 후 총 추정 크기 22.8MB의 6개 세그먼트(5개 완료 + 1개 소비 중)가 있어요.

예시 데이터셋

컴팩션 전에 쿼리 "set skipUpsert=true; select count(*) from transcript_upsert"를 제출하면 24,000,000개의 결과가 나와요:

세그먼트 컴팩션 전 결과

컴팩션 태스크가 완료된 후, Minion Task Manager UI가 다음을 보고해요.

Minion 컴팩션 태스크 완료

세그먼트 컴팩션은 컴팩션할 각 세그먼트에 대해 태스크를 하나 생성해요. 이 경우 5개의 태스크가 생성됐는데, 완료된 세그먼트의 레코드 중 90%(3.6~4.5M 레코드)가 설정된 임계값을 초과해 컴팩션 준비가 된 것으로 간주되기 때문이에요.

{% hint style="info" %} 완료된 세그먼트가 이전 레코드만 포함하는 경우 Pinot은 (컴팩션 태스크를 만드는 대신) 세그먼트를 즉시 삭제합니다. {% endhint %}

쿼리를 다시 제출하면 count가 240K 고유 키 집합과 일치함을 보여 줘요.

세그먼트 컴팩션 후 결과

세그먼트 컴팩션이 완료되면 총 세그먼트 수는 동일하게 유지되고 총 추정 크기는 2.77MB로 떨어져요.

{% hint style="info" %} 쿼리 지연을 더 개선하려면 작은 세그먼트를 더 큰 세그먼트로 병합하세요. {% endhint %}

더 알아보기 (Learn more)