This document describes the current default architecture of TimelessMetrics.
The important versioned truth is:
- the default engine is the Rust engine
- the legacy Elixir engine still exists, but it is no longer the primary design target
If you are reading older notes that describe ETS shard buffers, SegmentBuilder, ALP, or SQLite-backed raw storage as the hot path, those describe the legacy engine, not the default runtime on main.
High-Level Design
TimelessMetrics is split into two layers:
Rust hot path
- series resolution
- labeled writes and batch writes
- raw and aggregate reads
- chunk persistence and restart recovery
- label and metric listing
Elixir product layer
- supervision and configuration
- HTTP API
- background ingest workers
- alerts, annotations, metadata, scrape targets
- PromQL execution and response shaping
- charts, dashboard, forecasting, anomaly detection
- retention, rollups, backup orchestration
The Rust layer is responsible for the time-series engine. Elixir is responsible for the surrounding application behavior.
Supervision Tree
For a normal persisted store:
children = [
{TimelessMetrics, name: :metrics, data_dir: "/var/lib/metrics"},
{TimelessMetrics.HTTP, store: :metrics, port: 8428}
]Internally, the store supervisor starts different children depending on mode, but the current rust-default path is roughly:
TimelessMetrics.Supervisor
├── TimelessMetrics.DB
├── TimelessMetrics.RustEngine
├── TimelessMetrics.IngestWorker x N (non-memory mode)
├── TimelessMetrics.AlertEvaluator (non-memory mode)
├── TimelessMetrics.SelfMonitor (optional)
├── DynamicSupervisor (scraping enabled, non-memory mode)
└── TimelessMetrics.Scraper (scraping enabled, non-memory mode)TimelessMetrics.HTTP is a separate child you add alongside the store when you want HTTP ingest/query endpoints.
Engine Selection
The default supervisor defaults to:
engine: :rustThe legacy engine is still available through explicit configuration:
{TimelessMetrics, name: :metrics, data_dir: "/tmp/metrics", engine: :legacy}Current docs in this file describe the rust path unless stated otherwise.
Write Path
Programmatic API
Elixir writes call into TimelessMetrics.RustEngine, which forwards to the Rust NIF.
Flow:
TimelessMetrics.write / write_batch
-> TimelessMetrics.RustEngine
-> Rust NIF
-> resolve series
-> append to in-memory partition buffers
-> flush to chunk files when thresholds or maintenance triggers fireKey properties:
- batch writes are the primary high-throughput path
- the engine maintains its own series registry
- chunk metadata is stored and rebuilt from files on restart
- writes remain queryable before and after flush
HTTP Ingest
HTTP ingest is intentionally decoupled from parsing and storage:
HTTP request
-> handler enqueues raw body in ETS
-> returns quickly
-> IngestWorker drains queue
-> parse body
-> RustEngine/NIF write pathThis is true for:
- VictoriaMetrics JSON-line ingest
- Prometheus text ingest
- Influx line protocol ingest
The queue is an Elixir concern. The actual time-series write path is still the Rust engine.
Read Path
Raw and aggregate reads for the default engine go through the Rust layer.
Examples:
TimelessMetrics.query/4TimelessMetrics.query_multi/4TimelessMetrics.query_aggregate/4TimelessMetrics.query_aggregate_multi/4
PromQL and HTTP compatibility endpoints are layered above that:
HTTP / PromQL request
-> parse HTTP params or PromQL
-> call TimelessMetrics query functions
-> RustEngine / NIF returns data
-> Elixir formats HTTP / Prometheus responseSo the Rust engine owns the data retrieval, but Elixir still owns:
- PromQL planning
- Prometheus response envelopes
- dashboard/chart formatting
- filtering and endpoint-specific shaping
Storage Model
The rust engine persists data under a Rust-engine-specific directory inside the store data_dir.
Conceptually it keeps:
- a persisted series registry
- individual chunk files
- batched chunk files
- an in-memory index rebuilt on startup
Important behavior:
- chunk metadata is used to prune reads efficiently
- restart recovery rebuilds the in-memory index from disk
- chunk naming is designed to avoid restart-time overwrite collisions
- out-of-order points are normalized before chunk metadata is written
Memory-Only Mode
Memory-only mode disables durable raw-data persistence:
{TimelessMetrics, name: :metrics, mode: :memory}In memory-only mode:
- the rust engine still serves as the hot path
- scraping and alert evaluator are skipped
- raw series data is not persisted for recovery
This mode is useful for:
- tests
- local experiments
- ephemeral services
- constrained deployments
Metadata and Admin Data
The Rust engine handles time-series storage, but the DB process still matters.
Elixir-side admin data is still managed in SQLite-backed tables through TimelessMetrics.DB, including:
- metric metadata
- annotations
- alert rules and state
- scrape targets and scrape health
- rollup/admin metadata used by the higher-level product surface
That means the system is not “Rust only.” It is a Rust-default time-series engine with an Elixir application layer around it.
HTTP Surface
TimelessMetrics.HTTP exposes three groups of endpoints:
Native ingest/query
/api/v1/import/api/v1/import/prometheus/write/api/v1/query/api/v1/query_range/api/v1/export
Prometheus-compatible endpoints
/prometheus/api/v1/query/prometheus/api/v1/query_range/prometheus/api/v1/labels/prometheus/api/v1/label/:name/values/prometheus/api/v1/series
Product/ops endpoints
/health/health/detailed/chart- annotations, alerts, metadata, backup, dashboard, forecasting, anomalies
Benchmarks
The benchmark set was cleaned up to reflect the current architecture. See:
The maintained benchmarks are:
- embedded API throughput
- HTTP concurrency
- realistic HTTP workload ramp
- TSBS harness
- VictoriaMetrics comparison
Legacy Notes
If you need the legacy engine, keep these distinctions in mind:
- old docs describing ETS shard buffers and SegmentBuilder are about the legacy path
- old compression references to ALP as the primary active engine are legacy descriptions
- old benchmark scripts that depended on actor-era internals were intentionally removed
The codebase still contains compatibility paths, but the primary architecture is now the rust-default engine described above.