Time-Series Databases for IoT: InfluxDB vs TimescaleDB vs AWS Timestream
Your IoT sensors generate timestamps and values — a perfect fit for a time-series database. But InfluxDB, TimescaleDB, and AWS Timestream make radically different trade-offs in query language, cost model, and operational complexity. Picking wrong costs real money and team frustration.
What Makes a Time-Series Database Different
Relational databases store data in row order. Time-series databases organize data by time and measurement, enabling:
InfluxDB (v2/v3)
InfluxDB is the most widely deployed dedicated time-series database in IoT. Its data model uses measurements, tags (indexed metadata), and fields (time-series values).
Write API:
// Node.js: write to InfluxDB v2
const { InfluxDB, Point } = require('@influxdata/influxdb-client')const client = new InfluxDB({
url: process.env.INFLUXDB_URL,
token: process.env.INFLUXDB_TOKEN,
})
const writeApi = client.getWriteApi('my-org', 'sensors', 'ms')
writeApi.useDefaultTags({ gatewayId: 'gw-001' })
function writeSensorData(deviceId, temperature, humidity, battery) {
const point = new Point('environment')
.tag('deviceId', deviceId)
.tag('location', 'building-a')
.floatField('temperature', temperature)
.floatField('humidity', humidity)
.intField('battery', battery)
writeApi.writePoint(point)
}
// Flush every 1 second or 5000 points, whichever comes first
setInterval(() => writeApi.flush(), 1000)
Flux query (InfluxDB v2):
// 15-minute average temperature for the last 24 hours
from(bucket: "sensors")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "environment" and r._field == "temperature")
|> filter(fn: (r) => r.deviceId == "sensor-42")
|> aggregateWindow(every: 15m, fn: mean, createEmpty: false)
|> yield(name: "mean")
Strengths:
Weaknesses:
Best for: Teams comfortable with a dedicated TSDB tool, needing high write throughput and complex time-series math.
TimescaleDB
TimescaleDB is PostgreSQL with time-series superpowers. If your team already knows SQL and PostgreSQL, the learning curve is near-zero.
-- Create a hypertable (auto-partitioned by time)
CREATE TABLE sensor_readings (
time TIMESTAMPTZ NOT NULL,
device_id TEXT NOT NULL,
temperature DOUBLE PRECISION,
humidity DOUBLE PRECISION,
battery INTEGER
);
SELECT create_hypertable('sensor_readings', 'time', chunk_time_interval => INTERVAL '1 day');-- Add compression (50–70% size reduction)
ALTER TABLE sensor_readings SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'device_id'
);
SELECT add_compression_policy('sensor_readings', INTERVAL '7 days');
-- Continuous aggregate: hourly averages
CREATE MATERIALIZED VIEW hourly_averages
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 hour', time) AS bucket,
device_id,
AVG(temperature) AS avg_temp,
AVG(humidity) AS avg_humidity
FROM sensor_readings
GROUP BY bucket, device_id
WITH NO DATA;
SELECT add_continuous_aggregate_policy('hourly_averages',
start_offset => INTERVAL '3 hours',
end_offset => INTERVAL '1 hour',
schedule_interval => INTERVAL '1 hour');
Query with familiar SQL:
-- 15-minute averages for the last 24 hours, with gap filling
SELECT
time_bucket_gapfill('15 minutes', time) AS bucket,
device_id,
interpolate(AVG(temperature)) AS temperature
FROM sensor_readings
WHERE device_id = 'sensor-42'
AND time > NOW() - INTERVAL '24 hours'
GROUP BY bucket, device_id
ORDER BY bucket;
Strengths:
Weaknesses:
Best for: Teams already running PostgreSQL, or systems where sensor data joins relational data frequently.
AWS Timestream
Timestream is a fully managed serverless TSDB. No servers, no disk management, automatic scaling.
// Node.js: write to AWS Timestream
const { TimestreamWriteClient, WriteRecordsCommand } = require('@aws-sdk/client-timestream-write')const client = new TimestreamWriteClient({ region: 'us-east-1' })
async function writeToTimestream(deviceId, temperature, humidity) {
const now = BigInt(Date.now()).toString()
await client.send(new WriteRecordsCommand({
DatabaseName: 'IoTDatabase',
TableName: 'SensorReadings',
CommonAttributes: {
Dimensions: [{ Name: 'deviceId', Value: deviceId }],
Time: now,
TimeUnit: 'MILLISECONDS',
},
Records: [
{ MeasureName: 'temperature', MeasureValue: String(temperature), MeasureValueType: 'DOUBLE' },
{ MeasureName: 'humidity', MeasureValue: String(humidity), MeasureValueType: 'DOUBLE' },
],
}))
}
Query (SQL-compatible):
-- 15-minute averages, Timestream syntax
SELECT
bin(time, 15m) AS bucket,
deviceId,
AVG(measure_value::double) AS avg_temperature
FROM "IoTDatabase"."SensorReadings"
WHERE measure_name = 'temperature'
AND deviceId = 'sensor-42'
AND time BETWEEN ago(24h) AND now()
GROUP BY bin(time, 15m), deviceId
ORDER BY bucket
Strengths:
Weaknesses:
Best for: AWS-native deployments, teams that want zero ops burden, and systems with manageable data volumes.
Head-to-Head Comparison
| Dimension | InfluxDB | TimescaleDB | Timestream | |-----------|----------|-------------|------------| | Write throughput | Excellent | Good | Good | | Query language | Flux (custom) | SQL | SQL-like | | Ops overhead | Medium | Medium | Zero | | Grafana support | Native | Via PostgreSQL | Plugin | | Cost at 1M readings/day | ~$50/mo (cloud) | ~$30/mo (EC2) | ~$80/mo | | Data joins (relational) | Poor | Excellent | Poor | | AWS-native | No | No | Yes |
Retention and Downsampling
All three support automatic retention, but the configuration differs significantly.
For Timestream, set it in the table configuration: memory store retention (hours), magnetic store retention (days). For InfluxDB, define retention policies in the bucket settings. For TimescaleDB, use add_retention_policy('sensor_readings', INTERVAL '90 days').
The sweet spot for most IoT deployments: raw data for 30 days, 1-hour aggregates for 1 year, 1-day aggregates forever.
For the full picture of how these databases fit into your data pipeline, see [IoT Data Pipeline: Sensor to Dashboard](/blog/iot-data-pipeline-sensor-to-dashboard).
For real-time alerting on top of this data, see [Real-Time IoT Alerting](/blog/realtime-iot-alerting-threshold-anomaly).
Need help with IoT data storage architecture? [Contact Code Caracal](/contact) — we've shipped these systems for clients across 15+ countries.