-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdata_engineering_interactive.html
More file actions
579 lines (558 loc) · 46.2 KB
/
Copy pathdata_engineering_interactive.html
File metadata and controls
579 lines (558 loc) · 46.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Data Engineering — ETL/ELT, Data Warehouse, Lakehouse, Spark, dbt, Airflow</title>
<style>
:root{--bg:#f8fafc;--card:#fff;--border:#e2e8f0;--text:#1e293b;--muted:#64748b;--acc:#4f46e5;--acc2:#4338ca;--blue600:#2563eb;--green:#16a34a;--red:#dc2626;--orange:#ea580c;--purple:#7c3aed;--grad1:#1e1b4b;--grad2:#312e81}
@media(prefers-color-scheme:dark){:root{--bg:#0d0d0d;--card:#1a1a1a;--border:#2a2a2a;--text:#e2e8f0;--muted:#94a3b8}}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.6}
header{background:linear-gradient(135deg,var(--grad1),var(--grad2));padding:2rem 1rem;text-align:center;border-bottom:2px solid var(--acc)}
header h1{font-size:2rem;font-weight:800;color:#fff}
header p{color:#a5b4fc;margin-top:.4rem;font-size:.95rem}
nav{position:sticky;top:0;z-index:100;background:var(--card);border-bottom:1px solid var(--border);padding:.5rem 1rem;display:flex;gap:.5rem;flex-wrap:wrap;justify-content:center}
.tab-btn{padding:.4rem 1rem;border:none;border-radius:999px;cursor:pointer;font-size:.85rem;font-weight:600;background:var(--border);color:var(--muted);transition:all .2s}
.tab-btn.active{background:var(--blue600);color:#fff}
.tab-btn:hover:not(.active){background:var(--acc);color:#fff}
section{display:none;max-width:1100px;margin:0 auto;padding:1.5rem 1rem}
section.active{display:block}
h2{font-size:1.4rem;font-weight:700;margin-bottom:1rem;color:var(--acc)}
h3{font-size:1.1rem;font-weight:600;margin:1.2rem 0 .6rem}
.grid{display:grid;gap:1rem}.g2{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}.g3{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
.card{background:var(--card);border:1px solid var(--border);border-radius:.75rem;padding:1.2rem}
.card h4{font-size:1rem;font-weight:700;margin-bottom:.5rem;color:var(--acc)}
.codeblk{background:#1e1e2e;color:#cdd6f4;border-radius:.5rem;padding:1rem;font-family:'Cascadia Code','Fira Code',monospace;font-size:.82rem;overflow-x:auto;margin:.5rem 0;line-height:1.7;white-space:pre-wrap}
.c-kw{color:#89b4fa}.c-str{color:#a6e3a1}.c-comment{color:#6c7086;font-style:italic}.c-val{color:#f9e2af}.c-fn{color:#cba6f7}.c-op{color:#94e2d5}
table{width:100%;border-collapse:collapse;font-size:.85rem;margin:.5rem 0}
th{background:var(--acc);color:#fff;padding:.5rem .75rem;text-align:left}
td{padding:.45rem .75rem;border-bottom:1px solid var(--border)}
tr:hover td{background:var(--border)}
.info-box{background:var(--card);border-left:4px solid var(--acc);border-radius:.5rem;padding:.8rem 1rem;margin:.5rem 0}
.info-box.green{border-color:var(--green)}.info-box.red{border-color:var(--red)}.info-box.blue{border-color:var(--blue600)}.info-box.orange{border-color:var(--orange)}.info-box.purple{border-color:var(--purple)}
.badge{display:inline-block;padding:.2rem .6rem;border-radius:999px;font-size:.75rem;font-weight:600;margin:.1rem}
.badge-blue{background:#dbeafe;color:#1d4ed8}.badge-green{background:#dcfce7;color:#15803d}
.badge-red{background:#fee2e2;color:#b91c1c}.badge-yellow{background:#fef3c7;color:#92400e}
.badge-purple{background:#ede9fe;color:#6d28d9}.badge-indigo{background:#e0e7ff;color:#3730a3}
@media(prefers-color-scheme:dark){.badge-blue{background:#1e3a5f;color:#93c5fd}.badge-green{background:#14532d;color:#86efac}.badge-red{background:#450a0a;color:#fca5a5}.badge-yellow{background:#451a03;color:#fcd34d}.badge-purple{background:#2e1065;color:#c4b5fd}.badge-indigo{background:#1e1b4b;color:#a5b4fc}}
.stack{display:flex;flex-direction:column;gap:.3rem;margin:.5rem 0}
.layer{border-radius:.4rem;padding:.5rem .8rem;font-size:.82rem;font-weight:600;text-align:center;color:#fff}
.layer.hw{background:#475569}.layer.hyp{background:var(--acc)}.layer.host{background:#0891b2}.layer.guest{background:var(--green)}.layer.app{background:var(--purple)}.layer.ring{background:#7c3aed}
.quiz-q{background:var(--card);border:1px solid var(--border);border-radius:.75rem;padding:1.2rem;margin:.8rem 0}
.quiz-q p{font-weight:600;margin-bottom:.7rem}
.quiz-opt{padding:.5rem .8rem;border:1px solid var(--border);border-radius:.4rem;margin:.3rem 0;cursor:pointer;transition:all .2s}
.quiz-opt:hover{border-color:var(--acc);background:rgba(79,70,229,.05)}
.quiz-opt.correct{background:#dcfce7;border-color:var(--green);color:#166534}
.quiz-opt.wrong{background:#fee2e2;border-color:var(--red);color:#991b1b}
@media(prefers-color-scheme:dark){.quiz-opt.correct{background:#14532d;color:#86efac}.quiz-opt.wrong{background:#450a0a;color:#fca5a5}}
#quiz-score{display:none;text-align:center;padding:1.5rem;background:var(--card);border-radius:.75rem;margin-top:1rem;font-size:1.1rem;font-weight:700}
</style>
</head>
<body>
<header>
<h1>Data Engineering & ETL/ELT</h1>
<p>Pipelines de données, Data Warehouse, Data Lake & Lakehouse, Spark, dbt, Airflow, Kafka, formats & gouvernance</p>
</header>
<nav>
<button class="tab-btn active" onclick="showTab('overview')">Vue d'ensemble</button>
<button class="tab-btn" onclick="showTab('etlelt')">ETL vs ELT</button>
<button class="tab-btn" onclick="showTab('storage')">Stockage</button>
<button class="tab-btn" onclick="showTab('ingestion')">Ingestion</button>
<button class="tab-btn" onclick="showTab('transform')">Transformation & Orchestration</button>
<button class="tab-btn" onclick="showTab('quality')">Formats, Qualité & Gouvernance</button>
<button class="tab-btn" onclick="showTab('quiz')">Quiz</button>
</nav>
<section id="overview" class="active">
<h2>Vue d'ensemble</h2>
<div class="grid g2">
<div class="card">
<h4>Le rôle du data engineer</h4>
<p style="font-size:.88rem;margin-bottom:.5rem">Le <strong>data engineer</strong> conçoit, construit et exploite les <strong>pipelines de données</strong> qui acheminent, fiabilisent et transforment les données depuis les systèmes sources jusqu'aux consommateurs (analystes, data scientists, applications). Il garantit que les données soient <em>fraîches, fiables, documentées et performantes à interroger</em>.</p>
<div class="info-box">
<strong>Mission</strong> : faire en sorte que la donnée soit <em>disponible</em> (au bon endroit), <em>fiable</em> (qualité testée), <em>fraîche</em> (latence maîtrisée) et <em>gouvernée</em> (lineage, accès, coûts) — au service de la décision et du produit.
</div>
</div>
<div class="card">
<h4>Compétences clés</h4>
<ul style="font-size:.85rem;padding-left:1.2rem;line-height:1.9">
<li><strong>SQL</strong> avancé (fenêtrage, CTE, optimisation)</li>
<li><strong>Python / Scala / Java</strong> pour le traitement</li>
<li><strong>Modélisation</strong> (dimensionnel, Data Vault, normalisation)</li>
<li><strong>Systèmes distribués</strong> (Spark, partitionnement, shuffle)</li>
<li><strong>Orchestration</strong> (Airflow, Dagster, Prefect)</li>
<li><strong>Cloud & IaC</strong> (warehouses, objet S3/GCS, Terraform)</li>
<li><strong>Streaming</strong> (Kafka, Flink) & CDC</li>
<li><strong>Qualité & observabilité</strong> des données</li>
</ul>
</div>
</div>
<div class="card" style="margin-top:1rem">
<h4>Le pipeline de données — de la source au service</h4>
<div class="stack">
<div class="layer host">1 — Sources (BDD OLTP, API, logs, fichiers, événements)</div>
<div class="layer hyp">2 — Ingestion (batch / streaming / CDC)</div>
<div class="layer ring">3 — Stockage (Data Lake objet, Data Warehouse, Lakehouse)</div>
<div class="layer app">4 — Transformation (nettoyage, jointures, agrégats, modèles)</div>
<div class="layer guest">5 — Service (BI, dashboards, ML, API, reverse-ETL)</div>
</div>
<p style="font-size:.85rem;margin-top:.4rem">Chaque étape est <strong>orchestrée</strong> (ordonnancement, dépendances, retries) et <strong>observée</strong> (fraîcheur, volumétrie, qualité). Le pipeline doit être <em>idempotent</em> et <em>rejouable</em> pour permettre les corrections et les backfills sans dupliquer la donnée.</p>
</div>
<div class="card" style="margin-top:1rem">
<h4>Data engineer vs data scientist vs data analyst</h4>
<table>
<tr><th>Rôle</th><th>Focus</th><th>Livrables</th><th>Outils typiques</th></tr>
<tr><td><strong>Data engineer</strong></td><td>Infrastructure & pipelines, fiabilité de la donnée</td><td>Tables fiables, pipelines, modèles dbt, plateforme</td><td>Spark, dbt, Airflow, Kafka, SQL, cloud</td></tr>
<tr><td><strong>Data analyst</strong></td><td>Analyse descriptive, reporting, métriques métier</td><td>Dashboards, rapports, KPI, requêtes ad hoc</td><td>SQL, Looker/Power BI/Tableau, Excel</td></tr>
<tr><td><strong>Data scientist</strong></td><td>Modélisation prédictive, statistiques, ML</td><td>Modèles ML, expérimentations, features</td><td>Python, pandas, scikit-learn, notebooks</td></tr>
<tr><td><strong>Analytics engineer</strong></td><td>Modélisation de la couche analytique (rôle hybride)</td><td>Modèles dbt, métriques, documentation</td><td>dbt, SQL, warehouse</td></tr>
<tr><td><strong>ML engineer</strong></td><td>Industrialisation des modèles (MLOps)</td><td>Pipelines de features, déploiement modèles</td><td>MLflow, feature store, Kubernetes</td></tr>
</table>
<div class="info-box blue" style="margin-top:.5rem;font-size:.82rem">
Le data engineer est en <strong>amont</strong> : il prépare des données propres et fiables. Analystes et data scientists sont <strong>consommateurs</strong> de ce qu'il produit. L'<em>analytics engineer</em> (popularisé par dbt) est la jonction entre engineering et analyse.
</div>
</div>
<div class="card" style="margin-top:1rem">
<h4>Glossaire fondamental</h4>
<table>
<tr><th>Terme</th><th>Définition</th></tr>
<tr><td><strong>Pipeline</strong></td><td>Enchaînement d'étapes automatisées qui déplacent et transforment la donnée d'une source vers une destination.</td></tr>
<tr><td><strong>Idempotence</strong></td><td>Propriété d'une opération qui, exécutée plusieurs fois avec les mêmes entrées, produit le même résultat — clé pour rejouer un job sans dupliquer (ex. <em>MERGE</em>, <em>overwrite</em> de partition).</td></tr>
<tr><td><strong>Lineage</strong></td><td>Traçabilité du parcours de la donnée : quelles sources, transformations et tables ont produit un champ donné (impact analysis, debug, conformité).</td></tr>
<tr><td><strong>Schema</strong></td><td>Structure des données (colonnes, types, contraintes). <em>Schema-on-write</em> : imposé à l'écriture (warehouse). <em>Schema-on-read</em> : appliqué à la lecture (lake).</td></tr>
<tr><td><strong>Backfill</strong></td><td>Recalcul rétroactif d'une période passée (corriger un bug, ajouter une colonne historique).</td></tr>
<tr><td><strong>Schema drift / evolution</strong></td><td>Modification du schéma source au fil du temps ; le pipeline doit la gérer (ajout de colonne, changement de type).</td></tr>
<tr><td><strong>SCD</strong></td><td>Slowly Changing Dimension — gestion de l'historique d'une dimension (Type 1 écrase, Type 2 historise).</td></tr>
<tr><td><strong>Partition</strong></td><td>Découpage physique d'une table (souvent par date) pour élaguer les lectures (<em>partition pruning</em>) et paralléliser.</td></tr>
</table>
</div>
</section>
<section id="etlelt">
<h2>ETL vs ELT</h2>
<div class="grid g2">
<div class="card">
<h4>ETL — Extract, Transform, Load</h4>
<div class="stack">
<div class="layer host">Extract — lire les sources</div>
<div class="layer hyp">Transform — sur un moteur dédié (hors cible)</div>
<div class="layer guest">Load — charger les données déjà transformées</div>
</div>
<p style="font-size:.85rem;margin-top:.4rem">La transformation a lieu <strong>avant</strong> le chargement, sur un serveur ou moteur intermédiaire (Informatica, Talend, SSIS, Spark). On ne charge dans la cible que le résultat final, conforme au schéma.</p>
<p style="font-size:.83rem;margin-top:.3rem"><strong>Adapté</strong> quand : la cible a un compute limité, données sensibles à filtrer/masquer avant chargement, schéma figé.</p>
</div>
<div class="card">
<h4>ELT — Extract, Load, Transform</h4>
<div class="stack">
<div class="layer host">Extract — lire les sources</div>
<div class="layer guest">Load — charger le brut tel quel</div>
<div class="layer app">Transform — dans l'entrepôt (SQL / dbt)</div>
</div>
<p style="font-size:.85rem;margin-top:.4rem">On charge d'abord le <strong>brut</strong> dans l'entrepôt, puis on transforme <strong>sur place</strong> avec son compute (SQL). Le modèle dominant du <em>modern data stack</em>.</p>
<p style="font-size:.83rem;margin-top:.3rem"><strong>Adapté</strong> quand : entrepôt cloud scalable, besoin de rejouer/retransformer le brut, séparation stockage/compute.</p>
</div>
</div>
<div class="card" style="margin-top:1rem">
<h4>Pourquoi l'ELT domine avec le cloud</h4>
<ul style="font-size:.85rem;padding-left:1.2rem;line-height:1.9">
<li><strong>Compute élastique & découplé du stockage</strong> : Snowflake, BigQuery, Redshift facturent le calcul à la demande — il devient rentable de transformer <em>dans</em> l'entrepôt.</li>
<li><strong>Stockage objet bon marché</strong> : conserver le brut coûte peu, on garde la « source de vérité » pour rejouer.</li>
<li><strong>SQL accessible</strong> : la transformation en SQL (dbt) ouvre le travail aux analytics engineers sans cluster Spark.</li>
<li><strong>Idempotence & rejouabilité</strong> : le brut étant conservé, on peut recalculer les modèles après correction d'un bug (backfill).</li>
<li><strong>Moins de serveurs intermédiaires</strong> à maintenir (l'« E/L » est souvent délégué à Fivetran/Airbyte).</li>
</ul>
<div class="info-box green" style="margin-top:.5rem;font-size:.82rem">
Le pattern moderne : <strong>EL managé</strong> (Fivetran/Airbyte) + <strong>T en SQL</strong> (dbt) sur un entrepôt cloud. L'ETL classique reste pertinent pour le masquage RGPD avant chargement, les très gros volumes non-SQL, ou les contraintes de souveraineté.
</div>
</div>
<div class="card" style="margin-top:1rem">
<h4>Régimes de traitement — batch, micro-batch, streaming</h4>
<table>
<tr><th>Régime</th><th>Latence</th><th>Principe</th><th>Outils</th></tr>
<tr><td><strong>Batch</strong></td><td>Minutes à heures</td><td>Traitement périodique de gros lots (nuit, horaire)</td><td>Spark, dbt, jobs planifiés</td></tr>
<tr><td><strong>Micro-batch</strong></td><td>Secondes à minutes</td><td>Petits lots fréquents — compromis débit/latence</td><td>Spark Structured Streaming</td></tr>
<tr><td><strong>Streaming (temps réel)</strong></td><td>Millisecondes à secondes</td><td>Traitement événement par événement, en continu</td><td>Flink, Kafka Streams</td></tr>
</table>
<div class="info-box blue" style="margin-top:.5rem;font-size:.82rem">
<strong>Architecture Lambda</strong> : double chemin batch (exact, lent) + speed/streaming (rapide, approché) fusionnés en service. <strong>Architecture Kappa</strong> : un seul chemin streaming, le rejeu du log d'événements remplace le batch. Tendance actuelle : Kappa simplifiée grâce aux table formats (Iceberg/Delta) qui unifient batch et streaming.
</div>
</div>
</section>
<section id="storage">
<h2>Architectures de stockage</h2>
<div class="grid g2">
<div class="card">
<h4>Data Warehouse</h4>
<p style="font-size:.85rem;margin-bottom:.4rem">Entrepôt orienté analytique (OLAP), <strong>schema-on-write</strong>, optimisé pour le SQL et le reporting. Stockage <strong>colonnaire</strong>, compute massivement parallèle (MPP).</p>
<p style="font-size:.85rem;margin-bottom:.4rem"><strong>Exemples :</strong> Snowflake, Google BigQuery, Amazon Redshift, Azure Synapse, Databricks SQL.</p>
<ul style="font-size:.83rem;padding-left:1.2rem;line-height:1.8">
<li><strong>Snowflake</strong> : séparation stockage/compute, <em>virtual warehouses</em> indépendants, multi-cloud.</li>
<li><strong>BigQuery</strong> : serverless, facturation à l'octet scanné, slots de compute.</li>
<li><strong>Redshift</strong> : clusters (ou serverless), distribution keys & sort keys.</li>
</ul>
</div>
<div class="card">
<h4>Data Lake</h4>
<p style="font-size:.85rem;margin-bottom:.4rem">Dépôt de fichiers bruts à grande échelle, <strong>schema-on-read</strong>, tous formats (structuré, semi/non structuré). Stockage objet bon marché et illimité.</p>
<p style="font-size:.85rem;margin-bottom:.4rem"><strong>Stockage :</strong> Amazon S3, Azure Data Lake Storage (ADLS), Google Cloud Storage, HDFS (historique on-prem).</p>
<p style="font-size:.85rem;margin-bottom:.4rem"><strong>Formats :</strong> Parquet/ORC (colonne), Avro (ligne), JSON/CSV (brut).</p>
<div class="info-box red" style="margin-top:.4rem;font-size:.82rem">
Sans gouvernance, un lake dérive en <strong>data swamp</strong> (marécage) : fichiers non documentés, sans schéma fiable, introuvables. D'où les catalogues et table formats.
</div>
</div>
</div>
<div class="card" style="margin-top:1rem">
<h4>Lakehouse — fusionner lake et warehouse</h4>
<p style="font-size:.85rem;margin-bottom:.5rem">Architecture qui apporte les garanties d'un entrepôt (<strong>transactions ACID, schéma, performance SQL</strong>) directement sur le stockage objet d'un data lake, via un <strong>table format</strong> (Delta Lake, Iceberg, Hudi). Popularisé par <strong>Databricks</strong> autour de <strong>Delta Lake</strong>.</p>
<div class="stack">
<div class="layer guest">BI & SQL — ML — streaming (mêmes données)</div>
<div class="layer app">Table format : Delta / Iceberg / Hudi (ACID, time travel, schéma)</div>
<div class="layer ring">Fichiers colonne : Parquet / ORC</div>
<div class="layer hw">Stockage objet : S3 / ADLS / GCS</div>
</div>
<p style="font-size:.83rem;margin-top:.4rem">Avantage : une <strong>seule copie</strong> des données sert le BI, le ML et le streaming, sans dupliquer dans un entrepôt séparé. On évite la double pile lake + warehouse.</p>
</div>
<div class="card" style="margin-top:1rem">
<h4>Modélisation dimensionnelle — étoile & flocon</h4>
<div class="grid g2">
<div>
<p style="font-size:.85rem;margin-bottom:.4rem">Le modèle en <strong>étoile</strong> (Kimball) place une table de <strong>faits</strong> centrale (mesures : montant, quantité) entourée de tables de <strong>dimensions</strong> (contexte : client, produit, date, magasin).</p>
<ul style="font-size:.83rem;padding-left:1.2rem;line-height:1.8">
<li><strong>Fait</strong> : grain fin, clés étrangères + mesures numériques.</li>
<li><strong>Dimension</strong> : descriptive, dénormalisée, peu de lignes.</li>
<li><strong>Étoile</strong> : dimensions dénormalisées (rapide, simple).</li>
<li><strong>Flocon</strong> : dimensions normalisées en sous-tables (moins de redondance, plus de jointures).</li>
</ul>
</div>
<div>
<div class="codeblk"><span class="c-comment">-- Schéma en étoile (simplifié)</span>
<span class="c-kw">CREATE TABLE</span> fait_ventes (
date_id <span class="c-kw">INT</span>, <span class="c-comment">-- FK dim_date</span>
produit_id <span class="c-kw">INT</span>, <span class="c-comment">-- FK dim_produit</span>
client_id <span class="c-kw">INT</span>, <span class="c-comment">-- FK dim_client</span>
quantite <span class="c-kw">INT</span>,
montant <span class="c-kw">DECIMAL</span>(<span class="c-val">12</span>,<span class="c-val">2</span>)
);
<span class="c-comment">-- dim_produit (Type 2 = historisée)</span>
<span class="c-kw">CREATE TABLE</span> dim_produit (
produit_id <span class="c-kw">INT</span>,
libelle <span class="c-kw">VARCHAR</span>,
categorie <span class="c-kw">VARCHAR</span>,
valide_de <span class="c-kw">DATE</span>,
valide_a <span class="c-kw">DATE</span>,
est_courant <span class="c-kw">BOOLEAN</span>
);</div>
</div>
</div>
</div>
<div class="card" style="margin-top:1rem">
<h4>Architecture médaillon (bronze / silver / gold)</h4>
<div class="stack">
<div class="layer hw">Bronze — données brutes ingérées telles quelles (immuable, append, historique complet)</div>
<div class="layer host">Silver — nettoyé, dédupliqué, typé, conformé (jointures, qualité)</div>
<div class="layer guest">Gold — agrégats métier, modèles dimensionnels, prêt pour la BI/ML</div>
</div>
<p style="font-size:.85rem;margin-top:.4rem">Patron de raffinage progressif (popularisé par Databricks) : chaque couche augmente la qualité et la valeur métier. Le <strong>bronze</strong> sert de source de vérité rejouable ; le <strong>gold</strong> alimente directement les consommateurs.</p>
</div>
<div class="card" style="margin-top:1rem">
<h4>Comparatif des architectures</h4>
<table>
<tr><th>Critère</th><th>Data Warehouse</th><th>Data Lake</th><th>Lakehouse</th></tr>
<tr><td>Schéma</td><td>Schema-on-write</td><td>Schema-on-read</td><td>Schéma appliqué (table format)</td></tr>
<tr><td>Données</td><td>Structurées</td><td>Tous types</td><td>Tous types</td></tr>
<tr><td>Transactions ACID</td><td>Oui</td><td>Non (natif)</td><td>Oui (Delta/Iceberg/Hudi)</td></tr>
<tr><td>Coût stockage</td><td>Élevé</td><td>Faible (objet)</td><td>Faible (objet)</td></tr>
<tr><td>Charges</td><td>BI / SQL</td><td>ML / data brute</td><td>BI + ML + streaming</td></tr>
<tr><td>Risque</td><td>Coût, silos</td><td>Data swamp</td><td>Maturité outillage</td></tr>
<tr><td>Exemples</td><td>Snowflake, BigQuery, Redshift</td><td>S3 + Parquet, HDFS</td><td>Databricks/Delta, Iceberg</td></tr>
</table>
</div>
</section>
<section id="ingestion">
<h2>Ingestion</h2>
<div class="grid g2">
<div class="card">
<h4>Batch vs streaming</h4>
<table>
<tr><th>Aspect</th><th>Batch</th><th>Streaming</th></tr>
<tr><td>Déclenchement</td><td>Planifié (cron)</td><td>Continu (événements)</td></tr>
<tr><td>Latence</td><td>Élevée</td><td>Faible</td></tr>
<tr><td>Débit</td><td>Très élevé par lot</td><td>Élevé en continu</td></tr>
<tr><td>Complexité</td><td>Plus simple</td><td>Plus complexe (état, ordre, exactly-once)</td></tr>
<tr><td>Cas d'usage</td><td>Reporting J+1, ETL nocturne</td><td>Fraude, IoT, alerting, temps réel</td></tr>
</table>
</div>
<div class="card">
<h4>CDC — Change Data Capture</h4>
<p style="font-size:.85rem;margin-bottom:.4rem">Capturer les <strong>changements</strong> (INSERT/UPDATE/DELETE) d'une base source en temps quasi-réel, sans requête lourde, en lisant le <strong>journal de transactions</strong> (WAL PostgreSQL, binlog MySQL, redo log Oracle).</p>
<ul style="font-size:.83rem;padding-left:1.2rem;line-height:1.8">
<li><strong>Log-based</strong> (idéal) : lit le journal, faible impact — <strong>Debezium</strong>.</li>
<li><strong>Query-based</strong> : poll sur une colonne <code>updated_at</code> (rate les suppressions).</li>
<li><strong>Trigger-based</strong> : triggers SQL (surcharge la source).</li>
</ul>
<div class="info-box green" style="margin-top:.4rem;font-size:.82rem">
<strong>Debezium</strong> (sur Kafka Connect) émet un événement par changement de ligne → réplication quasi temps réel vers le lake/warehouse, idéal pour le médaillon bronze.
</div>
</div>
</div>
<div class="card" style="margin-top:1rem">
<h4>Outils d'ingestion</h4>
<table>
<tr><th>Outil</th><th>Catégorie</th><th>Rôle</th></tr>
<tr><td><strong>Apache Kafka</strong></td><td>Bus d'événements</td><td>Log distribué durable, pub/sub, replay, partitions — colonne vertébrale du streaming</td></tr>
<tr><td><strong>Kafka Connect</strong></td><td>Connecteurs</td><td>Source/Sink déclaratifs (Debezium, S3, JDBC) sans code</td></tr>
<tr><td><strong>Apache Flink</strong></td><td>Stream processing</td><td>Traitement stateful temps réel, exactly-once, fenêtrage événementiel</td></tr>
<tr><td><strong>Airbyte</strong></td><td>EL (open source)</td><td>Centaines de connecteurs EL, déploiement self-hosted ou cloud</td></tr>
<tr><td><strong>Fivetran</strong></td><td>EL (managé)</td><td>EL managé clé en main, schéma normalisé automatique</td></tr>
<tr><td><strong>Stitch / Meltano</strong></td><td>EL (Singer)</td><td>Standard Singer (taps/targets)</td></tr>
</table>
</div>
<div class="card" style="margin-top:1rem">
<h4>Exemple — ingestion Kafka (producer/consumer)</h4>
<div class="codeblk"><span class="c-comment"># Producer : publier un événement de commande</span>
<span class="c-kw">from</span> kafka <span class="c-kw">import</span> KafkaProducer
<span class="c-kw">import</span> json
producer = KafkaProducer(
bootstrap_servers=<span class="c-str">"broker:9092"</span>,
value_serializer=<span class="c-kw">lambda</span> v: json.dumps(v).encode(),
acks=<span class="c-str">"all"</span>, <span class="c-comment"># durabilité maximale</span>
enable_idempotence=<span class="c-val">True</span> <span class="c-comment"># pas de doublons producteur</span>
)
producer.send(<span class="c-str">"commandes"</span>, key=b<span class="c-str">"cmd-42"</span>,
value={<span class="c-str">"id"</span>: <span class="c-val">42</span>, <span class="c-str">"montant"</span>: <span class="c-val">99.9</span>})
producer.flush()
<span class="c-comment"># Consumer : lire en continu depuis un groupe</span>
<span class="c-kw">from</span> kafka <span class="c-kw">import</span> KafkaConsumer
consumer = KafkaConsumer(
<span class="c-str">"commandes"</span>, bootstrap_servers=<span class="c-str">"broker:9092"</span>,
group_id=<span class="c-str">"etl-bronze"</span>,
enable_auto_commit=<span class="c-val">False</span>, <span class="c-comment"># commit manuel après écriture</span>
auto_offset_reset=<span class="c-str">"earliest"</span>
)
<span class="c-kw">for</span> msg <span class="c-kw">in</span> consumer:
ecrire_bronze(msg.value)
consumer.commit() <span class="c-comment"># at-least-once -> MERGE idempotent en aval</span></div>
</div>
<div class="card" style="margin-top:1rem">
<h4>Backfill & idempotence</h4>
<div class="grid g2">
<div>
<h3 style="margin-top:0">Backfill</h3>
<p style="font-size:.84rem">Recalculer des partitions passées (nouveau champ, correction de bug, source corrigée). À concevoir dès le départ : pipeline <strong>paramétré par date</strong>, partitions indépendantes, capacité à relancer une plage sans toucher au reste.</p>
</div>
<div>
<h3 style="margin-top:0">Idempotence</h3>
<p style="font-size:.84rem">Rejouer un job ne doit pas dupliquer. Techniques : <strong>overwrite de partition</strong>, <strong>MERGE/UPSERT</strong> sur clé, déduplication par clé d'événement, <em>exactly-once</em> côté streaming (offsets transactionnels).</p>
</div>
</div>
<div class="codeblk" style="margin-top:.4rem"><span class="c-comment">-- UPSERT idempotent : rejouable sans doublon</span>
<span class="c-kw">MERGE INTO</span> silver.clients <span class="c-kw">AS</span> t
<span class="c-kw">USING</span> staging.clients <span class="c-kw">AS</span> s
<span class="c-kw">ON</span> t.client_id = s.client_id
<span class="c-kw">WHEN MATCHED THEN UPDATE SET</span> t.email = s.email, t.maj = s.maj
<span class="c-kw">WHEN NOT MATCHED THEN INSERT</span> (client_id, email, maj)
<span class="c-kw">VALUES</span> (s.client_id, s.email, s.maj);
<span class="c-comment">-- Backfill : réécriture atomique d'une partition</span>
<span class="c-kw">INSERT OVERWRITE</span> gold.ventes <span class="c-kw">PARTITION</span> (jour = <span class="c-str">'2026-06-01'</span>)
<span class="c-kw">SELECT</span> ... <span class="c-kw">FROM</span> silver.ventes <span class="c-kw">WHERE</span> jour = <span class="c-str">'2026-06-01'</span>;</div>
</div>
</section>
<section id="transform">
<h2>Transformation & orchestration</h2>
<div class="grid g2">
<div class="card">
<h4>Apache Spark</h4>
<p style="font-size:.85rem;margin-bottom:.4rem">Moteur de calcul distribué en mémoire pour le big data. Un <strong>driver</strong> orchestre des <strong>executors</strong> sur un cluster ; les transformations sont <em>lazy</em> et déclenchées par une <em>action</em>.</p>
<ul style="font-size:.83rem;padding-left:1.2rem;line-height:1.8">
<li><strong>RDD</strong> : abstraction bas niveau (collections distribuées immuables, tolérantes aux pannes via lineage).</li>
<li><strong>DataFrame / Dataset</strong> : API haut niveau optimisée par <strong>Catalyst</strong> (plan logique → physique) et <strong>Tungsten</strong> (mémoire).</li>
<li><strong>Shuffle</strong> : redistribution coûteuse des données (joins, groupBy) — à minimiser.</li>
<li><strong>Partitionnement</strong> & <em>broadcast join</em> pour la perf.</li>
</ul>
<div class="codeblk"><span class="c-comment"># PySpark : silver -> gold (agrégat)</span>
<span class="c-kw">from</span> pyspark.sql <span class="c-kw">import</span> functions <span class="c-kw">as</span> F
ventes = spark.read.format(<span class="c-str">"delta"</span>).load(<span class="c-str">"/silver/ventes"</span>)
gold = (ventes
.filter(F.col(<span class="c-str">"montant"</span>) > <span class="c-val">0</span>)
.groupBy(<span class="c-str">"jour"</span>, <span class="c-str">"categorie"</span>)
.agg(F.sum(<span class="c-str">"montant"</span>).alias(<span class="c-str">"ca"</span>),
F.countDistinct(<span class="c-str">"client_id"</span>).alias(<span class="c-str">"clients"</span>)))
(gold.write.format(<span class="c-str">"delta"</span>).mode(<span class="c-str">"overwrite"</span>)
.partitionBy(<span class="c-str">"jour"</span>).save(<span class="c-str">"/gold/ca_jour"</span>))</div>
</div>
<div class="card">
<h4>dbt — data build tool</h4>
<p style="font-size:.85rem;margin-bottom:.4rem">Outil de transformation <strong>en SQL</strong> dans l'entrepôt (le « T » de l'ELT). Chaque modèle est un <code>SELECT</code> versionné ; dbt gère les <strong>dépendances</strong> (le DAG via <code>ref()</code>), les <strong>tests</strong>, la <strong>documentation</strong> et le <strong>lineage</strong>.</p>
<ul style="font-size:.83rem;padding-left:1.2rem;line-height:1.8">
<li><strong>Materializations</strong> : view, table, incremental, ephemeral.</li>
<li><strong>Tests</strong> : not_null, unique, accepted_values, relationships.</li>
<li><strong>Sources, seeds, snapshots</strong> (SCD2), macros Jinja.</li>
</ul>
<div class="codeblk"><span class="c-comment">-- models/gold/ca_jour.sql (incremental)</span>
{{ config(materialized=<span class="c-str">'incremental'</span>,
unique_key=<span class="c-str">'jour_categorie'</span>) }}
<span class="c-kw">SELECT</span>
jour,
categorie,
jour || <span class="c-str">'-'</span> || categorie <span class="c-kw">AS</span> jour_categorie,
<span class="c-fn">SUM</span>(montant) <span class="c-kw">AS</span> ca
<span class="c-kw">FROM</span> {{ ref(<span class="c-str">'stg_ventes'</span>) }}
{% <span class="c-kw">if</span> is_incremental() %}
<span class="c-kw">WHERE</span> jour > (<span class="c-kw">SELECT</span> <span class="c-fn">MAX</span>(jour) <span class="c-kw">FROM</span> {{ this }})
{% endif %}
<span class="c-kw">GROUP BY</span> <span class="c-val">1</span>, <span class="c-val">2</span></div>
</div>
</div>
<div class="card" style="margin-top:1rem">
<h4>Orchestration de workflows</h4>
<table>
<tr><th>Outil</th><th>Abstraction</th><th>Particularités</th></tr>
<tr><td><strong>Apache Airflow</strong></td><td>DAG de tâches (Python)</td><td>Standard de facto, riche écosystème d'opérateurs, scheduling cron, retries, backfill natif</td></tr>
<tr><td><strong>Dagster</strong></td><td>Assets (données produites)</td><td>Orienté <em>data assets</em> & lineage, typage, tests, observabilité native</td></tr>
<tr><td><strong>Prefect</strong></td><td>Flows & tasks (Python)</td><td>API pythonique légère, exécution dynamique, hybride cloud/agent</td></tr>
<tr><td><strong>Mage / Kestra / Argo</strong></td><td>Pipelines déclaratifs</td><td>Alternatives modernes (UI, YAML, Kubernetes-native)</td></tr>
</table>
<div class="info-box blue" style="margin-top:.5rem;font-size:.82rem">
Concepts communs : <strong>DAG</strong> (graphe orienté acyclique de dépendances), <strong>scheduling</strong> (intervalle), <strong>retries</strong> & alerting, <strong>idempotence</strong> des tâches, <strong>backfill</strong> de périodes passées, et passage de l'orchestration <em>par tâche</em> vers <em>par asset/donnée</em>.
</div>
</div>
<div class="card" style="margin-top:1rem">
<h4>Exemple — DAG Airflow (ingestion → dbt → qualité)</h4>
<div class="codeblk"><span class="c-kw">from</span> airflow <span class="c-kw">import</span> DAG
<span class="c-kw">from</span> airflow.operators.bash <span class="c-kw">import</span> BashOperator
<span class="c-kw">from</span> airflow.operators.python <span class="c-kw">import</span> PythonOperator
<span class="c-kw">from</span> datetime <span class="c-kw">import</span> datetime, timedelta
default_args = {
<span class="c-str">"retries"</span>: <span class="c-val">2</span>,
<span class="c-str">"retry_delay"</span>: timedelta(minutes=<span class="c-val">5</span>),
}
<span class="c-kw">with</span> DAG(
dag_id=<span class="c-str">"pipeline_ventes"</span>,
schedule=<span class="c-str">"0 2 * * *"</span>, <span class="c-comment"># tous les jours à 02h00</span>
start_date=datetime(<span class="c-val">2026</span>, <span class="c-val">1</span>, <span class="c-val">1</span>),
catchup=<span class="c-val">True</span>, <span class="c-comment"># active le backfill des dates passées</span>
default_args=default_args,
) <span class="c-kw">as</span> dag:
ingest = PythonOperator(task_id=<span class="c-str">"ingest_bronze"</span>,
python_callable=charger_bronze)
transform = BashOperator(task_id=<span class="c-str">"dbt_run"</span>,
bash_command=<span class="c-str">"dbt run --select gold+"</span>)
test = BashOperator(task_id=<span class="c-str">"dbt_test"</span>,
bash_command=<span class="c-str">"dbt test"</span>)
<span class="c-comment"># dépendances : ingest -> transform -> test</span>
ingest >> transform >> test</div>
</div>
</section>
<section id="quality">
<h2>Formats, qualité & gouvernance</h2>
<div class="card">
<h4>Formats de fichiers</h4>
<table>
<tr><th>Format</th><th>Orientation</th><th>Caractéristiques</th><th>Usage</th></tr>
<tr><td><strong>Parquet</strong></td><td>Colonne</td><td>Compression forte, predicate pushdown, métadonnées par row group, standard de fait</td><td>Analytique (lecture)</td></tr>
<tr><td><strong>ORC</strong></td><td>Colonne</td><td>Optimisé Hive, index intégrés, bonne compression</td><td>Écosystème Hadoop/Hive</td></tr>
<tr><td><strong>Avro</strong></td><td>Ligne</td><td>Schéma embarqué (JSON), évolution de schéma, compact</td><td>Streaming, écriture, Kafka</td></tr>
<tr><td><strong>JSON / CSV</strong></td><td>Ligne / texte</td><td>Lisible, sans typage strict, peu compressé</td><td>Échange brut, ingestion</td></tr>
</table>
<div class="info-box blue" style="margin-top:.5rem;font-size:.82rem">
<strong>Colonne (Parquet/ORC)</strong> : excellent pour l'analytique (on ne lit que les colonnes utiles, compression par colonne). <strong>Ligne (Avro)</strong> : meilleur pour l'écriture et le streaming (un enregistrement entier à la fois, évolution de schéma).
</div>
</div>
<div class="card" style="margin-top:1rem">
<h4>Table formats — ACID sur le data lake</h4>
<p style="font-size:.85rem;margin-bottom:.5rem">Couche de métadonnées au-dessus des fichiers Parquet qui apporte transactions <strong>ACID</strong>, <strong>schema evolution</strong>, <strong>time travel</strong> et <em>upserts</em> au data lake — fondation du lakehouse.</p>
<table>
<tr><th>Format</th><th>Origine</th><th>Particularités</th></tr>
<tr><td><strong>Delta Lake</strong></td><td>Databricks</td><td>Transaction log JSON (_delta_log), MERGE, time travel, OPTIMIZE/Z-ORDER, large adoption</td></tr>
<tr><td><strong>Apache Iceberg</strong></td><td>Netflix</td><td>Spec ouverte multi-moteur (Spark, Trino, Flink, Snowflake), hidden partitioning, snapshots, branches</td></tr>
<tr><td><strong>Apache Hudi</strong></td><td>Uber</td><td>Copy-on-Write / Merge-on-Read, upserts & incremental pull, orienté CDC temps réel</td></tr>
</table>
<div class="codeblk" style="margin-top:.4rem"><span class="c-comment">-- Time travel & MERGE (Delta Lake)</span>
<span class="c-kw">SELECT</span> * <span class="c-kw">FROM</span> ventes <span class="c-kw">VERSION AS OF</span> <span class="c-val">12</span>; <span class="c-comment">-- état historique</span>
<span class="c-kw">SELECT</span> * <span class="c-kw">FROM</span> ventes <span class="c-kw">TIMESTAMP AS OF</span> <span class="c-str">'2026-06-01'</span>;
<span class="c-kw">MERGE INTO</span> ventes t <span class="c-kw">USING</span> maj s <span class="c-kw">ON</span> t.id = s.id
<span class="c-kw">WHEN MATCHED THEN UPDATE SET</span> *
<span class="c-kw">WHEN NOT MATCHED THEN INSERT</span> *;</div>
</div>
<div class="grid g2" style="margin-top:1rem">
<div class="card">
<h4>Qualité des données</h4>
<p style="font-size:.85rem;margin-bottom:.4rem">Tester la donnée comme on teste du code : détecter au plus tôt les anomalies (valeurs nulles, doublons, hors plage, volumétrie anormale).</p>
<ul style="font-size:.83rem;padding-left:1.2rem;line-height:1.8">
<li><strong>Tests dbt</strong> : not_null, unique, accepted_values, relationships, tests custom SQL.</li>
<li><strong>Great Expectations</strong> : suites d'attentes (<em>expectations</em>) déclaratives + docs de validation.</li>
<li><strong>Soda / Monte Carlo</strong> : observabilité & détection d'anomalies (data downtime).</li>
<li><strong>Dimensions</strong> : exactitude, complétude, fraîcheur, unicité, cohérence.</li>
</ul>
<div class="codeblk"><span class="c-comment"># schema.yml : tests dbt déclaratifs</span>
models:
- name: stg_clients
columns:
- name: client_id
tests:
- unique
- not_null
- name: statut
tests:
- accepted_values:
values: [<span class="c-str">'actif'</span>, <span class="c-str">'inactif'</span>]</div>
</div>
<div class="card">
<h4>Lineage, catalogue & gouvernance</h4>
<ul style="font-size:.83rem;padding-left:1.2rem;line-height:1.85">
<li><strong>Catalogue de données</strong> : inventaire searchable des tables, schémas, propriétaires, descriptions (<strong>DataHub</strong>, <strong>OpenMetadata</strong>, <strong>Amundsen</strong>, <strong>Unity Catalog</strong>, AWS Glue Catalog).</li>
<li><strong>Lineage</strong> : graphe source → colonne → modèle → dashboard (impact analysis, debug, conformité).</li>
<li><strong>Gouvernance</strong> : RBAC/ABAC, masquage des colonnes sensibles, classification (PII), RGPD (droit à l'oubli).</li>
<li><strong>Contrats de données</strong> (<em>data contracts</em>) : schéma + SLA garantis entre producteur et consommateur.</li>
<li><strong>FinOps données</strong> : surveiller le coût (octets scannés BigQuery, crédits Snowflake), partitionner, clusteriser, suspendre les warehouses inactifs.</li>
</ul>
<div class="info-box orange" style="margin-top:.4rem;font-size:.82rem">
<strong>Coûts</strong> : en ELT cloud, le compute est le poste majeur. Leviers : partition pruning, formats colonne, matérialisations incrémentales, auto-suspend, limiter les <code>SELECT *</code>.
</div>
</div>
</div>
<div class="card" style="margin-top:1rem">
<h4>Le « Modern Data Stack »</h4>
<table>
<tr><th>Couche</th><th>Rôle</th><th>Outils typiques</th></tr>
<tr><td><strong>Ingestion (EL)</strong></td><td>Extraire & charger le brut</td><td>Fivetran, Airbyte, Debezium, Kafka</td></tr>
<tr><td><strong>Stockage</strong></td><td>Entrepôt / lakehouse</td><td>Snowflake, BigQuery, Databricks, Redshift</td></tr>
<tr><td><strong>Transformation (T)</strong></td><td>Modéliser en SQL</td><td>dbt, Spark, SQLMesh</td></tr>
<tr><td><strong>Orchestration</strong></td><td>Planifier & ordonnancer</td><td>Airflow, Dagster, Prefect</td></tr>
<tr><td><strong>Qualité / catalogue</strong></td><td>Tester & documenter</td><td>Great Expectations, DataHub, Unity Catalog</td></tr>
<tr><td><strong>BI / activation</strong></td><td>Restituer & activer</td><td>Looker, Power BI, Tableau, reverse-ETL (Census, Hightouch)</td></tr>
</table>
<div class="info-box green" style="margin-top:.5rem;font-size:.82rem">
Tendances : <strong>lakehouse</strong> (Iceberg/Delta), <strong>data mesh</strong> (responsabilité décentralisée par domaine, données comme produit), <strong>data contracts</strong>, et l'arrivée de l'IA/LLM dans la préparation et la documentation des pipelines.
</div>
</div>
</section>
<section id="quiz">
<h2>Quiz — Data Engineering</h2>
<div id="quiz-container"></div>
<div id="quiz-score"></div>
</section>
<script>
function showTab(id){document.querySelectorAll('section').forEach(s=>s.classList.remove('active'));document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));document.getElementById(id).classList.add('active');event.target.classList.add('active')}
const quizData=[
{q:"Dans une approche ELT, où a lieu la transformation ?",opts:["Sur un serveur intermédiaire avant chargement","Directement dans l'entrepôt cible, après chargement du brut","Dans la base source","Elle n'a pas lieu"],a:1},
{q:"Pourquoi l'ELT s'est-il imposé avec le cloud ?",opts:["Le SQL a disparu","Le compute élastique des entrepôts rend rentable la transformation sur place","Les sources sont plus lentes","L'ETL est interdit"],a:1},
{q:"Qu'est-ce que l'idempotence d'un pipeline ?",opts:["La capacité à chiffrer la donnée","Rejouer l'opération avec les mêmes entrées produit le même résultat (sans doublon)","Compresser les fichiers","Supprimer la donnée"],a:1},
{q:"Que désigne le data lineage ?",opts:["La taille des fichiers","La traçabilité du parcours de la donnée (sources, transformations, tables)","Le coût du stockage","Le format Parquet"],a:1},
{q:"Dans un modèle dimensionnel en étoile, que contient la table de faits ?",opts:["Des descriptions textuelles","Les clés étrangères vers les dimensions + les mesures numériques","Uniquement des dates","La documentation"],a:1},
{q:"Quel est le principe de l'architecture médaillon ?",opts:["Trois copies identiques","Raffinage progressif bronze (brut) → silver (nettoyé) → gold (métier)","Chiffrement à trois niveaux","Sauvegarde en or"],a:1},
{q:"Qu'apporte un lakehouse par rapport à un data lake classique ?",opts:["Rien de plus","Transactions ACID, schéma et performance SQL sur le stockage objet","Il supprime le stockage objet","Il interdit le ML"],a:1},
{q:"Que fait le CDC (Change Data Capture) en mode log-based ?",opts:["Il interroge la base toutes les heures","Il lit le journal de transactions pour capturer les changements en quasi temps réel","Il copie la base entière chaque nuit","Il supprime les triggers"],a:1},
{q:"Quel est le rôle d'Apache Kafka dans un pipeline ?",opts:["Un entrepôt SQL","Un log d'événements distribué et durable (pub/sub, replay, partitions)","Un outil de BI","Un format de fichier"],a:1},
{q:"À quoi sert dbt principalement ?",opts:["Orchestrer des conteneurs","Transformer la donnée en SQL dans l'entrepôt avec dépendances, tests et lineage","Ingérer des fichiers CSV","Chiffrer les données"],a:1},
{q:"Dans Spark, pourquoi cherche-t-on à minimiser le shuffle ?",opts:["Il chiffre les données","C'est une redistribution réseau coûteuse des données (joins, groupBy)","Il supprime des partitions","Il est obligatoire à chaque action"],a:1},
{q:"Qu'est-ce qu'un DAG dans Airflow ?",opts:["Une base de données","Un graphe orienté acyclique de tâches et de leurs dépendances","Un format colonne","Un type de cluster Spark"],a:1},
{q:"Quel format de fichier est orienté colonne et optimisé pour l'analytique ?",opts:["CSV","Avro","Parquet","JSON"],a:2},
{q:"Que permet le « time travel » des table formats comme Delta ou Iceberg ?",opts:["Accélérer le réseau","Interroger l'état historique d'une table à une version/un instant donné","Supprimer définitivement la donnée","Désactiver les transactions ACID"],a:1}
];
let answers=new Array(quizData.length).fill(null);
function buildQuiz(){const c=document.getElementById('quiz-container');c.innerHTML=quizData.map((q,i)=>`<div class="quiz-q"><p>${i+1}. ${q.q}</p>${q.opts.map((o,j)=>`<div class="quiz-opt" onclick="answer(this,${i},${j})">${o}</div>`).join('')}</div>`).join('')}
function answer(el,qi,oi){const parent=el.parentElement;if(parent.querySelector('.correct,.wrong'))return;answers[qi]=oi;const correct=quizData[qi].a;parent.querySelectorAll('.quiz-opt').forEach((o,j)=>{if(j===correct)o.classList.add('correct');else if(j===oi&&oi!==correct)o.classList.add('wrong')});if(answers.every(a=>a!==null)){const score=answers.filter((a,i)=>a===quizData[i].a).length;const sc=document.getElementById('quiz-score');sc.style.display='block';sc.innerHTML=`Score : ${score}/${quizData.length} ${score>=12?'Excellent !':score>=9?'Bien !':'À revoir'}`}}
buildQuiz();
</script>
</body>
</html>