Cascading updates for referenced collections
Keep referencing collections consistent when a referenced document changes. This page covers discovery of references, partial vs full reindex, usage, and emitted instrumentation.Overview
- Discovery: Build a reverse reference graph from Typesense live schemas (preferred) using field-level
reference(e.g.,“authors.id”). Falls back to compiled local schemas from SearchEngine models when Typesense is unavailable. - Scope: Single-hop only. Immediate cycles (A ↔ B) are detected and skipped.
- Reindex: On document update, attempt a targeted partial reindex of referencing collections when safe; otherwise perform a full reindex of each referencing collection.
Reference discovery
- From Typesense:
GET /collections→ for eachGET /collections/; inspect each field’sreferencevalue. - Fallback: compile each registered model schema and use its
referenceannotations generated frombelongs_to :assoc(stored as“<collection>.<foreign_key>”, or;asyncvariant when enabled).
- Only single-hop references are considered. Multi-hop is not supported.
- Immediate cycles (e.g., A references B and B references A) are skipped to avoid ping-pong reindex.
Partial vs full reindex
- Partial reindex is attempted only when both conditions hold:
- Referencing collection uses the ActiveRecord source adapter, and
- No custom Partitioner is configured for that collection.
- Partial strategy: call
Indexer.rebuild_partition!(Referrer, partition: { foreign_key => ids })so the ActiveRecord source applieswhere(foreign_key: ids). - Full fallback:
- Triggered when partial is not eligible, or raises unexpectedly.
- Always used when
context: :full.
Partitioned referencers and parallelism
- When a referencer collection defines partitioning via the index DSL (
partitions,partition_fetch), cascade full reindex will honorpartition_max_paralleland process partitions concurrently, mirroring full indexation behavior. Ifpartition_max_parallel <= 1or there is only one partition, processing remains sequential.
Usage
- Opt-in cascade on instance update:
- Explicit API:
context: :update: prefer partial reindex; fallback to full per referencer.context: :full: always full reindex for all referencers.
Instrumentation
Emitssearch_engine.cascade.run via ActiveSupport::Notifications with keys:
source_collection(String)ids_count(Integer)context(:update|:full)targets_total(Integer)partial_count,full_count(Integers)skipped_unregistered(Integer)skipped_cycles(Array of pairs)outcomes(Array):{ collection, mode: :partial | :full | :skipped_unregistered | :skipped_cycle }
Caveats
- Single-hop only; deeper paths are out of scope.
- Partial reindex requires the ActiveRecord source and no Partitioner.
- Cascade runs best-effort; errors are swallowed when triggered from
Base#updateto preserve update semantics.