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 data2. カラムプルーニング
必要なカラムのみが読み込まれます:
# 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 selects3. 遅延評価
複数の操作は 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 count | 347 | 17 | DataStore (19.93x) |
| 複合操作 | 1,535 | 234 | DataStore (6.56x) |
| 複雑なパイプライン | 2,047 | 380 | DataStore (5.39x) |
| MultiFilter+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 | 同等 |
主な知見
- GroupBy 操作: DataStore は最大 19.93 倍高速
- 複雑なパイプライン: DataStore は 5〜6 倍高速 (SQL プッシュダウンの効果)
- 単純なスライス操作: 性能は同程度で、差はほとんどない
- 最適なユースケース: GroupBy/集約を含む複数ステップの操作
- ゼロコピー:
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 comparablePython のカスタムラムダ関数
# pandas required for custom Python code
def complex_function(row):
return custom_logic(row)
df['result'] = df.apply(complex_function, axis=1)ゼロコピー 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ファイルでのメモリ使用量を削減します。
詳細はパフォーマンスモードを参照してください。
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 columns5. 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 everything7. バッチ処理
# 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 again8. 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とメモリ使用量を削減 |
| GroupBy/集計を使用する | 最大20倍高速 |
| バッチ処理を行う | 繰り返し実行を避ける |
| 最適化の前にプロファイリングする | 実際のボトルネックを見つける |
| explain()を使用する | クエリ最適化を確認する |
| サンプルにはhead()を使用する | フルテーブルスキャンを避ける |
クイック判断ガイド
| ワークロード | 推奨事項 |
|---|---|
| GroupBy/集約 | DataStore を使用 |
| 複雑な多段階パイプライン | DataStore を使用 |
| フィルター付きの大容量ファイル | DataStore を使用 |
| 単純なスライス操作 | どちらでも可 (性能は同程度) |
| カスタム Python ラムダ関数 | pandas を使用するか、後から変換 |
| ごく小さいデータ (<1,000 行) | どちらでも可 (差はほとんどない) |