🟩 Niveau 2 : Intermédiaire
Cloud & Object Storage for Data Engineers
Bienvenue dans ce module où tu vas maîtriser le Cloud Computing et l'Object Storage, les fondations de tout Data Lake moderne.
Prérequis
| Niveau | Module | Compétence |
|---|---|---|
| ✅ Requis | Module 14 | Docker Fundamentals |
| ✅ Requis | Module 19 | PySpark Advanced |
| ✅ Requis | Module 21 | Spark on Kubernetes |
| 💡 Recommandé | Module 08 | Big Data Introduction (Medallion Architecture) |
Objectifs du module
À la fin de ce module, tu seras capable de :
- Comprendre les modèles Cloud (IaaS, PaaS, SaaS)
- Connaître les services Data Engineering sur AWS, GCP, Azure
- Maîtriser les concepts de l'Object Storage (buckets, keys, prefixes)
- Lire/écrire sur S3, Azure Blob, GCS avec Python et Spark
- Déployer MinIO pour pratiquer localement
- Optimiser les performances (formats, partitionnement, small files)
- Gérer la sécurité et les coûts
1. Introduction — Pourquoi le Cloud pour le Data Engineering ?
1.1 L'évolution du Data Engineering
2000s 2010s 2020s+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ On-Premise │ │ Hadoop │ │ Cloud │
│ ┌───────┐ │ │ ┌───────┐ │ │ ┌───────┐ │
│ │ RDBMS │ │ ────▶ │ │ HDFS │ │ ────▶ │ │ S3 │ │
│ └───────┘ │ │ └───────┘ │ │ └───────┘ │
│ Coûteux │ │ Complexe │ │ Scalable │
└─────────────┘ └─────────────┘ └─────────────┘
1.2 Avantages du Cloud pour le Data Engineering
| Avantage | Description |
|---|---|
| Scalabilité | Stockage et compute illimités à la demande |
| Coût | Pay-as-you-go, pas d'investissement initial |
| Managed Services | Moins d'ops, plus de focus sur les données |
| Séparation compute/storage | Scaler indépendamment |
| Durabilité | 99.999999999% (11 nines) pour S3 |
| Global | Données accessibles partout |
2. Introduction au Cloud Computing
2.1 C'est quoi le Cloud ?
Le Cloud = des serveurs, du stockage et des services accessibles via Internet, gérés par un provider.
┌─────────────────────────────────────────────────────────────┐
│ LE CLOUD │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Compute │ │ Storage │ │ Network │ │ .... │ │
│ │ (VMs) │ │ (Disks) │ │ (VPC) │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Tu ne gères pas le hardware, juste les services │
└─────────────────────────────────────────────────────────────┘
▲
│ Internet
│
┌─────────┴─────────┐
│ Ton application │
└───────────────────┘
2.2 Les 3 modèles de service
┌────────────────────────────────────────────────────────────────┐
│ RESPONSABILITÉ │
│ │
│ On-Premise IaaS PaaS SaaS │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Application│ │Application│ │Application│ │██████████│ ◀── Provider │
│ │──────────│ │──────────│ │██████████│ │██████████│ │
│ │ Data │ │ Data │ │██████████│ │██████████│ │
│ │──────────│ │──────────│ │██████████│ │██████████│ │
│ │ Runtime │ │ Runtime │ │██████████│ │██████████│ │
│ │──────────│ │──────────│ │██████████│ │██████████│ │
│ │ OS │ │ OS │ │██████████│ │██████████│ │
│ │──────────│ │██████████│ │██████████│ │██████████│ │
│ │ Virtual │ │██████████│ │██████████│ │██████████│ │
│ │──────────│ │██████████│ │██████████│ │██████████│ │
│ │ Servers │ │██████████│ │██████████│ │██████████│ │
│ │──────────│ │██████████│ │██████████│ │██████████│ │
│ │ Storage │ │██████████│ │██████████│ │██████████│ │
│ │──────────│ │██████████│ │██████████│ │██████████│ │
│ │ Network │ │██████████│ │██████████│ │██████████│ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ █ = Géré par le Cloud Provider │
└────────────────────────────────────────────────────────────────┘
| Modèle | Description | Exemples | Tu gères |
|---|---|---|---|
| IaaS | Infrastructure as a Service | EC2, VMs, VPC | OS, Runtime, App |
| PaaS | Platform as a Service | RDS, Cloud SQL, EKS | App, Data |
| SaaS | Software as a Service | Snowflake, Databricks | Rien (juste utiliser) |
2.3 Les 3 grands Cloud Providers
| Provider | Part de marché | Forces | Faiblesse |
|---|---|---|---|
| AWS | ~32% | Leader, plus de services, maturité | Complexité, coûts |
| Azure | ~23% | Intégration Microsoft, entreprises | UX parfois confuse |
| GCP | ~10% | BigQuery, ML/AI, Kubernetes | Moins de services |
2.4 Régions & Zones de disponibilité
┌─────────────────────────────────────────────────────────────┐
│ AWS Global │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Region: │ │ Region: │ │
│ │ eu-west-1 │ │ us-east-1 │ │
│ │ (Ireland) │ │ (N. Virginia) │ │
│ │ │ │ │ │
│ │ ┌───┐ ┌───┐ ┌───┐│ │ ┌───┐ ┌───┐ ┌───┐│ │
│ │ │AZ1│ │AZ2│ │AZ3││ │ │AZ1│ │AZ2│ │AZ3││ │
│ │ └───┘ └───┘ └───┘│ │ └───┘ └───┘ └───┘│ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ AZ = Availability Zone = Data Center isolé │
└─────────────────────────────────────────────────────────────┘
- Region : Zone géographique (Paris, Dublin, N. Virginia)
- Availability Zone : Data center isolé dans une région
- Latence : Choisir la région proche des utilisateurs
- Compliance : GDPR → données en Europe
Exercice 1 : Identifier IaaS / PaaS / SaaS
Classe ces services dans la bonne catégorie :
| Service | IaaS / PaaS / SaaS ? |
|---|---|
| Amazon EC2 | ? |
| Google BigQuery | ? |
| Snowflake | ? |
| Azure Kubernetes Service | ? |
| Amazon S3 | ? |
| Databricks | ? |
💡 Voir les réponses
| Service | Catégorie | Explication |
|---|---|---|
| Amazon EC2 | IaaS | Tu gères l'OS et tout ce qu'il y a dessus |
| Google BigQuery | PaaS/SaaS | Serverless, tu gères juste les requêtes |
| Snowflake | SaaS | Entièrement managé |
| Azure Kubernetes Service | PaaS | K8s managé, tu gères les workloads |
| Amazon S3 | PaaS | Stockage managé, tu gères les données |
| Databricks | PaaS/SaaS | Spark managé |
3. Services Cloud pour le Data Engineering
3.1 Tableau comparatif complet
| Catégorie | AWS | GCP | Azure |
|---|---|---|---|
| Object Storage | S3 | GCS | Blob Storage |
| Data Warehouse | Redshift | BigQuery | Synapse Analytics |
| ETL Serverless | Glue | Dataflow | Data Factory |
| Query Engine | Athena | BigQuery | Synapse Serverless |
| Orchestration | MWAA (Airflow) | Composer (Airflow) | Data Factory |
| Streaming | Kinesis | Pub/Sub + Dataflow | Event Hubs |
| Catalog | Glue Catalog | Data Catalog | Purview |
| Kubernetes | EKS | GKE | AKS |
| Serverless Compute | Lambda | Cloud Functions | Functions |
| NoSQL | DynamoDB | Firestore/Bigtable | CosmosDB |
| Message Queue | SQS/SNS | Pub/Sub | Service Bus |
3.2 Focus sur les services Data Engineering
┌─────────────────────────────────────────────────────────────────┐
│ ARCHITECTURE DATA CLOUD │
│ │
│ Sources Ingestion Storage │
│ ┌─────┐ ┌─────────┐ ┌─────────┐ │
│ │ API │────────────▶│ Kinesis │────────▶│ S3 │ │
│ │ DB │ │ Pub/Sub │ │ GCS │ │
│ │ Files│ │ EventHub│ │ Blob │ │
│ └─────┘ └─────────┘ └────┬────┘ │
│ │ │
│ ▼ │
│ Processing ┌─────────┐ │
│ ┌──────────────────────────────────────▶│ Glue │ │
│ │ │Dataflow │ │
│ │ │ ADF │ │
│ │ └────┬────┘ │
│ │ │ │
│ │ ▼ │
│ │ Serving ┌─────────┐ │
│ │ ┌─────────┐ │Redshift │ │
│ └──│ Athena │◀─────────────────────────│BigQuery │ │
│ │ Synapse │ │ Synapse │ │
│ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────────┘
4. Storage Models : Block vs File vs Object
4.1 Comparaison détaillée
| Critère | Block Storage | File Storage | Object Storage |
|---|---|---|---|
| Structure | Blocs bruts | Hiérarchie fichiers | Clé-valeur plat |
| Accès | Bas niveau (disque) | POSIX (NFS, SMB) | API HTTP (REST) |
| Metadata | Minimales | Basiques | Riches, custom |
| Scalabilité | Limitée (TB) | Limitée (TB) | Illimitée (PB+) |
| Performance | Très haute | Moyenne | Variable |
| Coût | Élevé | Moyen | Faible |
| Use case | Bases de données | Partage fichiers | Data Lakes |
| Exemples | EBS, Azure Disk | EFS, Azure Files | S3, GCS, Blob |
4.2 Pourquoi Object Storage pour les Data Lakes ?
✅ Scalabilité illimitée ✅ Coût faible
✅ Durabilité 11 nines ✅ API HTTP standard
✅ Séparation compute/storage ✅ Metadata riches
✅ Formats natifs (Parquet) ✅ Multi-cloud possible
4.3 Séparation Compute / Storage
AVANT (Hadoop/HDFS) APRÈS (Cloud/Object Storage)
┌─────────────────────┐ ┌─────────────────────┐
│ Node 1 │ │ Compute (Spark) │
│ ┌───────┐ ┌─────┐ │ │ ┌───────────┐ │
│ │Compute│ │Data │ │ │ │ Executor │ │
│ └───────┘ └─────┘ │ │ └─────┬─────┘ │
│ Couplés ! │ │ │ │
└─────────────────────┘ └──────────┼───────────┘
│ Network
▼
┌─────────────────────┐
│ Storage (S3) │
│ ┌─────────┐ │
│ │ Data │ │
│ └─────────┘ │
│ Scale séparément │
└─────────────────────┘
Exercice 2 : Choisir le bon type de storage
Quel type de storage pour chaque use case ?
| Use case | Block / File / Object ? |
|---|---|
| Base de données PostgreSQL | ? |
| Data Lake avec fichiers Parquet | ? |
| Partage de documents entre équipes | ? |
| Logs d'application (PB de données) | ? |
| VM avec OS Windows | ? |
💡 Voir les réponses
| Use case | Type | Raison |
|---|---|---|
| PostgreSQL | Block | IOPS élevés, accès bas niveau |
| Data Lake Parquet | Object | Scalable, coût faible |
| Partage documents | File | Accès POSIX, permissions |
| Logs application | Object | Volume énorme, coût faible |
| VM Windows | Block | Disque système |
5. Object Storage — Concepts fondamentaux
5.1 Buckets, Keys, Prefixes
s3://my-bucket/bronze/2024/01/transactions.parquet
└───┬───┘ └──────────┬─────────┘ └─────┬──────┘
Bucket Prefix Object Key
⚠️ IMPORTANT : Les préfixes NE SONT PAS des dossiers !
C'est juste une convention de nommage (clé-valeur plat)
| Concept | Description | Exemple |
|---|---|---|
| Bucket | Conteneur racine, nom unique global | my-company-datalake |
| Key | Identifiant unique de l'objet | bronze/2024/01/data.parquet |
| Prefix | "Faux dossier", filtre de listing | bronze/2024/ |
| Object | Le fichier + ses metadata | Parquet, CSV, JSON... |
5.2 Metadata & Tags
Chaque objet peut avoir des metadata custom :
pythonVoir le code
# Exemple de metadata sur un objet S3
metadata_example = {
# Metadata système (automatiques)
"Content-Type": "application/octet-stream",
"Content-Length": 1048576,
"Last-Modified": "2024-01-15T10:30:00Z",
"ETag": "d41d8cd98f00b204e9800998ecf8427e",
# Metadata custom (x-amz-meta-*)
"x-amz-meta-source": "kafka-topic-orders",
"x-amz-meta-pipeline": "etl-daily",
"x-amz-meta-schema-version": "2.1",
}
# Tags (pour billing, governance)
tags = {
"Environment": "production",
"Team": "data-engineering",
"CostCenter": "DE-001",
}
print("Metadata et Tags permettent de :")
print("- Tracer l'origine des données")
print("- Filtrer pour la gouvernance")
print("- Allouer les coûts par équipe")5.3 Versioning & Lifecycle Policies
Versioning : Garder plusieurs versions d'un même objet
s3://bucket/data.csv
│
├── Version 1 (2024-01-01) ← Ancienne
├── Version 2 (2024-01-15) ← Ancienne
└── Version 3 (2024-02-01) ← Current
Lifecycle Policies : Automatiser la gestion du stockage
┌─────────────┐ 30 jours ┌─────────────┐ 90 jours ┌─────────────┐
│ Standard │ ─────────────▶ │ Infrequent │ ────────────▶ │ Glacier │
│ $0.023/GB │ │ $0.0125/GB│ │ $0.004/GB │
└─────────────┘ └─────────────┘ └─────────────┘
Hot Cool Archive
5.4 Classes de stockage
| Classe | Usage | Latence | Coût stockage |
|---|---|---|---|
| Standard | Accès fréquent | ms | $0.023/GB |
| IA (Infrequent Access) | Accès rare | ms | $0.0125/GB |
| Glacier | Archivage | minutes-heures | $0.004/GB |
| Glacier Deep Archive | Compliance | heures | $0.00099/GB |
5.5 Protocoles & Drivers
🔥 Section essentielle — Source de confusion fréquente !
| Protocole | Cloud | Usage | Driver/SDK | Exemple |
|---|---|---|---|---|
s3:// |
AWS | CLI, Python | boto3, aws cli | s3://bucket/key |
s3a:// |
AWS | Spark/Hadoop | hadoop-aws | s3a://bucket/key |
abfs:// |
Azure | Spark (legacy) | hadoop-azure | abfs://container@account.dfs.core.windows.net/ |
abfss:// |
Azure | Spark (TLS) | hadoop-azure | abfss://container@account.dfs.core.windows.net/ |
gs:// |
GCP | Spark, CLI | gcs-connector | gs://bucket/key |
https:// |
Tous | Direct HTTP | requests | Signed URLs |
⚠️ ATTENTION :
s3:// ≠ s3a://
- s3:// → AWS CLI, boto3 (haut niveau)
- s3a:// → Hadoop/Spark (bas niveau, optimisé Big Data)
Dans Spark, TOUJOURS utiliser s3a://, abfss://, ou gs://
5.6 Metadata Catalogs — Transition vers le module 23
L'Object Storage stocke des fichiers bruts. Mais pour faire du SQL, on a besoin de :
- Schéma des tables (colonnes, types)
- Localisation des partitions
- Statistiques pour l'optimiseur
┌─────────────────────────────────────────────────────────────────┐
│ METADATA CATALOG │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Table: sales │ │
│ │ Location: s3://bucket/silver/sales/ │ │
│ │ Schema: id INT, amount DOUBLE, date DATE │ │
│ │ Partitions: date=2024-01-01, date=2024-01-02, ... │ │
│ └─────────────────────────────────────────────────────────┘ │
└───────────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ OBJECT STORAGE (S3) │
│ s3://bucket/silver/sales/date=2024-01-01/part-00000.parquet │
│ s3://bucket/silver/sales/date=2024-01-02/part-00000.parquet │
└─────────────────────────────────────────────────────────────────┘
Solutions Catalog :
| Solution | Type | Utilisé par |
|---|---|---|
| Hive Metastore | Open-source | Spark, Presto, Trino |
| AWS Glue Catalog | Managed | Athena, Glue, EMR |
| GCP Data Catalog | Managed | BigQuery, Dataproc |
| Azure Purview | Managed | Synapse, Databricks |
💡 Preview Module 23 : Delta Lake et Iceberg intègrent leur propre Transaction Log directement dans l'Object Storage. Plus besoin de catalogue externe !
5.7 Layout Data Lake — Rappel (réf module 08)
s3://my-datalake/
│
├── bronze/ ← Raw, immutable, source of truth
│ ├── orders/
│ │ └── 2024/01/01/
│ │ └── orders_raw.json
│ └── customers/
│ └── customers_full.csv
│
├── silver/ ← Cleaned, validated, deduplicated
│ ├── orders/
│ │ └── date=2024-01-01/
│ │ └── part-00000.parquet
│ └── customers/
│ └── part-00000.parquet
│
└── gold/ ← Aggregated, business-ready
├── daily_sales/
│ └── part-00000.parquet
└── customer_360/
└── part-00000.parquet
Exercice 3 : Calculer le coût de stockage
Scénario : Tu as un Data Lake avec :
- Bronze : 500 GB (accès rare)
- Silver : 200 GB (accès fréquent)
- Gold : 50 GB (accès très fréquent)
Calcule le coût mensuel optimal sur S3.
💡 Voir la solution
| Layer | Taille | Classe | Prix/GB | Coût |
|---|---|---|---|---|
| Bronze | 500 GB | S3 IA | $0.0125 | $6.25 |
| Silver | 200 GB | Standard | $0.023 | $4.60 |
| Gold | 50 GB | Standard | $0.023 | $1.15 |
| Total | 750 GB | $12.00/mois |
Sans optimisation (tout en Standard) : 750 × $0.023 = $17.25/mois
Économie : 30% !
6. AWS S3 — Deep Dive
6.1 Concepts & Classes de stockage
| Classe | Durabilité | Disponibilité | Min storage | Retrieval |
|---|---|---|---|---|
| Standard | 11 nines | 99.99% | - | Immédiat |
| Intelligent-Tiering | 11 nines | 99.9% | - | Immédiat |
| Standard-IA | 11 nines | 99.9% | 30 jours | Immédiat |
| Glacier Instant | 11 nines | 99.9% | 90 jours | ms |
| Glacier Flexible | 11 nines | 99.99% | 90 jours | 1-12h |
| Glacier Deep Archive | 11 nines | 99.99% | 180 jours | 12-48h |
6.2 Opérations CLI
pythonVoir le code
s3_cli_commands = """
# Lister les buckets
aws s3 ls
# Lister le contenu d'un bucket
aws s3 ls s3://my-bucket/bronze/
# Copier un fichier local vers S3
aws s3 cp data.csv s3://my-bucket/bronze/data.csv
# Copier un fichier S3 vers local
aws s3 cp s3://my-bucket/bronze/data.csv ./data.csv
# Synchroniser un dossier
aws s3 sync ./local-folder/ s3://my-bucket/bronze/
# Supprimer un fichier
aws s3 rm s3://my-bucket/bronze/old-data.csv
# Supprimer récursivement
aws s3 rm s3://my-bucket/temp/ --recursive
"""
print(s3_cli_commands)6.3 Python avec boto3
pythonVoir le code
# Installation : pip install boto3
import boto3
from botocore.exceptions import ClientError
# Créer un client S3
s3 = boto3.client('s3')
# --- Upload ---
def upload_file(file_path, bucket, key):
"""Upload un fichier vers S3."""
s3.upload_file(file_path, bucket, key)
print(f"✅ Uploaded {file_path} to s3://{bucket}/{key}")
# --- Download ---
def download_file(bucket, key, file_path):
"""Download un fichier depuis S3."""
s3.download_file(bucket, key, file_path)
print(f"✅ Downloaded s3://{bucket}/{key} to {file_path}")
# --- List objects ---
def list_objects(bucket, prefix=""):
"""Liste les objets dans un bucket."""
response = s3.list_objects_v2(Bucket=bucket, Prefix=prefix)
if 'Contents' in response:
for obj in response['Contents']:
print(f" {obj['Key']} ({obj['Size']} bytes)")
return response.get('Contents', [])
# --- Presigned URL ---
def generate_presigned_url(bucket, key, expiration=3600):
"""Génère une URL temporaire pour accès direct."""
url = s3.generate_presigned_url(
'get_object',
Params={'Bucket': bucket, 'Key': key},
ExpiresIn=expiration
)
return url
# Exemple d'utilisation (commenté car pas de credentials)
# upload_file('data.csv', 'my-bucket', 'bronze/data.csv')
# list_objects('my-bucket', 'bronze/')
print("📝 Fonctions boto3 définies (upload, download, list, presigned URL)")6.4 S3 avec Spark (s3a://)
pythonVoir le code
# Configuration Spark pour S3
spark_s3_config = """
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.appName("S3 Access") \
.config("spark.hadoop.fs.s3a.impl", "org.apache.hadoop.fs.s3a.S3AFileSystem") \
.config("spark.hadoop.fs.s3a.access.key", "YOUR_ACCESS_KEY") \
.config("spark.hadoop.fs.s3a.secret.key", "YOUR_SECRET_KEY") \
.config("spark.hadoop.fs.s3a.endpoint", "s3.amazonaws.com") \
.getOrCreate()
# Lire depuis S3
df = spark.read.parquet("s3a://my-bucket/silver/sales/")
# Écrire vers S3
df.write.mode("overwrite").parquet("s3a://my-bucket/gold/aggregates/")
"""
# Optimisations S3A
s3a_optimizations = """
# Performance optimizations
spark.hadoop.fs.s3a.connection.maximum=200
spark.hadoop.fs.s3a.threads.max=64
spark.hadoop.fs.s3a.fast.upload=true
spark.hadoop.fs.s3a.fast.upload.buffer=bytebuffer
spark.hadoop.fs.s3a.multipart.size=104857600 # 100MB
"""
print(spark_s3_config)
print("\n--- Optimisations S3A ---")
print(s3a_optimizations)6.5 Authentification & IAM
| Méthode | Sécurité | Usage | Production ? |
|---|---|---|---|
| Access Keys | ⚠️ Faible | Dev local | ❌ Non |
| Instance Profile | ✅ Haute | EC2 | ✅ Oui |
| IRSA (IAM Roles for Service Accounts) | ✅ Haute | EKS/K8s | ✅ Oui |
| AssumeRole | ✅ Haute | Cross-account | ✅ Oui |
🔐 Best Practice : JAMAIS de credentials dans le code !
Utiliser :
- Variables d'environnement
- Instance Profiles (EC2)
- IRSA (Kubernetes) ← Module 21
- AWS Secrets Manager
Exercice 4 : Upload/Download avec boto3
Écris un script Python qui :
- Crée un fichier CSV local avec 3 lignes
- L'upload vers S3 (ou MinIO)
- Liste les fichiers du bucket
- Télécharge le fichier sous un autre nom
💡 Voir la solution
import boto3
import csv
# 1. Créer un fichier CSV
with open('test_data.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['id', 'name', 'amount'])
writer.writerow([1, 'Alice', 100])
writer.writerow([2, 'Bob', 200])
writer.writerow([3, 'Charlie', 150])
# 2. Upload
s3 = boto3.client('s3')
s3.upload_file('test_data.csv', 'my-bucket', 'bronze/test_data.csv')
# 3. List
response = s3.list_objects_v2(Bucket='my-bucket', Prefix='bronze/')
for obj in response.get('Contents', []):
print(obj['Key'])
# 4. Download
s3.download_file('my-bucket', 'bronze/test_data.csv', 'downloaded_data.csv')
7. Azure Blob Storage — Deep Dive
7.1 Concepts
┌─────────────────────────────────────────────────────────────┐
│ AZURE STORAGE ACCOUNT │
│ (mystorageaccount) │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Container: │ │ Container: │ │
│ │ bronze │ │ silver │ │
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │
│ │ │ data.csv │ │ │ │ data.parq │ │ │
│ │ └───────────┘ │ │ └───────────┘ │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
| Concept Azure | Équivalent S3 |
|---|---|
| Storage Account | (pas d'équivalent, niveau compte) |
| Container | Bucket |
| Blob | Object |
| ADLS Gen2 | S3 + Glue Catalog intégré |
7.2 Access Tiers
| Tier | Usage | Coût stockage | Coût accès |
|---|---|---|---|
| Hot | Fréquent | Élevé | Faible |
| Cool | Rare (30+ jours) | Moyen | Moyen |
| Archive | Archivage (180+ jours) | Très faible | Élevé |
pythonVoir le code
# Installation : pip install azure-storage-blob
azure_blob_example = """
from azure.storage.blob import BlobServiceClient, BlobClient
# Connection string (dev only !)
connection_string = "DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net"
# Créer le client
blob_service = BlobServiceClient.from_connection_string(connection_string)
# Upload
blob_client = blob_service.get_blob_client(container="bronze", blob="data.csv")
with open("data.csv", "rb") as f:
blob_client.upload_blob(f, overwrite=True)
# Download
with open("downloaded.csv", "wb") as f:
f.write(blob_client.download_blob().readall())
# List blobs
container_client = blob_service.get_container_client("bronze")
for blob in container_client.list_blobs():
print(blob.name)
"""
print(azure_blob_example)7.3 Azure Blob avec Spark (abfss://)
pythonVoir le code
spark_azure_config = """
# Configuration Spark pour Azure Blob (ADLS Gen2)
storage_account = "mystorageaccount"
container = "bronze"
# Méthode 1 : Access Key (dev only)
spark.conf.set(
f"fs.azure.account.key.{storage_account}.dfs.core.windows.net",
"YOUR_ACCESS_KEY"
)
# Méthode 2 : Service Principal (production)
spark.conf.set(f"fs.azure.account.auth.type.{storage_account}.dfs.core.windows.net", "OAuth")
spark.conf.set(f"fs.azure.account.oauth.provider.type.{storage_account}.dfs.core.windows.net",
"org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider")
spark.conf.set(f"fs.azure.account.oauth2.client.id.{storage_account}.dfs.core.windows.net", "CLIENT_ID")
spark.conf.set(f"fs.azure.account.oauth2.client.secret.{storage_account}.dfs.core.windows.net", "CLIENT_SECRET")
spark.conf.set(f"fs.azure.account.oauth2.client.endpoint.{storage_account}.dfs.core.windows.net",
"https://login.microsoftonline.com/TENANT_ID/oauth2/token")
# Lire
df = spark.read.parquet(f"abfss://{container}@{storage_account}.dfs.core.windows.net/data/")
"""
print(spark_azure_config)7.4 Authentification Azure
| Méthode | Sécurité | Usage | Production ? |
|---|---|---|---|
| Access Keys | ⚠️ Faible | Dev | ❌ Non |
| SAS Token | ⚠️ Moyenne | Temporaire, externe | ⚠️ Limité |
| Service Principal | ✅ Haute | Apps, CI/CD | ✅ Oui |
| Managed Identity | ✅ Très haute | VMs, AKS | ✅ Oui |
Exercice 5 : Générer un SAS Token
Un SAS Token permet de donner un accès temporaire à un blob.
💡 Voir la solution
from azure.storage.blob import BlobServiceClient, generate_blob_sas, BlobSasPermissions
from datetime import datetime, timedelta
account_name = "mystorageaccount"
account_key = "YOUR_KEY"
container = "bronze"
blob_name = "data.csv"
# Générer SAS Token (valide 1 heure)
sas_token = generate_blob_sas(
account_name=account_name,
container_name=container,
blob_name=blob_name,
account_key=account_key,
permission=BlobSasPermissions(read=True),
expiry=datetime.utcnow() + timedelta(hours=1)
)
# URL complète
url = f"https://{account_name}.blob.core.windows.net/{container}/{blob_name}?{sas_token}"
print(url)
8. Google Cloud Storage — Deep Dive
8.1 Concepts
| Classe | Usage | SLA | Coût |
|---|---|---|---|
| Standard | Fréquent | 99.99% | $0.020/GB |
| Nearline | 1x/mois | 99.9% | $0.010/GB |
| Coldline | 1x/trimestre | 99.9% | $0.004/GB |
| Archive | 1x/an | 99.9% | $0.0012/GB |
pythonVoir le code
gcs_cli_commands = """
# gsutil - CLI pour GCS
# Lister les buckets
gsutil ls
# Lister le contenu d'un bucket
gsutil ls gs://my-bucket/bronze/
# Copier
gsutil cp data.csv gs://my-bucket/bronze/
gsutil cp gs://my-bucket/bronze/data.csv ./
# Synchroniser (comme rsync)
gsutil rsync -r ./local/ gs://my-bucket/bronze/
# Copie parallèle (gros fichiers)
gsutil -m cp -r ./data/ gs://my-bucket/bronze/
"""
print(gcs_cli_commands)pythonVoir le code
# Installation : pip install google-cloud-storage
gcs_python_example = """
from google.cloud import storage
# Créer le client (utilise GOOGLE_APPLICATION_CREDENTIALS)
client = storage.Client()
# Accéder au bucket
bucket = client.bucket("my-bucket")
# Upload
blob = bucket.blob("bronze/data.csv")
blob.upload_from_filename("data.csv")
# Download
blob.download_to_filename("downloaded.csv")
# List blobs
blobs = bucket.list_blobs(prefix="bronze/")
for blob in blobs:
print(blob.name)
# Signed URL (temporaire)
url = blob.generate_signed_url(expiration=3600) # 1 heure
"""
print(gcs_python_example)8.3 GCS avec Spark (gs://)
pythonVoir le code
spark_gcs_config = """
# Configuration Spark pour GCS
# Option 1 : Service Account Key (dev)
spark.conf.set("spark.hadoop.google.cloud.auth.service.account.enable", "true")
spark.conf.set("spark.hadoop.google.cloud.auth.service.account.json.keyfile", "/path/to/keyfile.json")
# Option 2 : Application Default Credentials (GKE, Cloud Functions)
# Pas de config nécessaire si ADC est configuré
# Lire depuis GCS
df = spark.read.parquet("gs://my-bucket/silver/data/")
# Écrire vers GCS
df.write.mode("overwrite").parquet("gs://my-bucket/gold/aggregates/")
"""
print(spark_gcs_config)8.4 Authentification GCP
| Méthode | Sécurité | Usage | Production ? |
|---|---|---|---|
| Service Account Key | ⚠️ Moyenne | Dev, CI/CD | ⚠️ Avec précaution |
| Workload Identity | ✅ Haute | GKE | ✅ Oui |
| ADC (Application Default Credentials) | ✅ Haute | Cloud Functions, Cloud Run | ✅ Oui |
Exercice 6 : Lister et télécharger depuis GCS
Écris un script qui liste tous les fichiers .parquet dans un bucket et télécharge le premier.
💡 Voir la solution
from google.cloud import storage
client = storage.Client()
bucket = client.bucket("my-bucket")
# Lister les fichiers .parquet
parquet_files = []
for blob in bucket.list_blobs(prefix="silver/"):
if blob.name.endswith('.parquet'):
parquet_files.append(blob)
print(f"Found: {blob.name}")
# Télécharger le premier
if parquet_files:
first_file = parquet_files[0]
first_file.download_to_filename("downloaded.parquet")
print(f"Downloaded: {first_file.name}")
9. MinIO — Object Storage Local
9.1 Pourquoi MinIO ?
| Avantage | Description |
|---|---|
| 100% S3 compatible | Même API, même code boto3 |
| Gratuit | Open-source, pas de compte cloud |
| Local | Parfait pour dev/test |
| Léger | Docker, un seul binaire |
| Production-ready | Utilisé aussi en production (on-premise) |
💡 Le code écrit pour MinIO fonctionne sur S3 sans modification !
Il suffit de changer l'endpoint.
9.2 Installation avec Docker
writefileVoir le code
version: '3.8'
services:
minio:
image: minio/minio:latest
container_name: minio
ports:
- "9000:9000" # API S3
- "9001:9001" # Console Web
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
command: server /data --console-address ":9001"
volumes:
- minio-data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 10s
retries: 3
volumes:
minio-data:pythonVoir le code
minio_commands = """
# Démarrer MinIO
docker-compose up -d
# Accéder à la console web
# http://localhost:9001
# Login: minioadmin / minioadmin
# Installer mc (MinIO Client)
# Linux
wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin/
# Mac
brew install minio/stable/mc
# Configurer mc
mc alias set myminio http://localhost:9000 minioadmin minioadmin
# Créer des buckets
mc mb myminio/bronze
mc mb myminio/silver
mc mb myminio/gold
# Lister
mc ls myminio/
# Upload
mc cp data.csv myminio/bronze/
"""
print(minio_commands)9.4 Python avec MinIO (boto3)
pythonVoir le code
# Le même code boto3 fonctionne avec MinIO !
# Il suffit de spécifier endpoint_url
minio_boto3_example = """
import boto3
from botocore.client import Config
# Configuration pour MinIO
s3 = boto3.client(
's3',
endpoint_url='http://localhost:9000', # ← La seule différence !
aws_access_key_id='minioadmin',
aws_secret_access_key='minioadmin',
config=Config(signature_version='s3v4')
)
# Créer un bucket
s3.create_bucket(Bucket='bronze')
# Upload (identique à S3)
s3.upload_file('data.csv', 'bronze', 'raw/data.csv')
# List (identique à S3)
response = s3.list_objects_v2(Bucket='bronze')
for obj in response.get('Contents', []):
print(obj['Key'])
# Download (identique à S3)
s3.download_file('bronze', 'raw/data.csv', 'downloaded.csv')
"""
print(minio_boto3_example)
print("\n💡 Ce code fonctionne sur S3 en enlevant juste endpoint_url !")9.5 Spark avec MinIO
pythonVoir le code
spark_minio_config = """
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.appName("MinIO Access") \
.config("spark.hadoop.fs.s3a.impl", "org.apache.hadoop.fs.s3a.S3AFileSystem") \
.config("spark.hadoop.fs.s3a.endpoint", "http://localhost:9000") \
.config("spark.hadoop.fs.s3a.access.key", "minioadmin") \
.config("spark.hadoop.fs.s3a.secret.key", "minioadmin") \
.config("spark.hadoop.fs.s3a.path.style.access", "true") \
.getOrCreate()
# Lire depuis MinIO (même syntaxe que S3 !)
df = spark.read.csv("s3a://bronze/raw/data.csv", header=True)
# Écrire vers MinIO
df.write.mode("overwrite").parquet("s3a://silver/clean/data/")
"""
print(spark_minio_config)Exercice 7 : Déployer MinIO et créer un bucket
- Lance MinIO avec Docker
- Accède à la console (http://localhost:9001)
- Crée les buckets bronze, silver, gold
- Upload un fichier via la console
# Solution rapide
docker run -d --name minio \
-p 9000:9000 -p 9001:9001 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
minio/minio server /data --console-address ":9001"
10. Performance & Optimisation
10.1 Le problème des petits fichiers
❌ MAUVAIS : 10,000 fichiers × 1 MB = 10 GB
- 10,000 requêtes API (LIST + GET)
- Overhead énorme pour Spark
- Temps de lecture : minutes
✅ BON : 100 fichiers × 100 MB = 10 GB
- 100 requêtes API
- Parallélisme optimal
- Temps de lecture : secondes
Taille idéale : 100 MB - 1 GB par fichier
Solutions :
df.coalesce(n)oudf.repartition(n)avant écriture- Compaction périodique
- Delta Lake / Iceberg (Module 23) = compaction automatique
pythonVoir le code
# Éviter les petits fichiers avec Spark
small_files_solution = """
# Lecture de beaucoup de petits fichiers
df = spark.read.parquet("s3a://bronze/data/") # 10,000 fichiers
# ❌ Écriture directe = même nombre de fichiers
# df.write.parquet("s3a://silver/data/")
# ✅ Repartitionner avant d'écrire
df.repartition(100) \ # 100 fichiers de ~100 MB
.write.mode("overwrite") \
.parquet("s3a://silver/data/")
# ✅ Ou coalesce (moins de shuffle)
df.coalesce(100) \
.write.mode("overwrite") \
.parquet("s3a://silver/data/")
"""
print(small_files_solution)10.2 Partitionnement
s3://bucket/silver/sales/
├── year=2024/
│ ├── month=01/
│ │ ├── day=01/
│ │ │ └── part-00000.parquet
│ │ └── day=02/
│ │ └── part-00000.parquet
│ └── month=02/
│ └── ...
└── year=2023/
└── ...
Avantages :
- Partition pruning (Spark ne lit que les partitions nécessaires)
- Requêtes plus rapides
⚠️ Attention au over-partitioning :
- Trop de partitions = trop de petits fichiers
- Règle : max 10,000 partitions
pythonVoir le code
# Partitionnement avec Spark
partitioning_example = """
# Écriture partitionnée
df.write \
.partitionBy("year", "month") \
.mode("overwrite") \
.parquet("s3a://silver/sales/")
# Lecture avec partition pruning
df = spark.read.parquet("s3a://silver/sales/")
# Cette requête ne lit QUE year=2024/month=01
df.filter("year = 2024 AND month = 1").show()
"""
print(partitioning_example)10.3 Formats de fichiers
| Format | Type | Compression | Lecture colonnes | Use case |
|---|---|---|---|---|
| CSV | Row | Non | ❌ | Échange, debug |
| JSON | Row | Non | ❌ | APIs, logs |
| Avro | Row | Oui | ❌ | Streaming, Kafka |
| Parquet | Columnar | Oui | ✅ | Analytics, Data Lake |
| ORC | Columnar | Oui | ✅ | Hive, analytics |
💡 Pour le Data Engineering : PARQUET est le standard
- Compression excellente (snappy, zstd)
- Lecture par colonnes
- Predicate pushdown
- Schema intégré
Exercice 8 : Comparer Parquet vs CSV
pythonVoir le code
# Exercice : Comparer la taille et le temps de lecture
format_comparison = """
import time
from pyspark.sql import SparkSession
spark = SparkSession.builder.appName("Format Comparison").getOrCreate()
# Créer un DataFrame de test (1M lignes)
df = spark.range(1000000).toDF("id") \
.withColumn("name", lit("test_name")) \
.withColumn("amount", rand() * 1000)
# Écrire en CSV
start = time.time()
df.write.mode("overwrite").csv("s3a://test/csv/")
csv_write_time = time.time() - start
# Écrire en Parquet
start = time.time()
df.write.mode("overwrite").parquet("s3a://test/parquet/")
parquet_write_time = time.time() - start
# Lire CSV
start = time.time()
df_csv = spark.read.csv("s3a://test/csv/").count()
csv_read_time = time.time() - start
# Lire Parquet
start = time.time()
df_parquet = spark.read.parquet("s3a://test/parquet/").count()
parquet_read_time = time.time() - start
print(f"CSV Write: {csv_write_time:.2f}s, Read: {csv_read_time:.2f}s")
print(f"Parquet Write: {parquet_write_time:.2f}s, Read: {parquet_read_time:.2f}s")
"""
print(format_comparison)
print("\n💡 Résultat attendu : Parquet 5-10x plus rapide et 5-10x plus petit")Exercice 9 : Impact de la taille des fichiers
Objectif : Mesurer l'impact du nombre de fichiers sur les performances.
# Scénario A : 1000 petits fichiers
df.repartition(1000).write.parquet("s3a://test/small-files/")
# Scénario B : 10 gros fichiers
df.repartition(10).write.parquet("s3a://test/large-files/")
# Mesurer le temps de lecture pour chaque
Résultat attendu : Scénario B sera beaucoup plus rapide.
11. Sécurité & Gouvernance
11.1 Encryption
| Type | Description | Qui gère la clé ? |
|---|---|---|
| SSE-S3 | Encryption côté serveur, clé AWS | AWS |
| SSE-KMS | Encryption avec AWS KMS | AWS (tu choisis la clé) |
| SSE-C | Encryption avec clé client | Toi |
| Client-side | Encryption avant upload | Toi |
💡 Best Practice : SSE-KMS pour la plupart des cas
- Rotation automatique des clés
- Audit dans CloudTrail
- Contrôle d'accès fin
11.2 Access Control
pythonVoir le code
# Exemple de bucket policy S3
bucket_policy_example = """
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowDataTeamRead",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789:role/DataEngineerRole"
},
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-datalake",
"arn:aws:s3:::my-datalake/*"
]
},
{
"Sid": "DenyPublicAccess",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::my-datalake/*",
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}
"""
print(bucket_policy_example)11.3 Audit & Compliance
| Service | Cloud | Ce qu'il trace |
|---|---|---|
| S3 Access Logs | AWS | Qui accède à quoi |
| CloudTrail | AWS | Toutes les API calls |
| Activity Logs | Azure | Opérations sur Blob |
| Audit Logs | GCP | Accès aux ressources |
12. Coûts & Optimisation financière
12.1 Composantes du coût
| Composante | Description | Exemple S3 |
|---|---|---|
| Stockage | GB/mois | $0.023/GB (Standard) |
| PUT/POST | Écritures | $0.005 / 1000 requêtes |
| GET | Lectures | $0.0004 / 1000 requêtes |
| LIST | Listing | $0.005 / 1000 requêtes |
| Egress | Sortie du cloud | $0.09/GB (vers Internet) |
12.2 Coûts cachés
⚠️ Attention aux coûts cachés :
1. LISTING fréquent
- Spark fait un LIST avant chaque lecture
- 1M de fichiers = 1000 LIST calls = $5
2. EGRESS
- Données sortant du cloud = coûteux
- Cross-region = $0.02/GB
- Vers Internet = $0.09/GB
3. Small files
- Plus de requêtes API
- Plus de listing
12.3 Lifecycle Policies
pythonVoir le code
# Exemple de lifecycle policy S3
lifecycle_policy = """
{
"Rules": [
{
"ID": "ArchiveBronzeData",
"Status": "Enabled",
"Filter": {
"Prefix": "bronze/"
},
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 90,
"StorageClass": "GLACIER"
}
],
"Expiration": {
"Days": 365
}
}
]
}
"""
print(lifecycle_policy)Exercice 10 : Estimer le coût mensuel d'un Data Lake
Scénario :
- Bronze : 1 TB (accès rare)
- Silver : 500 GB (accès fréquent)
- Gold : 100 GB (accès très fréquent)
- 100,000 requêtes GET/jour
- 10,000 requêtes PUT/jour
- 50 GB egress/mois
Calcule le coût mensuel sur AWS S3.
💡 Voir la solution
| Élément | Calcul | Coût |
|---|---|---|
| Bronze (S3 IA) | 1000 GB × $0.0125 | $12.50 |
| Silver (Standard) | 500 GB × $0.023 | $11.50 |
| Gold (Standard) | 100 GB × $0.023 | $2.30 |
| GET requests | 100K × 30 / 1000 × $0.0004 | $1.20 |
| PUT requests | 10K × 30 / 1000 × $0.005 | $1.50 |
| Egress | 50 GB × $0.09 | $4.50 |
| Total | ~$33.50/mois |
13. Mini-Projet : Data Lake avec MinIO
Objectif
Construire un Data Lake local complet avec MinIO.
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ MinIO (Docker) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ bronze/ │ │ silver/ │ │ gold/ │ │
│ │ │ │ │ │ │ │
│ │ raw.csv │───▶│ clean.parq │───▶│ agg.parq │ │
│ │ │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ▲ ▲ ▲ │
│ │ │ │ │
│ Upload Transform Aggregate │
│ (boto3) (PySpark) (Spark SQL) │
└─────────────────────────────────────────────────────────────────┘
Étapes du projet
pythonVoir le code
# Étape 1 : Démarrer MinIO
print("""
# Terminal 1 : Démarrer MinIO
docker run -d --name minio \
-p 9000:9000 -p 9001:9001 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
minio/minio server /data --console-address ":9001"
# Vérifier que MinIO tourne
curl http://localhost:9000/minio/health/live
""")pythonVoir le code
# Étape 2 : Créer les buckets et uploader les données
step2_code = """
import boto3
from botocore.client import Config
import csv
# Connexion à MinIO
s3 = boto3.client(
's3',
endpoint_url='http://localhost:9000',
aws_access_key_id='minioadmin',
aws_secret_access_key='minioadmin',
config=Config(signature_version='s3v4')
)
# Créer les buckets
for bucket in ['bronze', 'silver', 'gold']:
try:
s3.create_bucket(Bucket=bucket)
print(f"✅ Bucket '{bucket}' créé")
except Exception as e:
print(f"⚠️ Bucket '{bucket}' existe déjà")
# Créer des données de test
with open('sales_data.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['date', 'product', 'category', 'amount', 'quantity'])
writer.writerow(['2024-01-01', 'Laptop', 'Electronics', 1200, 1])
writer.writerow(['2024-01-01', 'Mouse', 'Electronics', 25, 3])
writer.writerow(['2024-01-02', 'Desk', 'Furniture', 350, 1])
writer.writerow(['2024-01-02', 'Chair', 'Furniture', 150, 2])
writer.writerow(['2024-01-03', 'Laptop', 'Electronics', 1200, 2])
writer.writerow(['2024-01-03', 'Monitor', 'Electronics', 400, 1])
# Upload vers bronze
s3.upload_file('sales_data.csv', 'bronze', 'raw/sales_data.csv')
print("✅ Données uploadées vers bronze/raw/sales_data.csv")
"""
print(step2_code)pythonVoir le code
# Étape 3 : Transformer avec PySpark (Bronze → Silver)
step3_code = """
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, to_date, upper
# Créer SparkSession avec config MinIO
spark = SparkSession.builder \
.appName("Bronze to Silver") \
.config("spark.hadoop.fs.s3a.impl", "org.apache.hadoop.fs.s3a.S3AFileSystem") \
.config("spark.hadoop.fs.s3a.endpoint", "http://localhost:9000") \
.config("spark.hadoop.fs.s3a.access.key", "minioadmin") \
.config("spark.hadoop.fs.s3a.secret.key", "minioadmin") \
.config("spark.hadoop.fs.s3a.path.style.access", "true") \
.getOrCreate()
# Lire depuis Bronze
df_bronze = spark.read.csv(
"s3a://bronze/raw/sales_data.csv",
header=True,
inferSchema=True
)
print("📥 Données Bronze:")
df_bronze.show()
# Transformer
df_silver = df_bronze \
.withColumn("date", to_date(col("date"))) \
.withColumn("category", upper(col("category"))) \
.withColumn("total", col("amount") * col("quantity")) \
.dropDuplicates()
print("🔄 Données transformées:")
df_silver.show()
# Écrire vers Silver (Parquet partitionné)
df_silver.write \
.mode("overwrite") \
.partitionBy("category") \
.parquet("s3a://silver/sales/")
print("✅ Données écrites vers silver/sales/")
"""
print(step3_code)pythonVoir le code
# Étape 4 : Agréger avec Spark SQL (Silver → Gold)
step4_code = """
# Lire depuis Silver
df_silver = spark.read.parquet("s3a://silver/sales/")
# Créer une vue temporaire
df_silver.createOrReplaceTempView("sales")
# Agrégation avec Spark SQL
df_gold = spark.sql(\"\"\"
SELECT
category,
COUNT(*) as num_transactions,
SUM(quantity) as total_quantity,
SUM(total) as total_revenue,
AVG(total) as avg_transaction
FROM sales
GROUP BY category
ORDER BY total_revenue DESC
\"\"\")
print("📊 Agrégations Gold:")
df_gold.show()
# Écrire vers Gold
df_gold.coalesce(1) \
.write \
.mode("overwrite") \
.parquet("s3a://gold/category_summary/")
print("✅ Données écrites vers gold/category_summary/")
"""
print(step4_code)pythonVoir le code
# Étape 5 : Vérifier les résultats
step5_code = """
# Lister les fichiers créés
import subprocess
# Avec mc CLI
print("📁 Contenu de bronze/:")
!mc ls myminio/bronze/ --recursive
print("\n📁 Contenu de silver/:")
!mc ls myminio/silver/ --recursive
print("\n📁 Contenu de gold/:")
!mc ls myminio/gold/ --recursive
# Ou avec boto3
for bucket in ['bronze', 'silver', 'gold']:
print(f"\n📁 {bucket}/")
response = s3.list_objects_v2(Bucket=bucket)
for obj in response.get('Contents', []):
print(f" {obj['Key']} ({obj['Size']} bytes)")
"""
print(step5_code)Quiz de fin de module
❓ Q1. Quelle est la différence entre un prefix et un dossier dans S3 ?
a) Aucune différence
b) Un prefix est un vrai dossier créé par S3
c) Un prefix est juste une convention de nommage, S3 est un key-value store plat
d) Un dossier peut contenir des sous-dossiers, pas un prefix
💡 Voir la réponse
✅ Réponse : c — S3 est un key-value store plat. Les "/" dans les clés sont juste des caractères comme les autres.
❓ Q2. Quel protocole utiliser pour lire S3 avec Spark ?
a) s3://
b) s3a://
c) https://
d) hdfs://
💡 Voir la réponse
✅ Réponse : b — s3a:// est le protocole Hadoop optimisé pour Spark. s3:// est pour AWS CLI/boto3.
❓ Q3. Pourquoi les petits fichiers sont-ils un problème ?
a) Ils prennent plus de place
b) Ils génèrent trop de requêtes API et d'overhead
c) Ils ne sont pas supportés par Parquet
d) Ils ne peuvent pas être partitionnés
💡 Voir la réponse
✅ Réponse : b — Chaque fichier = une requête API. 10,000 fichiers = 10,000 GET requests = lent et coûteux.
❓ Q4. Quelle est la différence entre SAS Token et Managed Identity sur Azure ?
a) SAS Token est plus sécurisé
b) Managed Identity est temporaire, SAS est permanent
c) SAS Token est temporaire et partageable, Managed Identity est liée à une ressource Azure
d) Aucune différence
💡 Voir la réponse
✅ Réponse : c — SAS Token = URL temporaire partageable. Managed Identity = identité attachée à une VM/AKS, plus sécurisé.
❓ Q5. Pourquoi MinIO est-il compatible avec S3 ?
a) C'est un produit AWS
b) Il implémente la même API REST que S3
c) Il utilise les mêmes serveurs
d) Il copie les données depuis S3
💡 Voir la réponse
✅ Réponse : b — MinIO implémente l'API S3 (REST). Le même code boto3 fonctionne avec les deux.
❓ Q6. Quelle classe de stockage pour des données rarement lues ?
a) S3 Standard
b) S3 Intelligent-Tiering
c) S3 Glacier
d) S3 One Zone-IA
💡 Voir la réponse
✅ Réponse : c — Glacier pour l'archivage (données rarement lues). Intelligent-Tiering si le pattern d'accès est imprévisible.
❓ Q7. Quel est l'avantage du partitionnement dans un Data Lake ?
a) Les fichiers sont plus petits
b) Spark peut ignorer les partitions non pertinentes (partition pruning)
c) Le stockage coûte moins cher
d) Les données sont automatiquement compressées
💡 Voir la réponse
✅ Réponse : b — Partition pruning = Spark lit uniquement les partitions qui matchent le filtre.
❓ Q8. Comment authentifier Spark on K8s vers S3 en production ?
a) Access Keys dans le code
b) Variables d'environnement
c) IAM Roles for Service Accounts (IRSA)
d) Fichier de config local
💡 Voir la réponse
✅ Réponse : c — IRSA permet d'associer un IAM Role à un ServiceAccount K8s. Pas de credentials dans le code.
❓ Q9. Qu'est-ce qu'un Metadata Catalog (Glue, Hive Metastore) ?
a) Un système de stockage
b) Un registre des schémas et partitions des tables
c) Un outil de requêtage SQL
d) Un système de cache
💡 Voir la réponse
✅ Réponse : b — Le catalog stocke les métadonnées : schéma, localisation, partitions, stats. L'Object Storage ne stocke que les fichiers bruts.
❓ Q10. Quel format de fichier est recommandé pour un Data Lake analytique ?
a) CSV
b) JSON
c) Parquet
d) XML
💡 Voir la réponse
✅ Réponse : c — Parquet est columnar, compressé, avec schema intégré. Idéal pour l'analytics.
📚 Ressources pour aller plus loin
🌐 Documentation officielle
📖 Articles & Tutoriels
➡️ Prochaine étape
Maintenant que tu maîtrises l'Object Storage, passons aux Table Formats pour transformer ton Data Lake en Lakehouse !
👉 Module suivant : 23_table_formats_delta_iceberg — Delta Lake & Apache Iceberg
Tu vas apprendre :
- Delta Lake : ACID, Time Travel, Schema Evolution
- Apache Iceberg : Table format open-source
- Transaction Log : Comment ça remplace le Metastore
- Optimisations : Compaction, Z-Ordering, Vacuum
📝 Récapitulatif de ce module
| Concept | Ce que tu as appris |
|---|---|
| Cloud Computing | IaaS, PaaS, SaaS, régions |
| Services Cloud DE | S3, Glue, BigQuery, Synapse... |
| Storage Models | Block vs File vs Object |
| Object Storage | Buckets, keys, prefixes, protocols |
| AWS S3 | boto3, s3a://, IAM |
| Azure Blob | SDK, abfss://, SAS, Managed Identity |
| GCS | gsutil, gs://, Workload Identity |
| MinIO | Object Storage local S3-compatible |
| Performance | Small files, partitioning, formats |
| Sécurité | Encryption, bucket policies |
| Coûts | Classes de stockage, lifecycle |
🎉 Félicitations ! Tu as terminé le module Cloud & Object Storage.