Embedded Time Series Database for Elixir
"I found it ironic that the first thing you do to time series data is squash the timestamp. That's how the name Timeless was born." --Mark Cotner
TimelessMetrics is an embedded time-series database for Elixir with a Rust-native hot path, built-in HTTP ingest/query APIs, retention, rollups, alerting, scraping, charts, and Prometheus/VictoriaMetrics compatibility.
It runs:
- as a library inside your Elixir application
- as a local service with the included HTTP API
- in memory-only mode for tests and constrained environments
Current Architecture
The default engine is the Rust engine.
Hot-path responsibilities live in the Rust NIF:
- labeled writes and batched writes
- raw and aggregate range queries
- series index and label filtering
- chunk persistence and recovery
Elixir still owns the surrounding product surface:
- HTTP API
- background ingest workers
- alerts, annotations, metadata, and scrape target management
- rollups, retention orchestration, charts, dashboard, and PromQL handling
If you need the detailed design, start with docs/architecture.md.
Documentation
- Getting Started
- Configuration Reference
- Architecture
- API Reference
- Alerting
- Scraping
- Annotations
- Forecasting & Anomaly Detection
- Charts & Embedding
- Grafana Integration
- Operations
- Capacity Planning
- Scaling
- Benchmarks
Highlights
- Rust-native storage engine enabled by default
- Embedded Elixir API plus optional HTTP API
- Prometheus text ingest, VictoriaMetrics JSON-line ingest, and Influx line protocol ingest
- Prometheus-compatible query endpoints for Grafana
- Fast batched writes and compact on-disk chunks
- Built-in dashboard, SVG charts, annotations, forecasting, anomaly detection, and alerts
- Scraping subsystem for pulling Prometheus targets into the local store
- Memory-only mode for ephemeral deployments and tests
Quick Start
Add to mix.exs:
{:timeless_metrics, "~> 6.0"}Add to your supervision tree:
children = [
{TimelessMetrics, name: :metrics, data_dir: "/var/lib/metrics"},
{TimelessMetrics.HTTP, store: :metrics, port: 8428}
]Write and query:
TimelessMetrics.write(:metrics, "cpu_usage", %{"host" => "web-1"}, 73.2)
{:ok, points} =
TimelessMetrics.query(:metrics, "cpu_usage", %{"host" => "web-1"},
from: System.os_time(:second) - 3600,
to: System.os_time(:second)
)Memory-only mode:
children = [
{TimelessMetrics, name: :metrics, mode: :memory},
{TimelessMetrics.HTTP, store: :metrics, port: 8428}
]Elixir API
Writes:
TimelessMetrics.write(:metrics, "cpu_usage", %{"host" => "web-1"}, 73.2)
TimelessMetrics.write_batch(:metrics, [
{"cpu_usage", %{"host" => "web-1"}, 73.2},
{"mem_usage", %{"host" => "web-1"}, 4096.0}
])Queries:
{:ok, points} =
TimelessMetrics.query(:metrics, "cpu_usage", %{"host" => "web-1"},
from: System.os_time(:second) - 3600
)
{:ok, series} =
TimelessMetrics.query_multi(:metrics, "cpu_usage", %{"host" => "web-1"},
from: System.os_time(:second) - 3600
)
{:ok, buckets} =
TimelessMetrics.query_aggregate(:metrics, "cpu_usage", %{"host" => "web-1"},
from: System.os_time(:second) - 3600,
bucket: {60, :seconds},
aggregate: :avg
)Discovery and operations:
TimelessMetrics.list_metrics(:metrics)
TimelessMetrics.list_series(:metrics, "cpu_usage")
TimelessMetrics.label_values(:metrics, "cpu_usage", "host")
TimelessMetrics.info(:metrics)
TimelessMetrics.flush(:metrics)
TimelessMetrics.backup(:metrics, "/tmp/metrics-backup")HTTP API
Core endpoints:
| Method | Path | Description |
|---|---|---|
POST | /api/v1/import | VictoriaMetrics JSON-line ingest |
POST | /api/v1/import/prometheus | Prometheus text ingest |
POST | /write | Influx line protocol ingest |
GET | /api/v1/query | Latest-value query |
GET | /api/v1/query_range | Native range query |
GET | /api/v1/export | Multi-series export |
GET | /prometheus/api/v1/query | Prometheus instant query |
GET | /prometheus/api/v1/query_range | Prometheus range query |
GET | /prometheus/api/v1/labels | Prometheus label names |
GET | /prometheus/api/v1/series | Prometheus series listing |
GET | /chart | SVG chart |
GET | /health | Lightweight health/status |
GET | /health/detailed | More expensive store diagnostics |
Example ingest:
curl -X POST http://localhost:8428/api/v1/import/prometheus \
-H "Content-Type: text/plain" \
--data-binary '
cpu_usage{host="web-1"} 73.2
cpu_usage{host="web-2"} 61.8
'
Example range query:
curl 'http://localhost:8428/api/v1/query_range?metric=cpu_usage&host=web-1&from=1700000000&to=1700003600&step=60'
Example Prometheus-compatible query:
curl 'http://localhost:8428/prometheus/api/v1/query_range?query=cpu_usage{host="web-1"}&start=1700000000&end=1700003600&step=60'
Benchmarks
The maintained benchmark set lives under bench/:
- embedded API throughput: bench/write_bench.exs
- HTTP concurrency: bench/http_concurrency.exs
- realistic workload ramp: bench/realistic_workload.exs
- TSBS harness: bench/tsbs_bench.exs
- VictoriaMetrics comparison: bench/vs_victoriametrics.exs
Notes
- The legacy Elixir engine still exists behind explicit
engine:selection for compatibility and migration work, but the default path is Rust. - The rust build may emit the upstream
rustler::resource!non_local_definitionswarning. That warning is currently expected.