AWS Timestream: Time Series Database for IoT and DevOps Metrics (2026)

AWS Timestream Time Series Database

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

  1. What is Timestream? Time Series vs Relational vs NoSQL
  2. Timestream Architecture: Memory Store and Magnetic Store
  3. Creating a Database and Table
  4. Writing Data: Records, Dimensions, and Measures
  5. Timestream Query Language
  6. Scheduled Queries and Derived Tables
  7. AWS IoT Core Integration
  8. Lambda Integration: Writing Metrics from Serverless Functions
  9. Grafana Plugin and AMG Dashboards
  10. Retention Policies
  11. Timestream vs InfluxDB vs DynamoDB TTL vs TimescaleDB
  12. 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 functionstime_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.
When to choose Timestream: Pick Timestream when your data is fundamentally time-stamped, arrives at high frequency, requires time-window aggregations, and you want zero ops overhead. For complex relational joins or OLAP workloads, consider Aurora or Redshift instead.

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.

Adaptive Query Processing: Timestream's query engine uses adaptive query processing to push predicates as close to storage as possible, pruning data by time range before scanning. This makes time-range queries extremely efficient compared to full table scans.

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.")
Tip: Table names within a Timestream database must be unique. Use a naming convention like {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
)
Best practice: Always use multi-measure records for sensor or application metrics. They reduce write costs by up to 4x compared to single-measure records and simplify query patterns.

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)))
Performance tip: Always include a 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'])
Cost benefit: Scheduled queries run on a server-side compute tier billed per GB scanned. Pre-aggregating hourly can cut dashboard query costs by 95%+ compared to running raw range queries interactively.

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"
    }
  }
}
IAM note: The IoT service role needs 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'}
Lambda cold starts: The Timestream write client uses a persistent HTTPS connection to a regional endpoint. Initialising it outside the handler means connection setup happens only on cold starts, keeping warm invocation latency to under 50 ms for small record batches.

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:

  1. In AMG, go to Configuration → Data Sources → Add data source and search for Amazon Timestream.
  2. Select the AWS region and choose the IAM role that AMG uses (it needs timestream:Select* and timestream:DescribeEndpoints).
  3. 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
Dashboard tip: Use Grafana variables mapped to Timestream dimension values (e.g., 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
    }
)
Deletion is permanent: When data ages beyond the magnetic store retention period, Timestream deletes it permanently. If you need long-term archival beyond Timestream's retention, export data to S3 via scheduled queries and query it later with Athena.

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
Verdict: If your stack is AWS-native and you need zero ops, Timestream is the clear winner. If you need the full PostgreSQL feature set plus time series, TimescaleDB on RDS is compelling. For multi-cloud or the Telegraf/TICK-stack ecosystem, InfluxDB remains strong. See our DynamoDB guide and RDS guide for deeper dives on those services.

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.
Free tier: Timestream has a limited free tier (1,000 write requests/month, 1 GB memory store, 1 GB magnetic store) that is useful for development and testing but should not be relied on for production workloads.

Read Next