Files
magnus919_agent-skills/data-engineering/references/time-series-databases.md
T
Magnus HedemarkandGitHub c7c4d3b74f Port 11 methodology skills from hermes-profiles (#69)
Engineering: backend-engineering, frontend-engineering, data-engineering,
ml-engineering, platform-engineering, qa-methodology

Executive: go-to-market, legal-strategy, operational-design, org-design,
product-strategy

ml-engineering: added missing training-infrastructure.md reference
qa-methodology: added test-data-management, performance-testing,
security-testing references

All frontmatter converted to agent-skills convention.
Source: https://github.com/magnus919/hermes-profiles
2026-07-21 00:58:26 -04:00

987 lines
35 KiB
Markdown

# Time-Series Database Patterns for Data Engineering
> A thorough reference on time-series databases with a focus on InfluxDB, written for the data engineering methodology skill.
---
## Table of Contents
1. [When to Choose a Time-Series DB Over a Relational DB](#1-when-to-choose-a-time-series-db-over-a-relational-db)
2. [InfluxDB Data Model](#2-influxdb-data-model)
3. [Schema Design for Time-Series](#3-schema-design-for-time-series)
4. [Flux Query Language Fundamentals](#4-flux-query-language-fundamentals)
5. [InfluxQL vs Flux vs SQL](#5-influxql-vs-flux-vs-sql)
6. [Downsampling and Continuous Queries / Tasks](#6-downsampling-and-continuous-queries--tasks)
7. [Data Lifecycle Management](#7-data-lifecycle-management)
8. [Ingest Patterns](#8-ingest-patterns)
9. [Integration with Data Engineering Pipelines](#9-integration-with-data-engineering-pipelines)
10. [Comparison: InfluxDB vs TimescaleDB vs Prometheus vs QuestDB](#10-comparison-influxdb-vs-timescaledb-vs-prometheus-vs-questdb)
---
## 1. When to Choose a Time-Series DB Over a Relational DB
### Time-Series Data Characteristics
Not all timestamped data is time-series data. True time-series data has these properties:
- **Append-heavy**: New data points arrive continuously; updates/upserts are rare.
- **Time-ordered**: Write order closely follows the timestamp order (recent data is hot).
- **Time-centric queries**: Analysts almost always filter, aggregate, and slice by time ranges.
- **Downsampling pattern**: Old data is routinely summarized into lower-resolution rollups.
- **Immutable by nature**: Historical records are almost never modified.
### Decision Matrix
| Factor | Choose Relational (PostgreSQL/MySQL) | Choose Time-Series DB (InfluxDB/TimescaleDB) |
|---|---|---|
| Write pattern | Mixed read-write, UPDATE-heavy | Append-only streaming writes |
| Data volume | Millions of rows | Billions to trillions of data points |
| Query pattern | OLTP: single-row lookups, JOINs, transactions | Time-bucket aggregates, range scans |
| Retention | Keep everything indefinitely, DELETE rare | Auto-expire raw data after N days |
| Schema | Frequently evolving, normalized | Stable, denormalized per measurement |
| Consistency | ACID strongly required | Eventual or tunable consistency acceptable |
| Cardinality | Low (user IDs, order IDs) | Can range from low to very high (device IDs, container IDs) |
### When to Use a General-Purpose TSDB (like InfluxDB)
- **Metrics infrastructure**: Server/container CPU, memory, disk, network (DevOps/SRE).
- **IoT/IIoT sensor data**: Temperature, pressure, vibration readings at high frequency.
- **Application telemetry**: Request latencies, error rates, user counts.
- **Industrial/historian workloads**: Replacing PI System, OSIsoft, or other historians.
- **Financial tick data**: Stock trades, order book snapshots (though QuestDB may be better here).
### When to Stick with a Relational DB + Time-Series Extension
- You already have a PostgreSQL ecosystem and want to avoid another infrastructure stack.
- Your time-series data has complex relational joins (e.g., sensor metadata normalized across 5 tables).
- You need full SQL with window functions, CTEs, and transactional guarantees.
- *Solution: TimescaleDB hypertables on PostgreSQL.*
### When to Use an Analytical Columnar DB (ClickHouse) Instead
- You run large ad-hoc analytical queries on time-series data (OLAP-style).
- You need sub-second aggregation over billions of rows across many dimensions.
- Your query patterns are more "GROUP BY time, dimension" than "latest value per series."
---
## 2. InfluxDB Data Model
InfluxDB v1 and v2 share a four-component data model. InfluxDB 3 (the current recommended version) retains compatibility with this model but adds SQL-on-Parquet support.
### The Four Components
```
Measurement -> Logical table name (e.g., "cpu", "sensor_temp")
Tag set -> Indexed metadata key=value pairs (e.g., host=server01, region=us-east)
Field set -> Actual data values (e.g., temperature=98.6, cpu_usage=0.85)
Timestamp -> Nanosecond-precision Unix timestamp
```
### Line Protocol Format
This is the canonical way to write data into InfluxDB (all versions):
```
<measurement>[,<tag_key>=<tag_value>[,<tag_key>=<tag_value>...]] <field_key>=<field_value>[,<field_key>=<field_value>...] [<timestamp>]
```
**Concrete example:**
```
sensor_temp,host=server01,region=us-east temperature=98.6,humidity=0.45 1717610400000000000
```
Or in InfluxDB 3 SQL terms, the CREATE TABLE equivalent would be:
```sql
-- InfluxDB 3 uses SQL for schema management
CREATE TABLE sensor_temp (
time TIMESTAMP,
host STRING, -- tag
region STRING, -- tag
temperature DOUBLE, -- field
humidity DOUBLE -- field
);
```
### How InfluxDB Stores Data
- **Tags are indexed** — they form the *series key*. Every unique combination of measurement + tag set defines a **time series**.
- **Fields are not indexed** — querying by field values requires a full scan.
- **Timestamp** is the primary sort key within each series.
- In InfluxDB 3 (IOx engine), data is stored in **Parquet files** on object storage, with an in-memory catalog for indexing.
- In InfluxDB 1.x/2.x (TSM engine), data is stored in **TSM (Time-Structured Merge Tree)** files with an in-memory index.
### Series Cardinality
```
series cardinality = number of unique (measurement, tag set) combinations
```
**Example:** If you have measurement `cpu` with tags `host` (1000 values) and `region` (5 values), you have up to 5,000 series.
**High cardinality is the #1 performance killer in InfluxDB v1/v2 (TSM engine).** InfluxDB 3 (IOx) largely solves this by using a columnar storage engine, but high cardinality still affects memory for the catalog.
**Values that cause high cardinality and should NEVER be tags:**
- Request IDs
- Session IDs
- User IDs (if unique per user and high volume)
- Timestamps as strings
- Email addresses
- Any value with millions of unique values
---
## 3. Schema Design for Time-Series
### Measurement Design
**Rule of thumb:** One measurement per logical data source type.
```
GOOD: measurement="cpu" fields={usage_user, usage_system, usage_idle}
GOOD: measurement="memory" fields={used_bytes, free_bytes, total_bytes}
BAD: measurement="metrics" fields={cpu_user, cpu_system, mem_used, mem_free, disk_read, disk_write}
```
### Tag vs Field Decision Guide
| Put in TAGS if... | Put in FIELDS if... |
|---|---|
| Low to moderate cardinality (< 100K unique values) | The actual measured numeric value |
| You filter or GROUP BY this attribute | High cardinality (request IDs, UUIDs) |
| It's static/reusable metadata (host, region, data_center) | It's the payload/metric value itself |
| You need fast indexed lookups | It changes on every data point |
### Tag Cardinality Management
**For InfluxDB v1/v2 (TSM):**
- Keep total series cardinality under 10 million per node (hard limit ~10-20M).
- Keep per-measurement cardinality under 1 million for good performance.
- Use TSI (Time Series Index) for higher cardinality in v1.7+ but expect memory pressure.
**For InfluxDB 3 (IOx/columnar):**
- Catalog memory scales with number of unique tag values, not combinations.
- Can handle 100M+ unique series; watch catalog memory (~2-4 GB per 100M series).
**Anti-patterns to avoid:**
- Putting timestamps, dates, or high-entropy strings as tags.
- Using tags for values that change on every write.
- Over-tagging (10+ tags per measurement when 3-4 would suffice).
- Tag values that grow unboundedly (e.g., container IDs in Kubernetes).
### Retention Policies (v1) vs Buckets (v2)
**InfluxDB v1 — Retention Policies (RPs):**
```sql
-- Create a retention policy: keep data for 30 days, 1 replica
CREATE RETENTION POLICY "thirty_days" ON "mydb" DURATION 30d REPLICATION 1 DEFAULT;
```
**InfluxDB v2 — Buckets:**
```bash
# A bucket combines a database + retention policy from v1
influx bucket create --name "sensor_data_30d" --retention 30d
```
**InfluxDB 3 — Retention at Database Level:**
```bash
# InfluxDB 3 Core: retention set at database level
# Core OSS enforces a 72-hour default; Cloud Dedicated and Enterprise allow custom retention
influxdb3 create database iot_sensors_prod --retention-period 90d
```
**Best practices:**
- Use separate buckets for different retention durations.
- For long-term storage, downsample raw data into a separate measurement with longer retention.
- In InfluxDB 3, the retention period is set per database and defines how long data is kept before automatic deletion.
---
## 4. Flux Query Language Fundamentals
> **Note:** Flux was introduced with InfluxDB v2.x. InfluxDB 3 now recommends **SQL** as the primary query language. Flux is still supported in InfluxDB 2.x and for backward compatibility, but new development on InfluxDB 3 should favor SQL. This section is retained for teams maintaining v2.x workloads.
### Basic Structure
Flux is a **functional, piped-data language**. Every query is a chain of transformations with data flowing left-to-right through pipes (`|>`).
```
data_source
|> transformation_1()
|> transformation_2()
|> transformation_3()
```
### Core Functions
```flux
// 1. Define the data source and time range
from(bucket: "sensor_data")
|> range(start: -1h) // last hour of data
|> filter(fn: (r) => r._measurement == "cpu")
|> filter(fn: (r) => r._field == "usage_user")
|> filter(fn: (r) => r.host == "server01")
|> yield(name: "cpu_usage")
```
### Common Flux Patterns
**Aggregation with windowing (downsampling):**
```flux
from(bucket: "sensor_data")
|> range(start: -7d)
|> filter(fn: (r) => r._measurement == "sensor_temp")
|> aggregateWindow(every: 1h, fn: mean)
|> yield(name: "hourly_mean")
```
**Multiple aggregations in one query:**
```flux
from(bucket: "sensor_data")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "cpu" and r._field == "usage_user")
|> aggregateWindow(every: 15m, fn: mean)
|> duplicate(column: "_stop", as: "_time")
|> drop(columns: ["_start", "_stop"])
|> set(key: "_field", value: "usage_user_mean")
|> to(bucket: "downsampled_cpu")
```
**Pivoting to wide format (useful for Grafana):**
```flux
from(bucket: "sensor_data")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "cpu")
|> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
|> yield(name: "wide")
```
**Joining data streams:**
```flux
cpu = from(bucket: "sensor_data")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "cpu" and r._field == "usage_user")
mem = from(bucket: "sensor_data")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "mem" and r._field == "used_percent")
join(tables: {cpu: cpu, mem: mem}, on: ["_time", "host"])
|> yield(name: "joined")
```
### Flux Task (recurring script)
```flux
// Run every hour
option task = {
name: "downsample_cpu_hourly",
every: 1h,
offset: 5m
}
from(bucket: "raw_sensor_data")
|> range(start: -2h)
|> filter(fn: (r) => r._measurement == "cpu")
|> aggregateWindow(every: 1h, fn: mean)
|> to(bucket: "downsampled_cpu")
```
---
## 5. InfluxQL vs Flux vs SQL
### Overview
| Feature | InfluxQL | Flux | SQL (InfluxDB 3) |
|---|---|---|---|
| Era | InfluxDB 1.x | InfluxDB 2.x | InfluxDB 3 (current) |
| Style | SQL-like | Functional / piped | Standard SQL |
| Complexity | Low | Medium-High | Low |
| Learning curve | Easy (if you know SQL) | Steep | Easy (if you know SQL) |
| Multi-bucket queries | No | Yes | Yes |
| Joins | Limited (subqueries only) | Native | Full SQL JOINs |
| Scripting | No | Yes (variables, conditionals, functions) | Via SQL functions |
| Window functions | Limited | Native | Yes |
| Performance | Good | Medium (interpreted) | Best (compiled) |
| Status in InfluxDB 3 | Read-only compatibility | Supported for compatibility | **Recommended** |
### When to Use Which
```
Use SQL (InfluxDB 3): Default for ALL new projects on InfluxDB 3.
Use Flux (v2.x only): Existing v2.x deployments, complex transformation pipelines.
Use InfluxQL (v1.x): Existing v1.x deployments, minimal migration path.
```
### InfluxQL vs Flux: Equivalent Queries
**InfluxQL:**
```sql
SELECT mean("usage_user")
FROM "cpu"
WHERE time > now() - 1h
AND "host" = 'server01'
GROUP BY time(15m)
```
**Flux:**
```flux
from(bucket: "mydb/autogen")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "cpu" and r._field == "usage_user" and r.host == "server01")
|> aggregateWindow(every: 15m, fn: mean)
```
**SQL (InfluxDB 3):**
```sql
SELECT DATE_BIN(INTERVAL '15 minutes', time) AS bucket,
AVG(usage_user) AS avg_usage
FROM cpu
WHERE time > now() - INTERVAL '1 hour'
AND host = 'server01'
GROUP BY bucket
ORDER BY bucket;
```
### Key Migration Notes
- InfluxQL `GROUP BY time(interval)` → Flux `aggregateWindow(every: interval, fn: ...)`
- InfluxQL `INTO` (downsample+write) → Flux `... |> to(bucket: "...")`
- Continuous Queries (InfluxQL) → Tasks (Flux)
- InfluxDB 3: Rewrite InfluxQL CQs as SQL scheduled queries or use external orchestrators.
---
## 6. Downsampling and Continuous Queries / Tasks
### The Downsampling Pattern
Downsampling is the most critical data engineering pattern for time-series:
```
RAW DATA (1-second resolution, keep 30 days)
|
v [downsample every hour]
HOURLY ROLLUPS (1-minute aggregates, keep 1 year)
|
v [downsample every day]
DAILY ROLLUPS (1-hour aggregates, keep 5 years)
```
Each tier provides exponentially smaller storage footprint while preserving analytical value.
### InfluxDB 1.x: Continuous Queries (CQs)
```sql
CREATE CONTINUOUS QUERY "cq_cpu_hourly" ON "mydb"
BEGIN
SELECT mean("usage_user") AS "mean_usage"
INTO "mydb"."downsampled"."cpu_hourly"
FROM "cpu"
GROUP BY time(1h), "host"
END;
```
CQs run automatically at the end of each time window. They are simple but limited: only one aggregation function per CQ, no chaining.
### InfluxDB 2.x: Tasks (Flux-based)
```flux
// downsample_cpu_hourly
option task = {
name: "downsample_cpu_hourly",
every: 1h,
offset: 10m
}
from(bucket: "raw_data")
|> range(start: -task.every)
|> filter(fn: (r) => r._measurement == "cpu")
|> aggregateWindow(every: 1h, fn: mean)
|> set(key: "_measurement", value: "cpu_hourly")
|> to(bucket: "downsampled_data")
```
**Chained (hierarchical) downsampling with tasks:**
```
Task 1: raw_1s -> hourly (runs every hour)
Task 2: hourly -> daily (runs every day, queries the hourly bucket)
Task 3: daily -> monthly (runs monthly, queries the daily bucket)
```
### InfluxDB 3: Scheduled Queries / External Orchestration
InfluxDB 3 Core does not have built-in continuous aggregates. Recommended approaches:
1. **External scheduler** (Airflow, cron, Prefect):
```sql
-- Run hourly via Airflow
INSERT INTO cpu_hourly
SELECT DATE_BIN(INTERVAL '1 hour', time) AS bucket,
host,
AVG(usage_user) AS avg_usage,
MIN(usage_user) AS min_usage,
MAX(usage_user) AS max_usage,
COUNT(*) AS sample_count
FROM cpu
WHERE time > now() - INTERVAL '2 hours'
GROUP BY bucket, host;
```
2. **InfluxDB 3 processing engine plugins** for real-time transformations on write.
3. **Write-time processing** via Telegraf aggregator plugins:
```toml
[[processors.aggregate]]
period = "60s"
[[processors.aggregate.config]]
measurement = "cpu"
columns = ["usage_user", "usage_system"]
functions = ["mean", "max", "min"]
```
### Downsampling Best Practices
- Store raw data at full resolution for the shortest practical window.
- Always include `COUNT(*)` in aggregates to track sample density.
- Use hierarchical aggregation (aggregate hourly data into daily, not raw into daily).
- Align your downsampling schedule with your retention policies.
- Consider **two-phase downsampling**: real-time via Telegraf aggregators for the first 1-5 minutes, then batch tasks for correction/backfill.
---
## 7. Data Lifecycle Management
### Storage Engine Architecture
**InfluxDB 1.x / 2.x (TSM Engine):**
- Data organized into **shards** by time range (typically 7 days).
- Each shard is a set of TSM files + Write-Ahead Log (WAL).
- **Compaction** merges smaller TSM files into larger ones, removing deleted/overwritten data.
- **Shard management**: Default 7-day shard duration; configurable.
- Memory index (in-memory) maps series keys to TSM file locations.
**InfluxDB 3 (IOx Engine):**
- Data stored as **Parquet files** in object storage (S3, local FS).
- Catalog (SQLite or PostgreSQL) tracks table/column metadata.
- **Compaction** merges Parquet files for read efficiency.
- No shards per se — data is partitioned by time but managed at the file level.
### Retention Lifecycle Strategy
```
Example: 3-tier retention for IoT sensor data
Tier 1: Raw (1-second resolution) -> 30 days -> bucket "raw_30d"
Tier 2: Hourly aggregates -> 1 year -> bucket "hourly_1y"
Tier 3: Daily aggregates -> 5 years -> bucket "daily_5y"
```
**InfluxDB v2/v3 implementation:**
1. Create three buckets with different retention periods.
2. Run downsampling tasks from raw -> hourly -> daily.
3. InfluxDB automatically deletes data older than each bucket's retention period.
### Compaction
**TSM compaction stages:**
- **Level 1** (snapshot): WAL -> TSM file (when WAL reaches threshold).
- **Level 2** (merge): 2-4 small TSM files -> 1 larger TSM file.
- **Level 3** (full): Multiple TSM files -> 1 optimized TSM file (deduplicates, removes tombstones).
- Compaction runs automatically; tune `cache-snapshot-write-cold-duration` and `compact-full-write-cold-duration` for write-heavy workloads.
**IOx/Parquet compaction:**
- Merges small Parquet files (< 100 MB) into larger ones (~100-500 MB).
- Runs automatically but can be triggered manually via API.
- Compaction also applies retention deletion.
### Shard Management (InfluxDB v1/v2)
```
Command: Effect:
ALTER RETENTION Change shard duration (default 7d)
POLICY ... DURATION
DROP SHARD Force-delete a specific shard and all its data
SHOW SHARDS List all shards with durations, sizes, status
influx_inspect Low-level TSM inspection and recovery tools
```
**Choosing shard duration:**
- Short shard duration (1-7d): More granular retention, easier to drop old data, more overhead.
- Long shard duration (1-4w): Less overhead, faster range queries, slower retention enforcement.
- Rule: shard duration should be ≤ 1/2 of your retention period for efficient expiry.
### Cold / Tiered Storage
- **InfluxDB Cloud Serverless**: Automatically tiers data to object storage.
- **InfluxDB Cloud Dedicated**: Configurable cold storage with Parquet.
- **TimescaleDB**: Native tiering to S3 via `tiering` policies.
- **InfluxDB OSS**: No built-in tiering; manage via external scripts or data migration.
---
## 8. Ingest Patterns
### Line Protocol
The core ingestion format for all InfluxDB versions.
**Format:**
```
measurement,tag1=val1,tag2=val2 field1=val1,field2=val2 timestamp
```
**Data types:**
- Tags: strings only (no quoting needed if no special chars).
- Fields: floats (default), integers (trailing `i`), strings (`"quoted"`), booleans (`t`/`f`/`true`/`false`).
- Timestamp: nanosecond epoch (default); configurable precision (s, ms, us, ns).
**Examples:**
```ini
# Float fields (default)
weather,location=us-midwest temperature=82.0 1465839830100400200
# Integer field (trailing i)
weather,location=us-midwest wind_speed=15i 1465839830100400200
# String field (double-quoted)
weather,location=us-midwest conditions="partly cloudy" 1465839830100400200
# Boolean field
weather,location=us-midwest is_raining=t 1465839830100400200
# Multiple fields
weather,location=us-midwest temperature=82.0,humidity=71.2 1465839830100400200
```
**Write via HTTP API:**
```bash
curl -X POST \
"http://localhost:8086/write?db=mydb&precision=s" \
--data-raw "weather,location=us-midwest temperature=82.0 1465839830"
```
### Batch vs Streaming Writes
| Factor | Batch | Streaming |
|---|---|---|
| Frequency | Every N seconds or N points | Every point as it arrives |
| Overhead | Low (HTTP overhead amortized) | High (per-request overhead) |
| Throughput | High (10K-100K points/s per node) | Low (1K-10K points/s per node) |
| Latency | Seconds to minutes | Sub-second |
| Use case | Backfill, batch ETL | Real-time monitoring, Telegraf |
**Best practice:** Always batch writes — send 1,000-10,000 points per HTTP request. Never send single points.
### Telegraf Agent
Telegraf is InfluxData's plugin-driven collection agent.
**Architecture:**
```
Input Plugins -> Aggregator/Processor Plugins -> Output Plugins
| | |
(CPU, disk, (aggregate, (InfluxDB,
MQTT, Kafka, transform, Prometheus,
Prometheus, enrich, file, Kafka,
syslog, SNMP, filter) CloudWatch)
Docker, k8s...)
```
**Example Telegraf config (`telegraf.conf`):**
```toml
# Global settings
[agent]
interval = "10s"
flush_interval = "10s"
metric_batch_size = 5000
# Input: CPU metrics
[[inputs.cpu]]
percpu = true
totalcpu = true
# Input: MQTT subscriber
[[inputs.mqtt_consumer]]
servers = ["tcp://broker.local:1883"]
topics = ["sensors/#"]
data_format = "json"
json_time_key = "timestamp"
json_time_format = "unix_ms"
tag_keys = ["device_id", "location"]
# Processor: apply transformation
[[processors.enum]]
[[processors.enum.mapping]]
tag = "status"
value_mappings = {online = 1, offline = 0}
# Aggregator: downsample in real-time
[[processors.aggregate]]
period = "60s"
[[processors.aggregate.config]]
measurement = "cpu"
columns = ["usage_idle", "usage_user"]
functions = ["mean", "min", "max"]
# Output: InfluxDB v2
[[outputs.influxdb_v2]]
urls = ["http://localhost:8086"]
token = "${INFLUX_TOKEN}"
organization = "myorg"
bucket = "sensor_data"
# Output: backup to file (for audit trail)
[[outputs.file]]
files = ["/var/log/telegraf_audit.log"]
data_format = "json"
```
**Telegraf best practices:**
- Use `metric_batch_size` (5,000-10,000) and `flush_interval` (5-10s) for efficient batching.
- Tag inputs with consistent metadata (data center, region, environment).
- Use processor and aggregator plugins rather than sending raw data and downsampling later.
- Set `fieldpass`/`fielddrop` on inputs to avoid collecting unused metrics.
- File-based logging output for auditability and data recovery.
### Ingest Performance Tuning
| Parameter | InfluxDB v1/v2 (TSM) | InfluxDB 3 (IOx) |
|---|---|---|
| Write batch size | 5,000-10,000 points | 10,000-50,000 points |
| HTTP workers | 8-16 | 16-64 |
| WAL flush interval | 1-10s (lower = safer) | N/A (no WAL) |
| Max points per second (single node) | 500K-1M | 3M-10M |
| Bottleneck | CPU (index updates) | Network I/O (Parquet writes) |
### Kafka Integration
**Telegraf as Kafka consumer:**
```toml
[[inputs.kafka_consumer]]
brokers = ["kafka:9092"]
topics = ["sensor_events"]
group_id = "telegraf_ingest"
data_format = "json"
consumer_fetch_min = "100KB"
consumer_fetch_default = "1MB"
max_undelivered_messages = 10000
```
**Telegraf as Kafka producer:**
```toml
[[outputs.kafka]]
brokers = ["kafka:9092"]
topic = "aggregated_metrics"
data_format = "json"
compression_codec = 2 # snappy
required_acks = -1 # all
```
---
## 9. Integration with Data Engineering Pipelines
### Apache Airflow DAG Example
```python
from airflow import DAG
from airflow.providers.http.operators.http import HttpOperator
from airflow.operators.python import PythonOperator
from datetime import datetime, timedelta
default_args = {
'owner': 'data_engineering',
'retries': 2,
'retry_delay': timedelta(minutes=5),
}
dag = DAG(
'influxdb_downsample_hourly',
schedule='0 * * * *', # Every hour
start_date=datetime(2025, 1, 1),
catchup=False,
)
def generate_line_protocol(**context):
"""Generate downsampled data from raw and write hourly rollup."""
import requests
import json
# Query raw hourly aggregates via SQL (InfluxDB 3)
raw_data = requests.get(
f"{INFLUX_HOST}/api/v3/query",
params={"db": "sensor_raw"},
headers={"Authorization": f"Bearer {INFLUX_TOKEN}"},
data={
"query": """
SELECT DATE_BIN(INTERVAL '1 hour', time) AS bucket,
host, region, AVG(temperature) AS avg_temp,
MIN(temperature) AS min_temp, MAX(temperature) AS max_temp,
COUNT(*) AS sample_count
FROM sensor_readings
WHERE time >= now() - INTERVAL '2 hours'
AND time < now() - INTERVAL '1 hour'
GROUP BY bucket, host, region
"""
}
)
# Convert to line protocol and write to hourly bucket
points = []
for row in raw_data.json():
lp = (
f"sensor_hourly,host={row['host']},region={row['region']} "
f"avg_temp={row['avg_temp']},min_temp={row['min_temp']},"
f"max_temp={row['max_temp']},sample_count={row['sample_count']}i "
f"{row['bucket']}"
)
points.append(lp)
# Batch write
requests.post(
f"{INFLUX_HOST}/api/v2/write",
params={"bucket": "sensor_hourly", "precision": "ms"},
headers={"Authorization": f"Bearer {INFLUX_TOKEN}"},
data="\n".join(points),
)
```
### Apache Spark / PySpark Integration
```python
from pyspark.sql import SparkSession
spark = SparkSession.builder.appName("influxdb_etl").getOrCreate()
# Read from InfluxDB v3 using JDBC (PostgreSQL-compatible driver)
df = spark.read \
.format("jdbc") \
.option("url", f"jdbc:postgresql://{INFLUX_HOST}:5432/mydb") \
.option("query", """
SELECT time, host, region, temperature
FROM sensor_readings
WHERE time >= now() - INTERVAL '24 hours'
""") \
.option("user", INFLUX_USER) \
.option("password", INFLUX_PASS) \
.option("driver", "org.postgresql.Driver") \
.load()
# Transform
hourly_agg = df.groupBy(
F.window("time", "1 hour").alias("bucket"),
"host", "region"
).agg(
F.avg("temperature").alias("avg_temp"),
F.min("temperature").alias("min_temp"),
F.max("temperature").alias("max_temp"),
F.count("*").alias("sample_count")
)
# Write back to InfluxDB
hourly_agg.write \
.format("jdbc") \
.option("url", f"jdbc:postgresql://{INFLUX_HOST}:5432/downsampled_db") \
.option("dbtable", "sensor_hourly") \
.option("user", INFLUX_USER) \
.option("password", INFLUX_PASS) \
.mode("append") \
.save()
```
### Grafana Integration
Grafana is the most common visualization layer for InfluxDB:
```
Grafana Data Source:
Type: InfluxDB
URL: http://influxdb:8086
Database: mydb (v1) / Organization: myorg, Bucket: sensor_data (v2/v3)
Min time interval: 10s (matches collection interval)
Version: InfluxQL (v1) / Flux (v2) / SQL (v3)
```
### ETL Pipeline Patterns
```
Pattern 1: Telegraf -> InfluxDB -> Grafana
(simplest, real-time monitoring)
Pattern 2: Sensors -> MQTT/Kafka -> Telegraf -> InfluxDB -> Grafana
(Kafka for buffering, backpressure handling)
Pattern 3: Sensors -> Kafka -> Flink/Spark -> InfluxDB -> Airflow (downsample) -> InfluxDB
(heavy stream processing + scheduled downsampling)
Pattern 4: Application -> InfluxDB -> Airflow/Spark -> Parquet -> S3/Data Lake
(time-series data lake architecture)
```
---
## 10. Comparison: InfluxDB vs TimescaleDB vs Prometheus vs QuestDB
### At a Glance
| Feature | InfluxDB 3 | TimescaleDB | Prometheus | QuestDB |
|---|---|---|---|---|
| **Type** | Purpose-built TSDB | PostgreSQL extension | Monitoring + TSDB | Purpose-built TSDB |
| **Engine** | Columnar (Parquet/IOx) | Hybrid row-columnar (Hypercore) | Custom TSDB engine | Columnar (custom) |
| **Query Language** | SQL + Flux + InfluxQL | Full SQL + PG extensions | PromQL | SQL |
| **Ingest Protocol** | Line Protocol, SQL, InfluxDB v2 API | PostgreSQL INSERT, COPY | Push via remote write | Line Protocol, InfluxDB, PostgreSQL wire |
| **Storage** | Parquet files on object storage | PostgreSQL on local/cloud disk | Local TSDB blocks | Memory-mapped files + disk |
| **Compression** | Good (Parquet) | Excellent (up to 95%) | Good (snappy) | Good |
| **High Availability** | Enterprise / Cloud (multi-node) | Streaming replication (PG built-in) | Sidecar (Thanos/Cortex) | Enterprise (pending) |
| **Clustering** | Enterprise only | PG-based | Built-in with Thanos | Enterprise |
| **Retention** | Per-database config | Per-hypertable via policies | Configurable (local) | Partition-based |
| **Continuous Aggregation** | External only (Core) | Built-in (continuous aggregates) | Recording rules | Materialized views |
| **Downsampling** | Tasks / external orchestration | Continuous aggregates | Recording rules + federation | SAMPLE BY + scheduled queries |
| **Real-time performance** | Excellent (3M+ points/s per node) | Very good (1M+ points/s) | Good (1M samples/s) | Excellent (5M+ points/s) |
| **SQL compatibility** | High (PostgreSQL-like) | **Complete (PostgreSQL)** | None (PromQL only) | High (custom SQL) |
| **Data lake export** | Native Parquet output | Via PG tools | Remote write / Thanos | Native Parquet output |
| **Best for...** | General TSDB, IoT, app metrics, edge | Teams already on PostgreSQL, need full SQL | Kubernetes monitoring, site reliability | **Lowest-latency**, financial tick data, HFT |
### Detailed Comparison
#### InfluxDB 3
- **Strengths:** Mature ecosystem (Telegraf, 300+ plugins), multiple deployment options (edge to cloud), native line protocol (de facto standard), good for heterogeneous data sources.
- **Weaknesses:** Flux deprecation path creates migration friction; Core OSS has 72h retention limit; no built-in continuous aggregates in Core; clustering only in Enterprise.
- **Best fit:** General-purpose time-series, DevOps/SRE monitoring, IoT/IIoT, replacing legacy historians.
#### TimescaleDB (Tiger Data)
- **Strengths:** Full PostgreSQL compatibility (all SQL, all PG extensions, all tools), continuous aggregates with incremental refresh, hierarchical aggregation, excellent compression (up to 95%), hypertables with automatic partitioning, mature replication/PITR/HA from PostgreSQL.
- **Weaknesses:** Slightly lower raw ingest throughput than InfluxDB 3 or QuestDB; requires PostgreSQL knowledge; not as lightweight for edge deployments; parent company rebranded to Tiger Data (some confusion).
- **Best fit:** Data teams already on PostgreSQL; workloads needing complex JOINs, transactions, or full SQL analytics; long-term historical storage.
#### Prometheus
- **Strengths:** Cloud-native standard for Kubernetes monitoring, simple operational model (single binary), PromQL is excellent for alerting and service-level metrics, pull-based model works well for dynamic infra.
- **Weaknesses:** Not a general-purpose TSDB (no SQL, no complex aggregations), limited retention (default 15d), single-node, no native HA (requires Thanos/Cortex), poor for IoT or high-cardinality label sets.
- **Best fit:** Kubernetes and container monitoring, service-level dashboards, alerting (PagerDuty/AlertManager), infra metrics.
#### QuestDB
- **Strengths:** **Highest raw ingest throughput** (claimed 5M+ points/s on single node), lowest query latency for time-bucket aggregations, designed for capital markets/finance, native InfluxDB line protocol and PostgreSQL wire protocol support, SQL-compatible, non-blocking ingestion (immutable append), parallelized and vectorized query execution.
- **Weaknesses:** Smaller ecosystem (fewer integrations, fewer client libraries), newer project (less mature), clustering is Enterprise-only, less tooling for alerting/monitoring out of the box.
- **Best fit:** Financial tick data, high-frequency trading, real-time dashboards demanding microsecond query latency, capital markets infrastructure.
### Decision Flowchart
```
Q: What infrastructure are you already running?
|
+-- PostgreSQL everywhere?
| |--> TimescaleDB (stay in PG ecosystem)
|
+-- Kubernetes / containers?
| |--> Prometheus for infra monitoring
| +--> InfluxDB for app metrics and IoT
|
+-- Need lowest possible latency (< 1ms queries)?
| |--> QuestDB (financial, HFT)
|
+-- Heterogeneous environment, edge devices, IoT?
|--> InfluxDB (Telegraf ecosystem, multiple deployment options)
Q: What query language do you need?
|--> Full SQL with JOINs, CTEs, window functions? -> TimescaleDB or InfluxDB 3
|--> Time-series specific: PromQL? -> Prometheus
|--> Time-series specific: Flux? -> InfluxDB 2.x
|--> DevOps dashboards: Grafana + search? -> Any (Grafana supports all)
Q: How much data are you ingesting?
|--> < 100K points/s -> Any
|--> 100K-1M points/s -> InfluxDB or TimescaleDB
|--> > 1M points/s -> QuestDB or InfluxDB 3
|--> > 5M points/s -> QuestDB (benchmark leader)
```
### Version Guidance (InfluxDB Specific)
| Deployment | Best For | Query Language |
|---|---|---|
| InfluxDB 3 Cloud Serverless | Rapid prototyping, variable workloads | **SQL** (recommended) |
| InfluxDB 3 Cloud Dedicated | Predictable production workloads | **SQL** (recommended) |
| InfluxDB 3 Enterprise | Self-managed HA production | **SQL** (recommended) |
| InfluxDB 3 Core | Edge, dev, prototypes | **SQL** (recommended) |
| InfluxDB OSS v2 | Existing v2.x deployments | Flux (migrate to SQL when moving to v3) |
| InfluxDB OSS v1 | Legacy, no migration budget yet | InfluxQL (migrate to SQL when possible) |
---
## Appendix: Quick Reference
### Line Protocol Cheatsheet
```
# Measurement name
weather
# Tags (comma-separated after measurement, space before fields)
weather,location=us-midwest,station=A
# Fields (comma-separated, space before timestamp)
weather temperature=82.0,humidity=71.2
# Timestamp (nanoseconds since epoch; space after fields)
weather temperature=82.0 1465839830100400200
# Data type suffixes:
# Integer: value=42i
# Float: value=3.14 (default)
# String: value="hello world"
# Boolean: value=t (t/true/True/TRUE)
# value=f (f/false/False/FALSE)
# Timestamp: value=1465839830100400200 (nanosecond)
# Escaping:
# Commas in tag values: tag=hello\,world
# Spaces in tag values: tag=hello\ world
# Equals in tag values: tag=hello\=world
# Double-quotes in string fields: field="say \"hello\""
# Backslashes: tag=path\\to\\dir
```
### Key InfluxDB v2/v3 CLI Commands
```bash
# InfluxDB v2
influx bucket create --name my_bucket --retention 30d
influx task create --file downsample.flux
influx query --file my_query.flux
# InfluxDB 3 Core
influxdb3 create database my_db --retention-period 90d
influxdb3 write --db my_db --file data.lp
influxdb3 query --db my_db "SELECT * FROM cpu WHERE time > now() - INTERVAL '1 hour'"
```
### Key TimescaleDB SQL Commands
```sql
-- Create hypertable
SELECT create_hypertable('sensor_readings', 'time');
-- Add compression
ALTER TABLE sensor_readings SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'sensor_id'
);
SELECT add_compression_policy('sensor_readings', INTERVAL '7 days');
-- Continuous aggregate
CREATE MATERIALIZED VIEW sensor_hourly
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 hour', time) AS bucket,
sensor_id,
AVG(value) AS avg_value
FROM sensor_readings
GROUP BY bucket, sensor_id;
```
---
*Generated: 2025-06-05 | InfluxDB 3 (IOx/columnar engine) is the current recommended version. Flux is supported for v2.x compatibility; new projects should prefer SQL.*