diff --git a/backend/app/services/analytics/aggregate.rb b/backend/app/services/analytics/aggregate.rb index c16a0ca..b3db0c9 100644 --- a/backend/app/services/analytics/aggregate.rb +++ b/backend/app/services/analytics/aggregate.rb @@ -3,6 +3,7 @@ module Analytics class Aggregate BATCH = 500 + UPSERT_RETRIES = 3 def call loop do @@ -45,13 +46,15 @@ module Analytics now = Time.current grouped.each do |(day, page_path, device, cell_x, cell_y), count| - cell = AnalyticsPageCell.find_or_initialize_by( - day: day, page_path: page_path, device: device, cell_x: cell_x, cell_y: cell_y - ) - cell[counter_attr] = cell[counter_attr].to_i + count - cell.created_at ||= now - cell.updated_at = now - cell.save! + with_unique_retry do + cell = AnalyticsPageCell.find_or_initialize_by( + day: day, page_path: page_path, device: device, cell_x: cell_x, cell_y: cell_y + ) + cell[counter_attr] = cell[counter_attr].to_i + count + cell.created_at ||= now + cell.updated_at = now + cell.save! + end end end @@ -76,14 +79,28 @@ module Analytics now = Time.current grouped.each do |(day, page_path, device), vals| - stat = AnalyticsPageStat.find_or_initialize_by(day: day, page_path: page_path, device: device) - stat.pageview_count = stat.pageview_count.to_i + vals[:pageviews] - stat.scroll_samples = stat.scroll_samples.to_i + vals[:scroll_samples] - stat.scroll_sum_pct = stat.scroll_sum_pct.to_i + vals[:scroll_sum] - stat.max_scroll_pct = [stat.max_scroll_pct.to_i, vals[:max_scroll]].max - stat.created_at ||= now - stat.updated_at = now - stat.save! + with_unique_retry do + stat = AnalyticsPageStat.find_or_initialize_by(day: day, page_path: page_path, device: device) + stat.pageview_count = stat.pageview_count.to_i + vals[:pageviews] + stat.scroll_samples = stat.scroll_samples.to_i + vals[:scroll_samples] + stat.scroll_sum_pct = stat.scroll_sum_pct.to_i + vals[:scroll_sum] + stat.max_scroll_pct = [stat.max_scroll_pct.to_i, vals[:max_scroll]].max + stat.created_at ||= now + stat.updated_at = now + stat.save! + end + end + end + + def with_unique_retry + attempts = 0 + begin + attempts += 1 + yield + rescue ActiveRecord::RecordNotUnique + raise if attempts >= UPSERT_RETRIES + + retry end end end diff --git a/backend/app/services/analytics/ingest.rb b/backend/app/services/analytics/ingest.rb index 7fe38d6..b4aaec8 100644 --- a/backend/app/services/analytics/ingest.rb +++ b/backend/app/services/analytics/ingest.rb @@ -31,7 +31,15 @@ module Analytics end AnalyticsEvent.insert_all(rows) if rows.any? - Analytics::Aggregate.new.call if rows.any? + if rows.any? + begin + Analytics::Aggregate.new.call + rescue ActiveRecord::RecordNotUnique => e + # Dopo i retry interni: non far fallire la richiesta analytics. + Rails.logger.warn("[analytics] aggregate race after retries, enqueue job: #{e.message}") + Analytics::AggregateJob.perform_later + end + end Result.new(accepted: accepted, rejected: rejected + (@events.size - slice.size), rate_limited: false) end