M-Bus Gateway
← Tilbage til blog
· InfluxDB· TimescaleDB· PostgreSQL· IoT· tidsserier· database· wM-Bus· sammenligning

InfluxDB vs TimescaleDB til IoT tidsseriedata — sammenligning 2025

InfluxDB vs TimescaleDB til IoT tidsseriedata: query-sprog, komprimering, SQL-kompatibilitet, skalering, pris og brug i wM-Bus gateway-platforme.

Af M-Bus Gateway

Valget mellem InfluxDB og TimescaleDB er afgørende for IoT-platformers datalagring. Her er den tekniske sammenligning med fokus på wM-Bus gateway-use cases.


Kerneforskel

InfluxDB 3.0:
  → Specialiseret tidsserie-database (ikke relationel)
  → InfluxQL / Flux query-sprog (proprietært)
  → Høj indlæsningshastighed — optimeret til sensor-streams
  → Cloud-first: InfluxDB Cloud Serverless
  → Ingen SQL — integration med eksisterende ORM'er er kompleks

TimescaleDB:
  → PostgreSQL extension — 100% SQL-kompatibel
  → Hypertables partitionerer automatisk på tid
  → Eksisterende PostgreSQL-tooling virker direkte
  → Kontinuerte aggregater: forudberegnede rollup-tabeller
  → Seamless join mod relationelle data (ejendomme, lejligheder)

Query-sprog sammenligning

-- TimescaleDB: Standard SQL
-- Gennemsnitlig HCA pr. lejlighed over seneste 7 dage

SELECT
    mi.unit_id,
    time_bucket('1 day', r.timestamp) AS day,
    AVG(r.value) AS avg_hca
FROM readings r
JOIN meter_installations mi ON r.meter_installation_id = mi.id
WHERE r.timestamp > NOW() - INTERVAL '7 days'
  AND mi.tenant_id = $1
GROUP BY mi.unit_id, day
ORDER BY day DESC;
// InfluxDB Flux — tilsvarende query
// Ingen join-mulighed mod relationelle data

from(bucket: "readings")
  |> range(start: -7d)
  |> filter(fn: (r) => r["_measurement"] == "hca")
  |> filter(fn: (r) => r["tenant_id"] == "550e8400...")
  |> aggregateWindow(every: 1d, fn: mean, createEmpty: false)
  |> yield(name: "mean")

Komprimering

TimescaleDB:
  Komprimering (column-store):
    Standard PostgreSQL row: ~200 bytes/aflæsning
    Komprimeret: ~15 bytes/aflæsning (93% reduktion)
    Aktivering: SELECT compress_chunk(chunk) for chunks > 7 dage
  
  Policy (automatisk):
    SELECT add_compression_policy('readings', INTERVAL '7 days');

InfluxDB 3.0:
  Apache Parquet column-format: typisk 85-95% komprimering
  Automatisk — ingen konfiguration nødvendig
  
Vinder: Tilnærmelsesvis ens komprimering i praksis

Skalering og indlæsningshastighed

Benchmark (wM-Bus use case):
  Ejendom med 100 lejligheder × 3 HCA pr. lejlighed = 300 målere
  Daglig indlæsning: 300 readings/dag
  10.000 ejendomme: 3.000.000 readings/dag

TimescaleDB (Hetzner CX32, 4 vCPU / 8GB):
  Indlæsning: 50.000 rows/sek via COPY — 3M/dag på 60 sek
  Query (7-dages aggregat): under 200ms med komprimering
  Disk: ~15GB efter 10 år (3M/dag × 365 × 10 × 15 bytes)

InfluxDB Cloud Serverless:
  Indlæsning: ubegrænset (serverless)
  Query: sammenlignelig ved enkle aggregater
  Pris: ~$0.004/MB indlæst + $0.01/GB lagret
  10 år disk-pris: ~$1.500/år (vs. ~$0 for TimescaleDB på eksisterende Hetzner)

Join med relationelle data

-- TimescaleDB: Direkte join er afgørende for afregning

-- Pro-rata beregning kræver:
-- reading.timestamp → meter_installation.unit_id → occupancy.start/end_date → lejer

SELECT
    o.tenant_name,
    o.start_date,
    o.end_date,
    SUM(r.value) AS total_hca
FROM readings r
JOIN meter_installations mi ON r.meter_installation_id = mi.id
JOIN units u ON mi.unit_id = u.id
JOIN occupancies o ON u.id = o.unit_id
    AND r.timestamp BETWEEN o.start_date AND COALESCE(o.end_date, NOW())
WHERE u.property_id = $1
  AND r.timestamp BETWEEN $2 AND $3
GROUP BY o.id, o.tenant_name, o.start_date, o.end_date;
InfluxDB: Ingen native join mod PostgreSQL
  → Kræver application-level join (to separate queries + Python merge)
  → Fejlbehæftet og langsommere end TimescaleDB's single query
  
Vinder: TimescaleDB (klar) — relationelle joins er kritiske for afregning

Kontinuerte aggregater

-- TimescaleDB: Forudberegnede månedlige aggregater

CREATE MATERIALIZED VIEW monthly_readings
WITH (timescaledb.continuous) AS
SELECT
    time_bucket('1 month', timestamp) AS month,
    meter_installation_id,
    AVG(value) AS avg_value,
    MAX(value) AS max_value,
    MIN(value) AS min_value,
    COUNT(*) AS reading_count
FROM readings
GROUP BY month, meter_installation_id;

-- Opdater automatisk:
SELECT add_continuous_aggregate_policy(
    'monthly_readings',
    start_offset => INTERVAL '3 months',
    end_offset => INTERVAL '1 hour',
    schedule_interval => INTERVAL '1 day'
);

-- Dashboard-query bruger aggregat (under 10ms):
SELECT month, avg_value FROM monthly_readings
WHERE meter_installation_id = $1
ORDER BY month DESC LIMIT 24;

Beslutningsguide

Brug TimescaleDB hvis:
  ✅ Du allerede bruger PostgreSQL (samme stack)
  ✅ Du har komplekse joins mod relationelle data
  ✅ Du vil bruge eksisterende ORM (SQLAlchemy/SQLModel)
  ✅ Du kører on-premise eller på en VPS
  ✅ SQL-kompetence i teamet
  ✅ GDPR kræver EU-hosting med fuld kontrol

Brug InfluxDB hvis:
  ✅ Ren sensor-stream uden relationelle joins
  ✅ Serverless-model foretrækkes
  ✅ Ekstrem skala (milliarder af datapunkter/dag)
  ✅ Team kender Flux/InfluxQL i forvejen
  ✅ Grafana som primær visualisering (InfluxDB er native Grafana-kilde)

M-Bus Gateway valg: TimescaleDB
  Årsag: Pro-rata afregning kræver joins mod occupancy-tabel.
  Hetzner hosting → ingen ekstra cloud-abonnement.
  SQLModel ORM virker direkte — ingen Flux-kompetence nødvendig.

Migration: InfluxDB til TimescaleDB

# Migrationscript: InfluxDB → TimescaleDB

from influxdb_client import InfluxDBClient
import asyncpg

async def migrate_readings(
    influx_url: str,
    influx_token: str,
    pg_dsn: str,
) -> None:
    influx = InfluxDBClient(url=influx_url, token=influx_token, org="myorg")
    pg = await asyncpg.connect(pg_dsn)

    query_api = influx.query_api()
    tables = query_api.query("""
        from(bucket: "readings")
          |> range(start: 2020-01-01T00:00:00Z)
          |> filter(fn: (r) => r["_measurement"] == "hca")
    """)

    rows = []
    for table in tables:
        for record in table.records:
            rows.append((
                record.get_time(),
                record["meter_id"],
                record.get_value(),
            ))

    # Bulk insert via COPY
    await pg.copy_records_to_table(
        "readings_migration",
        records=rows,
        columns=["timestamp", "meter_id", "value"],
    )
    await pg.close()

Konklusion

TimescaleDB er det rigtige valg for wM-Bus platforme der kræver joins mod relationelle data til afregning og pro-rata beregning. InfluxDB er overlegen ved ren sensor-streaming uden relationsdata. TimescaleDB's 93% komprimering og kontinuerte aggregater giver InfluxDB-lignende performance med fuld SQL-kompatibilitet.

Se TimescaleDB hypertable guide eller TimescaleDB aggregater guide.