Qwik Pub/Sub
Qwik Resumability Pub/Sub Architecture Event-driven WebSocket SSE Real-time Redis NATS Lazy Loading Fine-grained TTI
| Transport | Direction | Use Case | Complexity |
|---|---|---|---|
| SSE (Server-Sent Events) | Server → Client | Notification, Live Feed, Price | ง่าย |
| WebSocket | Bidirectional | Chat, Collaboration, Gaming | ปานกลาง |
| Long Polling | Client → Server | Legacy Fallback | ง่าย (แต่ Inefficient) |
| WebTransport | Bidirectional | Low Latency Gaming | สูง (ใหม่) |
Qwik Component Design
// === Qwik Pub/Sub Component ===
// import { component$, useSignal, useVisibleTask$ } from '@builder.io/qwik';
//
// interface Message {
// id: string;
// topic: string;
// data: any;
// timestamp: number;
// }
//
// export const LiveFeed = component$(() => {
// const messages = useSignal([]);
// const connected = useSignal(false);
//
// useVisibleTask$(({ cleanup }) => {
// // SSE Connection - Only runs on client, only when visible
// const eventSource = new EventSource('/api/subscribe?topic=prices');
//
// eventSource.onopen = () => {
// connected.value = true;
// };
//
// eventSource.onmessage = (event) => {
// const msg: Message = JSON.parse(event.data);
// messages.value = [msg, ...messages.value.slice(0, 49)];
// };
//
// eventSource.onerror = () => {
// connected.value = false;
// // Auto-reconnect built into EventSource
// };
//
// cleanup(() => {
// eventSource.close();
// connected.value = false;
// });
// });
//
// return (
//
// {connected.value ? 'Connected' : 'Disconnected'}
// {messages.value.map((msg) => (
// {msg.topic}: {JSON.stringify(msg.data)}
// ))}
//
// );
// });
// === Benefits of Resumability + Pub/Sub ===
// 1. Zero JS until user scrolls to LiveFeed component (Lazy)
// 2. SSE connection only starts when component is visible
// 3. cleanup() automatically closes connection on unmount
// 4. useSignal provides fine-grained reactivity (no re-render entire tree)
from dataclasses import dataclass
@dataclass
class QwikPattern:
pattern: str
api: str
when_runs: str
use_case: str
patterns = [
QwikPattern("Server Data Load",
"routeLoader$()",
"Server-side (SSR)",
"Initial Data, SEO Content, DB Query"),
QwikPattern("Client Subscription",
"useVisibleTask$()",
"Client-side เมื่อ Component Visible",
"WebSocket SSE Connection, Timer"),
QwikPattern("Reactive State",
"useSignal() / useStore()",
"ทั้ง Server และ Client",
"UI State, Message List, Connection Status"),
QwikPattern("Server Action",
"server$()",
"Server-side (เรียกจาก Client)",
"Publish Message, DB Write, API Call"),
QwikPattern("Form Mutation",
"routeAction$()",
"Server-side (จาก Form Submit)",
"Send Message, Create Post, Update Settings"),
]
print("=== Qwik Patterns ===")
for p in patterns:
print(f" [{p.pattern}] API: {p.api}")
print(f" Runs: {p.when_runs}")
print(f" Use: {p.use_case}")
Backend Pub/Sub
# === Backend Pub/Sub with Redis + SSE ===
# Server-side SSE Endpoint (Node.js / Qwik Server)
# export const onGet: RequestHandler = async ({ send, headers }) => {
# headers.set('Content-Type', 'text/event-stream');
# headers.set('Cache-Control', 'no-cache');
# headers.set('Connection', 'keep-alive');
#
# const redis = new Redis();
# await redis.subscribe('prices');
#
# redis.on('message', (channel, message) => {
# send(`data: \n\n`);
# });
# };
# Publish from another service
# import redis
# r = redis.Redis()
# r.publish('prices', json.dumps({
# 'id': str(uuid4()),
# 'topic': 'BTC/USD',
# 'data': {'price': 67500.50, 'change': 2.3},
# 'timestamp': time.time()
# }))
@dataclass
class PubSubBackend:
broker: str
protocol: str
throughput: str
persistence: str
best_for: str
backends = [
PubSubBackend("Redis Pub/Sub",
"TCP + RESP Protocol",
"100K+ msg/s",
"No (Fire & Forget)",
"Real-time ไม่ต้องเก็บ History"),
PubSubBackend("Redis Streams",
"TCP + RESP Protocol",
"100K+ msg/s",
"Yes (Append-only Log)",
"Real-time + History + Consumer Group"),
PubSubBackend("NATS",
"TCP + NATS Protocol",
"10M+ msg/s",
"Yes (JetStream)",
"High Throughput Microservices"),
PubSubBackend("Kafka",
"TCP + Kafka Protocol",
"1M+ msg/s",
"Yes (Durable Log)",
"Event Sourcing Big Data Pipeline"),
PubSubBackend("RabbitMQ",
"AMQP",
"50K+ msg/s",
"Yes (Queue)",
"Task Queue Complex Routing"),
]
print("=== Pub/Sub Backends ===")
for b in backends:
print(f"\n [{b.broker}] Protocol: {b.protocol}")
print(f" Throughput: {b.throughput}")
print(f" Persistence: {b.persistence}")
print(f" Best for: {b.best_for}")
Production Patterns
# === Production Patterns ===
@dataclass
class ProdPattern:
pattern: str
problem: str
solution: str
implementation: str
prod_patterns = [
ProdPattern("Reconnection with Backoff",
"Connection หลุดจาก Network Issue",
"Exponential Backoff: 1s 2s 4s 8s max 30s",
"SSE มี Auto-reconnect WS ต้องเขียนเอง"),
ProdPattern("Authentication",
"Unauthorized Subscribe",
"ส่ง JWT Token ใน URL param หรือ First Message",
"EventSource('/api/sub?token=xxx') หรือ WS onopen"),
ProdPattern("Topic-based Filtering",
"ไม่อยากรับทุก Message",
"Subscribe เฉพาะ Topic ที่สนใจ",
"redis.subscribe('prices:BTC', 'prices:ETH')"),
ProdPattern("Heartbeat / Keep-alive",
"Connection timeout จาก Proxy/LB",
"ส่ง Heartbeat ทุก 30 วินาที",
"setInterval(() => send(':heartbeat'), 30000)"),
ProdPattern("Message Deduplication",
"ได้ Message ซ้ำจาก Reconnect",
"ใช้ Message ID + Last Event ID",
"EventSource lastEventId, Server filter dup"),
ProdPattern("Cleanup on Unmount",
"Memory Leak จาก Connection ค้าง",
"Close Connection เมื่อ Component Unmount",
"useVisibleTask$ cleanup() callback"),
]
print("=== Production Patterns ===")
for p in prod_patterns:
print(f" [{p.pattern}]")
print(f" Problem: {p.problem}")
print(f" Solution: {p.solution}")
print(f" Impl: {p.implementation}")
เคล็ดลับ
- SSE: ใช้ SSE ถ้าต้องการแค่ Server Push ง่ายกว่า WebSocket
- useVisibleTask$: เชื่อมต่อเฉพาะเมื่อ Component Visible ลด Resource
- cleanup: ปิด Connection ใน cleanup() ทุกครั้ง ป้องกัน Memory Leak
- Redis Streams: ใช้แทน Pub/Sub ถ้าต้องเก็บ History
- Heartbeat: ส่ง Heartbeat ทุก 30s ป้องกัน Proxy ตัด Connection
Qwik Resumability คืออะไร
Web Framework Resumability แทน Hydration Zero JS TTI เร็ว Lazy Loading Fine-grained $ sign component$ useTask$ onClick$
Pub/Sub Pattern คืออะไร
Publisher Subscriber Topic Broker Decoupled Redis NATS Kafka RabbitMQ Scalable Real-time WebSocket SSE Notification Chat Dashboard
ใช้กับ Qwik อย่างไร
useVisibleTask$ SSE WebSocket useSignal useStore server$ routeLoader$ routeAction$ EventSource cleanup Reactive State Real-time
Production Best Practices คืออะไร
SSE ง่ายกว่า WS Reconnection Backoff Auth JWT Token Topic Filter Heartbeat Dedup Message ID Cleanup Unmount Rate Limiting Dead Letter
สรุป
Qwik Resumability Pub/Sub SSE WebSocket Redis NATS useVisibleTask$ useSignal Lazy Loading Real-time Reconnection Heartbeat Production
