Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

Guia de desempenho

O DataStore oferece ganhos significativos de desempenho em relação ao pandas em muitas operações. Este guia explica por quê e como otimizar suas cargas de trabalho.

Por que o DataStore é mais rápido

1. SQL Pushdown

As operações são executadas na fonte de dados:

# 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. Poda de colunas

Somente as colunas necessárias são lidas:

# 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. Avaliação preguiçosa

Múltiplas operações são compiladas em uma única consulta:

# 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

Benchmark: DataStore vs pandas

Ambiente de teste

  • Dados: 10 milhões de linhas
  • Hardware: Laptop comum
  • Formato de arquivo: CSV

Resultados

Operação pandas (ms) DataStore (ms) Vencedor
Contagem com GroupBy 347 17 DataStore (19.93x)
Operações combinadas 1,535 234 DataStore (6.56x)
Pipeline complexo 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/Limite 166 45 DataStore (3.69x)
Ultra complexo (10+ operações) 1,070 338 DataStore (3.17x)
Agregação com 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 (simples) 1,742 1,197 DataStore (1.45x)
Filter (simples) 276 526 Comparável
Sort (múltiplo) 947 1,477 Comparável

Principais conclusões

  1. Operações de GroupBy: DataStore até 19,93x mais rápido
  2. Pipelines complexos: DataStore 5–6x mais rápido (benefício do SQL pushdown)
  3. Operações simples de fatiamento: Desempenho comparável — diferença insignificante
  4. Melhor caso de uso: Operações em várias etapas com GroupBy/agregação
  5. Zero-copy: to_df() não tem sobrecarga de conversão de dados

Quando o DataStore leva vantagem

Agregações pesadas

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

Pipelines complexos

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

Processamento de arquivos grandes

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

Operações em várias colunas

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

Quando o pandas se equipara

Na maioria dos cenários, o DataStore iguala ou supera o desempenho do pandas. No entanto, o pandas pode ser um pouco mais rápido nestes casos específicos:

Pequenos volumes de dados (<1.000 linhas)

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

Operações simples de fatiamento

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

Funções lambda personalizadas em Python

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

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

Integração de DataFrame com zero-copy

O DataStore usa zero-copy para ler e gravar DataFrames do pandas. Isso significa:

# 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

Principais implicações:

  • to_df() praticamente não tem custo — sem serialização nem cópia de memória
  • Criar um DataStore a partir de um DataFrame do pandas é instantâneo
  • A memória é compartilhada entre o DataStore e as views do pandas

Dicas de otimização

1. Ative o modo de desempenho para cargas de trabalho com muita agregação

Para cargas de trabalho com muita agregação em que você não precisa do formato de saída exato do pandas (ordem das linhas, colunas MultiIndex, correções de dtype), ative o modo de desempenho para obter o máximo throughput:

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']})
)

Melhoria esperada: Até 2-8x mais rápido em cargas de trabalho com filter+groupby, com uso de memória reduzido para arquivos Parquet grandes.

Consulte Modo de desempenho para ver todos os detalhes.

2. Use Parquet em vez de CSV

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

Melhoria esperada: leituras de 3 a 10x mais rápidas

3. Aplique filtros o quanto antes

# 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. Selecione apenas as colunas necessárias

# 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. Use agregações SQL

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

6. Use head() em vez de consultas completas

# 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. Operações em lote

# 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. Use o explain() para otimizar

# 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()

Profiling da sua carga de trabalho

Habilite o profiling

from chdb.datastore.config import config, get_profiler

config.enable_profiling()

# Run your workload
result = your_pipeline()

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

Identifique gargalos

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

Compare as abordagens

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

Resumo das boas práticas

Prática Impacto
Ative o modo de desempenho 2 a 8x mais rápido para cargas de trabalho de agregação
Use arquivos Parquet 3 a 10x mais rápido nas leituras
Aplique filtros no início Reduz o processamento de dados
Selecione apenas as colunas necessárias Reduz E/S e uso de memória
Use GroupBy/agregações Até 20x mais rápido
Use operações em lote Evita execuções repetidas
Faça análise de desempenho antes de otimizar Encontra os gargalos reais
Use explain() Verifique a otimização de consultas
Use head() para amostras Evita varreduras completas de tabela

Guia rápido de decisão

Sua carga de trabalho Recomendação
GroupBy/agregação Use DataStore
Pipeline complexo de várias etapas Use DataStore
Arquivos grandes com filtros Use DataStore
Operações simples de fatiamento Qualquer um dos dois (desempenho comparável)
Funções lambda personalizadas em Python Use pandas ou converta depois
Dados muito pequenos (<1.000 linhas) Qualquer um dos dois (diferença insignificante)
Navigation