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 executionAvantages de l’évaluation paresseuse
- Optimisation des requêtes : Plusieurs opérations sont compilées en une seule requête SQL optimisée
- Pushdown des filtres : Les filtres sont appliqués au niveau de la source de données
- Élagage des colonnes : Seules les colonnes nécessaires sont lues
- Décisions différées : Le moteur d’exécution peut être choisi au moment de l’exécution
- 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 DataFrameOpé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 LazyGroupByExé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 queryPhase 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 nowPhase 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é :
ds = pd.read_csv("sales.csv")
query = (ds
.filter(ds['amount'] > 1000)
.groupby('region')
.agg({'amount': ['sum', 'mean']})
)
# View execution plan
query.explain()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 regionUtilisez 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 resultInvalidation 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éespivot_table()avec des agrégations complexesstack(),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 operationForcer le moteur chDB
config.set_execution_engine('chdb')
# All operations use ClickHouse SQLForcer l’utilisation du moteur pandas
config.set_execution_engine('pandas')
# All operations use pandasConsultez 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 oneRésumé des bonnes pratiques
- Chaînez les opérations avant d'exécuter - Construisez la requête complète, puis déclenchez l'exécution une seule fois
- Filtrez le plus tôt possible - Réduisez les données à la source
- Sélectionnez uniquement les colonnes nécessaires - L'élagage des colonnes améliore les performances
- Utilisez
explain()pour comprendre l'exécution - Effectuez le débogage avant de lancer l'exécution - Laissez SQL gérer les agrégations - ClickHouse est optimisé pour cela
- Soyez attentif aux déclencheurs d'exécution - Évitez tout déclenchement accidentel et prématuré
- Utilisez la mise en cache avec discernement - Sachez quand le cache est invalidé