Files
sure/app/models/concerns/syncable.rb
William Wei Ming f210d1ca4c Perf/accounts controller index optimization (#1926)
* fix Prism issue - assigned but unused variable plaid_item

* measurement to improve accounts_controller__index performance based on skylight

* fix FEEDBACK - Stop eager-loading full sync histories on accounts index

* fix FEEDBACK - Remove sticky memoization from SimplefinItem#accounts

* fix lint error in PR review

* fix FEEDBACK - Avoid returning map misses as authoritative results

* fix test error

* fix FEEDBACK - Avoid redundant query by using already-loaded @manual_accounts

* Address maintainer review on accounts index sync preloading
Memoize SimplefinItem#accounts, align syncing? with Sync#visible?,
and add fallback regression tests.

* Add composite index for sync DISTINCT ON queries
Support latest_by_syncable ordering with syncable_type, syncable_id,
created_at DESC, and id DESC.

* fix error in dockerfile.preview file

* Suppress Pipelock false positives on CI database fixtures.
Pipelock 2.7.0 full-repo audit flags ephemeral postgres:// URLs in
workflow env blocks. Re-apply inline suppressions lost in the main merge.

* Address review: guard partial Current sync maps and drop unrelated diff
Add key? checks so partially populated Current sync maps fall back to DB
queries. Revert Dockerfile.preview and pipelock.yml changes unrelated to
the accounts index N+1 optimization.

* fix(sync): fully populate Current sync maps for all preloaded syncables

* fix(ci): skip scheduled preview cleanup on forks
Only run the hourly Cloudflare preview cleanup on we-promise/sure,
where the required secrets exist.

* Remove schema.rb Postgres-version churn, keep only new syncs index

Reset db/schema.rb to upstream/main and re-add only the
index_syncs_on_syncable_and_created_at_and_id index. The prior diff
included ~30 lines of noise (check-constraint reformatting, virtual
column reformat, and column reorderings) caused by dumping under a
different PostgreSQL version, which obscured the single intended
schema change and invited merge conflicts.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-25 03:35:35 +02:00

122 lines
3.3 KiB
Ruby

module Syncable
extend ActiveSupport::Concern
included do
has_many :syncs, as: :syncable, dependent: :destroy
end
def syncing?
if Current.respond_to?(:syncing_by_syncable) && (syncing_by_syncable = Current.syncing_by_syncable)
key = [ self.class.base_class.name, id ]
return !!syncing_by_syncable[key] if syncing_by_syncable.key?(key)
end
if association(:syncs).loaded?
syncs.any?(&:visible?)
else
syncs.visible.any?
end
end
def latest_sync_record
if Current.respond_to?(:latest_sync_by_syncable) && (latest_sync_by_syncable = Current.latest_sync_by_syncable)
key = [ self.class.base_class.name, id ]
return latest_sync_by_syncable[key] if latest_sync_by_syncable.key?(key)
end
if association(:syncs).loaded?
syncs.max_by { |sync| [ sync.created_at, sync.id ] }
else
syncs.ordered.first
end
end
def latest_completed_sync_record
if Current.respond_to?(:latest_completed_sync_by_syncable) && (latest_completed_sync_by_syncable = Current.latest_completed_sync_by_syncable)
key = [ self.class.base_class.name, id ]
return latest_completed_sync_by_syncable[key] if latest_completed_sync_by_syncable.key?(key)
end
if association(:syncs).loaded?
syncs.select(&:completed?).max_by { |sync| [ sync.created_at, sync.id ] }
else
syncs.completed.ordered.first
end
end
# Schedules a sync for syncable. If there is an existing sync pending/syncing for this syncable,
# we do not create a new sync, and attempt to expand the sync window if needed.
#
# NOTE: Uses `visible` scope (syncs < 5 min old) instead of `incomplete` to prevent
# getting stuck on stale syncs after server/Sidekiq restarts. If a sync is older than
# 5 minutes, we assume its job was lost and create a new sync.
def sync_later(parent_sync: nil, window_start_date: nil, window_end_date: nil)
Sync.transaction do
with_lock do
sync = self.syncs.visible.first
if sync
Rails.logger.info("There is an existing recent sync, expanding window if needed (#{sync.id})")
sync.expand_window_if_needed(window_start_date, window_end_date)
# Update parent relationship if one is provided and sync doesn't already have a parent
if parent_sync && !sync.parent_id
sync.update!(parent: parent_sync)
end
else
sync = self.syncs.create!(
parent: parent_sync,
window_start_date: window_start_date,
window_end_date: window_end_date
)
SyncJob.perform_later(sync)
end
sync
end
end
end
def perform_sync(sync)
syncer.perform_sync(sync)
end
def perform_post_sync
syncer.perform_post_sync
end
def broadcast_sync_complete
sync_broadcaster.broadcast
end
def sync_error
latest_sync_record&.error || latest_sync_record&.children&.map(&:error)&.compact&.first
end
def last_synced_at
latest_completed_sync_record&.completed_at
end
def last_sync_created_at
latest_sync_record&.created_at
end
private
def latest_sync
latest_sync_record
end
def latest_completed_sync
latest_completed_sync_record
end
def syncer
self.class::Syncer.new(self)
end
def sync_broadcaster
self.class::SyncCompleteEvent.new(self)
end
end