Spark, Iceberg, Trino,
S3, Glue, DBeaver_
Construire une architecture moderne de traitement de données en combinant Spark, Iceberg et Trino avec AWS Glue et S3. L'objectif : créer une table compatible avec le format Iceberg, stockée sur S3, référencée dans Glue, interrogeable depuis Trino puis visualisable dans DBeaver. Le tout avec transactions ACID, snapshots et évolution de schéma.
Pourquoi ce POC
Le pattern lakehouse (lac de données + table transactionnelle) remplace progressivement les architectures Hive historiques. Ce POC illustre une mise en œuvre minimale mais réaliste, du script de création jusqu'à la requête SQL côté analyste.
Trois acteurs jouent des rôles complémentaires : Spark écrit la donnée, Iceberg garantit la cohérence transactionnelle (ACID, snapshots, évolution de schéma, partitionnement dynamique), et Trino permet l'interrogation analytique sans rejouer Spark. AWS Glue joue le rôle de catalogue partagé, S3 stocke les fichiers Parquet sous-jacents.
Vue d'ensemble de l'architecture
Glossaire pédagogique
Six briques à comprendre avant le code. Chacune a un rôle bien défini ; les emojis sont utilisés ici comme marqueurs visuels persistants pour suivre la stack de bout en bout.
AWS S3
StorageAWS Glue
CatalogueApache Iceberg
Table format- Transactions ACID
- Gestion des versions et snapshots
- Évolution de schéma sans réécriture
- Partitionnement dynamique
Apache Parquet
File formatApache Spark
ComputeS3FileIO
ConnecteurHive Metastore
LegacyComparatif Parquet vs CSV
| Format | Lecture par colonne | Compression | Typage | Évolution |
|---|---|---|---|---|
| CSV | ✗ | ✗ | ✗ | ✗ |
| Parquet | ✓ | ✓ | ✓ | ✓ |
Image mentale synthétique
Pour retenir qui fait quoi, une métaphore d'entrepôt. Chaque composant joue un rôle qu'on retrouve dans toute logistique physique : le hangar, les caisses, le registre, l'inventaire.
Script PySpark — création de la table Iceberg
Configuration complète d'une session Spark connectée à Glue en tant que
catalogue, à S3 en tant que warehouse, et embarquant tous les JARs AWS SDK
nécessaires pour que S3FileIO fonctionne avec une chaîne de credentials standard.
- Iceberg 1.5.0 → 1.9.x (branche stable 2026)
- Spark runtime 3.3 → 3.5.x
- AWS SDK v2 2.20.150 → 2.25+
- hadoop-aws 3.3.6 → 3.4.x
- Trino 447 → 460+
<votre-bucket-iceberg> dans le code —
à substituer par le nom réel de votre bucket, voir §08), des credentials AWS disponibles
via la chaîne par défaut (variables d'environnement, profil ~/.aws/credentials
ou rôle IAM si l'on tourne sur une instance EC2 / un container ECS).
from pyspark.sql import SparkSession
import os
# Variables d'environnement AWS
os.environ["AWS_REGION"] = "eu-west-3"
os.environ["AWS_DEFAULT_REGION"] = "eu-west-3"
# Configuration de la session Spark
spark = SparkSession.builder \
.appName("IcebergPOC") \
.config("spark.sql.catalog.demo", "org.apache.iceberg.spark.SparkCatalog") \
.config("spark.sql.catalog.demo.catalog-impl", "org.apache.iceberg.aws.glue.GlueCatalog") \
.config("spark.sql.catalog.demo.warehouse", "s3://<votre-bucket-iceberg>/") \
.config("spark.sql.catalog.demo.io-impl", "org.apache.iceberg.aws.s3.S3FileIO") \
.config("spark.hadoop.fs.s3a.aws.credentials.provider", "com.amazonaws.auth.DefaultAWSCredentialsProviderChain") \
.config("spark.jars.packages", ",".join([
"org.apache.iceberg:iceberg-spark-runtime-3.3_2.12:1.5.0",
"software.amazon.awssdk:s3:2.20.150",
"software.amazon.awssdk:glue:2.20.150",
"software.amazon.awssdk:sts:2.20.150",
"software.amazon.awssdk:kms:2.20.150",
"software.amazon.awssdk:dynamodb:2.20.150",
"org.apache.hadoop:hadoop-aws:3.3.6"
])) \
.getOrCreate()
# Création de la base
spark.sql("CREATE DATABASE IF NOT EXISTS demo.db")
# Création de la table
spark.sql("""
CREATE TABLE IF NOT EXISTS demo.db.person (
id INT,
nom STRING,
age INT
)
USING iceberg
""")
# Insertion de données
spark.sql("""
INSERT INTO demo.db.person VALUES
(1, 'Alice', 30),
(2, 'Bob', 25)
""")
# Lecture
spark.sql("SELECT * FROM demo.db.person").show()
spark.stop()
À l'exécution, Spark télécharge les JARs Maven, ouvre la session, crée la base via Glue, la table via Iceberg, écrit les données en Parquet sur S3, puis affiche le contenu.
software.amazon.awssdk:* pour
Iceberg/S3FileIO, mais pointe sur com.amazonaws.auth.DefaultAWSCredentialsProviderChain
qui est SDK v1. Cohabitation acceptée car hadoop-aws 3.3.x
embarque encore SDK v1. On aurait pu aligner sur SDK v2 partout via
software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider — à condition
de basculer sur hadoop-aws 3.4.x qui supporte officiellement le SDK v2. Sur ce POC,
on reste pragmatique : ça marche, c'est documenté, on note.
CREATE TABLE ci-dessus exhibe la mécanique USING iceberg mais
n'utilise aucune des fonctionnalités qui justifient Iceberg par rapport à un
bête Parquet sur S3. Pour un POC complet, on aurait pu ajouter :
-
›
Un
PARTITIONED BY (truncate(1, nom))pour montrer le partitionnement caché (hidden partitioning) — feature signature d'Iceberg -
›
Une requête time-travel après les deux INSERT :
SELECT * FROM demo.db.person FOR VERSION AS OF <snapshot_id> -
›
Une évolution de schéma à chaud :
ALTER TABLE demo.db.person ADD COLUMN email STRING, sans réécrire les données -
›
Une consultation des snapshots :
SELECT * FROM demo.db.person.snapshots
Insertion complémentaire
Iceberg gère les insertions successives sans réécrire les fichiers existants : un nouveau snapshot est créé, l'ancien reste accessible pour les requêtes time-travel.
spark.sql("""
INSERT INTO demo.db.person VALUES
(3, 'Charlie', 28),
(4, 'Diana', 32)
""")
Configuration de Trino avec Docker
Trino accède aux mêmes tables que Spark via le catalogue Glue. Aucune duplication, aucune synchronisation : un INSERT côté Spark est immédiatement visible côté Trino.
Arborescence des fichiers
config.properties
coordinator=true
node-scheduler.include-coordinator=true
http-server.http.port=8080
discovery-server.enabled=true
discovery.uri=http://localhost:8080
jvm.config
-Xmx2G
-XX:+UseG1GC
-XX:G1HeapRegionSize=32M
-XX:+ExplicitGCInvokesConcurrent
-XX:+HeapDumpOnOutOfMemoryError
-XX:+ExitOnOutOfMemoryError
node.properties
node.environment=iceberg
node.id=trino-docker
node.data-dir=/data/trino
catalog/iceberg.properties
connector.name=iceberg
iceberg.catalog.type=glue
hive.s3.region=eu-west-3
hive.s3.region
Cette propriété fonctionne mais est héritée du connecteur Hive S3 Select.
Trino moderne (460+) privilégie le natif fs.native-s3.enabled=true couplé à
s3.region=eu-west-3. On aurait pu partir directement sur le
pattern moderne ; sur ce POC en Trino 447, hive.s3.region reste accepté et largement
documenté.
docker-compose.yml
version: '3'
services:
trino:
image: trinodb/trino:447
container_name: trino
ports:
- "8888:8080"
volumes:
- ./etc:/etc/trino
environment:
AWS_REGION: eu-west-3
AWS_ACCESS_KEY_ID: " A REMPLIR "
AWS_SECRET_ACCESS_KEY: " A REMPLIR "
docker-compose.yml.
On aurait pu rendre ce POC déjà plus propre en référençant des variables :
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} avec un fichier
.env à côté du docker-compose.yml (et un .env.example
versionné, le .env ignoré par Git). Sur un poste de dev avec AWS SSO actif,
on peut aussi monter directement ~/.aws en read-only dans le
container et laisser Trino lire le profil — pas de clé à manipuler.
Lancer Trino
docker-compose up -d
Visualiser avec DBeaver
DBeaver Community Edition fournit un driver JDBC Trino par défaut. La connexion se configure
en quelques clics, puis on navigue dans le catalogue demo exactement comme sur
une base relationnelle classique.
jdbc:trino://localhost:8888
demo · Schéma : db
La table person apparaît sous demo.db. Un simple
SELECT * FROM demo.db.person renvoie les lignes insérées par Spark.
Annexe · Nettoyage S3
Pour repartir d'un environnement vierge, ce script vide le bucket utilisé comme warehouse Iceberg. À combiner avec le nettoyage Glue (annexe suivante) pour effacer également les métadonnées.
<votre-bucket-iceberg> comme placeholder.
Remplacer par le nom de votre propre bucket S3 avant exécution — sans cela,
le script échouera. Les noms de buckets S3 sont globalement uniques sur AWS : pas
possible de réutiliser un nom déjà pris par un autre compte.
import boto3
bucket_name = "<votre-bucket-iceberg>"
s3 = boto3.resource("s3")
bucket = s3.Bucket(bucket_name)
print(f"Suppression des objets dans le bucket {bucket_name}...")
deleted = bucket.objects.all().delete()
for res in deleted:
for item in res.get("Deleted", []):
print(f"Supprimé : {item['Key']}")
print("Tous les objets ont été supprimés.")
Annexe · Nettoyage Glue Catalog
Symétrique du nettoyage S3 : ce script parcourt toutes les bases du catalogue Glue
(sauf default), supprime les tables une à une, puis les bases elles-mêmes.
import boto3
glue = boto3.client("glue", region_name="eu-west-3")
databases = glue.get_databases()["DatabaseList"]
for db in databases:
db_name = db["Name"]
if db_name == "default":
continue
print(f"Suppression des tables de la base : {db_name}")
tables = glue.get_tables(DatabaseName=db_name)["TableList"]
for table in tables:
table_name = table["Name"]
print(f"Suppression table : {table_name}")
glue.delete_table(DatabaseName=db_name, Name=table_name)
print(f"Suppression base : {db_name}")
glue.delete_database(Name=db_name)
print("Nettoyage Glue terminé.")