Class: ConversionRetrySweepWorker
- Inherits:
-
Object
- Object
- ConversionRetrySweepWorker
- Includes:
- Sidekiq::Job
- Defined in:
- app/workers/conversion_retry_sweep_worker.rb
Overview
Daily sweep that re-enqueues failed-but-not-yet-reported CAPI conversions
for Pinterest, OpenAI Ads (ChatGPT), Microsoft Ads, and Facebook/Meta.
Why we need this. The CAPI conversion reporters each persist a row in
<provider>_conversion_meta whenever a send is attempted — result: :failed on transport / validation / 4xx errors, plus the upstream error
message. The dispatch workers (PinterestConversionWorker,
OpenaiAdsConversionWorker, FacebookConversionWorker, …) only fire on
Order#after_transition any => :invoiced and on
OpportunityFollowedUpHandler, so a transient failure that lands as
result: :failed (upstream outage, a network timeout/connection drop,
Meta's "An unknown error has occurred." Graph code 1/2 blip in AppSignal
#5248, the Faraday-positional-args bug fixed in AppSignal #5214) ends with
the meta carrying result: :failed and reported_at: nil forever — the
conversion is silently lost.
This sweep only re-enqueues result: 'failed' rows. HTTP 429s are handled
upstream where the client opts in: Facebook::ApiClient retries them inline
via the faraday-retry middleware (Heatwave::FaradayRetry, honoring Meta's
Retry-After), so a 429 only persists as result: 'rate_limited' if it
survives those inline retries — out of scope for this sweep (a future change
would normalize lingering rate_limited rows across all providers).
This worker scans for that exact state and re-enqueues each record
through its provider-specific worker. The reporter's already_reported
guard (checks reported_at, not attempted_at) lets the retry
proceed cleanly; if the previous failure was transient the retry
succeeds and reported_at is stamped, lifting it out of this sweep's
scope for future runs. Sidekiq's unique-jobs middleware dedupes
overlapping enqueues.
Parity with Google. GoogleOfflineConversionRetryWorker does the
equivalent thing for the Google reporter (different reporter shape, so
its own worker). Both share the 7-day cutoff convention — a quote that
attempted to send a week ago and never succeeded is almost certainly
permanent debt, not retry candidate; keep noise out of AppSignal.
Constant Summary collapse
- CUTOFF =
How far back to look. Older failures are treated as permanent and
stop appearing in the sweep — matches Google's 7-day window. 7.days
- BATCH_SIZE =
Pull DB rows in batches so a backlog of thousands doesn't load
everything at once. 200- PROVIDERS =
Each entry:
column(the JSONB column on Order/Opportunity),worker
(the Sidekiq class to re-enqueue against),name(for log messages). [ { name: 'Pinterest', worker: 'PinterestConversionWorker', column: 'pinterest_conversion_meta' }, { name: 'OpenAI Ads', worker: 'OpenaiAdsConversionWorker', column: 'openai_ads_conversion_meta' }, { name: 'Microsoft Ads', worker: 'MicrosoftAdsConversionWorker', column: 'microsoft_ads_conversion_meta' }, { name: 'Facebook', worker: 'FacebookConversionWorker', column: 'facebook_conversion_meta' } ].freeze
Instance Method Summary collapse
-
#perform ⇒ Object
Runs the job.
Instance Method Details
#perform ⇒ Object
Runs the job.
71 72 73 74 75 76 |
# File 'app/workers/conversion_retry_sweep_worker.rb', line 71 def perform PROVIDERS.each do |provider| retry_failed(provider, Order, 'order') retry_failed(provider, Opportunity, 'opportunity') end end |