빅쿼리 클러스터

클러스터링을 통한 성능 최적화

개요

BigQuery 클러스터링은 테이블 내의 데이터를 지정된 컬럼의 값에 따라 물리적으로 정렬하여 저장하는 기능입니다. 클러스터링을 통해 관련 데이터를 함께 저장하여 쿼리 성능을 향상시키고 비용을 절약할 수 있습니다.

클러스터링의 장점

  • 쿼리 성능 향상: 클러스터 컬럼 기반 필터링 시 스캔 데이터 양 감소
  • 비용 절약: 불필요한 데이터 스캔 방지로 쿼리 비용 절감
  • 자동 정렬: 데이터가 자동으로 클러스터 컬럼 기준으로 정렬되어 저장
  • 압축률 개선: 유사한 데이터가 함께 저장되어 압축 효율성 증대
  • 조인 성능 향상: 클러스터 컬럼을 사용한 조인 시 성능 개선

파티션 vs 클러스터링

구분 파티션 클러스터링
목적 시간 기반 데이터 분할 데이터 물리적 정렬
컬럼 수 1개 (시간 관련) 최대 4개
컬럼 타입 DATE, TIMESTAMP, INTEGER 모든 타입 가능
메타데이터 파티션별 통계 제공 자동 통계 수집
비용 예측 정확한 예측 가능 실행 후 확인
적용 시점 테이블 생성 시 생성 후에도 추가 가능

클러스터링 컬럼 선택 기준

1. 카디널리티(Cardinality)

카디널리티는 컬럼의 고유 값 개수를 의미하며, 클러스터링 성능에 중요한 영향을 미칩니다.

카디널리티 수준별 특징

카디널리티 수준 고유 값 비율 예시 클러스터링 효과
낮음 < 0.1% 지역, 국가, 카테고리 높은 압축률, 빠른 필터링
중간 0.1% ~ 10% 도시, 브랜드, 연령대 균형잡힌 성능
높음 > 10% 사용자ID, 이메일, UUID 세밀한 정렬, 조인 최적화

카디널리티별 최적 사용법

🔵 낮은 카디널리티 (권장: 첫 번째 클러스터 컬럼)

-- 지역 (10-50개 값) - 첫 번째 클러스터 컬럼으로 적합
CREATE TABLE sales_optimized (
    transaction_id INT64,
    region STRING,      -- 낮은 카디널리티 (Asia, Europe, Americas 등)
    customer_id STRING, -- 높은 카디널리티
    amount FLOAT64
)
CLUSTER BY region, customer_id;  -- 낮은 것부터 배치

🟡 중간 카디널리티 (두 번째 클러스터 컬럼 적합)

-- 도시나 상품 카테고리 (100-1000개 값)
CREATE TABLE product_sales (
    product_id INT64,
    category STRING,    -- 중간 카디널리티 (Electronics, Fashion 등)
    subcategory STRING, -- 높은 카디널리티
    price NUMERIC
)
CLUSTER BY category, subcategory;

🔴 높은 카디널리티 (마지막 클러스터 컬럼 적합)

-- 사용자 ID나 이메일 (수백만 개 값)
CREATE TABLE user_events (
    event_id INT64,
    event_type STRING,  -- 낮은 카디널리티 (click, view, purchase 등)
    user_id STRING,     -- 높은 카디널리티
    timestamp TIMESTAMP
)
CLUSTER BY event_type, user_id;  -- 높은 카디널리티는 뒤에

카디널리티 분석 쿼리

-- 컬럼별 카디널리티 분석
WITH cardinality_analysis AS (
  SELECT 
    'region' as column_name,
    COUNT(DISTINCT region) as distinct_count,
    COUNT(*) as total_count,
    COUNT(DISTINCT region) / COUNT(*) as cardinality_ratio,
    CASE 
      WHEN COUNT(DISTINCT region) / COUNT(*) < 0.001 THEN 'Low (Recommended for 1st cluster)'
      WHEN COUNT(DISTINCT region) / COUNT(*) < 0.1 THEN 'Medium (Good for 2nd cluster)'
      ELSE 'High (Use as last cluster column)'
    END as recommendation
  FROM mydataset.my_table
  
  UNION ALL
  
  SELECT 
    'customer_id' as column_name,
    COUNT(DISTINCT customer_id) as distinct_count,
    COUNT(*) as total_count,
    COUNT(DISTINCT customer_id) / COUNT(*) as cardinality_ratio,
    CASE 
      WHEN COUNT(DISTINCT customer_id) / COUNT(*) < 0.001 THEN 'Low (Recommended for 1st cluster)'
      WHEN COUNT(DISTINCT customer_id) / COUNT(*) < 0.1 THEN 'Medium (Good for 2nd cluster)'
      ELSE 'High (Use as last cluster column)'
    END as recommendation
  FROM mydataset.my_table
)
SELECT * FROM cardinality_analysis
ORDER BY cardinality_ratio;

잘못된 카디널리티 사용 예

-- ❌ 잘못된 예: 높은 카디널리티를 첫 번째로 배치
CREATE TABLE bad_clustering (
    transaction_id INT64,
    customer_id STRING,  -- 매우 높은 카디널리티를 첫 번째로
    region STRING,       -- 낮은 카디널리티를 두 번째로
    amount FLOAT64
)
CLUSTER BY customer_id, region;  -- 비효율적인 순서

-- ✅ 올바른 예: 낮은 카디널리티부터 배치
CREATE TABLE good_clustering (
    transaction_id INT64,
    region STRING,       -- 낮은 카디널리티를 첫 번째로
    customer_id STRING,  -- 높은 카디널리티를 두 번째로
    amount FLOAT64
)
CLUSTER BY region, customer_id;  -- 효율적인 순서

2. 쿼리 패턴 분석

  • 자주 필터링되는 컬럼
  • WHERE 절에 자주 사용되는 컬럼
  • JOIN 조건에 사용되는 컬럼
  • GROUP BY에 자주 사용되는 컬럼

클러스터링 테이블 생성

1. 단일 컬럼 클러스터링

-- 지역별 클러스터링
CREATE TABLE mydataset.sales_clustered (
    transaction_id INT64,
    transaction_date DATE,
    region STRING,
    amount FLOAT64
)
CLUSTER BY region;

2. 다중 컬럼 클러스터링

-- 지역과 카테고리로 클러스터링 (순서 중요)
CREATE TABLE mydataset.products_clustered (
    product_id INT64,
    region STRING,
    category STRING,
    price FLOAT64,
    created_date DATE
)
CLUSTER BY region, category;

3. 파티션과 클러스터링 조합

-- 날짜별 파티션 + 지역별 클러스터링
CREATE TABLE mydataset.sales_partitioned_clustered (
    transaction_id INT64,
    transaction_date DATE,
    region STRING,
    customer_id STRING,
    amount FLOAT64
)
PARTITION BY transaction_date
CLUSTER BY region, customer_id;

4. 시간별 파티션과 클러스터링

-- 시간별 파티션 + 사용자 ID로 클러스터링
CREATE TABLE mydataset.events_clustered (
    event_id INT64,
    event_timestamp TIMESTAMP,
    user_id STRING,
    event_type STRING,
    properties JSON
)
PARTITION BY DATE(event_timestamp)
CLUSTER BY user_id, event_type;

기존 테이블에 클러스터링 추가

DDL을 사용한 클러스터링 추가

-- 기존 테이블에 클러스터링 추가
ALTER TABLE mydataset.existing_table
CLUSTER BY region, category;

-- 클러스터링 제거
ALTER TABLE mydataset.existing_table
SET OPTIONS (clustering_fields = NULL);

CTAS를 사용한 클러스터링 테이블 생성

-- 기존 테이블 데이터로 클러스터링 테이블 생성
CREATE TABLE mydataset.new_clustered_table
CLUSTER BY region, category
AS SELECT * FROM mydataset.existing_table;

클러스터링 성능 최적화

1. 효율적인 쿼리 패턴

-- ✅ 좋은 예: 클러스터 컬럼으로 필터링
SELECT *
FROM mydataset.sales_clustered
WHERE region = 'Asia'
  AND category = 'Electronics';

-- ✅ 범위 쿼리도 효과적
SELECT *
FROM mydataset.sales_clustered
WHERE region IN ('Asia', 'Europe')
  AND category LIKE 'Elect%';

2. 클러스터 컬럼 순서 최적화

-- 클러스터 컬럼 순서: 카디널리티가 높은 것부터
-- region (낮은 카디널리티) -> customer_id (높은 카디널리티)
CREATE TABLE mydataset.optimized_table (
    transaction_id INT64,
    region STRING,          -- 10-20개 값
    customer_id STRING,     -- 수백만 개 값
    amount FLOAT64
)
CLUSTER BY region, customer_id;

3. 조인 최적화

-- 클러스터 컬럼을 조인 키로 사용
SELECT 
    s.transaction_id,
    s.amount,
    c.customer_name
FROM mydataset.sales_clustered s
JOIN mydataset.customers_clustered c
  ON s.customer_id = c.customer_id  -- 둘 다 customer_id로 클러스터링
WHERE s.region = 'Asia';

클러스터링 메타데이터 조회

1. 테이블 클러스터링 정보 확인

-- 테이블의 클러스터링 정보 조회
SELECT 
    table_name,
    clustering_ordinal_position,
    clustering_field_name
FROM mydataset.INFORMATION_SCHEMA.CLUSTERING_FIELDS
WHERE table_name = 'sales_clustered'
ORDER BY clustering_ordinal_position;

2. 클러스터링 효과 분석

-- 클러스터링 효과 측정
SELECT 
    table_name,
    partition_id,
    total_logical_bytes,
    total_billable_bytes,
    clustering_fields
FROM mydataset.INFORMATION_SCHEMA.PARTITIONS
WHERE table_name = 'sales_clustered';

3. 쿼리 성능 분석

-- 쿼리 실행 통계 확인
SELECT 
    job_id,
    query,
    total_bytes_processed,
    total_bytes_billed,
    creation_time
FROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE query LIKE '%sales_clustered%'
  AND creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY)
ORDER BY creation_time DESC;

데이터 타입별 클러스터링 전략

1. STRING 타입

-- 문자열 컬럼 클러스터링 (카테고리, 지역 등)
CREATE TABLE mydataset.string_clustered (
    id INT64,
    country STRING,        -- 낮은 카디널리티
    city STRING,          -- 중간 카디널리티
    user_agent STRING     -- 높은 카디널리티
)
CLUSTER BY country, city;

2. INTEGER/NUMERIC 타입

-- 숫자 컬럼 클러스터링 (ID, 점수 등)
CREATE TABLE mydataset.numeric_clustered (
    transaction_id INT64,
    user_id INT64,         -- 높은 카디널리티
    age_group INT64,       -- 낮은 카디널리티 (10, 20, 30, ...)
    amount NUMERIC
)
CLUSTER BY age_group, user_id;

3. DATE/TIMESTAMP 타입

-- 날짜 컬럼 클러스터링
CREATE TABLE mydataset.date_clustered (
    event_id INT64,
    event_date DATE,
    event_hour INT64,      -- 0-23
    user_id STRING
)
PARTITION BY event_date
CLUSTER BY event_hour, user_id;

DML 연산과 클러스터링

1. INSERT 연산

-- 클러스터링 테이블에 데이터 삽입
INSERT INTO mydataset.sales_clustered
SELECT 
    transaction_id,
    transaction_date,
    region,
    amount
FROM mydataset.raw_sales
WHERE transaction_date = CURRENT_DATE();

2. UPDATE 연산

-- 클러스터 컬럼 업데이트 (재클러스터링 발생)
UPDATE mydataset.sales_clustered
SET region = 'Asia-Pacific'
WHERE region = 'Asia';

3. MERGE 연산

-- MERGE를 사용한 효율적인 업데이트
MERGE mydataset.sales_clustered T
USING mydataset.daily_updates S
ON T.transaction_id = S.transaction_id
WHEN MATCHED THEN
  UPDATE SET amount = S.amount
WHEN NOT MATCHED THEN
  INSERT (transaction_id, transaction_date, region, amount)
  VALUES (S.transaction_id, S.transaction_date, S.region, S.amount);

자동 재클러스터링

BigQuery는 DML 연산 후 자동으로 재클러스터링을 수행합니다:

-- 재클러스터링 상태 확인
SELECT 
    table_name,
    partition_id,
    is_partitioning_enabled,
    clustering_fields,
    last_modified_time
FROM mydataset.INFORMATION_SCHEMA.TABLE_OPTIONS
WHERE table_name = 'sales_clustered';

CLI를 통한 클러스터링 관리

1. 클러스터링 테이블 생성

# 클러스터링 테이블 생성
bq mk \
    --table \
    --clustering_fields=region,category \
    --time_partitioning_field=transaction_date \
    mydataset.sales_table \
    schema.json

2. 기존 테이블에 클러스터링 추가

# 클러스터링 필드 추가
bq update \
    --clustering_fields=region,category \
    mydataset.existing_table

3. 테이블 정보 조회

# 클러스터링 정보 포함 테이블 상세 조회
bq show --format=prettyjson mydataset.sales_clustered

성능 측정 및 모니터링

1. 쿼리 성능 비교

-- 클러스터링 전후 성능 비교
WITH query_stats AS (
  SELECT 
    'clustered' as table_type,
    COUNT(*) as record_count
  FROM mydataset.sales_clustered
  WHERE region = 'Asia'
  
  UNION ALL
  
  SELECT 
    'non_clustered' as table_type,
    COUNT(*) as record_count
  FROM mydataset.sales_non_clustered
  WHERE region = 'Asia'
)
SELECT * FROM query_stats;

2. 스토리지 효율성 분석

-- 클러스터링 테이블의 압축률 분석
SELECT 
    table_name,
    SUM(total_logical_bytes) as logical_bytes,
    SUM(active_physical_bytes) as physical_bytes,
    (SUM(total_logical_bytes) - SUM(active_physical_bytes)) / SUM(total_logical_bytes) * 100 as compression_ratio
FROM mydataset.INFORMATION_SCHEMA.TABLE_STORAGE
WHERE table_name IN ('sales_clustered', 'sales_non_clustered')
GROUP BY table_name;

제한사항

  • 클러스터링 컬럼 수: 최대 4개
  • 컬럼 순서: 클러스터링 컬럼 순서는 성능에 영향
  • 데이터 타입: 모든 타입 지원하지만 일부 제한 있음
  • 중첩 필드: REPEATED 필드는 클러스터링 불가
  • 비용 예측: 정확한 스캔 비용 예측 어려움

모범 사례

1. 클러스터 컬럼 선택

  • 자주 필터링되는 컬럼 우선 선택: WHERE 절에서 가장 많이 사용되는 컬럼
  • 카디널리티 기반 선택:
    • 이상적: 낮은 카디널리티(< 0.1%) → 중간 카디널리티(0.1-10%) → 높은 카디널리티(> 10%) 순서
    • 피해야 할: 매우 높은 카디널리티만 있는 컬럼 (UUID, 타임스탬프 등)
    • 피해야 할: 매우 낮은 카디널리티만 있는 컬럼 (Boolean, 성별 등)
  • 쿼리 패턴 분석: 실제 쿼리 로그를 분석하여 가장 효과적인 컬럼 조합 선택
  • 데이터 분포 고려: 값이 고르게 분포된 컬럼 우선 선택

2. 컬럼 순서 최적화 (매우 중요!)

  • 카디널리티 순서: 낮음 → 중간 → 높음 순으로 배치
  • 필터링 빈도: 가장 자주 WHERE 절에서 사용되는 컬럼을 우선 배치
  • 조인 컬럼 고려: 조인에 자주 사용되는 컬럼은 뒤쪽에 배치하여 조인 성능 향상
  • 실제 예시:
    -- 올바른 순서: region(낮음) → category(중간) → customer_id(높음)
    CLUSTER BY region, category, customer_id
      
    -- 잘못된 순서: customer_id(높음) → region(낮음) → category(중간)  
    CLUSTER BY customer_id, region, category  -- 비효율적!
    

3. 파티션과 조합 사용

  • 시간 기반 데이터는 파티션 + 클러스터링 조합
  • 파티션은 큰 단위 분할, 클러스터링은 세부 정렬

4. 정기적인 성능 모니터링

  • 쿼리 성능 지표 추적
  • 스토리지 사용량 모니터링
  • 재클러스터링 빈도 확인

비용 최적화 전략

1. 쿼리 패턴 최적화

-- 클러스터 컬럼을 WHERE 절 초기에 사용
SELECT *
FROM mydataset.sales_clustered
WHERE region = 'Asia'          -- 클러스터 컬럼 우선
  AND transaction_date >= '2024-01-01'
  AND amount > 1000;

2. 스마트 조인 활용

-- 둘 다 같은 컬럼으로 클러스터링된 테이블 조인
SELECT *
FROM mydataset.sales_clustered s
JOIN mydataset.customers_clustered c
  ON s.region = c.region        -- 클러스터 컬럼으로 조인
  AND s.customer_id = c.customer_id;

실제 사용 예제

1. 전자상거래 주문 데이터

CREATE TABLE ecommerce.orders_optimized (
    order_id INT64,
    customer_id STRING,
    order_date DATE,
    region STRING,
    category STRING,
    amount NUMERIC,
    status STRING
)
PARTITION BY order_date
CLUSTER BY region, category, customer_id;

2. 로그 분석 테이블

CREATE TABLE analytics.web_logs_clustered (
    log_id INT64,
    timestamp TIMESTAMP,
    user_id STRING,
    page_path STRING,
    country STRING,
    device_type STRING
)
PARTITION BY DATE(timestamp)
CLUSTER BY country, device_type;

3. IoT 센서 데이터

CREATE TABLE iot.sensor_data_clustered (
    sensor_id STRING,
    measurement_time TIMESTAMP,
    device_type STRING,
    location STRING,
    value FLOAT64
)
PARTITION BY DATE(measurement_time)
CLUSTER BY device_type, location, sensor_id;

결론

BigQuery 클러스터링은 파티션과 함께 사용할 때 최대 효과를 발휘합니다. 적절한 클러스터링 전략을 통해 쿼리 성능을 크게 향상시키고 비용을 절약할 수 있습니다. 클러스터 컬럼 선택과 순서, 그리고 쿼리 패턴 최적화가 성공의 핵심입니다.

정기적인 성능 모니터링과 쿼리 패턴 분석을 통해 클러스터링 전략을 지속적으로 개선해나가는 것이 중요합니다.