Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

Modelo de execução do DataStore

Entender o modelo de avaliação preguiçosa do DataStore é essencial para usá-lo com eficiência e obter o melhor desempenho.

Avaliação preguiçosa

O DataStore usa avaliação preguiçosa - as operações não são executadas imediatamente, mas são registradas e compiladas em consultas SQL otimizadas. A execução só ocorre quando os resultados são realmente necessários.

Exemplo: Lazy vs Imediato

from pathlib import Path
Path("sales.csv").write_text("""\
region,product,category,amount,quantity,price,date,order_id
East,Widget,Electronics,5200,10,120,2024-01-15,1001
West,Gadget,Electronics,800,5,160,2024-02-20,1002
East,Gizmo,Home,6500,3,100,2024-03-10,1003
North,Widget,Electronics,4500,6,150,2024-06-18,1004
West,Gadget,Electronics,2000,8,250,2024-09-14,1005
""")

from chdb import datastore as pd

ds = pd.read_csv("sales.csv")

# These operations are NOT executed yet
result = (ds
    .filter(ds['amount'] > 1000)    # Recorded, not executed
    .select('region', 'amount')      # Recorded, not executed
    .groupby('region')               # Recorded, not executed
    .agg({'amount': 'sum'})          # Recorded, not executed
    .sort('sum', ascending=False)    # Recorded, not executed
)

# Still no execution - just building the query plan
print(result.to_sql())
# SELECT region, SUM(amount) AS sum
# FROM file('sales.csv', 'CSVWithNames')
# WHERE amount > 1000
# GROUP BY region
# ORDER BY sum DESC

# NOW execution happens
df = result.to_df()  # <-- Triggers execution

Benefícios da avaliação preguiçosa

  1. Otimização de consultas: várias operações são compiladas em uma única consulta SQL otimizada
  2. Pushdown de filtros: os filtros são aplicados no nível da fonte de dados
  3. Poda de colunas: apenas as colunas necessárias são lidas
  4. Decisões adiadas: o mecanismo de execução pode ser escolhido em tempo de execução
  5. Inspeção do plano: você pode visualizar/depurar a consulta antes de executá-la

Gatilhos de execução

A execução é iniciada automaticamente quando você precisa de valores reais:

Gatilhos automáticos

Gatilho Exemplo Descrição
print() / repr() print(ds) Exibe os resultados
len() len(ds) Obtém a contagem de linhas
.columns ds.columns Obtém os nomes das colunas
.dtypes ds.dtypes Obtém os tipos das colunas
.shape ds.shape Obtém as dimensões
.index ds.index Obtém o índice das linhas
.values ds.values Obtém um array NumPy
Iteração for row in ds Itera sobre as linhas
to_df() ds.to_df() Converte para pandas
to_pandas() ds.to_pandas() Alias de to_df
to_dict() ds.to_dict() Converte para dict
to_numpy() ds.to_numpy() Converte para array
.equals() ds.equals(other) Compara DataStores

Exemplos:

# All these trigger execution
print(ds)              # Display
len(ds)                # 1000
ds.columns             # Index(['name', 'age', 'city'])
ds.shape               # (1000, 3)
list(ds)               # List of values
ds.to_df()             # pandas DataFrame

Operações que permanecem lazy

Operação Retorna Descrição
filter() DataStore Adiciona cláusula WHERE
select() DataStore Adiciona seleção de colunas
sort() DataStore Adiciona ORDER BY
groupby() LazyGroupBy Prepara GROUP BY
join() DataStore Adiciona JOIN
ds['col'] ColumnExpr Referência de coluna
ds[['col1', 'col2']] DataStore Seleção de colunas

Exemplos:

# These do NOT trigger execution - they stay lazy
result = ds.filter(ds['age'] > 25)      # Returns DataStore
result = ds.select('name', 'age')        # Returns DataStore
result = ds['name']                      # Returns ColumnExpr
result = ds.groupby('city')              # Returns LazyGroupBy

Execução em três fases

As operações do DataStore seguem um modelo de execução em três fases:

Fase 1: Construção da consulta SQL (lazy)

As operações que podem ser expressas em SQL são acumuladas:

result = (ds
    .filter(ds['status'] == 'active')   # WHERE
    .select('user_id', 'amount')         # SELECT
    .groupby('user_id')                  # GROUP BY
    .agg({'amount': 'sum'})              # SUM()
    .sort('sum', ascending=False)        # ORDER BY
    .limit(10)                           # LIMIT
)
# All compiled into one SQL query

Fase 2: Momento da execução

Quando ocorre um disparo, o SQL acumulado é executado:

# Execution triggered here
df = result.to_df()  
# The single optimized SQL query runs now

Fase 3: Operações com DataFrame (se houver)

Se você encadear operações específicas do pandas após a execução:

# Mixed operations
result = (ds
    .filter(ds['amount'] > 100)          # Phase 1: SQL
    .to_df()                             # Phase 2: Execute
    .pivot_table(...)                    # Phase 3: pandas
)

Visualizando planos de execução

Use explain() para ver o que será executado:

Querypython
ds = pd.read_csv("sales.csv")

query = (ds
    .filter(ds['amount'] > 1000)
    .groupby('region')
    .agg({'amount': ['sum', 'mean']})
)

# View execution plan
query.explain()
Responsetext
Pipeline:
  1. Source: file('sales.csv', 'CSVWithNames')
  2. Filter: amount > 1000
  3. GroupBy: region
  4. Aggregate: sum(amount), avg(amount)

Generated SQL:
SELECT region, SUM(amount) AS sum, AVG(amount) AS mean
FROM file('sales.csv', 'CSVWithNames')
WHERE amount > 1000
GROUP BY region

Use verbose=True para obter mais detalhes:

query.explain(verbose=True)

Consulte Depuração: explain() para ver a documentação completa.


Cache

O DataStore armazena em cache os resultados da execução para evitar consultas redundantes.

Como o cache funciona

from pathlib import Path
Path("data.csv").write_text("""\
name,age,city,salary,department
Alice,25,NYC,55000,Engineering
Bob,30,LA,65000,Product
Charlie,35,NYC,80000,Engineering
Diana,28,SF,70000,Design
Eve,42,NYC,95000,Product
""")

ds = pd.read_csv("data.csv")
result = ds.filter(ds['age'] > 25)

# First access - executes query
print(result.shape)  # Executes and caches

# Second access - uses cache
print(result.columns)  # Uses cached result

# Third access - uses cache
df = result.to_df()  # Uses cached result

Invalidação do cache

O cache é invalidado quando operações modificam o DataStore:

result = ds.filter(ds['age'] > 25)
print(result.shape)  # Executes, caches

# New operation invalidates cache
result2 = result.filter(result['city'] == 'NYC')
print(result2.shape)  # Re-executes (different query)

Controle manual do cache

# Clear cache
ds.clear_cache()

# Disable caching
from chdb.datastore.config import config
config.set_cache_enabled(False)

Combinando operações SQL e do Pandas

O DataStore lida de forma inteligente com operações que combinam SQL e Pandas:

Operações compatíveis com SQL

Estas operações são compiladas em SQL:

  • filter(), where()
  • select()
  • groupby(), agg()
  • sort(), orderby()
  • limit(), offset()
  • join(), union()
  • distinct()
  • Operações em colunas (matemática, comparação, métodos de texto)

Operações exclusivas do Pandas

Estas operações disparam a execução e usam o Pandas:

  • apply() com funções personalizadas
  • pivot_table() com agregações complexas
  • stack(), unstack()
  • Operações em DataFrames já executados

Pipelines híbridos

# SQL phase
result = (ds
    .filter(ds['amount'] > 100)      # SQL
    .groupby('category')              # SQL
    .agg({'amount': 'sum'})           # SQL
)

# Execution + pandas phase
result = (result
    .to_df()                          # Execute SQL
    .pivot_table(...)                 # pandas operation
)

Seleção do mecanismo de execução

O DataStore pode executar operações usando diferentes motores:

Modo Automático (Padrão)

from chdb.datastore.config import config

config.set_execution_engine('auto')  # Default
# Automatically selects best engine per operation

Forçar o engine chDB

config.set_execution_engine('chdb')
# All operations use ClickHouse SQL

Forçar o engine do pandas

config.set_execution_engine('pandas')
# All operations use pandas

Consulte Configuração: mecanismo de execução para mais detalhes.


Implicações no desempenho

Bom: Filtrar logo no início

# Good: Filter in SQL, then aggregate
result = (ds
    .filter(ds['date'] >= '2024-01-01')  # Reduces data early
    .groupby('category')
    .agg({'amount': 'sum'})
)

Ruim: filtrar tarde

# Bad: Aggregate all, then filter
result = (ds
    .groupby('category')
    .agg({'amount': 'sum'})
    .to_df()
    .query('sum > 1000')  # Pandas filter after aggregation
)

Bom: selecione as colunas logo no início

# Good: Select columns in SQL
result = (ds
    .select('user_id', 'amount', 'date')
    .filter(ds['date'] >= '2024-01-01')
    .groupby('user_id')
    .agg({'amount': 'sum'})
)

Bom: deixe o SQL fazer o trabalho

# Good: Complex aggregation in SQL
result = (ds
    .groupby('category')
    .agg({
        'amount': ['sum', 'mean', 'count'],
        'quantity': 'sum'
    })
    .sort('sum', ascending=False)
    .limit(10)
)
# One SQL query does everything

# Bad: Multiple separate queries
sums = ds.groupby('category')['amount'].sum().to_df()
means = ds.groupby('category')['amount'].mean().to_df()
# Two queries instead of one

Resumo de boas práticas

  1. Encadeie as operações antes de executar - Monte a consulta completa e só então a execute uma vez
  2. Filtre o quanto antes - Reduza os dados na origem
  3. Selecione apenas as colunas necessárias - A poda de colunas melhora o desempenho
  4. Use explain() para entender a execução - Faça a depuração antes de executar
  5. Deixe o SQL cuidar das agregações - O ClickHouse é otimizado para isso
  6. Fique atento aos gatilhos de execução - Evite execuções antecipadas acidentais
  7. Use o cache com critério - Entenda quando o cache é invalidado
Navigation