Introduction
Un data lake est un stockage centralisé de données brutes à grande échelle, suivant le principe schema-on-read : les données arrivent sans schéma imposé, et sont interprétées lors de la lecture. Contrairement aux data warehouses structurés (schema-on-write), il gère volumes massifs de données hétérogènes : logs JSON, images, CSV, vidéos. En 2026, avec l'explosion des données IA (LLM fine-tuning, RAG), les data lakes alimentent les pipelines ML en données historiques riches.
Ce tutoriel intermediate vous guide pour un data lake local scalable avec MinIO (API S3 100% compatible), Python pour ingestion partitionnée (Parquet optimisé), et DuckDB pour queries SQL vectorielles. Analogie : comme un lac naturel où l'eau (données) s'accumule librement, filtrable à la demande. À la fin, setup fonctionnel, extensible à AWS S3 ou Azure. Temps estimé : 30 min. Idéal pour bookmark par data engineers. (142 mots)
Prérequis
- Docker et Docker Compose installés (version 20+).
- Python 3.10+ avec
pip. - Accès internet pour images Docker et paquets PyPI.
- Connaissances de base en data engineering : Parquet, partitioning, S3-like storage.
pip install minio pandas pyarrow duckdb. Téléchargez mc (MinIO Client) via le code fourni.Démarrer MinIO avec Docker Compose
version: '3.8'
services:
minio:
image: minio/minio:latest
container_name: minio-server
ports:
- "9000:9000" # API S3
- "9001:9001" # Console UI
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
volumes:
- minio_data:/data
command: server /data --console-address ":9001"
volumes:
minio_data:
networks:
default:
name: datalake-netCe fichier Docker Compose lance MinIO en serveur S3-like avec persistance de données. Ports 9000 (API) et 9001 (UI web). Utilisez docker compose up -d pour démarrer. Accédez à l'UI : http://localhost:9001 (login: minioadmin/minioadmin). Piège : sans volume, données perdues au redémarrage.
Accéder à l'interface MinIO
Lancez docker compose up -d dans le dossier du fichier. Vérifiez les logs : docker logs minio-server. Ouvrez http://localhost:9001 pour l'UI intuitive : explorer buckets, monitorer usage. MinIO émule parfaitement AWS S3, idéal pour dev/test sans coûts cloud. Prochaine étape : client CLI mc pour automation.
Installer et configurer le client MinIO mc
#!/bin/bash
# Télécharger mc (Linux/macOS, adaptez pour Windows)
curl -O https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin/mc
# Configurer alias pour notre MinIO local
mc alias set myminio http://localhost:9000 minioadmin minioadmin
# Vérifier connexion
echo "Buckets disponibles :"
mc ls myminio
# Nettoyage optionnel : rm mc si pas sudoCe script installe mc (MinIO Client, comme AWS CLI pour S3) et crée un alias 'myminio' pointant vers votre instance locale. Exécutez bash setup-mc.sh. Vérifiez : mc admin info myminio. Piège : firewall bloque port 9000 → autorisez-le. mc est essentiel pour scripting buckets/policies.
Créer le bucket Data Lake
Buckets = conteneurs logiques dans S3/MinIO, comme répertoires racine. Pour data lake, un bucket 'datalake' avec policy read publique (local only). Cela permet queries directes sans auth lourde. Utilisez mc pour idempotence : relancez sans erreur.
Créer bucket et policy de lecture
#!/bin/bash
BUCKET=datalake
# Créer bucket si inexistant
mc mb myminio/$BUCKET
# Policy JSON pour read public (local dev only)
cat > public-read.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::$BUCKET/*"]
}
]
}
EOF
mc policy add public-read myminio/$BUCKET public-read.json
# Vérifier
echo "Policy :"
mc policy get-json myminio/$BUCKET
mc ls myminioCrée bucket 'datalake' et applique policy JSON autorisant lectures anonymes (sûr en local). Exécutez bash create-bucket.sh. Vérifiez UI MinIO. Piège : policy malformée → JSON valide requis ; en prod, utilisez IAM fines. Cela rend le lake queryable sans creds.
Ingérer des données partitionnées
Simulons données de ventes : 1000 lignes (id, date, produit, montant, région). Partitioning par year/month optimise queries (pruning). Convertir CSV → Parquet (colonne, compressé, schema embedded). MinIO lib Python pour upload atomic.
Script Python : Générer et uploader en Parquet
import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq
from minio import Minio
from io import BytesIO
import numpy as np
from datetime import datetime, timedelta
client = Minio(
"localhost:9000",
access_key="minioadmin",
secret_key="minioadmin",
secure=False
)
# Générer data sample
np.random.seed(42)
dates = pd.date_range('2023-01-01', '2023-12-31', freq='H')[:1000]
data = pd.DataFrame({
'id': range(1000),
'date': dates,
'product': np.random.choice(['Laptop', 'Phone', 'Tablet'], 1000),
'amount': np.random.uniform(100, 1000, 1000),
'region': np.random.choice(['EU', 'US', 'ASIA'], 1000)
})
# Partitionner par year/month
data['year'] = data['date'].dt.year.astype(str)
data['month'] = data['date'].dt.month.astype(str).str.zfill(2)
for (year, month), group in data.groupby(['year', 'month']):
table = pa.Table.from_pandas(group)
buffer = BytesIO()
pq.write_table(table, buffer)
buffer.seek(0)
client.put_object(
"datalake",
f"sales/year={year}/month={month}/sales.parquet",
io.BytesIO(buffer.read()),
length=buffer.tell(),
content_type="application/parquet"
)
print(f"Uploadé : year={year}/month={month}")
print("Ingestion terminée. Vérifiez UI MinIO.")Génère dataset ventes partitionné, convertit en Parquet (efficace x10 vs CSV), upload via MinIO SDK. Partition year/month=pruning auto. Exécutez python ingest-data.py. Piège : sans pyarrow, schema perdu ; buffer pour in-memory write. Scalable à TB avec Dask.
Interroger le Data Lake avec SQL
DuckDB : OLAP engine embarqué, lit Parquet direct de S3 sans ETL. Configurez endpoint/credentials pour MinIO. Queries pushdown : filters partition prunés, agrégations vectorisées. Analogie : SQL sur fichiers comme sur DB.
Script Python : Query SQL avec DuckDB
import duckdb
con = duckdb.connect()
# Installer/charger extension S3
con.execute("INSTALL httpfs; LOAD httpfs;")
# Config MinIO comme S3 (no secure)
con.execute """
SET s3_endpoint='localhost:9000';
SET s3_access_key='minioadmin';
SET s3_secret_key='minioadmin';
SET s3_region='us-east-1';
"""
# Query partitionnée : ventes EU 2023
result = con.execute(
"""
SELECT
product,
AVG(amount) as avg_amount,
COUNT(*) as sales_count
FROM 's3://datalake/sales/year=2023/*/*.parquet'
WHERE region = 'EU'
GROUP BY product
ORDER BY avg_amount DESC
"""
).fetchdf()
print(result)
# Export optionnel
result.to_csv('query-result.csv', index=False)
print("Résultat exporté en CSV.")Configure DuckDB pour MinIO/S3, query SQL sur Parquet partitionné (pruning year=2023). Agrégations pushdown ultra-rapides. Exécutez python query-lake.py. Piège : oublie LOAD httpfs → erreur endpoint ; policy read requise. Scalable à PB avec workers.
Ajouter des données incrémentales
Data lakes évoluent : append sans downtime. Réutilisez ingest pour nouveaux lots (schema evolution via Parquet metadata). Vérifiez atomicité.
Script append données 2024
import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq
from minio import Minio
from io import BytesIO
import numpy as np
from datetime import datetime
client = Minio("localhost:9000", access_key="minioadmin", secret_key="minioadmin", secure=False)
# Nouvelles data 2024 (évolue : ajoute 'category')
np.random.seed(123)
dates = pd.date_range('2024-01-01', '2024-03-31', freq='H')[:500]
data_new = pd.DataFrame({
'id': range(1000, 1500),
'date': dates,
'product': np.random.choice(['Laptop Pro', 'Phone Ultra'], 500),
'amount': np.random.uniform(500, 2000, 500),
'region': np.random.choice(['EU', 'US'], 500),
'category': np.random.choice(['Electronics', 'Mobile'], 500) # Schema evolution
})
data_new['year'] = data_new['date'].dt.year.astype(str)
data_new['month'] = data_new['date'].dt.month.astype(str).str.zfill(2)
for (year, month), group in data_new.groupby(['year', 'month']):
table = pa.Table.from_pandas(group)
buffer = BytesIO()
pq.write_table(table, buffer)
buffer.seek(0)
client.put_object(
"datalake",
f"sales/year={year}/month={month}/sales.parquet",
io.BytesIO(buffer.read()),
length=buffer.tell(),
content_type="application/parquet"
)
print(f"Appended : year={year}/month={month}")
print("Append terminé. Re-query pour vérifier.")Appende data 2024 avec nouvelle colonne 'category' (evolution schema tolérée). Même pattern partitioning. Exécutez après ingest initial. Piège : overwrite accidentel → utilisez noms fichiers timestamped en prod. DuckDB lit auto nouvelles cols.
Bonnes pratiques
- Partitionnez toujours : year/month/day pour pruning (gain x100 perf).
- Utilisez table formats : Migrez vers Iceberg/Delta pour ACID, time-travel (vs Parquet pur).
- Catalog central : Intégrez Hive Metastore ou AWS Glue pour métadonnées.
- Data quality : Validez schemas/NULLs à l'ingestion (Great Expectations).
- Sécurité : Policies least-privilege, encryption SSE-S3 ; pas public en prod.
- Monitoring : Prometheus + MinIO metrics pour usage/throughput.
Erreurs courantes à éviter
- Data swamp : Sans gouvernance/catalog, données ingérables → imposez tagging/zones (raw/curated).
- Oubli partitioning/compression : Scans full → coûts/query lents ; toujours Snappy/Zstd.
- Schema-on-write forcé : Tue flexibilité lake → laissez schema-on-read, fixez à consommation.
- Pas de versioning : Perte audits → Iceberg snapshots ou S3 versioning.
- Ignorer local vs prod : Policy public OK dev, mais VPC/ IAM en cloud.
Pour aller plus loin
- Production : Migrez vers AWS S3 + Athena + Glue Catalog (remplacez endpoint).
- Avancé : Apache Iceberg avec Spark/Trino pour tables transactionnelles.
- Outils : Airflow pour orchestration, dbt pour transformations.
- Ressources : Doc MinIO, DuckDB S3.