mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-08 07:40:08 +09:00
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:
@@ -0,0 +1,53 @@
|
||||
require "test_helper"
|
||||
|
||||
class WebPush::PoolTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
stub_web_push_dns_resolution
|
||||
@pool = Rails.configuration.x.web_push_pool
|
||||
@payload = { title: "Designers", body: "Hello", path: Rails.application.routes.url_helpers.room_path(rooms(:designers)) }
|
||||
end
|
||||
|
||||
test "every notification carries its subscriber's unread room count as the badge" do
|
||||
memberships(:jason_pets).update! unread_at: Time.current
|
||||
memberships(:kevin_hq).update! unread_at: Time.current
|
||||
memberships(:kevin_david_and_kevin).update! unread_at: Time.current
|
||||
subscriptions = Push::Subscription.where(user: users(:david, :jason, :kevin))
|
||||
expected = subscriptions.to_h { |subscription| [ subscription.endpoint, subscription.user.memberships.unread.count ] }
|
||||
assert_equal [ 0, 1, 2 ], expected.values.sort
|
||||
|
||||
badges = Concurrent::Hash.new
|
||||
WebPush.stubs(:payload_send).with { |options| badges[options[:endpoint]] = JSON.parse(options[:message]).dig("options", "data", "badge") }
|
||||
@pool.queue(@payload, subscriptions)
|
||||
wait_for_deliveries(3)
|
||||
|
||||
assert_equal expected, badges
|
||||
end
|
||||
|
||||
test "queueing counts unread rooms per batch, not per subscription" do
|
||||
WebPush.stubs(:payload_send)
|
||||
|
||||
two = Push::Subscription.where(user: users(:david, :jason))
|
||||
four = Push::Subscription.where(user: users(:david, :jason, :jz, :kevin))
|
||||
assert_equal [ 2, 4 ], [ two.count, four.count ]
|
||||
|
||||
queries_for_two = count_queries { @pool.queue(@payload, two) }
|
||||
queries_for_four = count_queries { @pool.queue(@payload, four) }
|
||||
wait_for_deliveries(6)
|
||||
|
||||
assert_equal queries_for_two, queries_for_four
|
||||
end
|
||||
|
||||
private
|
||||
def wait_for_deliveries(count)
|
||||
Timeout.timeout(2) { sleep 0.01 while @pool.delivery_pool.completed_task_count < count }
|
||||
end
|
||||
|
||||
def count_queries(&block)
|
||||
count = 0
|
||||
counter = ->(*, payload) { count += 1 unless payload[:name] == "SCHEMA" || payload[:cached] }
|
||||
ActiveRecord::Base.uncached do
|
||||
ActiveSupport::Notifications.subscribed(counter, "sql.active_record", &block)
|
||||
end
|
||||
count
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user