Victor Lequay — Mars 2026 — Document de travail, v3 (post revue externe)
Notre stack actuelle (datapipe + dataprovider + hemis) a été conçue pour une vision de la donnée qui ne correspond plus à nos besoins. L'audit du code existant révèle des problèmes structurels majeurs.
1. Aucune préservation des données brutes
Quand Enedis envoie des index demi-horaires en Wh, DataConverter convertit immédiatement en kWh et mappe vers FIELD_NAME.DIFF. Le XML SOAP original est jeté. Si la logique de conversion a un bug ou si les hypothèses changent, impossible de retraiter depuis la source. L'API Enedis a une fenêtre glissante de 24 mois — au-delà, la donnée source est perdue.
2. La distinction FACT/SEN n'est pas respectée
CT-Injector écrit les relevés Enedis bruts avec nature=FACT, alors que FACT signifie "valeur calculée/agrégée". Hemis écrit les mesures capteurs (SEN) ET les agrégats (FACT). Rien n'empêche d'écrire du brut comme du calculé. La distinction n'existe que par convention.
3. Deux versions de DataProvider en production
Hemis utilise 1.2.0-SNAPSHOT (Java 8), datapipe utilise 2.0.0-SNAPSHOT (Java 21). Les ontologies, politiques de rétention et signatures d'API peuvent diverger. Aucun test de compatibilité.
4. Pertes de données silencieuses
DataProvider.persistData() attrape TSDBException et InvalidParameterException en interne sans les relancer. Datapipe retourne HTTP 200. CT-Injector avance lastFetchTime. Aucune donnée écrite, aucune anomalie envoyée. Mécanisme de perte latent sans chemin de détection.
5. Double ingestion Enedis
Hemis (EnedisComManager) et CT-Injector peuvent ingérer les données Enedis pour le même bâtiment. Les deux écrivent dans nature=FACT, group=CPOW_DIFF mais avec des dataSourceId différents. Sans filtre dsid, les requêtes retournent les deux.
6. Trois instances datapipe = trois systèmes différents
datapipe.ubiant.io (DATASTORE), datapipe-rt.ubiant.io et datapipe-flexompremium (HEMIS/BRIDGES) ont des ontologies, des rétentions, et des comportements différents. Le routage dépend de custom_datapipe_url dans metainfodb, vérifié seulement par ct-injector.
7. Le dispatch crée des données dérivées indistinguables des mesures
Quand CT-Injector dispatche l'énergie d'un parent vers ses enfants au prorata de surface, les données enfants sont stockées avec les mêmes tags (FACT/CPOW_DIFF) que les mesures réelles. Aucun tag n'indique qu'il s'agit d'estimations.
8. Qualité fragmentée
Le champ RELIABILITY dans InfluxDB 1.8 et les anomalies dans GEDB (InfluxDB 2.4) sont deux signaux de qualité complètement déconnectés. Un consommateur ne peut pas corréler les deux.
9. La transformation manageCpid() est irréversible
En profil DATASTORE, CPOW + cpid=DIFF est réécrit en CPOW_DIFF + cpid=null. L'information cpid originale est perdue en base.
Inspirée de l'architecture medallion (bronze/silver/gold) mais adaptée à notre domaine.
Principe directeur : logique forte > distribution. Les trois couches sont des séparations logiques (schémas dans une même base), pas nécessairement trois services distincts dès le départ. Les microservices viendront quand le scaling l'imposera.
Fournisseurs BRUT CORRIGÉ ENRICHI
(Bronze) (Silver) (Gold)
│ │ │
Enedis ──────┐ ┌────┴─────┐ ┌─────┴──────┐ ┌─────┴──────┐
GRDF ────────┤ │ Ingestion│ │Harmonisation│ │ Synthèse │
CPCU ────────┤───▶│ Archivage│────▶│ Correction │───▶│ Agrégation │
Fraîcheur ───┤ │ Contrôle │ │ Fiabilité │ │ Prédiction │
Capteurs ────┘ │ Alerte │ │ Gap filling │ │ KPIs │
└──────────┘ └────────────┘ └────────────┘
│ │ │
Donnée brute Donnée fiable Donnée métier
(preuve légale) (exploitable) (aide décision)
Rôle : Ingérer, archiver, ne pas transformer. Source de vérité légale.
Double stockage : un TSDB seul ne suffit pas pour la preuve légale. Le Raw est composé de :
| Aspect | Détail |
|---|---|
| Ce qui entre | Relevés bruts TELS QUELS : index Wh (Enedis), m³ (GRDF), MWh (CPCU), mesures capteurs, métadonnées fournisseur |
| Payload original | Archivé en object storage (S3/MinIO WORM). Référencé par payload_ref dans la base temps. |
| Contrôles | Format uniquement : timestamp valide, valeur numérique, identifiant compteur connu. Aucune conversion. |
| Stockage | Append-only, immuable. Pas de mise à jour, pas de suppression (sauf RGPD). |
| Rétention | 5+ ans — contrainte légale pour les données de facturation |
| Alertes | Absence de données (compteur silencieux), doublon, format invalide |
Modèle de données enrichi (vs le modèle initial trop pauvre) :
CREATE TABLE raw.events (
id BIGSERIAL,
received_at TIMESTAMPTZ NOT NULL DEFAULT now(), -- quand on l'a reçu
source_timestamp TIMESTAMPTZ NOT NULL, -- timestamp du fournisseur
source_interval_start TIMESTAMPTZ, -- début de la période mesurée
source_interval_end TIMESTAMPTZ, -- fin de la période mesurée
meter_id TEXT NOT NULL, -- PRM, PCE, n° client
provider TEXT NOT NULL, -- enedis, grdf, cpcu, ...
entity_id TEXT, -- ID bâtiment/entité
value DOUBLE PRECISION NOT NULL, -- valeur brute
unit TEXT NOT NULL, -- Wh, m3, MWh, °C
quality_code TEXT, -- code qualité fournisseur (Enedis: N, Tc, Iv, Ec)
source_timezone TEXT DEFAULT 'UTC', -- fuseau du fournisseur
payload_ref TEXT, -- lien vers payload S3/MinIO
payload_hash TEXT, -- SHA-256 du payload original
parser_version TEXT, -- version du parseur utilisé
source_event_id TEXT, -- ID unique côté fournisseur si disponible
PRIMARY KEY (source_timestamp, meter_id, provider)
);
SELECT create_hypertable('raw.events', 'source_timestamp');
Rôle : Nettoyer, harmoniser, qualifier. Produire un flux exploitable avec garanties de qualité.
Principe clé : append-only versionné, pas mutable. On ne modifie jamais une valeur corrigée — on la remplace par une nouvelle version. Chaque valeur pointe vers sa source brute et la règle qui l'a produite.
| Aspect | Détail |
|---|---|
| Harmonisation temporelle | Rééchantillonnage sur pas réguliers (horaire, journalier). Stockage UTC, métadonnée du fuseau source. |
| Conversion d'unités | Wh → kWh, index → delta. Gaz : m³ → kWh via PCS versionné (table dédiée par zone/période/compteur). |
| Dédoublonnage | Détection et suppression des relevés reçus en double |
| Détection d'aberrations | Valeurs hors plage physique. Règles paramétrables par type de compteur/usage. |
| Gap filling | Interpolation linéaire pour trous courts (<24h). Au-delà : point marqué "manquant". |
| Score de fiabilité | Chaque point : 1.0 = mesuré, 0.8 = interpolé court, 0.5 = estimé, 0.0 = manquant. |
| Traçabilité | Chaque valeur pointe vers raw_event_id + rule_version. |
| Versionnement | Pas d'UPDATE. Nouvelle version avec supersedes_id + is_current. Permet le rejeu. |
| Origine | Tag explicite : measured, dispatched, interpolated, estimated. |
Modèle versionné :
CREATE TABLE corrected.values (
id BIGSERIAL,
ts TIMESTAMPTZ NOT NULL, -- timestamp harmonisé (UTC)
meter_id TEXT NOT NULL,
provider TEXT NOT NULL,
entity_id TEXT,
value DOUBLE PRECISION NOT NULL, -- valeur corrigée (kWh, kWh, °C)
unit TEXT NOT NULL,
reliability DOUBLE PRECISION NOT NULL DEFAULT 1.0,
origin TEXT NOT NULL DEFAULT 'measured', -- measured, dispatched, interpolated, estimated
raw_event_id BIGINT, -- FK vers raw.events
rule_version TEXT, -- version de la règle de correction
supersedes_id BIGINT, -- version précédente (NULL si première)
is_current BOOLEAN NOT NULL DEFAULT true,
produced_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (ts, meter_id, provider, produced_at)
);
SELECT create_hypertable('corrected.values', 'ts');
CREATE INDEX idx_corrected_current ON corrected.values (meter_id, provider, ts) WHERE is_current = true;
Conversion gaz — PCS versionné :
CREATE TABLE corrected.pcs_coefficients (
id SERIAL PRIMARY KEY,
zone TEXT NOT NULL, -- zone géographique
meter_id TEXT, -- NULL = zone entière, sinon compteur spécifique
valid_from DATE NOT NULL,
valid_to DATE,
pcs_kwh_per_m3 DOUBLE PRECISION NOT NULL, -- coefficient de conversion
source TEXT, -- GRDF bulletin, contrat, etc.
UNIQUE(zone, meter_id, valid_from)
);
Rôle : Calculer, agréger, prédire. Indicateurs métier.
Principe clé : utiliser au maximum les continuous aggregates de TimescaleDB pour les agrégations standard (SUM, AVG, MIN, MAX par jour/mois/an). Le service d'enrichissement ne gère que la logique métier complexe.
| Via continuous aggregates (SQL natif) | Via service dédié |
|---|---|
| Agrégations temporelles (jour, mois, an) | Synthèse parent/enfant (dispatch) |
| Min / max / moyenne | DJU (degrés-jour, croisement météo) |
| Sommes par groupe/nature | Prédictions (ML) |
| Anomalies fines (corrélation multi-fluides) | |
| KPIs complexes (kWh/m²/an, classe DPE) |
ACID (Atomicity, Consistency, Isolation, Durability) garantit qu'une écriture en base est soit complètement réussie, soit complètement annulée. PostgreSQL/TimescaleDB offre ces garanties. InfluxDB et ClickHouse non — une écriture peut partiellement réussir sans transaction. Pour des données append-only (Raw), c'est acceptable (on peut ré-écrire). Pour le Corrigé versionné, ACID garantit qu'un remplacement de version est atomique.
Sept moteurs évalués. Prometheus inclus pour référence.
Architecture et modèle de données
| InfluxDB 2.x | InfluxDB 3.x | TimescaleDB | ClickHouse | QuestDB | VictoriaMetrics | GreptimeDB | |
|---|---|---|---|---|---|---|---|
| Écrit en | Go | Rust | C (PostgreSQL) | C++ | Java/C++/Rust | Go | Rust |
| Modèle | Measurement + tags + fields | Measurement + tags + fields | Tables relationnelles | Tables colonnes (MergeTree) | Tables colonnes | Metric name + labels | Tables (wide events) |
| Types de valeurs | float, int, string, bool | float, int, string, bool | Tous types SQL | Tous types SQL | Tous types SQL | float64 uniquement | Tous types SQL |
| Métadonnées | Tags (string indexé) | Tags (string indexé) | Colonnes SQL | Colonnes SQL | Colonnes SQL | Labels (string) | Colonnes SQL + labels |
| Schéma | Schemaless | Schemaless | Strict (DDL) | Semi-strict | Semi-strict | Schemaless | Semi-strict |
Langage de requête
| InfluxDB 2.x | InfluxDB 3.x | TimescaleDB | ClickHouse | QuestDB | VictoriaMetrics | GreptimeDB | |
|---|---|---|---|---|---|---|---|
| Langage | Flux QL | SQL + InfluxQL + Flux | SQL standard | SQL standard | SQL étendu | MetricsQL (superset PromQL) | SQL + PromQL |
| JOINs | Non | Oui (SQL) | Oui | Oui | Oui (incluant ASOF JOIN) | Non | Oui (INNER, LEFT, RIGHT, FULL) |
| Window functions | Limité | Oui | Oui | Oui | Oui | Non | Oui |
| Sous-requêtes | Limité | Oui | Oui | Oui | Oui | Limité | Oui |
| Continuous aggregates | CQ / Tasks | Tasks | Natif (materialized) | Materialized views | Non natif | Non | Oui (Flow engine) |
| PostgreSQL wire protocol | Non | Non | Natif | Non | Oui | Non | Oui |
Performance (benchmarks TSBS IoT + benchmarks éditeurs, 2024-2025)
| Métrique | InfluxDB 2.x | InfluxDB 3.x | TimescaleDB | ClickHouse | QuestDB | VictoriaMetrics | GreptimeDB |
|---|---|---|---|---|---|---|---|
| Ingestion (pts/s) | ~330K | ~3.3M (annoncé 10x) | ~1.3M (batch) | ~3M | ~960K–12M | ~2.2M | ~700K (edge ARM), ? (server) |
| vs InfluxDB 2.x | baseline | ~10x | ~4x | ~9x | ~12-36x | ~6.7x | ? |
| Requêtes simples | Bon | Très bon (sub-10ms) | Bon | Bon | Excellent (lastpoint) | Bon | ? |
| Requêtes complexes | Flux crash parfois | Bon | 1.75–8.6x vs Influx 2 | Le plus rapide | 43–418x vs Influx 2 | Bon (MetricsQL) | ? (SQL complet) |
| RAM (ingestion) | ~20.5 GB | ? | ~10 GB | ~10 GB | ? | ~6 GB | <150 MB (edge) |
Attention : les benchmarks éditeurs sont biaisés. Les benchmarks TSBS sont plus neutres mais ne couvrent pas tous les candidats.
Stockage et compression
| Métrique | InfluxDB 2.x | InfluxDB 3.x | TimescaleDB | ClickHouse | QuestDB | VictoriaMetrics | GreptimeDB |
|---|---|---|---|---|---|---|---|
| Compression | ~3:1 | ~10:1 (Parquet) | 3:1–8:1 (natif), 10-20:1 (policies) | 10:1–30:1 (LZ4/ZSTD) | Bonne (exact ?) | ~70x vs TimescaleDB | Bonne (Parquet/S3, exact ?) |
| Octets/point | ~6-8 | ~1-2 (Parquet) | ~29 | ~1-3 | ~4-6 | ~0.4-1 | ? |
| Disque relatif | 1x (référence) | ~0.3x | 4-12x | 0.3-0.5x | ~1x | 0.15-0.3x | ? (S3 natif) |
VictoriaMetrics affiche ~0.4 octet/point. Ces chiffres viennent de VM — à vérifier sur nos données.
Garanties et fonctionnalités
| InfluxDB 2.x | InfluxDB 3.x | TimescaleDB | ClickHouse | QuestDB | VictoriaMetrics | GreptimeDB | |
|---|---|---|---|---|---|---|---|
| ACID | Non | Non | Oui | Partiel (INSERT atomique) | Non | Non | Non |
| Immuabilité | Non garanti | Parquet = immuable | Contraintes SQL | Append-only natif | Append-only natif | Append-only natif | Append-only (S3/Parquet) |
| Updates | Oui (écrase) | ? | Oui (SQL UPDATE) | Limité (expérimental) | Non | Non | ? |
| Deletes | Oui | Oui | Oui (SQL DELETE) | Bulk seulement | Range seulement | Range seulement | ? |
| Multi-tenancy | Orgs/buckets | Databases | Schemas/databases | Databases | Tables | Labels | Databases |
| Haute dispo | Enterprise | Enterprise | Réplication PG | Cluster natif | Enterprise | Cluster natif | Natif (cloud-native) |
| Rétention configurable | Oui (par bucket) | Oui | Oui (drop_chunks) | Oui (TTL) | Oui | Oui | Oui |
Opérationnel
| InfluxDB 2.x | InfluxDB 3.x | TimescaleDB | ClickHouse | QuestDB | VictoriaMetrics | GreptimeDB | |
|---|---|---|---|---|---|---|---|
| Déploiement | Binaire unique | Binaire unique | Extension PG | Cluster (complexe) | Binaire unique | Binaire unique | Binaire unique ou cluster |
| Protocoles ingestion | Line Protocol | Line Protocol | SQL INSERT | Natif, HTTP | ILP + PG wire | ILP, Prometheus, Graphite, CSV | ILP, Prometheus, OTLP, PG wire |
| Licence | MIT (v2 OSS) | Apache 2.0 (Core) | Apache 2.0 | Apache 2.0 | Apache 2.0 | Apache 2.0 | Apache 2.0 |
| Maturité | ~10 ans | ~2 ans (Rust rewrite) | ~8 ans (PG: 35 ans) | ~9 ans | ~5 ans | ~6 ans | ~3 ans (v1.0 récent) |
| Risque éditeur | Pivot v3, breaking | Stabilisation en cours | Timescale + PG communauté | Ex-Yandex, fondation, très actif | Startup (plus petit) | VM Inc (solide) | Startup jeune (Greptime Inc) |
InfluxDB 3.x — réécriture Rust avec Apache Arrow/DataFusion/Parquet. Performances annoncées impressionnantes mais produit jeune (~2 ans), breaking changes vs v2. SQL ajouté mais Flux conservé (maintenance mode). Le format Parquet le rapproche de ClickHouse. Pas recommandé pour un déploiement critique aujourd'hui. À surveiller.
Prometheus — système de collecte et d'alerting avec stockage local limité (15j par défaut). Pour le stockage longue durée → backend externe (Thanos, Cortex, VictoriaMetrics). Pertinent comme couche de collecte (Prometheus → remote_write → VM) ou pour le monitoring de notre infra.
QuestDB — sous-estimé dans les versions précédentes. Supporte les JOIN (incluant ASOF JOIN, très utile pour le time-series), le PostgreSQL wire protocol, et l'InfluxDB Line Protocol. Candidat sérieux.
ClickHouse — la formulation "non ACID" est à nuancer. Les INSERT sont atomiques dans certains cas, transactions expérimentales existantes. Pas un moteur transactionnel complet.
VictoriaMetrics / Prometheus — analyse approfondie
VM et Prometheus sont souvent proposés pour le stockage time-series. Ils ont des arguments forts : communauté massive, performances éprouvées, compression record (VM), écosystème riche (Grafana, AlertManager), et nos données sont quasi exclusivement des floats. La question mérite une réponse précise.
Ce qui fonctionne parfaitement : relevés de compteurs (kWh, m³, °C) → float64. Métadonnées (meter_id, provider, entity_id) → labels string. Ingestion via ILP ou remote_write. Compression VM imbattable. Ops simple (binaire unique).
Ce qui coince pour notre cas d'usage :
Pas de jointures relationnelles — Le Corrigé a besoin de traçabilité : "cette valeur corrigée vient de l'événement brut X, via la règle Y, remplaçant la version Z." En MetricsQL, impossible d'exprimer un JOIN corrected↔raw. Il faudrait faire deux requêtes et joindre côté application — faisable mais fragile et lent. Si les audits de traçabilité sont rares (pas temps réel), c'est tolérable. Si c'est un besoin quotidien, non.
Pas de corrections versionnées — VM écrase un point au même timestamp pour la même métrique. Pas de supersedes_id, pas de is_current, pas d'historique de version. On perd la capacité de dire "la valeur était X, puis on l'a corrigée en Y le 15 mars avec la règle v2." Encoder la version dans les labels (rule_version="v2") est possible mais devient ingérable.
Sampling implicite dans les requêtes — VM et Prometheus peuvent silencieusement sous-échantillonner les résultats quand il y a trop de points dans la plage demandée. Pour un dashboard de monitoring, c'est transparent. Pour des données de facturation où chaque kWh compte, une approximation silencieuse est inacceptable. Contrôlable via le paramètre step, mais il faut savoir que ça existe.
Pas de continuous aggregates — TimescaleDB maintient automatiquement des vues matérialisées incrémentales (agrégations jour/mois/an mises à jour à chaque insertion). VM a du downsampling (Enterprise) et des recording rules, mais moins intégrés et moins flexibles que des agrégats SQL.
Pas de transaction outbox — Le pipeline nécessite "écrire les données brutes ET marquer pour traitement correctif dans une seule opération atomique." PostgreSQL le fait trivialement (même transaction). VM n'a aucun concept de transaction.
Conclusion : VM/Prometheus est le meilleur choix si on ne fait que du monitoring (stocker des métriques, afficher des dashboards, alerter sur des seuils). Notre cas a trois besoins que le monitoring ne couvre pas : - Traçabilité relationnelle (raw → corrected) - Corrections versionnées (historique, rejeu) - Résultats de requête exacts (pas de sampling implicite sur données facturables)
Si on accepte de renoncer à ces trois besoins, VM est le meilleur choix. Si on les garde, il faut SQL pour au moins le Corrigé/Enrichi. VM reste un excellent candidat pour le Raw seul (option E).
GreptimeDB — candidat récent (v1.0 2026, Rust, Apache 2.0) qui combine les avantages de VM et TimescaleDB : SQL complet avec JOINs, PromQL (~90% compatible), ILP natif, PostgreSQL wire protocol, continuous aggregates (Flow engine), stockage S3 natif (compute-storage separation), multi-types (pas limité à float64), edge-compatible (700K pts/s sur ARM, <150MB RAM).
C'est le seul candidat qui offre SQL + PromQL + ILP + JOINs + continuous aggregates + S3 natif dans un seul moteur. En théorie, il pourrait couvrir les trois étages sans compromis majeur — le SQL avec JOINs résout le problème de traçabilité du Corrigé, les continuous aggregates couvrent l'Enrichi, et l'ILP + S3 couvrent le Raw.
Ce qui manque : pas d'ACID (même limitation que VM/ClickHouse/QuestDB — les corrections versionnées ne sont pas atomiques), benchmarks indépendants quasi inexistants (produit trop jeune), et risque éditeur significatif (startup de ~3 ans, communauté petite). Beaucoup de "?" dans les tableaux — les performances serveur ne sont pas encore bien documentées hors du cas edge/IoT.
Verdict : le candidat le plus prometteur sur le papier. Si GreptimeDB mûrit et prouve ses performances à notre échelle, il pourrait devenir le "un seul moteur pour tout" qui rend l'option A (TimescaleDB pour tout) obsolète avec de meilleures performances. Aujourd'hui, trop jeune pour un pari en production. À évaluer via un POC si le timing le permet.
| Étage | Besoins dominants | Meilleurs candidats |
|---|---|---|
| Raw | Append-only, compression (5+ ans × scale), immuabilité, lectures simples + archivage payload | TimescaleDB + S3 (ACID + object store) ou ClickHouse/VM (compression) + S3 |
| Corrigé | ACID ou append-only versionné, SQL + jointures (traçabilité raw→corrected), continuous aggregates | TimescaleDB (seul candidat avec ACID + SQL + continuous aggregates) |
| Enrichi | Requêtes analytiques, agrégations, KPIs | TimescaleDB (continuous aggregates natifs) + service Go pour logique métier |
1 cluster PostgreSQL + TimescaleDB
├── schema raw (hypertable, compression policies)
├── schema corrected (hypertable, versionné)
├── schema enriched (continuous aggregates + tables)
└── + S3/MinIO pour les payloads bruts
| + | − |
|---|---|
| Un seul moteur | Compression Raw inférieure à VM/ClickHouse |
| SQL + jointures cross-schema | Ingestion plus lente à très haut volume |
| ACID, continuous aggregates | |
| Écosystème PostgreSQL | |
| Déjà utilisé (ct-injector-go, consent-manager) | |
| Compression policies atténuent le disque (10-20x sur données >7j) |
| + | − |
|---|---|
| Infra InfluxDB existante | Flux QL, avenir incertain |
| Pas de jointures Raw↔Corrigé | |
| Deux moteurs |
| + | − |
|---|---|
| Compression 10-30x, SQL, analytique | MergeTree complexe à opérer |
| Append-only natif | Deux moteurs |
| INSERT atomique | Courbe d'apprentissage |
| + | − |
|---|---|
| Ingestion record, SQL, ASOF JOIN | Startup (risque pérennité) |
| ILP + PG wire protocol | Écosystème plus petit |
| Simple à opérer |
| + | − |
|---|---|
| Compression record (70x vs TimescaleDB) | MetricsQL, pas SQL |
| Go natif, ILP compatible | Float64 only, modèle metrics |
| Coût stockage minimal | Moins adapté pour données facturables strictes |
| Simple à opérer |
1 cluster GreptimeDB
├── database raw (ILP ingestion, S3 backend)
├── database corrected (SQL + JOINs, Flow engine)
├── database enriched (continuous aggregates)
└── + S3/MinIO pour les payloads bruts (en plus du stockage natif S3)
| + | − |
|---|---|
| SQL + PromQL + ILP + JOINs + continuous aggregates en un seul moteur | Très jeune (v1.0, ~3 ans, startup) |
| S3 natif (compute-storage separation, coût optimisé) | Benchmarks indépendants quasi inexistants |
| Types multiples (pas limité à float64) | Pas d'ACID (corrections versionnées non atomiques) |
| PostgreSQL wire protocol | Communauté petite |
| Edge-compatible (IoT, ARM) | Risque éditeur |
Verdict : le candidat le plus complet sur le papier — il résout les limitations de VM (pas de SQL/JOINs) sans la lourdeur de TimescaleDB (compression, S3 natif). Mais trop jeune pour un pari en production aujourd'hui. Recommandation : POC pour valider les performances réelles et la stabilité avant tout engagement.
Tous les candidats sont open-source (Apache 2.0) et gratuits en self-hosted pour le core. Les features entreprise (RBAC, TLS, downsampling avancé, support) sont payantes chez QuestDB, VictoriaMetrics et ClickHouse. TimescaleDB a une licence "Timescale License" (TSL) pour la Community Edition : gratuite en self-hosted, mais interdiction de la revendre comme service managé.
Le coût réel n'est pas la licence — c'est l'infrastructure (CPU, RAM, disque).
Nous gérons des dizaines de milliers de bâtiments. Estimation pour 10 000 bâtiments, ~5 compteurs/bâtiment, relevés horaires, 5 ans de rétention sur le Raw :
| Moteur | Octets/point | Stockage Raw estimé | RAM ingestion | Coût disque SSD cloud (~10€/TB/mois) |
|---|---|---|---|---|
| TimescaleDB (sans compression) | ~29 | ~63 TB | ~10 GB | ~630€/mois |
| TimescaleDB (avec compression) | ~3-5 | ~7-11 TB | ~10 GB | ~70-110€/mois |
| InfluxDB 2.x | ~6-8 | ~13-18 TB | ~20 GB | ~130-180€/mois |
| ClickHouse | ~1-3 | ~2-7 TB | ~10 GB | ~20-70€/mois |
| QuestDB | ~4-6 | ~9-13 TB | ? | ~90-130€/mois |
| VictoriaMetrics | ~0.4-1 | ~1-2 TB | ~6 GB | ~10-20€/mois |
| GreptimeDB | ? (S3 natif) | ? (S3 = ~$23/TB/mois) | <1 GB (edge) | ? |
Ces chiffres concernent le Raw seul. Le Corrigé et l'Enrichi sont moins volumineux (données agrégées). Les octets/point sont des moyennes de benchmarks éditeurs — à valider sur nos données réelles.
À cette échelle, la compression est le facteur de coût dominant. VictoriaMetrics stocke en ~2 TB ce que TimescaleDB sans compression stocke en 63 TB. Même avec les compression policies activées (7-11 TB), la différence reste 5-10x.
Et c'est pour 10 000 bâtiments. Si on passe à 30 000 ou 50 000, les chiffres se multiplient linéairement.
| Option | Raw | Corrigé/Enrichi | Ops | Coût stockage Raw (10K bât, 5 ans) | Meilleur pour |
|---|---|---|---|---|---|
| A | TimescaleDB | TimescaleDB | ★☆☆ | ~70-110€/mois (compressé) | Simplicité, ACID, un seul moteur |
| B | InfluxDB 2.x | TimescaleDB | ★★☆ | ~130-180€/mois | Transition minimale (court terme) |
| C | ClickHouse | TimescaleDB | ★★★ | ~20-70€/mois | Analytique lourde, bonne compression |
| D | QuestDB | TimescaleDB | ★★☆ | ~90-130€/mois | Ingestion record, SQL + JOINs |
| E | VictoriaMetrics | TimescaleDB | ★★☆ | ~10-20€/mois | Coût minimal, Go natif |
| F | GreptimeDB | GreptimeDB | ★☆☆ | ? (S3 natif) | Futur potentiel — POC nécessaire |
Avec des dizaines de milliers de bâtiments, l'option A (TimescaleDB pour tout) reste viable grâce aux compression policies, mais n'est plus l'évidence. Le Raw sera le plus gros poste de stockage sur 5+ ans, et TimescaleDB est le moteur le plus gourmand en disque.
Deux approches :
Commencer simple (option A) — 1 cluster TimescaleDB, compression policies activées, ~70-110€/mois de stockage Raw. On accepte le surcoût disque en échange de la simplicité opérationnelle (un seul moteur, SQL partout, ACID). Si le coût devient un problème, on migre le Raw vers un moteur plus compact.
Optimiser dès le départ (option E ou C) — Raw sur VictoriaMetrics ou ClickHouse (~10-70€/mois), Corrigé/Enrichi sur TimescaleDB. Plus complexe à opérer (deux moteurs), mais coût de stockage 5-10x inférieur sur le long terme.
Le choix dépend de la priorité : simplicité ops vs coût stockage. À 10 000 bâtiments, la différence est de ~50-100€/mois. À 50 000 bâtiments, elle passe à ~250-500€/mois. Ce n'est pas négligeable mais ce n'est pas non plus un deal-breaker.
GreptimeDB (option F) mériterait un POC : s'il tient ses promesses, il offre la simplicité d'un seul moteur (comme A) avec le stockage S3 natif (comme E/C). Mais trop jeune pour s'engager sans validation.
Go. Cohérent avec metainfodb, gedb, ct-injector-go, consent-manager. Binaire statique, déploiement Nomad.
1 service Go (ou jobs internes SQL/cron)
│
├── Ingestion → raw.events + S3 payload
├── Correction (cron/trigger) → corrected.values
└── Enrichissement → enriched.* (continuous aggregates + jobs)
│
└── 1 cluster PostgreSQL+TimescaleDB
Pas de message broker au démarrage. LISTEN/NOTIFY n'est pas un bus fiable (limite 8KB, notifications non garanties, drops sous charge). Pattern recommandé :
- Table outbox transactionnelle : l'ingestion écrit dans raw.events ET dans raw.outbox dans la même transaction
- Consumer idempotent : le correcteur poll l'outbox, traite, marque comme fait
- NOTIFY = simple wake-up (optionnel) pour réduire la latence du polling
Référence : Transactional Outbox Pattern
Les services séparés viendront quand le scaling l'imposera — pas avant.
Aujourd'hui, l'ontologie est codée en dur dans DataSourceTypeOntology.java (~5000 lignes). C'est le cœur du couplage.
Proposition : externaliser l'ontologie dans une configuration déclarative (YAML ou table PostgreSQL) chargée par chaque étage.
data_types:
electricity_consumption:
raw_unit: Wh
corrected_unit: kWh
conversion: divide_1000
groups: [CPOW_DIFF]
retention_raw: 5y
retention_corrected: infinite
aberration_rules:
min: 0
max_delta_per_hour: 100000 # dépend du type de bâtiment
gap_fill_max_hours: 24
gas_consumption:
raw_unit: m3
corrected_unit: kWh
conversion: pcs_table # PCS versionné par zone/période, pas un simple multiply
groups: [CGAS_DIFF]
pcs_source: corrected.pcs_coefficients
...
Note gaz : la conversion m³ → kWh n'est PAS un simple multiply_pcs. Le PCS (Pouvoir Calorifique Supérieur) dépend de l'altitude, de la composition du gaz, et de la pression. Il est recalculé régulièrement par GRDF. La table pcs_coefficients doit être versionnée par zone, par période, et potentiellement par compteur.
| Aujourd'hui | Demain |
|---|---|
| InfluxDB 1.8 + Flux QL | TimescaleDB + SQL |
| Une base, tout mélangé | Trois schémas : raw, corrected, enriched |
| DataConverter jette le brut | Raw stocke la valeur originale + payload archivé en S3 |
| nature=FACT pour tout | Tags explicites : measured, dispatched, interpolated, estimated |
| RELIABILITY déconnecté de GEDB | Score de fiabilité rattaché à chaque point |
| Dispatch indistinguable des mesures | origin = 'dispatched' explicite |
| Corrections = UPDATE (écrasement) | Corrections = nouvelle version avec supersedes_id |
| SyntheticJob/PerMonthJob dans ct-injector | Continuous aggregates TimescaleDB + service enrichissement |
| CQ InfluxDB pour agrégations | Continuous aggregates TimescaleDB (natif, incrémental) |
| Ontologie codée en dur (Java 5000 lignes) | Configuration déclarative (YAML/DB) |
| dataprovider lib partagée Java 8/21 | Service(s) Go |
| Trois datapipe instances, routage fragile | Un pipeline unifié |
| PCS gaz = constante | PCS versionné par zone/période/compteur |
Pas de big bang. Migration progressive en parallèle de l'existant.
| Phase | Contenu | Impact |
|---|---|---|
| 1 | TimescaleDB + schéma raw + S3 payloads. ct-injector écrit en parallèle dans datapipe ET dans le Raw. | Aucun sur l'existant. |
| 2 | Pipeline Corrigé consomme le Raw (outbox). Validation croisée avec datapipe. | L'existant continue. |
| 3 | Continuous aggregates + service enrichissement. Remplace SyntheticJob/PerMonthJob. | Désactiver les jobs ct-injector. |
| 4 | Applications basculent vers Corrigé/Enrichi. | Reporter, MCP datapipe, chatbot. |
| 5 | Décommissionnement datapipe/dataprovider. | Extinction. |
| Date | Changement |
|---|---|
| 2026-03-24 | v1 — diagnostic + architecture trois étages + comparaison TSDB |
| 2026-03-25 | v2 — ajout QuestDB, VictoriaMetrics, InfluxDB 3.x, Prometheus. Comparaison détaillée 6 moteurs. |
| 2026-03-26 | v3 — intégration retour technique externe : pas de microservices day 1, Raw hybride (TSDB + object store), Corrigé versionné (pas mutable), outbox transactionnelle (pas LISTEN/NOTIFY), continuous aggregates pour Enrichi, PCS versionné, modèle Raw enrichi. Corrections QuestDB (JOINs), ClickHouse (ACID partiel), VictoriaMetrics (limites données facturables). |