Module: ActiveRecord::Materialized::Instrumentation
- Defined in:
- lib/activerecord/materialized/instrumentation.rb
Overview
Emits ActiveSupport::Notifications events at the read / refresh /
maintenance lifecycle points so adopters can wire freshness and behavior
SLIs to any APM / StatsD / OpenTelemetry backend without the gem taking a
dependency on a specific telemetry vendor.
The event names and payload keys documented here are a stable contract — subscribe with ActiveSupport::Notifications:
ActiveSupport::Notifications.subscribe("read.active_record_materialized") do |event|
StatsD.increment("mv.read.#{event.payload[:source]}", tags: ["view:#{event.payload[:view]}"])
end
Events:
read.active_record_materialized— one per routed read. Payload::view(the view class),:source(+:cache+,:read_through,:serve_stale, or:raise),:staleness(seconds since the last successful refresh, ornilwhen never refreshed).refresh.active_record_materialized— one per +refresh!+/+rebuild!+, timed. Payload::view,:operation(+:incremental+ or:rebuild),:mode(+:summary_delta+,:scoped_recompute, or:full),:partition_count(partitions recomputed, ornilfor a full pass),:row_count,:skipped, and — on failure — the standard +:exception+/+:exception_object+ keys set byActiveSupport::Notifications.maintenance.active_record_materialized— one per dependency write that records pending maintenance. Payload::view,:table,:operation(+:create+/+:update+/+:destroy+),:path(+:summary_delta+ or:scoped_recompute),:scope(+:scoped+, or:fullwhen the write widened to a full recompute because its partition key could not be derived),:partition_count(distinct partitions scoped, 0 on widen).reconcile.active_record_materialized— one per Reconciler run. Payload::view,:mode(the drift-check depth),:repaired_partition_count(partitions found divergent and repaired with scoped maintenance),:deferred(+true+ when a concurrent refresh deferred the run to the next tick).
Constant Summary collapse
- READ =
"read.active_record_materialized"- REFRESH =
"refresh.active_record_materialized"- MAINTENANCE =
"maintenance.active_record_materialized"- RECONCILE =
"reconcile.active_record_materialized"
Class Method Summary collapse
-
.maintenance(view_class, change:, path:, scope:, partition_count:) ⇒ Object
Fired once per dependency write that records pending maintenance.
-
.read(view_class, source:) ⇒ Object
Fired once per routed read.
-
.reconcile(view_class, mode:, repaired_partition_count:, deferred:) ⇒ Object
Fired once per Reconciler run — after it verifies and (if needed) repairs via scoped maintenance — carrying the run's mode, repaired count, and defer flag.
-
.refresh(view_class, operation:, &block) ⇒ Object
Wraps a refresh/rebuild so the event carries its wall-clock duration and any raised exception.
Class Method Details
.maintenance(view_class, change:, path:, scope:, partition_count:) ⇒ Object
Fired once per dependency write that records pending maintenance.
73 74 75 76 77 78 79 |
# File 'lib/activerecord/materialized/instrumentation.rb', line 73 def maintenance(view_class, change:, path:, scope:, partition_count:) ::ActiveSupport::Notifications.instrument( MAINTENANCE, view: view_class, table: change.table_name, operation: change.operation.to_sym, path: path, scope: scope, partition_count: partition_count ) end |
.read(view_class, source:) ⇒ Object
Fired once per routed read. The staleness lookup costs a metadata read, so it is skipped entirely unless a subscriber is attached.
49 50 51 52 53 54 55 56 |
# File 'lib/activerecord/materialized/instrumentation.rb', line 49 def read(view_class, source:) return unless ::ActiveSupport::Notifications.notifier.listening?(READ) ::ActiveSupport::Notifications.instrument( READ, view: view_class, source: source, staleness: staleness_for(view_class) ) end |
.reconcile(view_class, mode:, repaired_partition_count:, deferred:) ⇒ Object
Fired once per Reconciler run — after it verifies and (if needed) repairs via scoped maintenance — carrying the run's mode, repaired count, and defer flag.
83 84 85 86 87 88 89 |
# File 'lib/activerecord/materialized/instrumentation.rb', line 83 def reconcile(view_class, mode:, repaired_partition_count:, deferred:) ::ActiveSupport::Notifications.instrument( RECONCILE, view: view_class, mode: mode, repaired_partition_count: repaired_partition_count, deferred: deferred ) end |
.refresh(view_class, operation:, &block) ⇒ Object
Wraps a refresh/rebuild so the event carries its wall-clock duration and
any raised exception. The block runs the refresh and returns its
RefreshResult; the row count and skipped flag are read back from it,
while the block annotates the payload with the :mode and
:partition_count it can only know mid-flight.
63 64 65 66 67 68 69 70 |
# File 'lib/activerecord/materialized/instrumentation.rb', line 63 def refresh(view_class, operation:, &block) ::ActiveSupport::Notifications.instrument(REFRESH, view: view_class, operation: operation) do |payload| result = yield(payload) payload[:row_count] = result.row_count payload[:skipped] = result.skipped result end end |