davidmabillot.com
pocs / spark-iceberg-trino
Code ZIP
POC · Data Lakehouse · 2025 Architecture moderne · Iceberg ❄️

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.

Apache Spark ❄️Apache Iceberg 🔎Trino ☁️AWS S3 🧪AWS Glue 📦Parquet 🐳Docker 🧭DBeaver
Public
Architecte Data · Data Engineer
Niveau
Avancé
Région AWS
eu-west-3 (Paris)
Livrables
Scripts PySpark + Docker
01 / 09

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.

Pourquoi déployer Spark et Trino ? Question légitime : les deux savent lire et écrire Iceberg sur S3 via Glue. La division du travail courante est la suivante : Spark pour les pipelines batch et la transformation lourde (ETL, agrégations, ML feature engineering), Trino pour le SQL interactif et le BI (DBeaver, Tableau, Metabase). Spark scale en cluster ; Trino est optimisé pour la latence faible sur des requêtes ad hoc. Sur un POC mono-machine la distinction s'efface — c'est l'usage en production qui la justifie.

Vue d'ensemble de l'architecture

⚡ PySpark WRITE batch · ETL 🧭 DBeaver READ SQL · BI 🔎 Trino SQL engine ❄️ Iceberg table format ACID · snapshots schema evolution ☁️ AWS S3 DATA fichiers Parquet 📦 .parquet 🧪 AWS Glue METADATA schémas · partitions 📋 registre write read JDBC data metadata WRITE READ référence interne
Fig. — Flux d'écriture (vert) et de lecture (bleu). Iceberg orchestre les références croisées entre la donnée (S3) et son catalogue (Glue).
02 / 09

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

Storage
Stockage d'objets dans le cloud, massif et économique. Iceberg y dépose ses fichiers de données au format Parquet. → Entrepôt brut, très robuste, sans organisation sémantique.
🧪

AWS Glue

Catalogue
Catalogue de métadonnées managé par AWS. Il répertorie les tables, schémas, partitions, types de colonnes, versions. → Remplace Hive Metastore : registre officiel pour Spark et Trino.
❄️

Apache Iceberg

Table format
Format de table transactionnel pour Data Lake.
  • Transactions ACID
  • Gestion des versions et snapshots
  • Évolution de schéma sans réécriture
  • Partitionnement dynamique
📦

Apache Parquet

File format
Format de fichier colonne, compressé et typé, utilisé par Iceberg. Contrairement au CSV (texte rangé en lignes), Parquet stocke les données par colonnes.

Apache Spark

Compute
Moteur de traitement distribué pour lire, transformer et écrire les données. Il crée les tables Iceberg, les peuple et les interroge en s'appuyant sur Glue et S3.
🧩

S3FileIO

Connecteur
Composant interne à Iceberg pour accéder efficacement aux fichiers dans S3. Il gère les permissions, le multipart upload et la cohérence des écritures.
📋

Hive Metastore

Legacy
Catalogue historique du monde Hadoop : une base de données (souvent MySQL ou PostgreSQL) qui répertorie tables et schémas pour Hive, Spark, Presto. → Glue est sa version managée AWS, sans serveur à maintenir.

Comparatif Parquet vs CSV

Format Lecture par colonne Compression Typage Évolution
CSV
Parquet
03 / 09

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.

☁️S3
🏢 Le hangar où l'on empile les caisses
📦Parquet
Les caisses bien rangées, en colonnes, compressées
🧪Glue
📋 Le registre qui répertorie les caisses, leur contenu, leur version
❄️Iceberg
L'inventaire intelligent qui gère tout sans perdre une vis
04 / 09

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.

Versions datées · POC réalisé en 2024-2025 Les versions ci-dessous sont celles utilisées au moment de l'écriture du POC. L'écosystème Spark/Iceberg/Trino évolue vite ; pour un nouveau projet, viser plutôt :
  • 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+
Le pattern reste identique, seuls les coordonnées Maven changent.
Pré-requis Un bucket S3 existant (référencé comme <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).
iceberg_poc.py · création + insertion
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.

Subtilité — mélange SDK v1 / SDK v2 Le script charge AWS SDK v2 via 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.
Ce que le script ne démontre pas Le 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
Le POC actuel pose les fondations ; ces quatre ajouts transformeraient un « Hello World Iceberg » en démonstration complète de la valeur ajoutée du format.
05 / 09

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.

PySpark · INSERT INTO
spark.sql("""
INSERT INTO demo.db.person VALUES
    (3, 'Charlie', 28),
    (4, 'Diana', 32)
""")
06 / 09

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

trino-iceberg/ ├── docker-compose.yml └── etc/ ├── config.properties ├── jvm.config ├── node.properties └── catalog/ └── iceberg.properties

config.properties

etc/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

etc/jvm.config
-Xmx2G
-XX:+UseG1GC
-XX:G1HeapRegionSize=32M
-XX:+ExplicitGCInvokesConcurrent
-XX:+HeapDumpOnOutOfMemoryError
-XX:+ExitOnOutOfMemoryError

node.properties

etc/node.properties
node.environment=iceberg
node.id=trino-docker
node.data-dir=/data/trino

catalog/iceberg.properties

etc/catalog/iceberg.properties
connector.name=iceberg
iceberg.catalog.type=glue
hive.s3.region=eu-west-3
Configuration legacy — 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

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 "
Sécurité Les credentials AWS sont ici injectés en variables d'environnement pour la démo. En production, passer par un rôle IAM, AWS SSO ou un secret manager — jamais des clés en clair dans 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

bash
docker-compose up -d
07 / 09

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.

Télécharger DBeaver (Community Edition suffit).
Ajouter une nouvelle connexion JDBC Trino.
URL : jdbc:trino://localhost:8888
Catalog : 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.

08 / 09

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.

Adapter le nom du bucket Le code ci-dessous utilise <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.
cleanup_s3.py
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.")
Attention Suppression non réversible. Vérifier le nom du bucket avant exécution — il n'y a pas de corbeille S3.
09 / 09

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.

cleanup_glue.py
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é.")