StarRocks는 데이터 적재 방식, 변경(Update/Delete) 빈도, 집계 요구사항에 따라 4가지 데이터 모델을 제공합니다.
| 모델명 | 핵심 동작 방식 | Update/Delete 지원 | 추천 유스케이스 |
|---|---|---|---|
| Duplicate Key | 데이터 중복을 허용하며 순차 적재 (Append-Only) | 미지원 (DELETE 구문만 가능) | 원시 로그, 클릭스트림, 시계열 이벤트 |
| Primary Key | PK 기반 실시간 Upsert/Delete (Merge-on-Write) | 완벽 지원 (__deleted 자동 처리) | CDC, 실시간 상태 테이블, 인벤토리 동기화 |
| Unique Key | Key 기준 중복 제거 (Merge-on-Read 기반) | Upsert 지원 | 배치 데이터 갱신 (PK 모델 등장 이후 사용 감소) |
| Aggregate Key | non-Key 컬럼에 집계 함수를 적용하여 자동 사전 집계 | REPLACE 함수로 일부 지원 | 롤업 대시보드, 일별/시간별 통계 집계 |
1. Duplicate Key Model (중복 키 모델)
데이터 변경이나 정렬 키 중복 제거 없이 들어오는 대로 빠르게 저장하는 가장 기본적이고 단순한 구조입니다.
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 (기본 키 모델)
실시간 데이터 업데이트 및 삭제 조작에 최적화된 모델입니다.
__deleted = 1 시스템 연동을 통한 컴팩션 시 디스크 자동 정리가 지원됩니다.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 모델입니다.
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)합니다.
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);
모델 선택 가이드
===
네, 충분히 구현 가능하며, 적절한 설계(Async Materialized View 또는 Aggregate Key 기반 요약 테이블)를 적용하면 외부 API나 대시보드에서 수ms~수십ms 수준의 초고속 응답을 얻을 수 있습니다.
단, S3 Prefix는 디렉토리 구조처럼 a/b/c/ 하위의 객체가 상위 a/b/ 및 a/에도 재귀적으로(Recursive) 집계되어야 하므로, 조회 시점에 실시간으로 계산하는 View 대신 사전 집계된 집계 구조를 만드는 것이 성능의 핵심입니다.
object_current_state 원천 테이블 위에 StarRocks 비동기 구체화 뷰(Async Materialized View)를 구성하는 방식이 가장 깔끔합니다.
object_current_state)기존에 구현하신 Primary Key 모델 그대로 유지합니다.
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;
외부 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';
| 구분 | Raw 테이블 직접 실시간 Group By | Async MV / 요약 테이블 조회 |
|---|---|---|
| 응답 속도 (Latency) | 수억 건 기준 수 초~수십 초 | pre-computed 상태이므로 수 ms (초고속) |
| CPU / Memory 부하 | 외부에서 API 요청 올 때마다 Full Scan 발생 | 이미 계산된 결과(몇 천~몇 만 행)만 읽으므로 부하 거의 없음 |
| 확장성 (Concurrency) | 동시 API 요청 많아지면 DB 다운 위험 | 높은 TPS(초당 처리량) 수용 가능 |
/) Prefix 처리recursive_prefix CTE에서 array_to_string 결과 외에 빈 값('' 또는 '/')을 추가하는 요소를 포함시켜야 합니다.REFRESH MATERIALIZED VIEW mv_prefix_summary; 수동 갱신 쿼리를 추가하면, 배치 완료 직후 항상 최신의 집계 상태를 보장할 수 있습니다.