Aller au contenu

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

temperature,device_id=sensor-042,site=factory-A value=72.3,quality=1.0 1744702320000000000

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.