26Z10c2

Young-Kyoo Kim·5일 전

StarRocks는 데이터 적재 방식, 변경(Update/Delete) 빈도, 집계 요구사항에 따라 4가지 데이터 모델을 제공합니다.

모델명핵심 동작 방식Update/Delete 지원추천 유스케이스
Duplicate Key데이터 중복을 허용하며 순차 적재 (Append-Only)미지원 (DELETE 구문만 가능)원시 로그, 클릭스트림, 시계열 이벤트
Primary KeyPK 기반 실시간 Upsert/Delete (Merge-on-Write)완벽 지원 (__deleted 자동 처리)CDC, 실시간 상태 테이블, 인벤토리 동기화
Unique KeyKey 기준 중복 제거 (Merge-on-Read 기반)Upsert 지원배치 데이터 갱신 (PK 모델 등장 이후 사용 감소)
Aggregate Keynon-Key 컬럼에 집계 함수를 적용하여 자동 사전 집계REPLACE 함수로 일부 지원롤업 대시보드, 일별/시간별 통계 집계

1. Duplicate Key Model (중복 키 모델)

데이터 변경이나 정렬 키 중복 제거 없이 들어오는 대로 빠르게 저장하는 가장 기본적이고 단순한 구조입니다.

  • 특징: Key 컬럼은 데이터 정렬(Sort) 용도로만 사용되며, 동일한 Key 값이 들어와도 모두 별도 레코드로 유지됩니다.
  • DDL 특징: DUPLICATE KEY(...) 구문을 명시하며, 모든 컬럼에 일반 데이터 타입을 지정합니다.
CREATE TABLE raw_access_logs (
    event_time      DATETIME NOT NULL,
    user_id         BIGINT NOT NULL,
    path            VARCHAR(256),
    status_code     INT
) ENGINE=OLAP
DUPLICATE KEY(event_time, user_id)
DISTRIBUTED BY HASH(user_id);

2. Primary Key Model (기본 키 모델)

실시간 데이터 업데이트 및 삭제 조작에 최적화된 모델입니다.

  • 특징: 메모리 기반 PK 인덱스와 Merge-on-Write 방식을 사용하여 높은 읽기 성능을 유지하면서 실시간 Upsert를 처리합니다. __deleted = 1 시스템 연동을 통한 컴팩션 시 디스크 자동 정리가 지원됩니다.
  • DDL 특징: PRIMARY KEY(...)를 선언하며, PK 컬럼은 반드시 NOT NULL이어야 합니다.
CREATE TABLE object_current_state (
    object_id       VARCHAR(32) NOT NULL,
    bucket          VARCHAR(64) NOT NULL,
    object_key      VARCHAR(1024),
    size_bytes      BIGINT,
    event_time      DATETIME,
    __deleted       TINYINT DEFAULT "0"
) ENGINE=OLAP
PRIMARY KEY(object_id)
DISTRIBUTED BY HASH(object_id);

3. Unique Key Model (유일 키 모델)

Primary Key 모델이 도입되기 전에 쓰이던 이전 세대 Upsert 모델입니다.

  • 특징: 기본적으로 Merge-on-Read 방식(조회 시점에 최신 버전을 병합)으로 동작하여 읽기 성능이 PK 모델보다 낮습니다. PK 모델 사용을 권장하지만, 메모리 자원이 극도로 제한된 환경에서 대안으로 쓰입니다.
  • DDL 특징: UNIQUE KEY(...) 구문을 사용합니다.
CREATE TABLE user_profiles (
    user_id         BIGINT NOT NULL,
    email           VARCHAR(128),
    updated_at      DATETIME
) ENGINE=OLAP
UNIQUE KEY(user_id)
DISTRIBUTED BY HASH(user_id);

4. Aggregate Key Model (집계 키 모델)

데이터 적재(Ingestion) 및 컴팩션(Compaction) 시점에 지정한 Key를 기준으로 non-Key 컬럼 데이터를 자동으로 사전 집계(Rollup)합니다.

  • 특징: Raw 데이터를 저장하지 않고 지정한 정밀도로 축약하므로 디스크 용량을 획기적으로 줄이고 대용량 통계 쿼리를 가속합니다.
  • DDL 특징: AGGREGATE KEY(...)를 선언하고, non-Key 컬럼에 집계 연산자(SUM, MAX, MIN, REPLACE, BITMAP_UNION 등)를 지정해야 합니다.
CREATE TABLE daily_page_views (
    stat_date       DATE NOT NULL,
    page_id         INT NOT NULL,
    pv_count        BIGINT SUM DEFAULT "0",        -- 자동으로 SUM 집계
    max_latency_ms  INT MAX DEFAULT "0",            -- 최대값 유지
    last_user_agent VARCHAR(256) REPLACE            -- 가장 최근 값으로 덮어쓰기
) ENGINE=OLAP
AGGREGATE KEY(stat_date, page_id)
DISTRIBUTED BY HASH(page_id);

모델 선택 가이드

  • 원시 로그/이벤트 수집: Duplicate Key Model
  • CDC 수집, S3 인벤토리 sync, 상태 테이블, 실시간 삭제 필요: Primary Key Model
  • 일별/시간별 메트릭 사전 집계 대시보드: Aggregate Key Model

===

네, 충분히 구현 가능하며, 적절한 설계(Async Materialized View 또는 Aggregate Key 기반 요약 테이블)를 적용하면 외부 API나 대시보드에서 수ms~수십ms 수준의 초고속 응답을 얻을 수 있습니다.

단, S3 Prefix는 디렉토리 구조처럼 a/b/c/ 하위의 객체가 상위 a/b/a/에도 재귀적으로(Recursive) 집계되어야 하므로, 조회 시점에 실시간으로 계산하는 View 대신 사전 집계된 집계 구조를 만드는 것이 성능의 핵심입니다.


1. 추천 아키텍처 및 구현 방법

object_current_state 원천 테이블 위에 StarRocks 비동기 구체화 뷰(Async Materialized View)를 구성하는 방식이 가장 깔끔합니다.

Step 1. 원천 테이블 (object_current_state)

기존에 구현하신 Primary Key 모델 그대로 유지합니다.

Step 2. 계층형 Prefix 분해 및 집계 MV 생성

S3 경로 a/b/c/file.txt가 있을 때, 이 파일은 a/, a/b/, a/b/c/ 세 Prefix의 크기와 객체 수에 모두 포함되어야 합니다. StarRocks의 Array Explode (unnest)와 문자열 함수를 활용해 상위 Prefix들을 자동으로 추출하고 사전 집계합니다.

-- Prefix별 재귀 집계 Async Materialized View 생성
CREATE MATERIALIZED VIEW mv_prefix_summary
BUILD DEFERRED
REFRESH ASYNCHRONOUS EVERY (INTERVAL 5 MINUTE) -- 또는 Reconciliation 완료 후 SQL로 REFRESH
DISTRIBUTED BY HASH(bucket, prefix)
AS
WITH recursive_prefix AS (
    SELECT 
        bucket,
        -- 'a/b/c' 경로를 ['a', 'a/b', 'a/b/c'] 배열로 분해 후 unnest로 행 확장
        unnest(
            transform(
                generate_series(1, array_length(split(prefix, '/'))),
                i -> array_to_string(slice(split(prefix, '/'), 1, i), '/')
            )
        ) AS prefix,
        size_bytes
    FROM object_current_state
    WHERE __deleted = 0 AND prefix != ''
)
SELECT 
    bucket,
    prefix,
    COUNT(1) AS total_object_count,
    SUM(size_bytes) AS total_size_bytes
FROM recursive_prefix
GROUP BY bucket, prefix;

Tip: Reconciliation 스크립트 실행 직후 아래 명령을 호출하면 인벤토리 반영에 맞춰 즉시 갱신할 수 있습니다.

REFRESH MATERIALIZED VIEW mv_prefix_summary;

2. 외부 조회용 통합 View 작성

외부 API나 UI에서는 구체화 뷰(mv_prefix_summary)를 직접 바라보거나, 아래와 같이 표준화된 View를 통해 조회하도록 래핑합니다.

CREATE VIEW v_prefix_summary AS
SELECT 
    bucket,
    prefix,
    total_object_count,
    total_size_bytes,
    -- 읽기 쉬운 용량 단위 변환 (선택 사항)
    ROUND(total_size_bytes / 1024 / 1024 / 1024, 2) AS total_size_gb
FROM mv_prefix_summary;

외부 조회 쿼리 예시:

-- 특정 버킷 하위의 특정 Prefix 요약 조회 (1~5ms 소요)
SELECT * 
FROM v_prefix_summary 
WHERE bucket = 'my-data-bucket' 
  AND prefix = 'logs/2026/08';

3. 성능 및 효율성 분석

구분Raw 테이블 직접 실시간 Group ByAsync MV / 요약 테이블 조회
응답 속도 (Latency)수억 건 기준 수 초~수십 초pre-computed 상태이므로 수 ms (초고속)
CPU / Memory 부하외부에서 API 요청 올 때마다 Full Scan 발생이미 계산된 결과(몇 천~몇 만 행)만 읽으므로 부하 거의 없음
확장성 (Concurrency)동시 API 요청 많아지면 DB 다운 위험높은 TPS(초당 처리량) 수용 가능

4. 고려사항 및 최적화 포인트

  1. Root (/) Prefix 처리
  • 버킷 전체 요약(루트 경로)도 필요하다면 recursive_prefix CTE에서 array_to_string 결과 외에 빈 값('' 또는 '/')을 추가하는 요소를 포함시켜야 합니다.
  1. 배치 수집 주기와의 연동
  • 인벤토리 Reconciliation 파이썬 스크립트의 맨 마지막 단에 REFRESH MATERIALIZED VIEW mv_prefix_summary; 수동 갱신 쿼리를 추가하면, 배치 완료 직후 항상 최신의 집계 상태를 보장할 수 있습니다.

0개의 댓글