Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

성능 가이드

DataStore는 많은 작업에서 pandas보다 훨씬 뛰어난 성능을 제공합니다. 이 가이드에서는 그 이유와 워크로드를 최적화하는 방법을 설명합니다.

DataStore가 더 빠른 이유

1. SQL 푸시다운

연산이 데이터 소스로 푸시다운됩니다:

# pandas: Loads ALL data, then filters in memory
df = pd.read_csv("huge.csv")       # Load 10GB
df = df[df['year'] == 2024]        # Filter in Python

# DataStore: Filter at source
ds = pd.read_csv("huge.csv")       # Just metadata
ds = ds[ds['year'] == 2024]        # Filter in SQL
df = ds.to_df()                    # Only load filtered data

2. 컬럼 프루닝(Column Pruning)

필요한 컬럼만 읽습니다:

# DataStore: Only reads name, age columns
ds = pd.read_parquet("wide_table.parquet")
result = ds.select('name', 'age').to_df()

# vs pandas: Reads all 100 columns, then selects

3. 지연 실행

여러 연산이 하나의 쿼리로 컴파일됩니다:

# DataStore: One optimized SQL query
result = (ds
    .filter(ds['amount'] > 100)
    .groupby('region')
    .agg({'amount': 'sum'})
    .sort('sum', ascending=False)
    .head(10)
    .to_df()
)

# Becomes:
# SELECT region, SUM(amount) FROM data
# WHERE amount > 100
# GROUP BY region ORDER BY sum DESC LIMIT 10

벤치마크: DataStore와 pandas 비교

테스트 환경

  • 데이터: 1,000만 행
  • 하드웨어: 표준 노트북
  • 파일 포맷: CSV

결과

연산 pandas (ms) DataStore (ms) 우위
GroupBy count 347 17 DataStore (19.93x)
복합 연산 1,535 234 DataStore (6.56x)
복잡한 파이프라인 2,047 380 DataStore (5.39x)
다중 Filter+Sort+Head 1,963 366 DataStore (5.36x)
Filter+Sort+Head 1,537 350 DataStore (4.40x)
Head/Limit 166 45 DataStore (3.69x)
초복잡도 (10개 이상 연산) 1,070 338 DataStore (3.17x)
GroupBy 집계 406 141 DataStore (2.88x)
Select+Filter+Sort 1,217 443 DataStore (2.75x)
Filter+GroupBy+Sort 466 184 DataStore (2.53x)
Filter+Select+Sort 1,285 533 DataStore (2.41x)
Sort (단일) 1,742 1,197 DataStore (1.45x)
Filter (단일) 276 526 유사
Sort (다중) 947 1,477 유사

핵심 인사이트

  1. GroupBy 연산: DataStore가 최대 19.93배 더 빠릅니다
  2. 복잡한 파이프라인: DataStore가 5-6배 더 빠릅니다 (SQL 푸시다운의 이점)
  3. 단순 슬라이스 작업: 성능이 비슷해 차이가 거의 없습니다
  4. 가장 적합한 사용 사례: GroupBy/집계를 포함한 다단계 작업
  5. zero-copy: to_df()에는 데이터 변환 오버헤드가 없습니다

DataStore가 더 적합한 경우

대규모 집계

# DataStore excels: 19.93x faster
result = ds.groupby('category')['amount'].sum()

복잡한 파이프라인

# DataStore excels: 5-6x faster
result = (ds
    .filter(ds['date'] >= '2024-01-01')
    .filter(ds['amount'] > 100)
    .groupby('region')
    .agg({'amount': ['sum', 'mean', 'count']})
    .sort('sum', ascending=False)
    .head(20)
)

대용량 파일 처리

# DataStore: Only loads what you need
ds = pd.read_parquet("huge_file.parquet")
result = ds.filter(ds['id'] == 12345).to_df()  # Fast!

여러 컬럼 연산

# DataStore: Combines into single SQL
ds['total'] = ds['price'] * ds['quantity']
ds['is_large'] = ds['total'] > 1000
ds = ds.filter(ds['is_large'])

pandas와 비슷한 성능을 보이는 경우

대부분의 시나리오에서 DataStore는 pandas와 비슷하거나 더 나은 성능을 보입니다. 하지만 다음과 같은 특정한 경우에는 pandas가 약간 더 빠를 수 있습니다:

소규모 데이터셋 (<1,000행)

# For very small datasets, overhead is minimal for both
# Performance difference is negligible
small_df = pd.DataFrame({'x': range(100)})

간단한 슬라이싱 연산

# Single slice operations without aggregation
df = df[df['x'] > 10]  # pandas slightly faster
ds = ds[ds['x'] > 10]  # DataStore comparable

사용자 정의 Python 람다 함수

# pandas required for custom Python code
def complex_function(row):
    return custom_logic(row)

df['result'] = df.apply(complex_function, axis=1)

Zero-Copy 데이터프레임 통합

DataStore는 pandas 데이터프레임을 읽고 쓸 때 zero-copy를 사용합니다. 이는 다음을 의미합니다:

# to_df() does NOT copy data - it's a zero-copy operation
result = ds.filter(ds['x'] > 10).to_df()  # No data conversion overhead

# Same for creating DataStore from DataFrame
ds = DataStore(existing_df)  # No data copy

주요 시사점:

  • to_df()는 직렬화(serialization)나 메모리 복사 없이 사실상 비용이 들지 않습니다
  • pandas DataFrame에서 DataStore를 생성하는 작업은 즉시 완료됩니다
  • 메모리는 DataStore와 pandas 뷰 사이에서 공유됩니다

최적화 팁

1. 부하가 큰 워크로드에 성능 모드 활성화

정확한 pandas 출력 형식(행 순서, MultiIndex 컬럼, Dtype 보정)이 필요하지 않은 집계 작업이 많은 워크로드에서는 최대 처리량을 위해 성능 모드를 활성화하세요:

from chdb.datastore.config import config

config.use_performance_mode()

# Now all operations use SQL-first execution with no pandas overhead:
# - Parallel Parquet reading (no preserve_order)
# - Single-SQL aggregation (filter+groupby in one query)
# - No row-order preservation overhead
# - No MultiIndex, no dtype corrections
result = (ds
    .filter(ds['amount'] > 100)
    .groupby('region')
    .agg({'amount': ['sum', 'mean', 'count']})
)

예상 개선 사항: filter+groupby 워크로드에서 최대 2~8배 빨라지고, 대용량 Parquet 파일의 메모리 사용량이 줄어듭니다.

자세한 내용은 성능 모드를 참조하십시오.

2. CSV 대신 Parquet 사용하기

# CSV: Slower, reads entire file
ds = pd.read_csv("data.csv")

# Parquet: Faster, columnar, compressed
ds = pd.read_parquet("data.parquet")

# Convert once, benefit forever
df = pd.read_csv("data.csv")
df.to_parquet("data.parquet")

기대 효과: 읽기 속도 3~10배 향상

3. 가능한 한 일찍 필터링하기

# Good: Filter first, then aggregate
result = (ds
    .filter(ds['date'] >= '2024-01-01')  # Reduce data early
    .groupby('category')['amount'].sum()
)

# Less optimal: Process all data
result = (ds
    .groupby('category')['amount'].sum()
    .filter(ds['sum'] > 1000)  # Filter too late
)

4. 필요한 컬럼만 선택

# Good: Column pruning
result = ds.select('name', 'amount').filter(ds['amount'] > 100)

# Less optimal: All columns loaded
result = ds.filter(ds['amount'] > 100)  # Loads all columns

5. SQL 집계 활용하기

# GroupBy is where DataStore shines
# Up to 20x speedup!
result = ds.groupby('category').agg({
    'amount': ['sum', 'mean', 'count', 'max'],
    'quantity': 'sum'
})

6. 전체 쿼리 대신 head() 사용

# Don't load entire result if you only need a sample
result = ds.filter(ds['type'] == 'A').head(100)  # LIMIT 100

# Avoid this for large results
# result = ds.filter(ds['type'] == 'A').to_df()  # Loads everything

7. 배치 작업

# Good: Single execution
result = ds.filter(ds['x'] > 10).filter(ds['y'] < 100).to_df()

# Bad: Multiple executions
result1 = ds.filter(ds['x'] > 10).to_df()  # Execute
result2 = result1[result1['y'] < 100]       # Execute again

8. explain()으로 최적화하기

# View the query plan before executing
query = ds.filter(...).groupby(...).agg(...)
query.explain()  # Check if operations are pushed down

# Then execute
result = query.to_df()

워크로드 프로파일링하기

프로파일링 활성화

from chdb.datastore.config import config, get_profiler

config.enable_profiling()

# Run your workload
result = your_pipeline()

# View report
profiler = get_profiler()
profiler.report()

병목 지점 파악

Performance Report
==================
Step                    Duration    % Total
----                    --------    -------
SQL execution           2.5s        62.5%     <- Bottleneck!
read_csv                1.2s        30.0%
Other                   0.3s        7.5%

접근 방식 비교

# Test approach 1
profiler.reset()
result1 = approach1()
time1 = profiler.get_steps()[-1]['duration_ms']

# Test approach 2
profiler.reset()
result2 = approach2()
time2 = profiler.get_steps()[-1]['duration_ms']

print(f"Approach 1: {time1:.0f}ms")
print(f"Approach 2: {time2:.0f}ms")

모범 사례 요약

Practice Impact
성능 모드 활성화 집계 워크로드에서 2~8배 더 빠름
Parquet 파일 사용 읽기 속도 3~10배 향상
초기에 필터 적용 데이터 처리량 감소
필요한 컬럼 선택 I/O 및 메모리 사용량 감소
GroupBy/집계 사용 최대 20배 더 빠름
배치 작업 반복 실행 방지
최적화 전에 프로파일링 실제 병목 파악
explain() 사용 쿼리 최적화 검증
샘플에는 head() 사용 전체 테이블 스캔 방지

빠른 의사결정 가이드

워크로드 권장 사항
GroupBy/집계 DataStore 사용
복잡한 다단계 파이프라인 DataStore 사용
필터가 있는 대용량 파일 DataStore 사용
단순 슬라이스 작업 어느 쪽이든 가능(성능 유사)
사용자 정의 Python 람다 함수 pandas를 사용하거나 나중에 변환
매우 작은 데이터(<1,000행) 어느 쪽이든 가능(차이 미미)
Navigation