feat: presence and cross-region event forwarding (PUT-1679) (#3686)

* feat: presence and cross-region event forwarding (PUT-1679)

* fix: fan cache bumps to sibling nodes and stop the forward shed cascading (PUT-1679)

`outer.events.generationBumped` and `outer.events.presenceBumped` rode
`outer.*`, which the broadcast service only webhooks to peer regions;
only `outer.pubsub.*` also fans over Redis to a region's other nodes.
Both caches are per-process maps with no expiry, so a bump landing on
one node left its siblings stale until that user's next transition.
Renamed onto `outer.pubsub.events.*`; the listeners already accept the
`from_outside` copy the Redis re-emit carries.

`PeerForwardQueue.push` called `onOverflow` synchronously and the
handler pushed markers straight back, each of which re-tripped the
bound and shed the next item: one item over a 5000 bound recursed ~2200
deep, threw a RangeError, and turned ~2200 queued deliveries into gap
markers. It also re-summed `bytes` over the whole queue per drop. The
handler now returns its markers and the queue appends them past the
bound check, sheds deliveries before markers, keeps one pending marker
per (peer, subscription), and subtracts bytes per dropped item.
This commit is contained in:
Daniel Salazar
2026-09-03 15:39:21 -07:00
committed by GitHub
parent 32a838d2e2
commit eb8f497e9f
22 changed files with 3438 additions and 58 deletions
+6
View File
@@ -176,6 +176,12 @@ Pass `handler` as a **function** and it runs here too, whenever this client is t
A persistent subscription can also stop without you unsubscribing: its handler was removed, its holder ran out of credit, the handler kept failing, or the share it was made under was withdrawn. It is then *suspended* rather than deleted, and [`list()`](/Events/list/) reports `suspendedAt` and `suspendedReason`. Everything but a withdrawn grant can resume.
### Where your client is connected does not matter
Puter runs in several places, and a client connects to whichever one is nearest. Nothing about that is yours to think about: an event finds the connection wherever it is, `ack()` settles the delivery it belongs to whichever connection you called it on, and the shape of everything you receive is identical either way.
The one consequence worth knowing is the one already stated: a `single` delivery is **at-least-once**. Undelivered events are held where the change happened, so a deployment going down loses only what it was still holding — the subscription itself, and everything already delivered, is unaffected. Handlers are asked to be idempotent for this reason, and `event.id` is the key to deduplicate on.
## Limits
Subscriptions per connection, persistent subscriptions per account, published handlers per app, subscribe calls per minute, and how much one event may fan out are all capped — see [Rate Limits and Quotas](/rate-limits-and-quotas/). Deliveries are coalesced over 250 ms per subject, so a multipart upload or a save loop arrives as one event rather than one per write.