apps/docs/content/troubleshooting/realtime-egress-faq.mdx
Realtime Egress is billed as part of your project's unified egress usage. This FAQ walks through what's counted, what isn't, and how to investigate a gap between the two.
Realtime Egress measures data sent from the Realtime server to a connected client over an open WebSocket connection, plus any request made to the HTTP Broadcast endpoint. This includes Broadcast messages, Postgres Changes payloads, Presence events, and Phoenix protocol frames sent over the same socket (see below).
It does not include internal traffic between the Realtime server and your Postgres database (logical replication, pgoutput reads for Broadcast from Database, or replication slot management). That traffic never reaches a client and isn't part of this metric.
Only data the server sends to the WebSocket, and any HTTP Broadcast request. DB → Realtime logical replication traffic (the connection Realtime uses internally to read changes for Broadcast from Database or Postgres Changes) is not counted — it never leaves the Realtime service to reach a client, so it isn't part of Egress.
No. Replication slot creation, checks, and reconnection attempts to your database are internal to the Realtime service and Postgres. Even if your tenant is frequently idling out and starting back up (for example, logs showing Stop tenant ... because of no connected users followed by slot recreation), that activity is not what's reflected in your billed Realtime Egress.
Yes — any message the server sends over an open socket counts, including heartbeats and Phoenix control frames (phx_reply, phx_close, rejoin payloads). In practice this overhead is minimal compared to actual application data, unless a client is stuck in a join/leave loop (for example, a broken reconnect handler that repeatedly subscribes and unsubscribes from the same channel). Realtime tracks join rate internally, so a runaway rejoin loop is something worth ruling out if your numbers look unusually high relative to your connection count.
No. Tenant idling out (no connected users) and starting back up on the next connection is a normal part of the multi-tenant architecture and is not a mechanism that inflates Realtime Egress. If you're seeing a large gap between your application-measured payload and your billed egress, look at message volume and fan-out first (see below) rather than tenant lifecycle events.
No known issue causes this. The daily Realtime Egress figure reflects real usage attributed to that day, not an artifact of how the graph is rendered or rolled up.
Not currently. Realtime Egress is reported as a single aggregate number — there's no per-source or per-event-type breakdown available in the dashboard or usage APIs today.
This is the most common source of confusion, and almost always comes down to counting writes instead of counting delivered events. A few things to check before assuming a billing issue:
INSERT, UPDATE, and DELETE subscriptions, or multiple overlapping postgres_changes filters), the same underlying row change can be delivered multiple times to the same client.REPLICA IDENTITY FULL), which can be several kilobytes even for a small logical change — especially with wide tables or TOASTed columns. You can reduce this by filtering which changes are sent so irrelevant rows are never delivered, and by selecting specific columns to shrink the payload of the rows that are.Before escalating a gap between measured payload and billed egress, re-derive your estimate using delivered events, not writes: (events per day) × (average delivered payload size) × (average concurrent subscribers). This calculation frequently accounts for a gap that initially looks inexplicable. The Realtime observability report can help here — it shows number of connected clients, payload size, and rate of joins for your project.
postgres_changes subscriptions that could cause duplicate delivery.phx_join/phx_leave cycles) as a source of excess protocol overhead.