All configuration is set under the :timeless_traces application key in config.exs or at runtime via Application.put_env/3.
Options
| Option | Type | Default | Description |
|---|---|---|---|
data_dir | string | "priv/span_stream" | Root directory for blocks and index |
storage | atom | :disk | Storage backend (:disk or :memory) |
flush_interval | integer (ms) | 1_000 | Buffer auto-flush interval |
max_buffer_size | integer | 1_000 | Max spans before forced flush |
query_timeout | integer (ms) | 30_000 | Query timeout |
compaction_threshold | integer | 500 | Min raw entries to trigger compaction |
compaction_interval | integer (ms) | 30_000 | Compaction check interval |
compaction_max_raw_age | integer (s) | 60 | Force compact raw blocks older than this |
compaction_format | atom | :openzl | Compression format (:openzl or :zstd) |
merge_compaction_target_size | integer | 2_000 | Target entries per merged compressed block |
merge_compaction_min_blocks | integer | 4 | Min small compressed blocks before merge triggers |
compression_level | integer | 6 | Compression level (1-22) |
index_publish_interval | integer (ms) | 2_000 | Index batch write interval |
retention_max_age | integer (s) or nil | 604_800 (7 days) | Max span age (nil = keep forever) |
retention_max_size | integer (bytes) or nil | 536_870_912 (512 MB) | Max storage size (nil = unlimited) |
retention_check_interval | integer (ms) | 300_000 (5 min) | Retention check interval |
http | boolean or keyword | false | Enable HTTP API |
HTTP options
When http is a keyword list:
| Option | Type | Default | Description |
|---|---|---|---|
port | integer | 10428 | HTTP listen port |
bearer_token | string or nil | nil | Bearer token for authentication |
Full example
# config/config.exs
config :timeless_traces,
# Storage
storage: :disk,
data_dir: "priv/span_stream",
# Buffer
flush_interval: 1_000,
max_buffer_size: 1_000,
# Compaction
compaction_threshold: 500,
compaction_interval: 30_000,
compaction_max_raw_age: 60,
compaction_format: :openzl,
compression_level: 6,
merge_compaction_target_size: 2_000,
merge_compaction_min_blocks: 4,
# Indexing
index_publish_interval: 2_000,
# Query
query_timeout: 30_000,
# Retention
retention_max_age: 7 * 86_400,
retention_max_size: 512 * 1_048_576,
retention_check_interval: 300_000,
# HTTP API
http: [port: 10428, bearer_token: "my-secret-token"]
# OTel exporter
config :opentelemetry,
traces_exporter: {TimelessTraces.Exporter, []}OpenTelemetry exporter configuration
Wire TimelessTraces as the OTel traces exporter:
config :opentelemetry,
traces_exporter: {TimelessTraces.Exporter, []}The exporter reads spans directly from the OTel SDK's ETS table -- no HTTP or protobuf involved. This is significantly lower overhead than sending spans to an external collector.
Storage backends
Disk (default)
Blocks are stored as files in data_dir/blocks/ and the index is persisted as data_dir/index.snapshot + data_dir/index.log:
config :timeless_traces, storage: :diskMemory
Blocks are stored in ETS tables only. No files are written to disk. Data does not survive restarts. Useful for testing:
config :timeless_traces, storage: :memoryTuning guidance
Buffer size and flush interval
The defaults (1000 spans / 1 second) balance latency and throughput. For high-volume services:
- Increase
max_buffer_sizeto 5000-10000 for higher throughput - Increase
flush_intervalto 5000ms to reduce disk I/O - Decrease both for lower query latency (spans become queryable after flush)
Compression
The default format is :openzl (columnar compression), which achieves ~10x compression on span data with faster query-time decompression than zstd.
| Format | Compression ratio | Best for |
|---|---|---|
:openzl | ~10.0x | General use (default), fastest queries |
:zstd | ~6.8x | Simpler, fast compression |
The compression_level setting applies to both formats (1-22, higher = smaller but slower).
Retention
Both age-based and size-based retention are enabled by default. Set either to nil to disable:
config :timeless_traces,
retention_max_age: nil, # No age limit
retention_max_size: nil # No size limitMerge compaction
After initial compaction, many small compressed blocks accumulate (one per flush cycle). Merge compaction consolidates them into larger blocks for better compression and fewer blocks to scan during queries.
- High volume: the defaults (target 2000 entries, min 4 blocks) work well
- Low volume: lower
merge_compaction_min_blocksto 2 so merges happen with fewer blocks - Large queries: increase
merge_compaction_target_sizeto 5000+ to produce fewer, larger blocks
Merge compaction can also be triggered manually via TimelessTraces.merge_now().
Index publish interval
The index journals mutations to a disk log every 2 seconds by default. Lower values mean faster persistence but increase disk I/O. For most workloads the default is fine.