Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

Modelo de ejecución de DataStore

Comprender el modelo de evaluación diferida de DataStore es clave para usarlo de forma eficaz y lograr un rendimiento óptimo.

Evaluación diferida

DataStore usa evaluación diferida: las operaciones no se ejecutan inmediatamente, sino que se registran y se compilan en consultas SQL optimizadas. La ejecución solo se produce cuando los resultados realmente son necesarios.

Ejemplo: Diferido vs Inmediato

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

Beneficios de la evaluación diferida

  1. Optimización de consultas: Varias operaciones se compilan en una sola consulta SQL optimizada
  2. Pushdown de filtros: Los filtros se aplican a nivel de la fuente de datos
  3. Poda de columnas: Solo se leen las columnas necesarias
  4. Decisiones diferidas: El motor de ejecución puede elegirse en tiempo de ejecución
  5. Inspección del plan: Puede ver o depurar la consulta antes de ejecutarla

Activadores de ejecución

La ejecución se activa automáticamente cuando se necesitan valores reales:

Activadores automáticos

Activador Ejemplo Descripción
print() / repr() print(ds) Mostrar resultados
len() len(ds) Obtener el número de filas
.columns ds.columns Obtener nombres de columnas
.dtypes ds.dtypes Obtener tipos de columna
.shape ds.shape Obtener dimensiones
.index ds.index Obtener el índice de filas
.values ds.values Obtener un array de NumPy
Iteration for row in ds Iterar sobre las filas
to_df() ds.to_df() Convertir a un DataFrame de pandas
to_pandas() ds.to_pandas() Alias de to_df
to_dict() ds.to_dict() Convertir a un diccionario
to_numpy() ds.to_numpy() Convertir a un array
.equals() ds.equals(other) Comparar DataStores

Ejemplos:

# 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

Operaciones que se mantienen en diferido

Operación Devuelve Descripción
filter() DataStore Añade la cláusula WHERE
select() DataStore Añade la selección de columnas
sort() DataStore Añade ORDER BY
groupby() LazyGroupBy Prepara GROUP BY
join() DataStore Añade JOIN
ds['col'] ColumnExpr Referencia de columna
ds[['col1', 'col2']] DataStore Selección de columnas

Ejemplos:

# 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

Ejecución en tres fases

Las operaciones de DataStore siguen un modelo de ejecución en tres fases:

Fase 1: Construcción de consultas SQL (diferida)

Se acumulan las operaciones que pueden expresarse en SQL:

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 de ejecución

Cuando se produce el evento desencadenante, se ejecuta el SQL acumulado:

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

Fase 3: Operaciones con DataFrame (si las hay)

Si encadenas operaciones exclusivas de pandas después de la ejecución:

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

Ver planes de ejecución

Usa explain() para ver qué se va a ejecutar:

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

Usa verbose=True para ver más detalles:

query.explain(verbose=True)

Consulte Depuración: explain() para obtener la documentación completa.


Almacenamiento en caché

DataStore almacena en caché los resultados de la ejecución para evitar consultas repetidas.

Cómo funciona la caché

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

Invalidación de caché

La caché se invalida cuando las operaciones modifican el 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)

Control manual de caché

# Clear cache
ds.clear_cache()

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

Combinación de SQL y operaciones de Pandas

DataStore gestiona inteligentemente las operaciones que combinan SQL y Pandas:

Operaciones compatibles con SQL

Estas se compilan a SQL:

  • filter(), where()
  • select()
  • groupby(), agg()
  • sort(), orderby()
  • limit(), offset()
  • join(), union()
  • distinct()
  • Operaciones con columnas (matemáticas, comparaciones y métodos de cadena)

Operaciones solo de Pandas

Estas activan la ejecución y usan pandas:

  • apply() con funciones personalizadas
  • pivot_table() con agregaciones complejas
  • stack(), unstack()
  • Operaciones sobre DataFrames ya ejecutados

Canalizaciones híbridas

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

Selección del motor de ejecución

DataStore puede ejecutar operaciones con distintos motores:

Modo automático (predeterminado)

from chdb.datastore.config import config

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

Forzar el uso del motor chDB

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

Forzar el motor de pandas

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

Consulta Configuración: Motor de ejecución para más detalles.


Impacto en el rendimiento

Bueno: Filtra pronto

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

Malo: filtrar al final

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

Bien: Selecciona las columnas desde el principio

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

Bien: deja que SQL haga el trabajo

# 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

Resumen de buenas prácticas

  1. Encadena las operaciones antes de ejecutar - Construye la consulta completa y ejecútala una sola vez
  2. Filtra cuanto antes - Reduce los datos en el origen
  3. Selecciona solo las columnas necesarias - La poda de columnas mejora el rendimiento
  4. Usa explain() para entender la ejecución - Depura antes de ejecutar
  5. Deja que SQL se encargue de las agregaciones - ClickHouse está optimizado para ello
  6. Ten en cuenta qué desencadena la ejecución - Evita ejecutar antes de tiempo por accidente
  7. Usa la caché con criterio - Entiende cuándo se invalida la caché
Navigation