メインコンテンツへスキップ
メインコンテンツへスキップ

パフォーマンスガイド

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. カラムプルーニング

必要なカラムのみが読み込まれます。

# 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. 遅延評価

複数の処理が1つのクエリにまとめてコンパイルされます。

# 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万行
  • ハードウェア: 一般的なノートPC
  • ファイル形式: CSV

結果

操作pandas (ms)DataStore (ms)優位
GroupBy count34717DataStore(19.93倍)
Combined ops1,535234DataStore(6.56倍)
Complex pipeline2,047380DataStore(5.39倍)
MultiFilter+Sort+Head1,963366DataStore(5.36倍)
Filter+Sort+Head1,537350DataStore(4.40倍)
Head/Limit16645DataStore(3.69倍)
Ultra-complex (10+ ops)1,070338DataStore(3.17倍)
GroupBy agg406141DataStore(2.88倍)
Select+Filter+Sort1,217443DataStore(2.75倍)
Filter+GroupBy+Sort466184DataStore(2.53倍)
Filter+Select+Sort1,285533DataStore(2.41倍)
Sort (single)1,7421,197DataStore(1.45倍)
Filter (single)276526同程度
Sort (multiple)9471,477同程度

重要なポイント

  1. GroupBy 処理: DataStore は最大 19.93 倍高速
  2. 複雑なパイプライン: DataStore は 5〜6 倍高速(SQL pushdown の効果)
  3. 単純なスライス処理: 性能は同程度で、差はごくわずか
  4. 最適なユースケース: groupby/aggregation を含むマルチステップ処理
  5. ゼロコピー: 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 Lambda 関数

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

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

DataStore が「遅い」ように見えるケースでも、パフォーマンスは通常 pandas と同等 であり、実用上の影響はほとんどありません。複雑な処理における DataStore の利点は、これらの例外的なケースを大きく上回ります。

実行をよりきめ細かく制御したい場合は、Execution Engine Configuration を参照してください。


ゼロコピー DataFrame 統合

DataStore は pandas の DataFrame を読み書きする際に ゼロコピー を利用します。これは次のことを意味します。

# 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() は本質的にコストほぼゼロ ― シリアライズやメモリコピーは発生しない
  • 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 ファイルに対するメモリ使用量を削減します。

詳細については、Performance Mode を参照してください。

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")

ベストプラクティスの概要

プラクティス効果
パフォーマンスモードを有効にする集約ワークロードが 2〜8 倍高速
Parquet ファイルを使用する読み取りが 3〜10 倍高速
早期にフィルタリングするデータ処理量を削減
必要なカラムのみを選択するI/O とメモリを削減
GROUP BY/集約関数を使用する最大 20 倍高速
バッチ処理を行う同じ処理の繰り返し実行を回避
最適化前にプロファイリングを行う実際のボトルネックを特定
explain() を使用するクエリ最適化を検証
サンプルには head() を使用するテーブル全体スキャンを回避

クイック判断ガイド

ワークロード推奨
GroupBy/集約DataStore を使用
複雑なマルチステップパイプラインDataStore を使用
フィルターを伴う大きなファイルDataStore を使用
単純なスライス操作どちらでも可(性能は同程度)
カスタム Python lambda 関数pandas を使用するか、後段で変換する
ごく小さいデータ (<1,000 行)どちらでも可(差はごくわずか)
ヒント

最適なエンジンを自動選択するには、config.set_execution_engine('auto')(デフォルト)を使用します。 集約ワークロードでスループットを最大化するには、config.use_performance_mode() を使用します。 詳細は Execution Engine および Performance Mode を参照してください。