Batch consumer lookups in EnrichmentService to eliminate N+1 find_or_create queries
Problem
Billing::Usage::EnrichmentService calls load_consumer per event inside process_saas_events (line 112) and process_sm_events (line 155). Each call delegates to Consumers.find_or_create, which runs Consumer.find_or_create_by! inside an ActiveRecord::Base.transaction:
EnrichmentService#process_saas_events / process_sm_events
→ load_consumer(source, event) ← called per event
→ Consumers.find_or_create(entity_id:, source:, ...)
→ FindOrCreateWithSubscriptionService#execute
→ source.eligible_usage_billing_base_charges(date) ← re-filters charges
→ ActiveRecord::Base.transaction { Consumer.find_or_create_by!(...) }
This results in one SELECT consumers.* query per event, even when many events share the same consumer (same entity_id + source combination).
Additionally, load_eligible_base_charge! calls source.eligible_usage_billing_base_charges(effective_date) which re-iterates rate plan charges and calls effective_on? per charge — duplicating work already done in the preload phase.
Evidence from production span
-
47
SELECT consumers.*queries in a single job execution - 330 ms total duration
- All queries are identical-pattern single-row lookups:
SELECT "consumers".* FROM "consumers" WHERE "consumers"."entity_id" = $1 AND ... - Zero INSERTs observed — all consumers already existed, meaning all 47 queries were pure lookups that could have been batched
Proposed solution
-
Batch prefetch existing consumers: After
preload_event_mappingresolves sources, collect all unique(entity_id, source)pairs and batch-load existing consumers in a single query. Store them in a lookup hash. -
Only call
find_or_createfor missing consumers: During the per-event loop, check the prefetched hash first. Only fall through tofind_or_createfor genuinely new consumers. -
Cache
eligible_usage_billing_base_chargesresult per source: Since multiple events share the same source, the base charge lookup should be computed once per source, not per event.
Relevant files
-
app/services/billing/usage/enrichment_service.rb:38-59—load_consumercalled per event -
app/services/billing/usage/consumers.rb:6-12— delegates to find_or_create services -
app/services/billing/usage/consumers/base_find_or_create_service.rb:83-109—find_or_create_consumer!in transaction -
app/services/billing/usage/consumers/find_or_create_with_subscription_service.rb:17-21—load_eligible_base_charge!re-filters charges per event -
app/models/zuora/local/subscription.rb:193—eligible_usage_billing_base_charges