Reuse epoch-verified record snapshots and avoid empty push payloads

This commit is contained in:
GPT on behalf of DHH
2026-10-08 12:26:50 +02:00
parent 74381e8b58
commit bbee420097
9 changed files with 159 additions and 21 deletions
@@ -1,7 +1,12 @@
module Authentication::SessionLookup
def find_session_by_cookie
if token = cookies.signed[:session_token]
Session.find_by(token: token)
version = @response_cache_version if @response_cache_version && (request.get? || request.head?)
session, user = RecordCache.fetch([ "session", Digest::SHA256.hexdigest(token.to_s) ], version) do
session = Session.find_by(token: token)
[ session, session&.user ]
end
session&.tap { |record| record.user = user }
end
end
end
@@ -35,6 +35,10 @@ module CachedResponses
end
private
def read_record_cache_version
@response_cache_version if request.get? || request.head?
end
def capture_response_cache_version
# Capture for native HTML/JSON/stream renders too, even with page reuse off.
# Detached renderers do not run callbacks and therefore render uncached.
@@ -81,6 +85,10 @@ module CachedResponses
def cache_completed_response(key, original_session, encoding)
if response.status == 200 && response.media_type == "text/html" && session.to_hash == original_session && !response.headers["Content-Encoding"]
body = encoding == "gzip" ? Zlib.gzip(response.body) : response.body
unless body.empty? || response.headers["ETag"] || response.headers["Last-Modified"]
# Match Rack::ETag once, rather than hashing the same bytes on each hit.
response.headers["ETag"] = %(W/"#{Digest::SHA256.hexdigest(body).byteslice(0, 32)}")
end
response.headers["Content-Encoding"] = "gzip" if encoding == "gzip"
response.headers["Vary"] = (response.headers["Vary"].to_s.split(/,\s*/) | [ "Accept-Encoding" ]).join(", ")
headers = response.headers.slice(*CACHE_HEADERS).to_h.freeze
+5 -2
View File
@@ -7,7 +7,10 @@ module RoomScoped
private
def set_room
@membership = Current.user.memberships.find_by!(room_id: params[:room_id])
@room = @membership.room
@membership, @room = RecordCache.fetch([ "membership", Current.user.id, params[:room_id] ], read_record_cache_version) do
membership = Current.user.memberships.find_by!(room_id: params[:room_id])
[ membership, membership.room ]
end
@membership.room = @room
end
end
+4 -1
View File
@@ -21,7 +21,10 @@ class RoomsController < ApplicationController
private
def set_room
if room = room_scope.find_by(id: params[:room_id] || params[:id])
room = RecordCache.fetch([ "room", self.class.name, Current.user.id, params[:room_id] || params[:id] ], read_record_cache_version) do
[ room_scope.find_by(id: params[:room_id] || params[:id]) ]
end.first
if room
@room = room
else
redirect_to root_url, alert: "Room not found or inaccessible"
+24
View File
@@ -0,0 +1,24 @@
# Rebuild request-local models from bounded, immutable database snapshots.
# The observer must still see the captured epoch after lookup and admission.
class RecordCache
def self.fetch(key, version)
return yield unless version && ResponseCache.instance.budget.positive? && !ActiveRecord::Base.connection.transaction_open?
key = ActiveSupport::Cache.expand_cache_key([ "record-snapshot-v1", version, key ])
return yield if key.bytesize > ResponseCache::MAX_KEY_BYTES
if snapshot = FragmentCache.store.read(key)
if ResponseCache.instance.version == version
return ActiveSupport::JSON.decode(snapshot).map { |name, attributes| name.constantize.instantiate(attributes) }
end
end
records = yield
if records.all? { |record| record&.persisted? && !record.changed? } && ResponseCache.instance.version == version
# Raw database values retain timestamp precision and native enum binding.
snapshot = ActiveSupport::JSON.encode(records.map { |record| [ record.class.name, record.attributes_before_type_cast ] })
FragmentCache.store.write(key, snapshot) if snapshot.bytesize <= ResponseCache::MAX_ENTRY_BYTES
end
records
end
end
+4 -16
View File
@@ -6,9 +6,9 @@ class Room::MessagePusher
end
def push
build_payload.tap do |payload|
push_to_users_involved_in_everything(payload)
push_to_users_involved_in_mentions(payload)
subscriptions = push_subscriptions_for_users_involved_in_everything.or(push_subscriptions_for_mentionable_users(message.mentionees))
if subscriptions.exists?
Rails.configuration.x.web_push_pool.queue(build_payload, subscriptions)
end
end
@@ -37,14 +37,6 @@ class Room::MessagePusher
}
end
def push_to_users_involved_in_everything(payload)
enqueue_payload_for_delivery payload, push_subscriptions_for_users_involved_in_everything
end
def push_to_users_involved_in_mentions(payload)
enqueue_payload_for_delivery payload, push_subscriptions_for_mentionable_users(message.mentionees)
end
def push_subscriptions_for_users_involved_in_everything
relevant_subscriptions.merge(Membership.involved_in_everything)
end
@@ -59,10 +51,6 @@ class Room::MessagePusher
Push::Subscription
.joins(user: :memberships)
.merge(User.active)
.merge(Membership.visible.disconnected.where(room: room).where.not(user: message.creator))
end
def enqueue_payload_for_delivery(payload, subscriptions)
Rails.configuration.x.web_push_pool.queue(payload, subscriptions)
.merge(Membership.visible.disconnected.where(room: room).where.not(user_id: message.creator_id))
end
end
+2 -1
View File
@@ -11,8 +11,9 @@
<div class="message__actions-menu border shadow" data-popup-target="menu">
<div class="quick-boosts">
<% boost_path = message_boosts_path(message) %>
<% EmojiHelper::REACTIONS.each do |character, title| %>
<%= form_with model: [ message, Boost.new ], data: { turbo_frame: dom_id(message, :boosting), action: "popup#close"} do |form| %>
<%= form_with url: boost_path, data: { turbo_frame: dom_id(message, :boosting), action: "popup#close"} do |form| %>
<%= hidden_field_tag "boost[content]", character %>
<%= form.button type: "submit", title: title, class: "btn message__action-btn", data: { emoji: character } do %>
<figure class="margin-none boost-character"><%= character %></figure>