Iceberg pour une équipe de dix
Ce qu'un format de table comme Iceberg résout réellement, ce qu'il vous oblige à exploiter, et le critère pour savoir si des fichiers Parquet versionnés par préfixe vous suffisent.
Par Elias VarenMis à jour le 7 min de lecture
Un format de table résout un problème précis : plusieurs écrivains, plusieurs moteurs, une même table posée sur un stockage objet qui ne sait pas renommer atomiquement et, selon le fournisseur, pas même refuser une écriture concurrente. Avec un seul écrivain et un seul moteur, vous n'avez pas ce problème, et vous allez pourtant en payer la solution, parce qu'il faudra faire tourner un catalogue, planifier de la compaction, expirer des snapshots et ramasser des fichiers orphelins.
Soit l'analytique d'un SaaS, avec un seul écrivain par dataset. Je recommande alors des fichiers Parquet, DuckDB et un stockage objet compatible S3, sans format de table. Une liste de conditions ferait changer cet avis, et tant qu'aucune n'est remplie, il tient. Cette liste a plus de valeur que la conclusion elle-même.
Ce qu'Iceberg ajoute au-dessus de Parquet
Un répertoire de fichiers Parquet n'est pas une table. Rien ne dit quels fichiers en font partie à un instant donné, rien n'empêche un lecteur de lister le préfixe pendant qu'un écrivain y dépose la moitié d'un lot, et le schéma est celui de chaque fichier pris isolément.
Iceberg pose une couche de métadonnées par-dessus :
catalogue ──► pointeur vers metadata.json courant
│
▼
snapshot N ──► liste de manifestes
│
▼
manifestes ──► fichiers Parquet
(+ statistiques min/max par colonne)Chaque écriture produit de nouveaux fichiers de données, de nouveaux manifestes, un nouveau fichier de métadonnées, puis demande au catalogue de basculer le pointeur de l'ancien vers le nouveau. Cette bascule est la seule opération atomique du système, et c'est le catalogue qui la garantit, le stockage objet n'y est pour rien. Si deux écrivains tentent la bascule en même temps, l'un perd et recommence.
Les fonctionnalités utiles découlent toutes de ce mécanisme :
- Des commits atomiques. Un lecteur voit le snapshot N ou le snapshot N+1, jamais un état intermédiaire.
- Des écrivains concurrents, arbitrés par verrouillage optimiste sur le pointeur.
- Une évolution de schéma sûre. Les colonnes sont suivies par identifiant et non par nom, si bien que renommer une colonne ne casse pas les anciens fichiers.
- Des suppressions et mises à jour ligne à ligne, par réécriture de fichiers ou par fichiers de suppression associés.
- Un partitionnement que le lecteur n'a pas à connaître, et qui peut changer sans réécrire l'historique.
- Le retour à un snapshot antérieur, tant qu'il n'a pas été expiré.
Rien de tout cela n'est cosmétique. La bonne question est de savoir lesquelles de ces lignes vous manquent aujourd'hui.
L'entretien qu'une table Iceberg exige
La documentation décrit le format. Elle insiste moins sur le fait qu'une table Iceberg est un système qui se dégrade si personne ne l'entretient.
Le catalogue. C'est un service avec un état, dont dépend la cohérence de toutes vos tables. Il lui faut une base, des sauvegardes, des montées de version, une authentification. Perdu sans sauvegarde, il vous laisse des fichiers intacts dont vous ne savez plus lesquels composent quelle table.
La compaction. Chaque commit ajoute des fichiers. Une ingestion qui écrit toutes les minutes produit 1 440 commits par jour ; à quelques fichiers par commit, on passe le million de petits fichiers en moins d'un an. La planification d'une requête doit alors lire des milliers de manifestes avant d'ouvrir la première donnée. Il faut donc un travail planifié qui réécrit les petits fichiers en gros, et ce travail est lui-même un écrivain concurrent qui peut entrer en conflit avec l'ingestion.
L'expiration des snapshots. Tant qu'un snapshot existe, les fichiers qu'il référence ne peuvent pas être supprimés. Sans expiration, une table réécrite chaque nuit occupe autant de fois sa taille qu'il y a de nuits. Avec une expiration trop agressive, on supprime les fichiers sous les pieds d'une requête longue.
Les fichiers orphelins. Un écrivain qui meurt entre le dépôt des fichiers et le commit laisse des objets que plus rien ne référence. Il faut un ramasse-miettes, qui ne doit pas confondre un orphelin avec un fichier en cours d'écriture.
La matrice de compatibilité. Tous les moteurs ne supportent pas toutes les versions du format, ni toutes les opérations. Lire est largement répandu ; écrire, supprimer ligne à ligne ou compacter l'est moins. Vous choisissez en réalité un couple moteur-catalogue, et chaque fonctionnalité se vérifie sur ce couple.
Dans une équipe de dix, ce propriétaire est quelqu'un qui a déjà un autre métier. Comptez honnêtement une demi-journée par mois quand tout va bien, et plusieurs jours la première fois que quelque chose casse.
Des préfixes immuables et un pointeur
Dans ce cas, le besoin réel tient en trois phrases. Un seul processus écrit chaque dataset. Les lecteurs ne doivent jamais voir un lot à moitié écrit. On doit pouvoir revenir à la version d'avant.
Des préfixes immuables et un pointeur y suffisent. Chaque exécution écrit une release complète sous un préfixe neuf, que plus personne ne modifie ensuite :
COPY (
SELECT * FROM staging_transactions
) TO 's3://lake/normalized/transactions/release=2026-10-01'
(FORMAT parquet, PARTITION_BY (annee), COMPRESSION zstd);Quand l'écriture est terminée et contrôlée, on remplace un petit objet current.json pour qu'il désigne la nouvelle release. L'écriture d'un objet unique est atomique sur un stockage compatible S3, donc le lecteur lit l'ancien pointeur ou le nouveau. Les lecteurs résolvent le pointeur, puis lisent le préfixe :
SELECT annee, count(*)
FROM read_parquet(
's3://lake/normalized/transactions/release=2026-10-01/*/*.parquet',
hive_partitioning = true
)
GROUP BY annee;Le rollback consiste à réécrire le pointeur. La purge consiste à supprimer les préfixes plus vieux que les trois dernières releases. Il n'y a ni catalogue, ni compaction, ni orphelin, puisqu'un préfixe dont le pointeur n'a jamais été basculé est par construction un déchet identifiable.
Ce montage a un coût, qu'il faut dire. Chaque release réécrit tout le dataset, ce qui reste acceptable pour quelques dizaines de gigaoctets recalculés chaque nuit et devient absurde pour un téraoctet dont un pour cent change. Il n'y a pas de suppression ligne à ligne : effacer une personne impose de régénérer une release, raison de plus pour ne pas mettre de données personnelles dans la couche analytique. Quant à l'évolution de schéma, elle repose sur une convention et non sur un mécanisme ; si une release renomme une colonne, les requêtes qui lisent deux releases à la fois cassent.
Les signaux qui justifient le format
Je passerai à un format de table le jour où l'une de ces conditions sera vraie, pas avant.
| Signal | Pourquoi les préfixes ne suffisent plus |
|---|---|
| Deux processus écrivent la même table | Un pointeur réécrit sans condition donne raison au dernier ; l'arbitrage exige une écriture conditionnelle (If-Match), que tous les stockages compatibles S3 n'offrent pas, et le code de reprise qui va avec |
| Deux moteurs différents doivent écrire | La convention de préfixe n'est connue que de votre code |
| Mises à jour ou suppressions fréquentes sur une grosse table | Réécrire la release entière coûte plus cher que la maintenance du format |
| Ingestion continue, avec lecture cohérente pendant l'écriture | Une release par minute n'a pas de sens |
| Le partitionnement doit changer sans tout réécrire | Le chemin Hive fige le partitionnement dans le nom des fichiers |
D'autres arguments reviennent souvent et ne pèsent rien. Que tout le monde y aille ne fait pas un besoin. « On sera prêts quand on grossira » oublie que la migration de fichiers Parquet vers une table Iceberg est l'une des plus simples qui soient : les fichiers de données restent où ils sont, on leur ajoute des métadonnées, avec un mapping de noms puisque vos fichiers Parquet ne portent pas d'identifiants de colonnes. Et le voyage dans le temps, des préfixes immuables vous le donnent déjà, en plus lisible.
Avant d'installer un catalogue
La décision se prend par écrit, et elle commence par le nombre de processus qui écrivent la même table au même moment. S'il n'y en a qu'un, aucun arbitrage n'est nécessaire. Vient ensuite la part de la table qui change à chaque exécution ; au-dessus de la moitié, la réécriture complète reste plus simple que tout le reste. Il faut aussi un nom pour la compaction et l'expiration, et quelqu'un de prévenu quand elles échouent, faute de quoi mieux vaut ne pas commencer. Le couple moteur-catalogue visé doit savoir faire, en écriture, chaque opération dont vous avez besoin, ce qui se vérifie sur une table jetable et non sur la page de présentation. Reste la perte du catalogue : si vous ne savez pas reconstruire l'état des tables sans lui, sa sauvegarde passe avant tout le reste.
Si vos réponses sont « un », « presque tout » et « personne », la suite est connue : des préfixes, un pointeur, et la liste des signaux affichée quelque part pour le jour où l'un d'eux s'allumera.
Sources et historique
- Apache Iceberg — Table Spec (atomic swap, column projection, name mapping)
- Amazon S3 User Guide — How to prevent object overwrites with conditional writes
- DuckDB — Partitioned Writes
Vérification technique : 6 octobre 2026. Une erreur ? Voici comment elle est corrigée.
À lire ensuite
Toute la rubrique DonnéesDonnées
Partitionner : quand la table unique ne suffit plus
Le partitionnement règle un problème de cycle de vie des données bien plus qu'un problème de requêtes lentes. Ce qu'il résout, ce qu'il aggrave, et le critère pour décider avant de migrer une table de production.
8 minLecture libre
Données
ALTER TABLE sans panne : la file de verrous
Pourquoi un ALTER TABLE instantané peut bloquer toute une table pendant des minutes, et le patron lock_timeout plus retry, avec les variantes de DDL en plusieurs étapes, qui permet de migrer sans fenêtre de maintenance.
7 minLecture libre
Données
Montée de version majeure de PostgreSQL sans arrêt
Comment passer d'une version majeure à la suivante par réplication logique, ce que la bascule coûte vraiment, comment garder un retour possible, et à partir de quand un simple pg_upgrade est le meilleur choix.
7 minLecture libre