Packages
reckon_db
5.5.4
5.11.0
5.10.4
5.10.3
5.10.1
5.10.0
5.9.1
5.9.0
5.8.3
5.8.2
5.8.1
5.8.0
5.7.0
5.6.1
5.6.0
5.5.5
5.5.4
5.5.3
5.5.2
5.5.1
5.5.0
5.4.0
5.2.2
5.2.1
5.2.0
5.1.0
5.0.0
4.0.0
3.1.2
3.1.1
3.0.0
2.3.7
2.3.6
2.3.5
2.3.4
2.3.3
2.3.2
2.3.1
2.3.0
2.2.2
2.2.0
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.0
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.3
1.3.2
1.3.1
1.3.0
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.1
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
BEAM-native Event Store built on Khepri/Ra with Raft consensus. Event sourcing, persistent subscriptions, snapshots, and automatic cluster formation via UDP multicast discovery. Ships embedded Rust NIFs for 3-15x acceleration of crypto, hashing, compression, aggregation, filter matching, and grap...
Current section
Files
Jump to
Current section
Files
guides/telemetry.md
# Telemetry
ReckonDB is instrumented with [`telemetry`](https://hexdocs.pm/telemetry) — the
standard BEAM instrumentation library. Every significant operation emits a
telemetry event; you attach handlers to turn those events into logs, metrics,
traces, or alerts. Nothing leaves the node on its own: telemetry is in-process
pub/sub, and emitting an event for which no handler is attached costs a single
ETS lookup.
This guide covers how the mechanism works, how to attach handlers (including
metrics exporters), and the full event catalogue.
## How it works
An event is a list of atoms — a stable name like `[reckon_db, stream, write, stop]`.
When the operation happens, ReckonDB calls:
```erlang
telemetry:execute(Event, Measurements, Metadata).
```
- **Measurements** — a map of *numbers* (durations, counts, sizes). These feed
counters and histograms.
- **Metadata** — a map of *context* (store_id, stream_id, reason). These become
labels/tags you filter and group by.
A handler is a 4-arity function attached to one or more events. It runs
synchronously in the process that emitted the event, so handlers must be cheap
— do the heavy lifting (aggregation, network I/O) in a separate process, or use
an exporter library that already does.
## Quick start
Attach the built-in logger handler and you will see ReckonDB events in your
logs:
```erlang
ok = reckon_db_telemetry:attach_default_handler().
```
This is done automatically at application start when the `telemetry_handlers`
environment key includes `logger` (the default — see [Configuration](#configuration)).
### Attach your own handler
```erlang
reckon_db_telemetry:attach(my_metrics, fun my_app_metrics:handle/4, #{}).
```
`my_app_metrics:handle/4` receives `(EventName, Measurements, Metadata, Config)`
for every ReckonDB event. A minimal example that counts stream writes:
```erlang
handle([reckon_db, stream, write, stop], #{event_count := N}, #{store_id := Store}, _Cfg) ->
my_counter:add({writes, Store}, N),
ok;
handle(_Event, _Measurements, _Metadata, _Cfg) ->
ok.
```
Detach when done:
```erlang
reckon_db_telemetry:detach(my_metrics).
```
## The facade
`reckon_db_telemetry` is the public entry point:
| Function | Purpose |
|---|---|
| `attach_default_handler/0` | Attach the built-in logger handler. |
| `detach_default_handler/0` | Remove it. |
| `attach/3` | Attach a custom `HandlerId, Fun, Config` across ReckonDB events. |
| `detach/1` | Remove a handler by id. |
| `emit/3` | Emit an event (thin wrapper over `telemetry:execute/3`). |
| `span/3` | Run a function bracketed by `:start`/`:stop` events with a duration. |
> **Note on `attach/3` and the default handler.** Both attach to the list
> returned by the internal `all_events/0`, which currently covers the core
> stream / subscription / snapshot / cluster / store / emitter events. Events in
> the extended categories below (temporal, scavenge, causation, backpressure,
> schema, memory, consistency, health, link) are *emitted* but are not in that
> list — to receive them, attach directly to the event name:
>
> ```erlang
> telemetry:attach(my_id, [reckon_db, consistency, quorum, lost], fun m:h/4, #{}).
> ```
## Metrics exporters
For a datacenter deployment, bridge telemetry to your metrics backend instead of
(or alongside) the logger:
- **Prometheus** — use [`telemetry_metrics_prometheus`](https://hexdocs.pm/telemetry_metrics_prometheus)
to define counters/histograms over these events and scrape them.
- **OpenTelemetry** — use [`opentelemetry`](https://hexdocs.pm/opentelemetry) +
a telemetry bridge to export spans/metrics via OTLP.
- **StatsD / custom** — attach a handler that forwards to your collector.
Because the event names are stable, exporter configuration is just a mapping
from event → metric. Example (Elixir `telemetry_metrics` style, shown for
reference):
```
counter("reckon_db.stream.write.stop", tags: [:store_id])
distribution("reckon_db.stream.write.stop.duration", unit: {:native, :microsecond})
last_value("reckon_db.consistency.quorum.lost")
```
## Configuration
At application start ReckonDB reads:
```erlang
{reckon_db, [
{telemetry_handlers, [logger]} %% default
]}.
```
- `[logger]` — attach the built-in logger handler (default).
- `[]` — attach nothing; emit events silently until you attach your own.
- Add your own atoms and extend `reckon_db_app`'s handler wiring, or simply call
`reckon_db_telemetry:attach/3` from your own supervision tree.
## Event catalogue
All events, their measurements, and metadata. Durations are in microseconds
unless the key says otherwise.
### Stream
| Event | Measurements | Metadata |
|---|---|---|
| `[reckon_db, stream, write, start]` | `system_time` | `store_id, stream_id, event_count, expected_version` |
| `[reckon_db, stream, write, stop]` | `duration, event_count` | `store_id, stream_id, new_version` |
| `[reckon_db, stream, write, error]` | `duration` | `store_id, stream_id, reason` |
| `[reckon_db, stream, read, start]` | `system_time` | `store_id, stream_id, start_version, count, direction` |
| `[reckon_db, stream, read, stop]` | `duration, event_count` | `store_id, stream_id` |
### Subscription
| Event | Measurements | Metadata |
|---|---|---|
| `[reckon_db, subscription, created]` | `system_time` | `store_id, subscription_id, type, selector` |
| `[reckon_db, subscription, deleted]` | `system_time` | `store_id, subscription_id` |
| `[reckon_db, subscription, event_delivered]` | `duration` | `store_id, subscription_id, event_id, event_type` |
| `[reckon_db, subscription, checkpoint]` | `position` | `store_id, subscription_name` |
| `[reckon_db, subscription, backpressure, warning]` | `queue_size, max_size` | `store_id, subscription_key` |
| `[reckon_db, subscription, backpressure, dropped]` | `count` | `store_id, subscription_key, strategy` |
### Snapshot
| Event | Measurements | Metadata |
|---|---|---|
| `[reckon_db, snapshot, created]` | `system_time, size_bytes` | `store_id, stream_id, version` |
| `[reckon_db, snapshot, read]` | `duration, size_bytes` | `store_id, stream_id, version` |
### Store
| Event | Measurements | Metadata |
|---|---|---|
| `[reckon_db, store, started]` | `system_time` | `store_id, mode, data_dir` |
| `[reckon_db, store, stopped]` | `system_time, uptime_ms` | `store_id, reason` |
### Cluster
| Event | Measurements | Metadata |
|---|---|---|
| `[reckon_db, cluster, node, up]` | `system_time` | `store_id, node, member_count` |
| `[reckon_db, cluster, node, down]` | `system_time` | `store_id, node, member_count, reason` |
| `[reckon_db, cluster, leader, elected]` | `system_time` | `store_id, leader_node, previous_leader` |
> The `leader, elected` event fires on first-leader detection *and* every
> leadership change (see [Cluster Consistency](cluster_consistency.md)). It is
> the recommended hook for "run this singleton on whichever node currently
> leads store X" — attach, compare `leader_node` to `node()`, start/stop your
> work accordingly.
### Emitter
| Event | Measurements | Metadata |
|---|---|---|
| `[reckon_db, emitter, broadcast]` | `duration` | `store_id, subscription_id, event_id, recipient_count` |
| `[reckon_db, emitter, pool, created]` | `system_time` | `store_id, subscription_id, pool_size` |
### Temporal queries
| Event | Measurements | Metadata |
|---|---|---|
| `[reckon_db, temporal, read_until]` | `duration, event_count` | `store_id, stream_id, timestamp` |
| `[reckon_db, temporal, read_range]` | `duration, event_count` | `store_id, stream_id, timestamp ({from, to})` |
### Scavenge
| Event | Measurements | Metadata |
|---|---|---|
| `[reckon_db, scavenge, complete]` | `duration, deleted_count` | `store_id, stream_id, archived` |
### Causation
| Event | Measurements | Metadata |
|---|---|---|
| `[reckon_db, causation, query]` | `duration, event_count` | `store_id, id, query_type` |
### Schema
| Event | Measurements | Metadata |
|---|---|---|
| `[reckon_db, schema, registered]` | `version` | `store_id, event_type` |
| `[reckon_db, schema, unregistered]` | `version (0)` | `store_id, event_type` |
| `[reckon_db, schema, upcasted]` | `duration` | `store_id, event_type, from_version, to_version` |
### Memory pressure
| Event | Measurements | Metadata |
|---|---|---|
| `[reckon_db, memory, pressure_changed]` | `usage_ratio` | `old_level, new_level` |
### Consistency
| Event | Measurements | Metadata |
|---|---|---|
| `[reckon_db, consistency, check, complete]` | `duration_us` | `store_id, status, checks` |
| `[reckon_db, consistency, status, changed]` | `system_time` | `store_id, old_status, new_status` |
| `[reckon_db, consistency, split_brain, detected]` | `system_time` | `store_id, result` |
| `[reckon_db, consistency, quorum, lost]` | `system_time` | `store_id, available_nodes, required_quorum` |
| `[reckon_db, consistency, quorum, restored]` | `system_time` | `store_id, available_nodes, required_quorum` |
### Health prober
| Event | Measurements | Metadata |
|---|---|---|
| `[reckon_db, health, probe, complete]` | `duration_us, success_count, failure_count` | `store_id` |
| `[reckon_db, health, node, failed]` | `system_time, consecutive_failures` | `store_id, node` |
| `[reckon_db, health, node, recovered]` | `system_time` | `store_id, node` |
### Stream links
| Event | Measurements | Metadata |
|---|---|---|
| `[reckon_db, link, created]` | `system_time` | `store_id, link_name` |
| `[reckon_db, link, deleted]` | `system_time` | `store_id, link_name` |
| `[reckon_db, link, started]` | `system_time` | `store_id, link_name` |
| `[reckon_db, link, stopped]` | `system_time` | `store_id, link_name` |
## What to watch
A pragmatic starter dashboard:
- **Throughput / latency** — `stream.write.stop` and `stream.read.stop` counts +
duration histograms, grouped by `store_id`.
- **Errors** — rate of `stream.write.error`.
- **Cluster health** — `consistency.quorum.lost` / `quorum.restored`,
`cluster.leader.elected` (leadership churn), `health.node.failed`.
- **Backpressure** — `subscription.backpressure.warning` / `dropped` signal that
a subscriber can't keep up.
- **Memory** — `memory.pressure_changed` transitions.
The event names are the source of truth; the definitive list with inline
measurement/metadata docs lives in `include/reckon_db_telemetry.hrl`.