reckon_db_index (reckon_db v5.11.14)
View SourceGeneric write-maintained secondary index (opt-in per store).
Turns cross-cutting lookups — "all events with tag X", "all events of type Y", "all events whose metadata key K = V" — from O(total events) scans into O(matches) subtree reads, by maintaining reference entries keyed by the indexed value. This module owns the index layout; nothing else constructs idx paths.
Layout
[idx, tag, Tag, OrderKey] -> EventRef
[idx, event_type, EventType, OrderKey] -> EventRef
[idx, meta, Key, Value, OrderKey] -> EventRefOrderKey = pad(epoch_us) | stream_id | pad(version) — globally ordered (subtree iteration yields event/time order) and unique (no two events share stream+version). EventRef = #{stream_id := binary(), version := non_neg_integer()} points at the primary event under the Model C layout; resolution is a point khepri:get via reckon_db_stream_path:event_path/2.
Maintenance
entries/2 returns the {Path, EventRef} pairs an event produces under a store's declared indexes; the append path writes them transactionally with the event (so the index is never partially populated). This module performs no writes itself — it builds paths and reads.
Reads
lookup_* read the relevant subtree, resolve refs to events (dropping refs whose event has since been removed, e.g. by scavenge), and sort them into global order with sort_in_global_order/1. Compound tag all intersects ref sets across tags.
See plans/DESIGN_SECONDARY_INDEX.md.
Summary
Functions
The index entries an event produces under a store's declared indexes. Each entry is a {Path, EventRef} the caller writes transactionally with the event. Empty when no declared index applies.
The declared kinds this module keeps entries for (tags, event_type, {meta, Key}); the payload kinds are DCB-scoped and written by reckon_db_dcb under their own paths.
The reference stored at an index leaf — enough to point-get the primary event under the Model C layout.
Events of any of the given types (union).
Events whose metadata key = value. The sanctioned primitive apps build causation/correlation/saga read models on; the store does not interpret the key.
Events carrying the given tags. any = union, all = intersection. Empty tag list returns [] (no criteria, no results — matches the pre-index scan semantics).
The unique, time-orderable leaf key for an event's index entries. epoch_us is the append's wall clock in microseconds, never negative.
Events sorted into the store's global order: epoch_us, then stream_id, then version, the order order_key/1 encodes.
Types
-type event() :: #event{event_id :: binary(), event_type :: binary(), stream_id :: binary(), version :: non_neg_integer(), data :: map() | binary(), metadata :: map(), tags :: [binary()] | undefined, timestamp :: integer(), epoch_us :: integer(), data_content_type :: binary(), metadata_content_type :: binary(), prev_event_hash :: binary() | undefined, mac :: mac_value(), signature :: binary() | undefined}.
-type event_ref() :: #{stream_id := binary(), version := non_neg_integer()}.
-type mac_value() :: {KeyId :: non_neg_integer(), MacBytes :: binary()} | {v2, KeyId :: non_neg_integer(), MacBytes :: binary()} | undefined.
Functions
-spec entries(event(), [index_decl()]) -> [{khepri_path:native_path(), event_ref()}].
The index entries an event produces under a store's declared indexes. Each entry is a {Path, EventRef} the caller writes transactionally with the event. Empty when no declared index applies.
-spec entry_kinds([index_decl()]) -> [index_decl()].
The declared kinds this module keeps entries for (tags, event_type, {meta, Key}); the payload kinds are DCB-scoped and written by reckon_db_dcb under their own paths.
The reference stored at an index leaf — enough to point-get the primary event under the Model C layout.
Events of any of the given types (union).
Events whose metadata key = value. The sanctioned primitive apps build causation/correlation/saga read models on; the store does not interpret the key.
Events carrying the given tags. any = union, all = intersection. Empty tag list returns [] (no criteria, no results — matches the pre-index scan semantics).
-spec order_key(#event{epoch_us :: non_neg_integer(), event_id :: binary(), event_type :: binary(), stream_id :: binary(), version :: non_neg_integer(), data :: map() | binary(), metadata :: map(), tags :: [binary()] | undefined, timestamp :: integer(), data_content_type :: binary(), metadata_content_type :: binary(), prev_event_hash :: binary() | undefined, mac :: mac_value(), signature :: binary() | undefined}) -> <<_:16, _:_*8>>.
The unique, time-orderable leaf key for an event's index entries. epoch_us is the append's wall clock in microseconds, never negative.
Events sorted into the store's global order: epoch_us, then stream_id, then version, the order order_key/1 encodes.
epoch_us alone is not an order. One append stamps a single epoch_us on every event it writes (reckon_db_streams:append_events_to_stream/4), so comparing epoch_us leaves a batch in whatever order the input list had, and callers build that list from Khepri maps, whose iteration order Erlang leaves undefined. A catch-up replay then applied one command's events out of order, and projections built on them silently lost fields (hecate-tube, 2026-09-11). The unique order key puts the events of one append back in version order.