Stockage time-series¶
Les bases de données relationnelles classiques s'effondrent face aux insertions IoT continues — les moteurs time-series sont conçus spécifiquement pour ce problème.
Pourquoi pas PostgreSQL ou MySQL ?¶
Une table classique INSERT INTO measures (device_id, timestamp, value) fonctionne jusqu'à quelques millions de lignes. Au-delà, les problèmes apparaissent :
- Index B-tree sur timestamp : les insertions en queue de la distribution temporelle fragmentent l'index et ralentissent les requêtes de plage.
- Vacuum PostgreSQL : les mises à jour et suppressions créent un overhead de nettoyage qui consomme du CPU en continu.
- Compression : les bases génériques ne compressent pas les données numériques en exploitant la corrélation temporelle.
Un pipeline IoT industriel génère facilement 10 millions de points par jour pour 500 capteurs actifs. Sur 1 an, c'est 3,6 milliards de lignes. Les bases time-series résolvent ce problème structurellement.
InfluxDB¶
InfluxDB est la base time-series la plus répandue dans l'IoT. Sa version 2.x introduit le langage Flux et un modèle de données unifié ; sa version 3.x (2024) migre vers un moteur colonnaire Apache Arrow.
Modèle de données¶
measurement: temperature
tags: device_id=sensor-042, site=factory-A, unit=celsius
fields: value=72.3, quality=1.0
timestamp: 2026-04-15T08:32:00Z
Les tags sont indexés (requêtes de filtrage). Les fields sont les valeurs numériques non indexées. La distinction est critique pour les performances.
Écriture en ligne protocol¶
Requête Flux — moyenne glissante 5 minutes¶
from(bucket: "iot_prod")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "temperature" and r.device_id == "sensor-042")
|> aggregateWindow(every: 5m, fn: mean, createEmpty: false)
|> yield(name: "mean_5m")
Rétention et downsampling¶
// Tâche de downsampling : agréger le raw 1s en moyenne 1min, stocker 30j
option task = {name: "downsample_1s_to_1min", every: 1m}
from(bucket: "iot_raw")
|> range(start: -task.every)
|> filter(fn: (r) => r._measurement == "temperature")
|> aggregateWindow(every: 1m, fn: mean)
|> to(bucket: "iot_1min", org: "nuevolia")
TimescaleDB¶
TimescaleDB est une extension PostgreSQL. Avantage décisif : vous gardez le SQL standard, les jointures avec des tables relationnelles, et l'écosystème PostgreSQL entier (pgAdmin, JDBC, SQLAlchemy…).
Hypertable et chunking¶
-- Créer une hypertable partitionnée par temps (chunks de 1 jour)
SELECT create_hypertable(
'temperature_readings',
'time',
chunk_time_interval => INTERVAL '1 day'
);
-- Index automatique sur (device_id, time)
CREATE INDEX ON temperature_readings (device_id, time DESC);
Compression native¶
-- Activer la compression sur les chunks de plus de 7 jours
ALTER TABLE temperature_readings SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'device_id',
timescaledb.compress_orderby = 'time DESC'
);
SELECT add_compression_policy('temperature_readings', INTERVAL '7 days');
Taux de compression observés : de 10× à 40× selon la variance des valeurs.
Continuous aggregates (materialized views temporelles)¶
-- Vue matérialisée rafraîchie automatiquement : moyenne horaire
CREATE MATERIALIZED VIEW temp_hourly
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 hour', time) AS bucket,
device_id,
AVG(value) AS avg_temp,
MIN(value) AS min_temp,
MAX(value) AS max_temp,
COUNT(*) AS nb_points
FROM temperature_readings
GROUP BY bucket, device_id;
SELECT add_continuous_aggregate_policy('temp_hourly',
start_offset => INTERVAL '2 hours',
end_offset => INTERVAL '1 minute',
schedule_interval => INTERVAL '1 minute'
);
QuestDB¶
QuestDB est une base time-series entièrement réécrite en Java/C++, orientée haute performance analytique. Elle utilise un format colonnaire sur disque (inspiré de ClickHouse) et un moteur SIMD pour les agrégations.
Points forts :
- Ingestion : 1,6 million de lignes/seconde sur un serveur standard (benchmark officiel)
- Requêtes SAMPLE BY pour les agrégations temporelles natives
- Interface SQL + REST + InfluxDB Line Protocol (compatibilité directe)
- Adapté aux workloads analytiques lourds (calculs sur historique long)
-- Requête QuestDB : déviation standard par heure sur 30 jours
SELECT
timestamp,
device_id,
avg(value) AS avg_temp,
stddev(value) AS stddev_temp
FROM temperature_readings
WHERE timestamp >= dateadd('d', -30, now())
SAMPLE BY 1h
ORDER BY timestamp;
Flux de données et rétention¶
flowchart LR
subgraph "Kafka"
KT[Topic\niot.telemetry\nrétention 48h]
end
subgraph "Base time-series"
RAW[(Bucket raw\n1s - rétention 7j)]
MIN1[(Bucket 1min\nrétention 90j)]
HOUR1[(Bucket 1h\nrétention 2 ans)]
DAILY[(Bucket 1j\nrétention illimitée)]
end
subgraph "Consommateurs"
DASH[Grafana]
ANAL[Analytique\nJupyter / Superset]
ARCH[Archive froide\nS3 / MinIO Parquet]
end
KT -->|Kafka Connect\nInfluxDB Sink| RAW
RAW -->|Tâche downsampling\n1min| MIN1
MIN1 -->|Tâche downsampling\n1h| HOUR1
HOUR1 -->|Tâche downsampling\n1j| DAILY
RAW --> ARCH
MIN1 --> DASH
HOUR1 --> DASH
DAILY --> ANAL
DAILY --> ARCH Tableau comparatif¶
| Critère | InfluxDB 2.x | TimescaleDB | QuestDB |
|---|---|---|---|
| Langage de requête | Flux (+ InfluxQL legacy) | SQL standard | SQL (extensions SAMPLE BY) |
| Intégration PostgreSQL | Non | Oui (extension native) | Non |
| Compression | Oui (TSM engine) | Oui (10-40×) | Oui (colonnaire) |
| Continuous aggregates | Oui (tâches Flux) | Oui (native) | Via vues matérialisées |
| Débit ingestion | ~500 K pts/s (single) | ~300 K pts/s (single) | ~1,6 M pts/s (single) |
| Déploiement cloud managé | InfluxDB Cloud | Timescale Cloud | Non (self-hosted) |
| Protocole ingestion | Line Protocol, REST | PostgreSQL wire | Line Protocol, REST |
| Maturité opérationnelle | Élevée (8 ans) | Très élevée (héritage PG) | Modérée (6 ans) |
| Cas d'usage idéal | IoT général, Grafana | IoT + SQL existant | Analytique haute perf |
Stratégie de rétention recommandée¶
| Résolution | Durée de rétention | Usage |
|---|---|---|
| Brut (1s à 10s) | 7 à 30 jours | Débogage, investigation incidents |
| 1 minute | 90 jours | Tableaux de bord opérationnels |
| 1 heure | 2 ans | Tendances, rapports mensuels |
| 1 jour | Illimitée | KPIs long terme, régulation |
La politique de downsampling doit être définie dès le début : il est difficile de remonter dans le temps une fois les données brutes supprimées.
Ce qu'il faut retenir¶
- Les bases time-series (InfluxDB, TimescaleDB, QuestDB) sont indispensables dès que le volume dépasse 1 million de points par jour.
- Le downsampling et les continuous aggregates réduisent le volume de 99 % tout en préservant la valeur analytique.
- TimescaleDB est le choix naturel si l'équipe maîtrise déjà PostgreSQL.
Chapitre suivant : Traitement et règles — stream processing, moteur de règles et alertes temps réel sur les flux IoT.