Class: ActiveRecord::Materialized::MaintenanceStore Private

Inherits:
Object
  • Object
show all
Defined in:
lib/activerecord/materialized/maintenance_store.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Persists a view's pending maintenance (a delta or a scope) in its metadata row.

Instance Method Summary collapse

Constructor Details

#initialize(view_class) ⇒ MaintenanceStore

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of MaintenanceStore.



9
10
11
# File 'lib/activerecord/materialized/maintenance_store.rb', line 9

def initialize(view_class)
  @view_class = view_class
end

Instance Method Details

#clear!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



68
69
70
# File 'lib/activerecord/materialized/maintenance_store.rb', line 68

def clear!
  .clear_maintenance_payload!
end

#consume_pending_delta!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Atomically consume the pending scoped delta under a row lock on the metadata row, so two cross-process cycles can't both consume it and recompute the same partition twice — which duplicated rows on Postgres (no unique constraint, no gap locks under READ COMMITTED). Whoever clears the payload owns the delta; a loser reads an empty payload and gets nil (a benign no-op). Returns the delta, or nil. Unlike the additive summary path (which applies inside its lock for crash-atomicity), the scoped recompute is idempotent (delete + re-aggregate), so the caller applies OUTSIDE this lock, keeping it short; a crash between consume and apply loses the delta, which self-healing reconciliation (#64) repairs on the next tick.



42
43
44
45
46
47
48
49
50
# File 'lib/activerecord/materialized/maintenance_store.rb', line 42

def consume_pending_delta!
  .record.with_lock do # blocking FOR UPDATE on the metadata row + a transaction
    delta = pending_delta
    next nil if delta.nil?

    clear!
    delta
  end
end

#merge!(delta) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Accumulates pending maintenance of either kind. A view's mode is fixed within a window, so existing pending is always the same kind. Once the tracked partitions exceed the configured cap, the payload collapses to a single full recompute, so a bulk write spanning many partitions stays O(1) per write instead of re-serializing an ever-growing blob.



18
19
20
21
22
23
# File 'lib/activerecord/materialized/maintenance_store.rb', line 18

def merge!(delta)
  combined = combine(pending, delta)
  return drop_cold_full_recompute! if cold_full_recompute?(combined)

  .record_maintenance_payload!(combined.serialize)
end

#pendingObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



25
26
27
28
# File 'lib/activerecord/materialized/maintenance_store.rb', line 25

def pending
  payload = .maintenance_payload
  SummaryDelta.deserialize(payload) || MaintenanceDelta.deserialize(payload)
end

#pending_deltaObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



30
31
32
# File 'lib/activerecord/materialized/maintenance_store.rb', line 30

def pending_delta
  MaintenanceDelta.deserialize(.maintenance_payload)
end

#with_consumed_summary_deltaObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Atomically consume the pending SummaryDelta and apply it under a row lock on the metadata row, so concurrent cycles across servers can't apply the same additive delta twice. Yields the delta to the block (which applies it) inside the locked transaction, so a failed apply rolls back the clear and the delta is retried. Returns the block's result, or nil when another cycle already consumed it — the loser blocks on the lock, then reads an empty payload (a benign no-op). DML-only (no DDL/callbacks), so the lock holds for the whole critical section.



58
59
60
61
62
63
64
65
66
# File 'lib/activerecord/materialized/maintenance_store.rb', line 58

def with_consumed_summary_delta
  .record.with_lock do # blocking FOR UPDATE on the metadata row + a transaction
    delta = SummaryDelta.deserialize(.maintenance_payload)
    next nil if delta.nil?

    clear!
    yield delta
  end
end