AWS Timestream: Time Series Database for IoT and DevOps Metrics (2026)
AWS Timestream is Amazon's fully managed, serverless time series database purpose-built for storing and analyzing trillions of time-stamped data points at scale. Whether you are capturing sensor readings from thousands of IoT devices, tracking application performance metrics, or storing DevOps telemetry from microservices, Timestream delivers fast ingestion, intelligent data tiering, and a powerful SQL-like query engine — all without managing infrastructure.
In this guide you will learn how Timestream works under the hood, how to create databases and tables, ingest data via Python boto3, write expressive queries using Timestream's time series functions, integrate with AWS IoT Core and Lambda, connect Grafana dashboards, configure retention policies, and understand where Timestream fits compared to InfluxDB, DynamoDB TTL, and TimescaleDB on RDS.
Table of Contents
- What is Timestream? Time Series vs Relational vs NoSQL
- Timestream Architecture: Memory Store and Magnetic Store
- Creating a Database and Table
- Writing Data: Records, Dimensions, and Measures
- Timestream Query Language
- Scheduled Queries and Derived Tables
- AWS IoT Core Integration
- Lambda Integration: Writing Metrics from Serverless Functions
- Grafana Plugin and AMG Dashboards
- Retention Policies
- Timestream vs InfluxDB vs DynamoDB TTL vs TimescaleDB
- Cost Model
1. What is Timestream? Time Series vs Relational vs NoSQL
A time series is a sequence of data points indexed by time. Temperature readings every 10 seconds, CPU utilisation sampled every minute, stock ticks arriving thousands of times per second — these datasets share a common pattern: the timestamp is the primary dimension, data arrives continuously in append-only fashion, and queries almost always involve time ranges, aggregations over windows, and trend analysis.
Relational databases like MySQL or PostgreSQL were designed for transactional workloads with random reads and writes across rows. Storing time series in RDS is possible but leads to massive table bloat, slow range scans, and painful schema management as metric names evolve. NoSQL databases like DynamoDB solve throughput problems but lack native time-series query primitives and force you to model retention and TTL manually.
Timestream fills this gap with:
- Automatic data tiering — hot data in memory store, cold data on magnetic store, no manual migration.
- Time series SQL functions —
time_series(),bin(),interpolate_linear(),ago(),date_trunc(). - Serverless scaling — no clusters to provision; throughput scales automatically.
- Native integrations — IoT Core, Kinesis Data Streams, Telegraf, Grafana, and Flink.
2. Timestream Architecture: Memory Store and Magnetic Store
Timestream uses a two-tier storage model that is transparent to the application but critical to understand for cost and performance tuning.
Memory Store
The memory store is an in-memory, SSD-backed layer optimised for high-throughput writes and low-latency reads on recent data. All ingested records land here first. You configure how long data lives in the memory store — typically between 1 hour and 8,760 hours (1 year). Memory store is the most expensive storage tier.
Magnetic Store
The magnetic store is a columnar, compressed storage layer backed by S3-compatible durable storage. When records age out of the memory store, Timestream automatically moves them to the magnetic store. Queries can seamlessly span both tiers — Timestream's query engine merges results transparently. Magnetic store costs roughly 1/10th of memory store per GB-month.
Magnetic Store Writes
As of 2024, Timestream also supports magnetic store writes — allowing you to write historical or late-arriving data directly to the magnetic store, bypassing the memory store and avoiding premium storage costs for backfill operations.
Timestream also automatically partitions data by time and dimensions, so queries with WHERE time > ago(1h) touch only the relevant partitions rather than the full dataset.
3. Creating a Database and Table
Timestream organises data into databases (logical namespaces) and tables (collections of time series records). You can create them via the AWS Console, AWS CLI, or SDK.
AWS CLI
# Create a Timestream database
aws timestream-write create-database \
--database-name IoTMetrics \
--region us-east-1
# Create a table with retention configuration
aws timestream-write create-table \
--database-name IoTMetrics \
--table-name SensorReadings \
--retention-properties \
"MemoryStoreRetentionPeriodInHours=24,MagneticStoreRetentionPeriodInDays=365" \
--magnetic-store-write-properties \
"EnableMagneticStoreWrites=true" \
--region us-east-1
Python boto3
import boto3
write_client = boto3.client('timestream-write', region_name='us-east-1')
# Create database
write_client.create_database(DatabaseName='IoTMetrics')
# Create table with memory + magnetic retention
write_client.create_table(
DatabaseName='IoTMetrics',
TableName='SensorReadings',
RetentionProperties={
'MemoryStoreRetentionPeriodInHours': 24,
'MagneticStoreRetentionPeriodInDays': 365
},
MagneticStoreWriteProperties={
'EnableMagneticStoreWrites': True
}
)
print("Database and table created successfully.")
{service}-{environment}-{metric-group} (e.g., app-prod-latency) to keep tables organised across teams.
4. Writing Data: Records, Dimensions, and Measures
Timestream records consist of three parts:
- Dimensions — metadata attributes that describe the source (device ID, region, environment). Dimensions are not aggregated but are used for filtering.
- Measures — the actual numeric or string values being tracked (temperature, CPU %, error count).
- Time — the event timestamp in milliseconds, microseconds, or nanoseconds.
Single-measure records
import boto3
import time
write_client = boto3.client('timestream-write', region_name='us-east-1')
current_time_ms = str(int(time.time() * 1000))
records = [
{
'Dimensions': [
{'Name': 'device_id', 'Value': 'sensor-001'},
{'Name': 'location', 'Value': 'warehouse-a'},
{'Name': 'region', 'Value': 'us-east-1'}
],
'MeasureName': 'temperature',
'MeasureValue': '22.7',
'MeasureValueType': 'DOUBLE',
'Time': current_time_ms
},
{
'Dimensions': [
{'Name': 'device_id', 'Value': 'sensor-001'},
{'Name': 'location', 'Value': 'warehouse-a'},
{'Name': 'region', 'Value': 'us-east-1'}
],
'MeasureName': 'humidity',
'MeasureValue': '58.3',
'MeasureValueType': 'DOUBLE',
'Time': current_time_ms
}
]
try:
result = write_client.write_records(
DatabaseName='IoTMetrics',
TableName='SensorReadings',
Records=records,
CommonAttributes={}
)
print(f"WriteRecords Status: {result['ResponseMetadata']['HTTPStatusCode']}")
except write_client.exceptions.RejectedRecordsException as e:
print(f"Rejected records: {e.response['RejectedRecords']}")
Multi-measure records (recommended)
Multi-measure records allow you to write multiple measures in a single record, reducing the number of API calls and lowering ingestion costs significantly.
records_multi = [
{
'Dimensions': [
{'Name': 'device_id', 'Value': 'sensor-001'},
{'Name': 'location', 'Value': 'warehouse-a'}
],
'MeasureName': 'sensor_metrics',
'MeasureValues': [
{'Name': 'temperature', 'Value': '22.7', 'Type': 'DOUBLE'},
{'Name': 'humidity', 'Value': '58.3', 'Type': 'DOUBLE'},
{'Name': 'pressure', 'Value': '1013.2','Type': 'DOUBLE'},
{'Name': 'battery_pct', 'Value': '87', 'Type': 'BIGINT'}
],
'MeasureValueType': 'MULTI',
'Time': str(int(time.time() * 1000))
}
]
write_client.write_records(
DatabaseName='IoTMetrics',
TableName='SensorReadings',
Records=records_multi
)
5. Timestream Query Language
Timestream uses a SQL-compatible query language with extensions for time series analysis. The query endpoint is separate from the write endpoint and is accessed via boto3.client('timestream-query').
Basic time range query
SELECT device_id, AVG(temperature) AS avg_temp
FROM "IoTMetrics"."SensorReadings"
WHERE measure_name = 'temperature'
AND time > ago(1h)
GROUP BY device_id
ORDER BY avg_temp DESC
Binning with time windows
The bin() function truncates timestamps into fixed-width buckets — essential for charting and dashboarding.
SELECT bin(time, 5m) AS time_bucket,
device_id,
AVG(temperature) AS avg_temp,
MAX(temperature) AS max_temp,
MIN(temperature) AS min_temp
FROM "IoTMetrics"."SensorReadings"
WHERE measure_name = 'temperature'
AND time > ago(6h)
GROUP BY bin(time, 5m), device_id
ORDER BY time_bucket ASC
Linear interpolation for sparse data
IoT sensors sometimes miss readings. interpolate_linear() fills gaps smoothly:
SELECT device_id,
INTERPOLATE_LINEAR(
CREATE_TIME_SERIES(time, measure_value::double),
SEQUENCE(min(time), max(time), 1m)
) AS interpolated_temp
FROM "IoTMetrics"."SensorReadings"
WHERE measure_name = 'temperature'
AND device_id = 'sensor-001'
AND time > ago(2h)
GROUP BY device_id
Python query execution
import boto3, json
query_client = boto3.client('timestream-query', region_name='us-east-1')
query = """
SELECT bin(time, 5m) AS bucket,
device_id,
ROUND(AVG(temperature), 2) AS avg_temp
FROM "IoTMetrics"."SensorReadings"
WHERE measure_name = 'temperature'
AND time > ago(1h)
GROUP BY bin(time, 5m), device_id
ORDER BY bucket ASC
"""
paginator = query_client.get_paginator('query')
pages = paginator.paginate(QueryString=query)
for page in pages:
columns = [c['Name'] for c in page['ColumnInfo']]
for row in page['Rows']:
values = [d.get('ScalarValue', '') for d in row['Data']]
print(dict(zip(columns, values)))
time predicate using ago() or explicit ISO timestamps. Queries without a time filter scan the full magnetic store and can be slow and expensive.
6. Scheduled Queries and Derived Tables
Scheduled queries let Timestream automatically run aggregation queries on a cron schedule and write results into a separate derived table. This is the recommended pattern for dashboard backends — pre-aggregate hourly summaries so Grafana panels query a small derived table rather than scanning billions of raw records.
import boto3
query_client = boto3.client('timestream-query', region_name='us-east-1')
scheduled_query_str = """
SELECT region,
bin(time, 1h) AS hour_bucket,
AVG(temperature) AS avg_temp,
MAX(temperature) AS max_temp
FROM "IoTMetrics"."SensorReadings"
WHERE measure_name = 'temperature'
AND time BETWEEN @scheduled_query_last_invocation_time
AND @scheduled_query_execution_time
GROUP BY region, bin(time, 1h)
"""
response = query_client.create_scheduled_query(
Name='HourlySensorAggregation',
QueryString=scheduled_query_str,
ScheduleConfiguration={
'ScheduleExpression': 'cron(0 * * * ? *)' # every hour
},
NotificationConfiguration={
'SnsConfiguration': {
'TopicArn': 'arn:aws:sns:us-east-1:123456789012:timestream-alerts'
}
},
TargetConfiguration={
'TimestreamConfiguration': {
'DatabaseName': 'IoTMetrics',
'TableName': 'SensorHourlyAggregates',
'TimeColumn': 'hour_bucket',
'DimensionMappings': [
{'Name': 'region', 'DimensionValueType': 'VARCHAR'}
],
'MultiMeasureMappings': {
'TargetMultiMeasureName': 'aggregated_metrics',
'MultiMeasureAttributeMappings': [
{'SourceColumn': 'avg_temp', 'MeasureValueType': 'DOUBLE'},
{'SourceColumn': 'max_temp', 'MeasureValueType': 'DOUBLE'}
]
}
}
},
ScheduledQueryExecutionRoleArn='arn:aws:iam::123456789012:role/TimestreamScheduledQueryRole'
)
print("Scheduled query created:", response['Arn'])
7. AWS IoT Core Integration
AWS IoT Core can route MQTT messages directly into Timestream using an IoT Rule action — no Lambda in the middle, reducing latency and cost. See our AWS IoT Core guide for the full IoT architecture context.
The IoT Rule SQL extracts fields from the MQTT payload, and the Timestream action maps them to dimensions and measures.
// IoT Rule definition (CloudFormation / CDK JSON equivalent)
{
"sql": "SELECT device_id, temperature, humidity, pressure, timestamp() AS ts FROM 'sensors/+/telemetry'",
"actions": [
{
"timestream": {
"databaseName": "IoTMetrics",
"tableName": "SensorReadings",
"dimensions": [
{
"name": "device_id",
"value": "${device_id}"
},
{
"name": "rule_name",
"value": "${topic(1)}"
}
],
"timestamp": {
"value": "${ts}",
"unit": "MILLISECONDS"
},
"roleArn": "arn:aws:iam::123456789012:role/IoTTimestreamRole"
}
}
],
"errorAction": {
"sns": {
"targetArn": "arn:aws:sns:us-east-1:123456789012:iot-errors",
"roleArn": "arn:aws:iam::123456789012:role/IoTSNSRole",
"messageFormat": "RAW"
}
}
}
timestream:WriteRecords and timestream:DescribeEndpoints permissions. Create a dedicated role rather than reusing broad execution roles.
For high-throughput streaming scenarios (thousands of devices), consider routing through Amazon Kinesis Data Streams first, then using a Kinesis Data Analytics or Flink application to batch-write to Timestream.
8. Lambda Integration: Writing Metrics from Serverless Functions
Instrumenting Lambda functions to emit custom metrics to Timestream gives you fine-grained observability beyond what CloudWatch provides — especially for business-level KPIs like order processing latency, payment success rates, and API call durations per customer tier.
import boto3
import time
import os
import json
# Initialise client outside handler for connection reuse
write_client = boto3.client('timestream-write',
region_name=os.environ.get('AWS_REGION', 'us-east-1'))
DATABASE = os.environ['TIMESTREAM_DATABASE']
TABLE = os.environ['TIMESTREAM_TABLE']
def write_metric(function_name: str, metric_name: str,
value: float, unit: str = 'DOUBLE',
extra_dimensions: dict = None):
"""Write a single metric to Timestream from a Lambda function."""
dims = [
{'Name': 'function_name', 'Value': function_name},
{'Name': 'environment', 'Value': os.environ.get('ENV', 'prod')},
{'Name': 'region', 'Value': os.environ.get('AWS_REGION', 'us-east-1')}
]
if extra_dimensions:
dims += [{'Name': k, 'Value': str(v)} for k, v in extra_dimensions.items()]
write_client.write_records(
DatabaseName=DATABASE,
TableName=TABLE,
Records=[{
'Dimensions': dims,
'MeasureName': metric_name,
'MeasureValue': str(value),
'MeasureValueType': unit,
'Time': str(int(time.time() * 1000))
}]
)
def lambda_handler(event, context):
start = time.time()
try:
# --- your business logic here ---
result = process_order(event)
duration_ms = (time.time() - start) * 1000
write_metric(
function_name=context.function_name,
metric_name='order_processing_duration_ms',
value=duration_ms,
extra_dimensions={'order_type': event.get('type', 'standard')}
)
write_metric(
function_name=context.function_name,
metric_name='order_success_count',
value=1,
unit='BIGINT'
)
return {'statusCode': 200, 'body': json.dumps(result)}
except Exception as exc:
write_metric(
function_name=context.function_name,
metric_name='order_error_count',
value=1,
unit='BIGINT',
extra_dimensions={'error_type': type(exc).__name__}
)
raise
def process_order(event):
# placeholder
return {'orderId': event.get('orderId'), 'status': 'processed'}
9. Grafana Plugin and AMG Dashboards
Amazon Managed Grafana (AMG) includes a first-party Timestream data source plugin. See our Grafana and Prometheus guide for AMG setup. Once AMG is running, connecting to Timestream takes three steps:
- In AMG, go to Configuration → Data Sources → Add data source and search for Amazon Timestream.
- Select the AWS region and choose the IAM role that AMG uses (it needs
timestream:Select*andtimestream:DescribeEndpoints). - Set the default database and table, then click Save & Test.
Sample Grafana panel query — temperature heatmap
-- Grafana variable: $__timeFilter injects the dashboard time range
SELECT bin(time, $__interval) AS time,
device_id AS metric,
AVG(temperature) AS value
FROM "IoTMetrics"."SensorReadings"
WHERE measure_name = 'temperature'
AND $__timeFilter
GROUP BY bin(time, $__interval), device_id
ORDER BY time ASC
Alerting panel query — devices exceeding threshold
SELECT device_id,
MAX(temperature) AS max_temp
FROM "IoTMetrics"."SensorReadings"
WHERE measure_name = 'temperature'
AND time > ago(5m)
GROUP BY device_id
HAVING MAX(temperature) > 40.0
ORDER BY max_temp DESC
device_id) to create multi-device dashboards where a dropdown filters all panels simultaneously.
For self-hosted Grafana, install the plugin with: grafana-cli plugins install grafana-timestream-datasource.
10. Retention Policies
Timestream retention is configured at the table level and controls how long data lives in each storage tier before being permanently deleted.
Memory store retention
Configurable from 1 hour to 8,760 hours (1 year). Data in the memory store is immediately queryable with sub-second latency. Set this to the minimum window you need for real-time alerting and dashboards — typically 24–72 hours for most IoT workloads.
Magnetic store retention
Configurable from 1 day to 73,000 days (~200 years). Data moved to the magnetic store is still fully queryable but with slightly higher latency for cold reads. For compliance-sensitive workloads, set this to 7 years (2,555 days).
# Update retention on an existing table
aws timestream-write update-table \
--database-name IoTMetrics \
--table-name SensorReadings \
--retention-properties \
"MemoryStoreRetentionPeriodInHours=48,MagneticStoreRetentionPeriodInDays=1825" \
--region us-east-1
# Python: update retention via boto3
write_client.update_table(
DatabaseName='IoTMetrics',
TableName='SensorReadings',
RetentionProperties={
'MemoryStoreRetentionPeriodInHours': 48, # 2 days hot
'MagneticStoreRetentionPeriodInDays': 1825 # 5 years cold
}
)
11. Timestream vs InfluxDB vs DynamoDB TTL vs TimescaleDB
Choosing the right time series store depends on your existing infrastructure, query complexity, and operational preferences.
| Feature | AWS Timestream | InfluxDB Cloud | DynamoDB + TTL | RDS TimescaleDB |
|---|---|---|---|---|
| Managed / Serverless | Fully serverless | Managed (SaaS) | Fully managed | Managed (RDS) |
| Query language | SQL + time functions | Flux / InfluxQL | DynamoDB API / PartiQL | Full PostgreSQL + TimescaleDB SQL |
| Auto data tiering | Memory → Magnetic (automatic) | Hot → cold (configurable) | TTL deletion only (no tiering) | Manual partitioning / compression |
| Native AWS integration | Excellent (IoT Core, Kinesis, Lambda, Grafana) | Requires connectors | Native (but not time-series optimised) | Via RDS; no IoT Core direct action |
| Write throughput | Scales automatically | Up to plan tier | Very high (WCU-based) | Bounded by RDS instance size |
| Aggregation / windowing | Native (bin, time_series, interpolate) | Native (Flux windows) | Limited; must do in application | Native (time_bucket, continuous aggregates) |
| Cost model | Per GB written + GB scanned + GB stored | Per write, storage, query tier | Per WCU/RCU + GB stored | Per RDS instance + storage |
| Best for | AWS-native IoT, DevOps, serverless metrics | Multi-cloud, Telegraf ecosystem | Simple event logs with TTL, high-throughput lookups | Complex relational + time series on PostgreSQL |
12. Cost Model
Timestream pricing has four dimensions. All prices are approximate for us-east-1 as of mid-2026 — always check the AWS pricing page for current rates.
| Cost Dimension | Unit | Approximate Price | Notes |
|---|---|---|---|
| Writes | Per million writes | ~$0.50 | Multi-measure records count as 1 write regardless of measure count — major savings over single-measure |
| Memory store storage | Per GB-hour | ~$0.036 / GB-hour | Charged while data resides in memory store. Keep memory retention short to control cost. |
| Magnetic store storage | Per GB-month | ~$0.03 / GB-month | Much cheaper than memory store. Long retention is practical here. |
| Queries | Per GB scanned | ~$0.01 / GB | Always add time predicates to minimise scan. Scheduled queries reduce ad-hoc query costs. |
Cost optimisation checklist
- Use multi-measure records — reduce write API calls by grouping measures, cutting write costs by up to 75%.
- Set short memory store retention (24–48 hours) unless you need sub-second latency on older data.
- Add time predicates to every query to avoid full magnetic store scans.
- Use scheduled queries to pre-aggregate dashboards — one scheduled query replaces hundreds of interactive scans.
- For data older than your Timestream retention window, export to S3 and query with Athena at $5/TB scanned.
- Use AWS Glue to transform and compact raw S3 exports for long-term archival.
Read Next
- AWS IoT Core: Connecting Devices and Routing Messages
- Amazon Kinesis: Real-Time Data Streaming on AWS
- Amazon Managed Grafana and Prometheus
- AWS CloudWatch: Monitoring, Logs, and Alarms
- AWS Lambda: Serverless Computing Guide
- Amazon DynamoDB: NoSQL Database Guide
- Amazon RDS: Managed Relational Database Service
- AWS Athena and Glue: Serverless Analytics