04
Product
16
Backend
09
Auth
12
iOS
07
Infra
02
Real-Time

Use device ID filtering for subscription signals

ADR-0042 ACCEPTED · 2026-02-15
Use device ID filtering for subscription signals

Context

With real-time subscriptions (ADR-0041), a mutation on Device A publishes a signal via Redis pub/sub. All subscribers to that trip receive the signal — including Device A itself. Without filtering, the originating device would receive its own mutation echoed back and trigger a redundant refetch of data it already has.

This is the "No Local" problem, a well-known pattern in pub/sub systems. MQTT 5.0 has a built-in noLocal subscription option. Google Cloud Pub/Sub supports attribute-based filtering. Most pub/sub systems require the application to solve this.

Two approaches:

  1. Client-side filtering: Include an origin identifier in the signal payload. Each client checks whether it sent the signal and ignores it if so. Simple but trusts the client to filter correctly, and the client still receives unnecessary network traffic.

  2. Server-side filtering: The server checks origin identity before delivering the signal to each subscriber. Requires the server to know both the signal's origin and the subscriber's identity.

The app already has device identity infrastructure: PASETO tokens carry a device_id claim bound at authentication time (ADR-0028). The X-Device-ID header is validated against the token claim in both HTTP middleware and WebSocket connection init, making it tamper-proof.

Decision

We filter subscription signals server-side using the PASETO-bound device_id. The publisher includes device_id in the Redis payload. The subscription resolver compares the signal's origin device against the subscriber's device and drops matches.

This is done in the subscription resolver's stream filter, not at the Redis level. Redis pub/sub has no built-in filtering — all messages reach all subscribers on a channel. The filtering happens per-connection in the GraphQL subscription stream.

The failure mode is fail-open: if the payload can't be parsed, the signal passes through unfiltered. An unparseable origin will never match a real device_id, so the originator receives its own echo (a harmless redundant refetch) rather than other devices silently dropping the signal.

Consequences

Benefits

  • No redundant refetches: The originating device doesn't refetch data it just wrote. Saves one network round-trip per mutation on the editing device.
  • Tamper-proof: device_id comes from signed PASETO token claims, validated at connection time. A client cannot suppress signals destined for another device by fabricating a device_id.
  • Multi-device correct: Device A's edit is delivered to Device B but not echoed back to Device A. Each device has a unique identity bound to its authentication token.
  • Reuses existing infrastructure: No new identity system — device binding was built for session security (ADR-0028) and is repurposed here for signal routing.

Trade-offs

  • Same device, multiple connections: Two connections from the same device share a device_id. Both filter out that device's mutations. For iOS (single app instance per device), this is correct. For a future web client with multiple tabs, cross-tab updates would be suppressed. Solving this would require connection-level identity rather than device-level.
  • Fail-open on parse error: A malformed payload delivers to all subscribers including the originator, causing one redundant refetch. Preferable to fail-closed which could silently drop legitimate signals.