SiamCafe.net Blog
Technology

Supabase Realtime Observability Stack

supabase realtime observability stack
Supabase Realtime Observability Stack | SiamCafe Blog
2025-11-02· อ. บอม — SiamCafe.net· 1,493 คำ

Supabase Realtime Observability Stack คืออะไร

Supabase เป็น open source Firebase alternative ที่ให้บริการ PostgreSQL database, authentication, real-time subscriptions, storage และ edge functions Realtime คือ feature ที่ช่วยให้ clients รับ database changes แบบ real-time ผ่าน WebSocket Observability Stack คือชุดเครื่องมือสำหรับ monitor, trace และ debug ระบบ การรวมแนวคิดเหล่านี้ช่วยสร้าง real-time applications ที่มี observability ครบถ้วน ติดตาม performance, errors และ user behavior ได้แบบ real-time

Supabase Architecture

# supabase_arch.py — Supabase platform architecture
import json

class SupabaseArch:
    SERVICES = {
        "postgres": {
            "name": "PostgreSQL Database",
            "description": "Core database — full PostgreSQL with extensions (pgvector, PostGIS)",
            "port": 5432,
        },
        "realtime": {
            "name": "Realtime Server",
            "description": "WebSocket server ที่ broadcast database changes ให้ clients",
            "protocol": "WebSocket (Phoenix Channels)",
        },
        "auth": {
            "name": "GoTrue (Auth)",
            "description": "Authentication server — email, OAuth, magic links, MFA",
            "providers": "Google, GitHub, Apple, Discord, etc.",
        },
        "storage": {
            "name": "Storage",
            "description": "S3-compatible object storage สำหรับ files, images",
            "features": "Policies, transformations, CDN",
        },
        "edge_functions": {
            "name": "Edge Functions",
            "description": "Serverless functions (Deno runtime) deploy globally",
            "use": "Custom logic, webhooks, integrations",
        },
        "postgrest": {
            "name": "PostgREST",
            "description": "Auto-generate REST API จาก PostgreSQL schema",
            "feature": "Instant CRUD API, filtering, pagination",
        },
    }

    REALTIME_FEATURES = {
        "postgres_changes": {
            "name": "Postgres Changes",
            "description": "Subscribe to INSERT, UPDATE, DELETE events บน tables",
            "example": "Real-time chat, live dashboards, notifications",
        },
        "broadcast": {
            "name": "Broadcast",
            "description": "Send messages ระหว่าง clients ผ่าน channels",
            "example": "Cursor tracking, typing indicators, multiplayer",
        },
        "presence": {
            "name": "Presence",
            "description": "Track online users และ shared state",
            "example": "Online indicators, collaborative editing",
        },
    }

    def show_services(self):
        print("=== Supabase Services ===\n")
        for key, svc in self.SERVICES.items():
            print(f"[{svc['name']}]")
            print(f"  {svc['description']}")
            print()

    def show_realtime(self):
        print("=== Realtime Features ===")
        for key, feature in self.REALTIME_FEATURES.items():
            print(f"  [{feature['name']}] {feature['description']}")
            print(f"    Example: {feature['example']}")

arch = SupabaseArch()
arch.show_services()
arch.show_realtime()

Realtime Implementation

# realtime.py — Supabase Realtime implementation
import json

class RealtimeImplementation:
    JS_CLIENT = """
// supabase-realtime.js — Client-side realtime subscription
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  'https://your-project.supabase.co',
  'your-anon-key'
)

// 1. Subscribe to database changes
const channel = supabase
  .channel('db-changes')
  .on('postgres_changes', 
    { event: '*', schema: 'public', table: 'messages' },
    (payload) => {
      console.log('Change:', payload.eventType, payload.new)
      if (payload.eventType === 'INSERT') {
        addMessage(payload.new)
      } else if (payload.eventType === 'UPDATE') {
        updateMessage(payload.new)
      } else if (payload.eventType === 'DELETE') {
        removeMessage(payload.old)
      }
    }
  )
  .subscribe()

// 2. Broadcast (client-to-client)
const broadcastChannel = supabase.channel('room-1')
broadcastChannel
  .on('broadcast', { event: 'cursor' }, (payload) => {
    updateCursor(payload.payload)
  })
  .subscribe()

// Send cursor position
broadcastChannel.send({
  type: 'broadcast',
  event: 'cursor',
  payload: { x: 100, y: 200, user: 'alice' }
})

// 3. Presence (online users)
const presenceChannel = supabase.channel('online-users')
presenceChannel
  .on('presence', { event: 'sync' }, () => {
    const state = presenceChannel.presenceState()
    console.log('Online:', Object.keys(state).length)
  })
  .subscribe(async (status) => {
    if (status === 'SUBSCRIBED') {
      await presenceChannel.track({ user: 'alice', online_at: new Date() })
    }
  })
"""

    PYTHON_CLIENT = """
# supabase_realtime.py — Python backend integration
from supabase import create_client
import os

SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_SERVICE_KEY")

supabase = create_client(SUPABASE_URL, SUPABASE_KEY)

# Insert data (triggers realtime for subscribers)
def send_message(room_id, user_id, content):
    result = supabase.table("messages").insert({
        "room_id": room_id,
        "user_id": user_id,
        "content": content,
    }).execute()
    return result.data

# Query with filters
def get_messages(room_id, limit=50):
    result = supabase.table("messages") \\
        .select("*, users(name, avatar)") \\
        .eq("room_id", room_id) \\
        .order("created_at", desc=True) \\
        .limit(limit) \\
        .execute()
    return result.data

# Enable Row Level Security (RLS)
# CREATE POLICY "users can read own room messages"
# ON messages FOR SELECT
# USING (room_id IN (SELECT room_id FROM room_members WHERE user_id = auth.uid()))
"""

    def show_js(self):
        print("=== JavaScript Client ===")
        print(self.JS_CLIENT[:600])

    def show_python(self):
        print(f"\n=== Python Backend ===")
        print(self.PYTHON_CLIENT[:500])

rt = RealtimeImplementation()
rt.show_js()
rt.show_python()

Observability Stack

# observability.py — Observability stack for Supabase
import json
import random

class ObservabilityStack:
    PILLARS = {
        "metrics": {
            "name": "Metrics",
            "tools": ["Prometheus", "Grafana", "Supabase Dashboard"],
            "what": "CPU, memory, connections, queries/sec, realtime subscribers",
        },
        "logs": {
            "name": "Logs",
            "tools": ["Supabase Logs (built-in)", "Logflare", "Loki"],
            "what": "API logs, auth logs, realtime logs, edge function logs",
        },
        "traces": {
            "name": "Traces",
            "tools": ["OpenTelemetry", "Jaeger", "Sentry"],
            "what": "Request traces, database query traces, function execution",
        },
    }

    SUPABASE_METRICS = """
# supabase_monitor.py — Monitor Supabase metrics
import requests
import json

class SupabaseMonitor:
    def __init__(self, project_ref, service_key):
        self.base_url = f"https://{project_ref}.supabase.co"
        self.headers = {
            "apikey": service_key,
            "Authorization": f"Bearer {service_key}",
        }
    
    def health_check(self):
        endpoints = {
            "rest": f"{self.base_url}/rest/v1/",
            "auth": f"{self.base_url}/auth/v1/health",
            "realtime": f"{self.base_url}/realtime/v1/",
            "storage": f"{self.base_url}/storage/v1/",
        }
        results = {}
        for name, url in endpoints.items():
            try:
                resp = requests.get(url, headers=self.headers, timeout=5)
                results[name] = {"status": resp.status_code, "healthy": resp.status_code < 400}
            except Exception as e:
                results[name] = {"status": "error", "healthy": False, "error": str(e)}
        return results
    
    def db_metrics(self):
        query = '''
            SELECT 
                count(*) as total_connections,
                count(*) FILTER (WHERE state = 'active') as active,
                count(*) FILTER (WHERE state = 'idle') as idle
            FROM pg_stat_activity
        '''
        resp = requests.post(
            f"{self.base_url}/rest/v1/rpc/exec_sql",
            headers=self.headers,
            json={"query": query}
        )
        return resp.json()

monitor = SupabaseMonitor("your-project", "your-service-key")
health = monitor.health_check()
for svc, status in health.items():
    print(f"  [{svc}] {'OK' if status['healthy'] else 'FAIL'} ({status['status']})")
"""

    def show_pillars(self):
        print("=== Observability Pillars ===\n")
        for key, pillar in self.PILLARS.items():
            print(f"[{pillar['name']}]")
            print(f"  Tools: {', '.join(pillar['tools'])}")
            print(f"  What: {pillar['what']}")
            print()

    def show_monitor(self):
        print("=== Supabase Monitor ===")
        print(self.SUPABASE_METRICS[:500])

    def dashboard(self):
        print(f"\n=== Live Dashboard ===")
        metrics = {
            "DB Connections": f"{random.randint(10, 50)}/100",
            "Realtime Subscribers": random.randint(50, 500),
            "API Requests/min": random.randint(100, 5000),
            "Auth Sessions": random.randint(200, 2000),
            "Storage Usage": f"{random.randint(1, 50)} GB",
            "Edge Function Invocations": f"{random.randint(100, 10000)}/day",
            "P95 Latency": f"{random.randint(20, 200)}ms",
        }
        for m, v in metrics.items():
            print(f"  {m}: {v}")

obs = ObservabilityStack()
obs.show_pillars()
obs.show_monitor()
obs.dashboard()

Alerting & Error Tracking

# alerting.py — Alerting and error tracking
import json
import random

class AlertingSetup:
    SENTRY_INTEGRATION = """
// sentry-supabase.js — Sentry integration
import * as Sentry from '@sentry/browser'
import { createClient } from '@supabase/supabase-js'

Sentry.init({
  dsn: 'https://xxx@sentry.io/yyy',
  tracesSampleRate: 0.1,
  integrations: [Sentry.browserTracingIntegration()],
})

const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)

// Wrap Supabase calls with error tracking
async function fetchData(table, filters = {}) {
  const span = Sentry.startSpan({ name: `supabase.` })
  try {
    let query = supabase.from(table).select('*')
    for (const [key, value] of Object.entries(filters)) {
      query = query.eq(key, value)
    }
    const { data, error } = await query
    if (error) {
      Sentry.captureException(new Error(`Supabase error: `))
      throw error
    }
    return data
  } finally {
    span?.end()
  }
}
"""

    def show_sentry(self):
        print("=== Sentry Integration ===")
        print(self.SENTRY_INTEGRATION[:500])

    def alert_rules(self):
        print(f"\n=== Alert Rules ===")
        rules = [
            {"condition": "DB connections > 80%", "action": "Slack warning", "priority": "P2"},
            {"condition": "API error rate > 5%", "action": "PagerDuty alert", "priority": "P1"},
            {"condition": "Realtime disconnections spike", "action": "Slack + investigate", "priority": "P2"},
            {"condition": "Auth failures > 50/min", "action": "Block IP + alert", "priority": "P1"},
            {"condition": "Edge function timeout > 10s", "action": "Slack warning", "priority": "P3"},
            {"condition": "Storage > 90% capacity", "action": "Email notification", "priority": "P2"},
        ]
        for r in rules:
            print(f"  [{r['priority']}] {r['condition']} → {r['action']}")

    def incident_simulation(self):
        print(f"\n=== Recent Incidents ===")
        incidents = [
            {"time": "2h ago", "type": "Realtime", "desc": f"WebSocket reconnections spike ({random.randint(50, 200)} disconnections)", "status": "Resolved"},
            {"time": "1d ago", "type": "Database", "desc": f"Slow query ({random.randint(5, 30)}s) blocking connections", "status": "Resolved"},
            {"time": "3d ago", "type": "Auth", "desc": f"Brute force attempt ({random.randint(100, 500)} attempts)", "status": "Mitigated"},
        ]
        for i in incidents:
            print(f"  [{i['status']:>8}] {i['time']:>5} [{i['type']}] {i['desc']}")

alert = AlertingSetup()
alert.show_sentry()
alert.alert_rules()
alert.incident_simulation()

Performance Optimization

# performance.py — Supabase performance optimization
import json

class PerformanceOptimization:
    TIPS = {
        "database": {
            "name": "Database Optimization",
            "tips": [
                "สร้าง indexes สำหรับ columns ที่ query บ่อย",
                "ใช้ connection pooling (PgBouncer built-in)",
                "Enable RLS แต่เขียน policies ให้ efficient",
                "ใช้ materialized views สำหรับ complex aggregations",
                "VACUUM ANALYZE เป็นประจำ (auto-vacuum enabled by default)",
            ],
        },
        "realtime": {
            "name": "Realtime Optimization",
            "tips": [
                "Subscribe เฉพาะ tables/columns ที่จำเป็น",
                "ใช้ filters ใน subscription (eq, in, gte)",
                "Unsubscribe เมื่อ component unmount",
                "Rate limit broadcast messages",
                "ใช้ Presence เฉพาะ features ที่ต้องการจริง",
            ],
        },
        "api": {
            "name": "API Optimization",
            "tips": [
                "ใช้ select() เฉพาะ columns ที่ต้องการ",
                "Pagination: range() แทน limit+offset สำหรับ large datasets",
                "Batch operations สำหรับ multiple inserts",
                "Cache responses ด้วย SWR/React Query",
                "ใช้ Edge Functions สำหรับ complex logic",
            ],
        },
    }

    def show_tips(self):
        print("=== Performance Tips ===\n")
        for key, category in self.TIPS.items():
            print(f"[{category['name']}]")
            for tip in category["tips"][:3]:
                print(f"  • {tip}")
            print()

    def benchmark(self):
        import random
        print("=== Performance Benchmark ===")
        benchmarks = {
            "REST API (simple select)": f"{random.randint(5, 30)}ms",
            "REST API (join query)": f"{random.randint(20, 100)}ms",
            "Realtime latency": f"{random.randint(10, 50)}ms",
            "Auth (sign in)": f"{random.randint(50, 200)}ms",
            "Storage (upload 1MB)": f"{random.randint(100, 500)}ms",
            "Edge Function (cold start)": f"{random.randint(50, 300)}ms",
        }
        for name, latency in benchmarks.items():
            print(f"  {name}: {latency}")

perf = PerformanceOptimization()
perf.show_tips()
perf.benchmark()

FAQ - คำถามที่พบบ่อย

Q: Supabase กับ Firebase อันไหนดี?

A: Supabase: PostgreSQL (relational), open source, self-host ได้, SQL Firebase: NoSQL (Firestore), Google ecosystem, mature, serverless ใช้ Supabase: relational data, SQL preference, open source, migrate ง่าย ใช้ Firebase: NoSQL fits, Google Cloud integration, mobile-first

Q: Realtime มี limits ไหม?

A: Free plan: 200 concurrent connections, 2M realtime messages/month Pro plan: 500 connections, 5M messages, increase ได้ Self-hosted: ไม่มี limit (ขึ้นกับ server resources) Tips: unsubscribe เมื่อไม่ใช้, filter subscriptions ให้แคบ

Q: Supabase self-host ยากไหม?

A: ใช้ Docker Compose: ง่ายสำหรับ development Production self-host: ต้อง manage PostgreSQL, Kong, GoTrue, Realtime แยก แนะนำ: ใช้ hosted service สำหรับ production (ลด ops burden) Self-host เหมาะ: compliance requirements, data sovereignty, cost optimization

Q: Observability ต้องใช้ tools อะไรบ้าง?

A: เริ่มต้น: Supabase Dashboard (built-in logs + metrics) เพิ่ม: Sentry (error tracking), Grafana (dashboards) Advanced: OpenTelemetry (traces), Loki (logs), Prometheus (metrics) สำคัญที่สุด: monitor DB connections, API error rate, realtime subscriber count

📖 บทความที่เกี่ยวข้อง

Supabase Realtime Monitoring และ Alertingอ่านบทความ → Supabase Realtime Real-time Processingอ่านบทความ → GCP Anthos Observability Stackอ่านบทความ → Burp Suite Pro Observability Stackอ่านบทความ → Hugo Module Observability Stackอ่านบทความ →

📚 ดูบทความทั้งหมด →