Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

Modèle d’exécution de DataStore

Comprendre le modèle d’évaluation paresseuse de DataStore est essentiel pour l’utiliser efficacement et obtenir des performances optimales.

Évaluation paresseuse

DataStore utilise l’évaluation paresseuse : les opérations ne sont pas exécutées immédiatement ; elles sont enregistrées puis compilées en requêtes SQL optimisées. L’exécution n’a lieu que lorsque les résultats sont effectivement nécessaires.

Exemple : évaluation paresseuse vs évaluation immédiate

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

Avantages de l’évaluation paresseuse

  1. Optimisation des requêtes : Plusieurs opérations sont compilées en une seule requête SQL optimisée
  2. Pushdown des filtres : Les filtres sont appliqués au niveau de la source de données
  3. Élagage des colonnes : Seules les colonnes nécessaires sont lues
  4. Décisions différées : Le moteur d’exécution peut être choisi au moment de l’exécution
  5. Inspection du plan : Vous pouvez inspecter/déboguer la requête avant de l’exécuter

Déclencheurs d’exécution

L’exécution est déclenchée automatiquement lorsque vous avez besoin de valeurs réelles :

Déclencheurs automatiques

Déclencheur Exemple Description
print() / repr() print(ds) Afficher les résultats
len() len(ds) Obtenir le nombre de lignes
.columns ds.columns Obtenir les noms des colonnes
.dtypes ds.dtypes Obtenir les types de colonnes
.shape ds.shape Obtenir les dimensions
.index ds.index Obtenir l’index des lignes
.values ds.values Obtenir le tableau NumPy
Itération for row in ds Itérer sur les lignes
to_df() ds.to_df() Convertir en DataFrame pandas
to_pandas() ds.to_pandas() alias de to_df
to_dict() ds.to_dict() Convertir en dictionnaire
to_numpy() ds.to_numpy() Convertir en tableau
.equals() ds.equals(other) Comparer les DataStore

Exemples :

# 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

Opérations qui restent paresseuses

Opération Renvoie Description
filter() DataStore Ajoute une clause WHERE
select() DataStore Ajoute une sélection de colonnes
sort() DataStore Ajoute ORDER BY
groupby() LazyGroupBy Prépare GROUP BY
join() DataStore Ajoute JOIN
ds['col'] ColumnExpr Référence de colonne
ds[['col1', 'col2']] DataStore Sélection de colonnes

Exemples :

# 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

Exécution en trois phases

Les opérations de DataStore suivent un modèle d’exécution en trois phases :

Phase 1 : construction de la requête SQL (paresseuse)

Les opérations pouvant être exprimées en SQL sont accumulées :

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

Phase 2 : Moment de l’exécution

Lorsqu’un déclenchement a lieu, le SQL accumulé est exécuté :

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

Phase 3 : opérations sur le DataFrame (le cas échéant)

Si vous enchaînez des opérations propres à pandas après l’exécution :

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

Afficher les plans d’exécution

Utilisez explain() pour voir ce qui sera exécuté :

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

Utilisez verbose=True pour plus de détails :

query.explain(verbose=True)

Consultez Débogage : explain() pour la documentation complète.


Mise en cache

DataStore met en cache les résultats d’exécution afin d’éviter les requêtes redondantes.

Fonctionnement du cache

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

Invalidation du cache

Le cache est invalidé lorsque des opérations modifient le 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)

Gestion manuelle du cache

# Clear cache
ds.clear_cache()

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

Combiner des opérations SQL et pandas

DataStore gère intelligemment les opérations mêlant SQL et pandas :

Opérations compatibles avec SQL

Ces opérations sont compilées en SQL :

  • filter(), where()
  • select()
  • groupby(), agg()
  • sort(), orderby()
  • limit(), offset()
  • join(), union()
  • distinct()
  • Opérations sur les colonnes (calculs, comparaisons, méthodes sur les chaînes de caractères)

Opérations uniquement avec Pandas

Les opérations suivantes déclenchent l’exécution et utilisent Pandas :

  • apply() avec des fonctions personnalisées
  • pivot_table() avec des agrégations complexes
  • stack(), unstack()
  • Opérations sur des DataFrames déjà exécutés

Pipelines hybrides

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

Sélection du moteur d’exécution

DataStore peut exécuter des opérations à l’aide de différents moteurs :

Mode automatique (par défaut)

from chdb.datastore.config import config

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

Forcer le moteur chDB

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

Forcer l’utilisation du moteur pandas

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

Consultez Configuration : moteur d’exécution pour en savoir plus.


Impact sur les performances

Bien : filtrer en amont

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

Mauvais : filtrer trop tard

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

Bon réflexe : sélectionnez les colonnes dès le départ

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

Bon : laissez SQL faire le travail

# 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

Résumé des bonnes pratiques

  1. Chaînez les opérations avant d'exécuter - Construisez la requête complète, puis déclenchez l'exécution une seule fois
  2. Filtrez le plus tôt possible - Réduisez les données à la source
  3. Sélectionnez uniquement les colonnes nécessaires - L'élagage des colonnes améliore les performances
  4. Utilisez explain() pour comprendre l'exécution - Effectuez le débogage avant de lancer l'exécution
  5. Laissez SQL gérer les agrégations - ClickHouse est optimisé pour cela
  6. Soyez attentif aux déclencheurs d'exécution - Évitez tout déclenchement accidentel et prématuré
  7. Utilisez la mise en cache avec discernement - Sachez quand le cache est invalidé
Navigation