Count unread rooms for push badges once per batch

Each push notification carries the subscriber's unread room count as its
badge. The pool built it per subscription, loading the user and counting
their unread memberships: two queries for every subscriber, all in the job
before the deliveries reach the threads. With 1,000 subscribed members the
job spent ~180 ms and 2,000 queries there; with 5,000, a second.

The pool now counts the unread rooms of a whole batch with one grouped query
and hands each subscription its badge; nothing else in the notification needs
the user. The queries still run before the work is posted to the threads,
which run outside the Rails executor. Push::Subscription#notification still
counts by itself when no badge is given, as for the test notification.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0142qgjggdJ2KDdGk7RF9Xm9
This commit is contained in:
Marcello Costagliola
2026-10-05 16:49:59 +02:00
parent c73ea37e0c
commit d485db6038
4 changed files with 72 additions and 6 deletions
+8 -4
View File
@@ -10,8 +10,12 @@ class WebPush::Pool
end
def queue(payload, subscriptions)
subscriptions.find_each do |subscription|
deliver_later(payload, subscription)
subscriptions.find_in_batches do |batch|
unread_counts = Membership.unread.where(user_id: batch.map(&:user_id)).group(:user_id).count
batch.each do |subscription|
deliver_later(payload, subscription, badge: unread_counts.fetch(subscription.user_id, 0))
end
end
end
@@ -22,9 +26,9 @@ class WebPush::Pool
end
private
def deliver_later(payload, subscription)
def deliver_later(payload, subscription, badge:)
# Ensure any AR operations happen before we post to the thread pool
notification = subscription.notification(**payload)
notification = subscription.notification(**payload, badge: badge)
subscription_id = subscription.id
delivery_pool.post do