Secondary index support for efficient value-based queries.
Secondary indexes enable fast lookups by specific value fields without scanning all keys. Each index maintains a mapping from indexed values to the keys that contain those values.
Features
- Automatic Maintenance: Indexes update automatically on put/delete
- Multiple Indexes: Support for multiple indexes per store
- Declarative Extractors: Define indexes with data specs (safe for Raft replication)
- Backward Compatible: Anonymous functions still accepted during migration
- Efficient Lookups: O(1) lookup by indexed value
- Multi-value Support: Index multiple values per key (e.g., tags)
Declarative Extractor Specs (Recommended)
# Index on a map key
:ok = Concord.Index.create("users_by_email", {:map_get, :email})
# Index on a nested path
:ok = Concord.Index.create("by_city", {:nested, [:address, :city]})
# Index on the raw value
:ok = Concord.Index.create("by_value", {:identity})Legacy Function Extractors (Deprecated)
# Still works but stores anonymous functions in Raft log — unsafe across upgrades
:ok = Concord.Index.create("by_email", fn u -> u.email end)
Summary
Types
Extractor: declarative spec or legacy function
Index name (unique identifier)
Value to index (must be comparable)
Functions
Creates a new secondary index.
Drops an existing secondary index.
Lists all secondary indexes.
Looks up keys by indexed value.
Rebuilds an index from all existing keys. Uses the declarative extractor spec stored in the state machine.
Types
@type extractor() :: Concord.Index.Extractor.spec() | (term() -> term())
Extractor: declarative spec or legacy function
@type index_name() :: String.t()
Index name (unique identifier)
@type index_value() :: term()
Value to index (must be comparable)
Functions
@spec create(index_name(), extractor(), keyword()) :: :ok | {:error, term()}
Creates a new secondary index.
Accepts either a declarative extractor spec (recommended) or a legacy anonymous function (deprecated — unsafe across code upgrades).
Declarative Specs (Recommended)
:ok = Concord.Index.create("by_email", {:map_get, :email})
:ok = Concord.Index.create("by_city", {:nested, [:address, :city]})
:ok = Concord.Index.create("by_value", {:identity})
:ok = Concord.Index.create("by_first", {:element, 0})Options
:reindex- If true, reindex all existing keys (default: false):timeout- Operation timeout in milliseconds (default: 5000)
@spec drop( index_name(), keyword() ) :: :ok | {:error, term()}
Drops an existing secondary index.
Lists all secondary indexes.
@spec lookup(index_name(), index_value(), keyword()) :: {:ok, [String.t()]} | {:error, term()}
Looks up keys by indexed value.
@spec reindex( index_name(), keyword() ) :: :ok | {:error, term()}
Rebuilds an index from all existing keys. Uses the declarative extractor spec stored in the state machine.