MagickMind

Realtime Subscription Patterns

This document explains the architectural patterns for realtime subscriptions in the Magick Mind SDK, with a focus on why the per-user model is the correct choice for most…

Overview

The SDK uses WebSocket connections for realtime communication. The fundamental question when designing your architecture is:

How do you subscribe to user updates?

There are three common patterns, but only one is correct for personal user data.


Architecture

Backend Service (Your SDK Instance)

    └─ Single WebSocket Connection to the Magick Mind API
        ├─ subscribe("user_1")  
        ├─ subscribe("user_2")  
        ├─ subscribe("user_3")  
        └─ ... (500 users = 500 subscriptions)

Each subscription is multiplexed over the same connection.

Implementation

from magick_mind.realtime.events import ChatMessageEvent, EventContext

# Subscribe to multiple users
await client.realtime.subscribe_many([
    "user_1", "user_2", "user_3", ..., "user_500"
])

# Handler receives per-user messages with EventContext
@client.realtime.on("chat_message")
async def handle(event: ChatMessageEvent, ctx: EventContext):
    # ctx.target_user_id is parsed from the channel automatically
    # Only receives messages for subscribed users
    print(f"Message for {ctx.target_user_id}: {event.payload.message}")

Why This is Correct

AspectBenefit
SecurityEach user can only see their own data. No cross-user leakage.
PrivacyGDPR/compliance friendly. Users isolated by design.
EfficiencyServer only sends relevant data. No client-side filtering.
ScalabilityRealtime server handles millions of channels. 500 = ~500KB metadata.
DebuggingEasy to trace per-user issues. Clean logs.

Performance Reality

  • Metadata: ~1KB per subscription
  • 500 subscriptions: ~500KB total memory
  • Centrifugo capacity: 1M+ subscriptions per instance
  • Network: Single WebSocket connection (multiplexed)

Verdict: Scales effortlessly to thousands of users.


Pattern 2: Room/Group Channels ❌ (Anti-Pattern for Personal Data)

Architecture

Backend Service

    └─ subscribe("room_mindspace_123")

    ALL users in mindspace receive ALL messages

    user_1: [msg_a, msg_b, msg_c, msg_d, msg_e]
    user_2: [msg_a, msg_b, msg_c, msg_d, msg_e]  # Same messages!
    user_3: [msg_a, msg_b, msg_c, msg_d, msg_e]  # Same messages!

    Manual client-side filtering: "Is this message for me?"

Implementation

# ❌ DON'T DO THIS for personal messages
await client.realtime.subscribe("room_mindspace_123")

# Receives EVERYTHING — must manually filter
@client.realtime.on("chat_message")
async def handle(event: ChatMessageEvent, ctx: EventContext):
    if ctx.target_user_id != current_user_id:
        return  # Discard (but you still received it over network!)
    # Process it...

Why This is Wrong

ProblemImpact
Privacy ViolationAll users see all data in network traffic
Security RiskCan't prevent users from inspecting traffic
Bandwidth WasteSend 100% of data to all users, use 1%
CPU WasteClient-side filtering on every message
ComplexityManual filtering logic in every client

When to Use (Rarely)

Room channels are ONLY appropriate when:

  • ✅ Real-time collaboration (shared document editing)
  • ✅ Live dashboards (everyone sees same metrics)
  • ✅ Chat rooms (everyone participates)
  • ✅ System-wide announcements

For personal user messages, NEVER use room channels.


Pattern 3: Firehose ❌ (Anti-Pattern)

Architecture

Backend Service

    └─ subscribe("root:*")  # Wildcard subscription

    Receives EVERY EVENT from ENTIRE SYSTEM

    - user_1 messages
    - user_2 messages
    - admin actions
    - system logs
    - ALL events from ALL users

Why This is Wrong

ProblemImpact
OverwhelmingCan't scale. Will crash under load.
SecurityShouldn't have access to all system data
CostMassive bandwidth usage
UnusableMust filter 99.9% of irrelevant data

When to Use (Almost Never)

Only appropriate for:

  • System-level analytics services
  • Central logging/monitoring
  • Audit trail collectors

For application logic, NEVER use firehose pattern.


Decision Matrix

Use CasePatternRationale
Personal notifications (SMS, email triggers)Per-User ✅Privacy required
AI chat responses to usersPer-User ✅Each user gets own conversation
Admin dashboard monitoring 500 agentsPer-User ✅Need per-agent isolation
Backend relay to user frontendsPer-User ✅Security + efficiency
Shared whiteboard (everyone edits)Room ✅True collaboration
Live sports scores (same for all)Room ✅Genuine broadcast
System-wide announcementRoom ✅Everyone needs same message
Analytics pipelineFirehose ⚠️Only if system-level access needed

Common Misconceptions

"500 subscriptions will be slow"

False. The realtime infrastructure is designed for exactly this pattern. Each subscription is lightweight metadata. The bottleneck is network bandwidth, not subscription count.

"One room channel is more efficient"

False. You save on subscription metadata but waste on:

  • Bandwidth (send 100%, use 1%)
  • CPU (client-side filtering)
  • Security (everyone sees everything)

"This means 500 WebSocket connections"

False. This is ONE connection with 500 multiplexed subscriptions. That's the whole point of the architecture.

"I need room channels to group users"

Depends. If each user sees different data within the "room," use per-user. If everyone truly sees the same data, use room.


Migration Guide

If you currently use room channels for personal data:

Before (Anti-Pattern)

# ❌ One room for all users
await client.realtime.subscribe("room_project_123")

# Receive all messages, filter manually
@client.realtime.on("chat_message")
async def handle(event: ChatMessageEvent, ctx: EventContext):
    if ctx.target_user_id in my_users:
        process(event.payload)

After (Correct Pattern)

# ✅ Subscribe to each user
await client.realtime.subscribe_many(my_users)

# Receive only relevant messages — EventContext identifies the user
@client.realtime.on("chat_message")
async def handle(event: ChatMessageEvent, ctx: EventContext):
    # Already filtered server-side, ctx.target_user_id parsed for you
    process(ctx.target_user_id, event.payload)

Benefits After Migration

  • ✅ Reduced bandwidth (10x-100x reduction typical)
  • ✅ Better security (users isolated)
  • ✅ Simpler code (no manual filtering)
  • ✅ Better performance (server-side filtering)

Summary

For personal user data: Always use per-user subscriptions.

  • 500 users = 500 subscriptions ✅
  • Scales to millions of users
  • Secure, private, efficient
  • What the SDK is designed for

Room channels: Only when users genuinely share the same data.

Firehose: Almost never. Only for system-level services.

On this page