논리 테이블

논리 테이블 (Logical Table)

Apache Pinot의 논리 테이블에 대해 배우는 페이지예요. 논리 테이블은 여러 물리 테이블에 걸쳐 통합된 쿼리 인터페이스를 제공해 유연한 데이터 구성을 가능하게 해줘요.

출처: Logical Table

본문

Pinot의 논리 테이블은 여러 물리 테이블에 걸쳐 통합된 쿼리 인터페이스를 제공해요. 개별 테이블을 따로 쿼리하는 대신, 사용자는 단일 논리 테이블을 쿼리해 모든 하위 물리 테이블로 투명하게 쿼리를 라우팅하고 결과를 집계할 수 있어요.

개요 (Overview)

논리 테이블은 다음에 유용해요:

  • 지리적/지역적 파티셔닝: 지역별로 데이터 분할(예: ordersUS, ordersEU, ordersAPAC)하면서 쿼리에는 통합된 orders 테이블 제공
  • 테이블 파티셔닝 전략: 비즈니스 로직에 따라 여러 물리 테이블에 데이터 구성
  • 시간 기반 테이블 분할: 서로 다른 물리 테이블의 과거 및 최근 데이터 결합

💡 논리 테이블은 모든 하위 물리 테이블이 동일한 스키마 구조를 공유해야 해요. 논리 테이블을 생성하기 전에 논리 테이블과 같은 이름의 스키마를 먼저 만들어야 해요.

작동 방식 (How It Works)

논리 테이블을 쿼리하면 Pinot는:

  1. 논리 테이블 이름을 물리 테이블 목록으로 해석
  2. 관련된 모든 물리 테이블(오프라인 및 실시간 모두)로 쿼리를 라우팅
  3. 모든 물리 테이블의 결과를 집계
  4. 클라이언트에 통합된 결과 집합을 반환

오프라인 및 실시간 물리 테이블을 모두 포함하는 하이브리드 논리 테이블의 경우 Pinot는 구성 가능한 시간 경계 전략을 사용해 각 테이블 유형에서 쿼리할 세그먼트를 결정함으로써 중복 데이터를 방지해요.

세그먼트 가지치기 최적화 (Segment Pruning Optimization)

Pinot는 논리 테이블을 쿼리할 때 자동으로 크로스 테이블 세그먼트 가지치기(cross-table segment pruning)를 수행해요. 각 물리 테이블에 대해 독립적으로 세그먼트를 가지치기하는 대신, 세그먼트 가지치기가 모든 물리 테이블에 걸쳐 한 번에 동작해요. 이 최적화는 특히 SelectionQuerySegmentPruner가 전체 논리 테이블에 걸쳐 세그먼트를 가지치기할 수 있는 ORDER BY + LIMIT 쿼리에 유용해요.

예를 들어 세 개의 물리 테이블(US, EU, APAC)에 걸친 논리 테이블에서 다음 쿼리를 실행한다고 해요:

SELECT * FROM orders ORDER BY createdTime DESC LIMIT 10

이전에는 프루너가 각 물리 테이블 내에서 독립적으로 세그먼트를 가지치기해 필요한 것보다 더 많은 세그먼트를 반환할 수 있었어요. 이제는 가지치기가 모든 물리 테이블에 걸쳐 함께 일어나므로 프루너가 쿼리 요구사항을 충족하는 데 필요한 최소 세그먼트 집합만 식별해 반환할 수 있어요.

주요 이점:

  • 처리되는 세그먼트를 줄여 쿼리 성능 개선
  • 구성 변경 없이 자동 최적화
  • 특히 논리 테이블 전체의 ORDER BY + LIMIT 쿼리에 효과적
  • 단일 테이블 동작은 변경 없음

논리 테이블 구성 (Logical Table Configuration)

논리 테이블 구성은 논리 테이블과 물리 테이블 사이의 매핑을 정의해요.

구성 속성 (Configuration Properties)

속성 설명 필수
tableName 논리 테이블의 이름 예
brokerTenant 라우팅에 사용할 브로커 테넌트 예
physicalTableConfigMap 물리 테이블 이름과 그 구성의 맵 예
refOfflineTableName 테이블 설정 메타데이터용 참조 오프라인 테이블 오프라인 테이블이 있으면 필수
refRealtimeTableName 테이블 설정 메타데이터용 참조 실시간 테이블 실시간 테이블이 있으면 필수
query 쿼리 구성 (타임아웃, 응답 크기 제한 등) 아니오
quota 비율 제한을 위한 쿼터 구성 아니오
timeBoundaryConfig 하이브리드 테이블용 시간 경계 구성 하이브리드 논리 테이블에 필수

예시 구성 (Example Configuration)

{
  "tableName": "orders",
  "brokerTenant": "DefaultTenant",
  "physicalTableConfigMap": {
    "ordersUS_OFFLINE": {},
    "ordersEU_OFFLINE": {},
    "ordersAPAC_OFFLINE": {}
  },
  "refOfflineTableName": "ordersUS_OFFLINE"
}

하이브리드 논리 테이블 구성 (Hybrid Logical Table Configuration)

오프라인과 실시간 물리 테이블을 모두 결합하는 논리 테이블:

{
  "tableName": "events",
  "brokerTenant": "DefaultTenant",
  "physicalTableConfigMap": {
    "eventsHistorical_OFFLINE": {},
    "eventsRecent_OFFLINE": {},
    "eventsLive_REALTIME": {}
  },
  "refOfflineTableName": "eventsHistorical_OFFLINE",
  "refRealtimeTableName": "eventsLive_REALTIME",
  "timeBoundaryConfig": {
    "boundaryStrategy": "min",
    "parameters": {
      "includedTables": ["eventsRecent_OFFLINE"]
    }
  }
}

논리 테이블 생성 (Creating a Logical Table)

1단계: 스키마 생성

물리 테이블의 구조와 일치하는 스키마를 만들어요:

{
  "schemaName": "orders",
  "dimensionFieldSpecs": [
    { "name": "orderId", "dataType": "STRING" },
    { "name": "customerId", "dataType": "STRING" },
    { "name": "region", "dataType": "STRING" },
    { "name": "productId", "dataType": "STRING" },
    { "name": "status", "dataType": "STRING" }
  ]
}

스키마를 업로드해요:

curl -F schemaName=@orders_schema.json localhost:9000/schemas

2단계: 논리 테이블 생성

curl -X POST -H 'Content-Type: application/json' \
  -d '{
    "tableName": "orders",
    "brokerTenant": "DefaultTenant",
    "physicalTableConfigMap": {
      "ordersUS_OFFLINE": {},
      "ordersEU_OFFLINE": {},
      "ordersAPAC_OFFLINE": {}
    },
    "refOfflineTableName": "ordersUS_OFFLINE"
  }' \
  http://localhost:9000/logicalTables

논리 테이블 관리 (Managing Logical Tables)

논리 테이블 목록

curl http://localhost:9000/logicalTables

논리 테이블 구성 가져오기

curl http://localhost:9000/logicalTables/{tableName}

논리 테이블 업데이트

curl -X PUT -H 'Content-Type: application/json' \
  -d '{
    "tableName": "orders",
    "brokerTenant": "DefaultTenant",
    "physicalTableConfigMap": {
      "ordersUS_OFFLINE": {},
      "ordersEU_OFFLINE": {},
      "ordersAPAC_OFFLINE": {},
      "ordersANZ_OFFLINE": {}
    },
    "refOfflineTableName": "ordersUS_OFFLINE"
  }' \
  http://localhost:9000/logicalTables/orders

논리 테이블 삭제

curl -X DELETE http://localhost:9000/logicalTables/{tableName}

⚠️ 논리 테이블을 삭제하면 논리 테이블 구성만 제거돼요. 하위 물리 테이블과 그 데이터는 영향을 받지 않아요.

논리 테이블 쿼리 (Querying Logical Tables)

다른 Pinot 테이블처럼 논리 테이블을 쿼리하면 돼요:

-- 논리 테이블 쿼리
SELECT COUNT(*) FROM orders

-- 지역으로 필터링
SELECT orderId, customerId, region, status
FROM orders
WHERE region = 'us'
LIMIT 10

-- 모든 지역에 걸쳐 집계
SELECT region, COUNT(*) as orderCount
FROM orders
GROUP BY region
ORDER BY region

논리 테이블은 단일 스테이지와 멀티 스테이지 쿼리 엔진 모두에서 동작해요.

시간 경계 구성 (Time Boundary Configuration)

오프라인 및 실시간 물리 테이블을 모두 포함하는 하이브리드 논리 테이블의 경우 중복 데이터를 쿼리하지 않도록 시간 경계 전략을 구성해야 해요.

사용 가능한 전략 (Available Strategies)

전략 설명
min 지정된 테이블에서 최소 시간 경계를 사용

구성 예시 (Configuration Example)

{
  "timeBoundaryConfig": {
    "boundaryStrategy": "min",
    "parameters": {
      "includedTables": ["eventsRecent_OFFLINE"]
    }
  }
}

includedTables 파라미터는 시간 경계를 계산할 때 고려할 물리 테이블을 지정해요.

쿼리 구성 (Query Configuration)

논리 테이블은 쿼리 수준 구성을 지원해요:

{
  "tableName": "orders",
  "brokerTenant": "DefaultTenant",
  "physicalTableConfigMap": { ... },
  "refOfflineTableName": "ordersUS_OFFLINE",
  "query": {
    "timeoutMs": 30000,
    "disableGroovy": true,
    "maxServerResponseSizeBytes": 1000000,
    "maxQueryResponseSizeBytes": 5000000
  }
}
속성 설명
timeoutMs 쿼리 타임아웃 (밀리초)
disableGroovy 쿼리에서 Groovy 함수 비활성화
maxServerResponseSizeBytes 각 서버의 최대 응답 크기
maxQueryResponseSizeBytes 최대 전체 쿼리 응답 크기

쿼터 구성 (Quota Configuration)

논리 테이블에 비율 제한을 적용해요:

{
  "tableName": "orders",
  "brokerTenant": "DefaultTenant",
  "physicalTableConfigMap": { ... },
  "refOfflineTableName": "ordersUS_OFFLINE",
  "quota": {
    "maxQueriesPerSecond": 100
  }
}

💡 논리 테이블은 데이터를 직접 저장하지 않으므로 스토리지 쿼터(quota.storage)는 지원되지 않아요.

Controller UI로 논리 테이블 관리 (Managing Logical Tables via the Controller UI)

Pinot Controller UI는 주요 Tables 페이지에서 직접 접근할 수 있는 논리 테이블 탐색 및 인플레이스 관리를 제공해요.

논리 테이블 접근 (Accessing Logical Tables)

  1. Controller UI(기본: http://<controller-host>:9000)를 엽니다.
  2. 왼쪽 사이드바에서 Tables로 이동합니다.
  3. Tables 페이지는 물리 테이블과 논리 테이블을 별도 섹션으로 표시해요.
  4. 논리 테이블 이름을 클릭하면 상세 페이지가 열리며 다음을 보여줘요:
    • 현재 구성 (JSON)
    • 물리 테이블 매핑

지원되는 작업 (Supported Operations)

작업 설명
List 검색 및 필터로 모든 논리 테이블 보기
View 논리 테이블 구성과 물리 테이블 할당 검사
Update 논리 테이블 구성 인플레이스 편집
Delete 클러스터에서 논리 테이블 제거

💡 논리 테이블은 POST /logicalTables로 생성해요. 가져오기, 업데이트, 삭제는 GET, PUT, DELETE로 /logicalTables/{tableName}에서 가능해요.

퀵스타트 예시 (Quick Start Example)

기능이 실제로 동작하는 모습을 보려면 논리 테이블 퀵스타트를 시도해 보세요:

Docker:

docker run \
    -p 9000:9000 \
    apachepinot/pinot:latest QuickStart \
    -type LOGICAL_TABLE

런처 스크립트:

./bin/pinot-admin.sh QuickStart -type LOGICAL_TABLE

이 퀵스타트는:

  1. ordersUS_OFFLINE, ordersEU_OFFLINE, ordersAPAC_OFFLINE 세 개의 물리 테이블 생성
  2. 세 테이블을 모두 통합하는 논리 테이블 orders 생성
  3. 물리 테이블과 논리 테이블 모두에 대한 쿼리 시연

검증 규칙 (Validation Rules)

논리 테이블을 생성하거나 업데이트할 때 Pinot는 다음을 검증해요:

  • 테이블 이름이 _OFFLINE 또는 _REALTIME으로 끝나지 않음
  • 모든 물리 테이블이 존재함 (multiCluster로 표시된 경우 제외)
  • 물리 테이블이 논리 테이블과 같은 데이터베이스에 있음
  • 논리 테이블과 같은 이름의 스키마가 존재함
  • 브로커 테넌트가 존재함
  • 참조 테이블 이름(refOfflineTableName, refRealtimeTableName)이 올바르게 설정됨
  • 하이브리드 테이블에는 시간 경계 구성이 제공됨

제한 사항 (Limitations)

  • 모든 물리 테이블은 호환 가능한 스키마를 가져야 함
  • 스토리지 쿼터는 지원되지 않음
  • 같은 논리 테이블의 물리 테이블은 최적의 쿼리 성능을 위해 가능하면 일관된 인덱싱을 가져야 함

플러그형 LogicalTableConfig 직렬화 (Pluggable LogicalTableConfig Serialization)

기본적으로 LogicalTableConfig는 내장 JSON 형식을 사용해 ZooKeeper로 직렬화/역직렬화돼요. 커스텀 저장 형식이 필요한 고급 사용 사례에는 LogicalTableConfigSerDe를 구현하고 LogicalTableConfigSerDeProvider로 등록하세요.

언제 사용하나요? (When to Use This)

  • 매우 많은 수의 논리 테이블이 있는 배포에 대해 컴팩트한 이진 형식이 필요할 때
  • ZooKeeper 스키마가 특정 비기본 인코딩을 요구할 때
  • 자체 직렬화 요구사항을 가진 외부 메타데이터 시스템과 Pinot를 통합할 때

구현 (Implementation)

1단계: LogicalTableConfigSerDe 인터페이스를 구현해요:

public class MyCustomSerDe implements LogicalTableConfigSerDe {
    @Override
    public byte[] serialize(LogicalTableConfig config) { /* ... */ }

    @Override
    public LogicalTableConfig deserialize(byte[] bytes) { /* ... */ }
}

2단계: 커스텀 SerDe를 반환하도록 LogicalTableConfigSerDeProvider를 구현해요.

3단계: Java Service Provider Interface (SPI)를 사용해 다음과 같은 파일을 만들어 프로바이더를 등록해요:

META-INF/services/org.apache.pinot.spi.config.table.logical.LogicalTableConfigSerDeProvider

프로바이더 구현의 정규화된 클래스 이름을 포함해요.

💡 이것은 특수 배포를 위한 고급 확장 포인트예요. 대부분의 사용자는 기본 JSON 기반 직렬화에 의존해야 해요.

참고 항목 (See Also)

더 알아보기 (Learn more)