개요

Supabase Edge Functions는 Deno 런타임 기반의 서버리스 함수로, 전 세계 엣지 로케이션에서 실행됩니다. Supabase Realtime은 PostgreSQL의 변경사항을 WebSocket으로 실시간 전달하는 시스템입니다. 이 두 기능을 결합하면 별도의 서버 인프라 없이도 실시간 협업 도구, 알림 시스템, 라이브 대시보드를 구축할 수 있습니다.

핵심 개념

Edge Functions: Deno Deploy 위에서 동작하며, TypeScript를 네이티브로 지원합니다. Cold start가 매우 빠르고(약 200ms), 전 세계 엣지 네트워크에서 실행되어 사용자에게 가까운 위치에서 응답합니다. 외부 API 연동, Webhook 처리, 이메일 발송, 결제 처리 등에 활용됩니다.

Realtime 채널: Supabase Realtime은 세 가지 모드를 지원합니다. Postgres Changes(DB 변경 감지), Broadcast(클라이언트 간 메시지), Presence(온라인 상태 공유)입니다. 이 세 가지를 조합하면 대부분의 실시간 기능을 구현할 수 있습니다.

Database Webhooks: 테이블의 INSERT, UPDATE, DELETE 이벤트가 발생하면 자동으로 Edge Function을 호출하는 트리거입니다. 이벤트 기반 아키텍처를 간단하게 구현할 수 있습니다.

RLS와 Realtime: Realtime 구독도 RLS 정책을 따릅니다. 사용자는 자신이 접근 권한이 있는 데이터의 변경사항만 수신할 수 있어 보안이 유지됩니다.

실전 예제

Edge Functions와 Realtime을 결합한 실시간 알림 시스템 구현 예제입니다.

// supabase/functions/send-notification/index.ts
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'

serve(async (req) => {
  const payload = await req.json()
  const { record } = payload

  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  )

  // 게시물 작성자 조회
  const { data: post } = await supabase
    .from('posts')
    .select('user_id, title')
    .eq('id', record.post_id)
    .single()

  // 알림 저장
  await supabase.from('notifications').insert({
    user_id: post.user_id,
    type: 'new_comment',
    message: `"${post.title}"에 새 댓글이 달렸습니다.`,
    data: { comment_id: record.id, post_id: record.post_id },
    is_read: false,
  })

  return new Response(JSON.stringify({ success: true }), {
    headers: { 'Content-Type': 'application/json' },
  })
})

클라이언트에서 실시간 구독을 설정하는 예제입니다.

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)

// 1. Postgres Changes - 알림 테이블 변경 감지
const notificationChannel = supabase
  .channel('my-notifications')
  .on('postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'notifications',
      filter: `user_id=eq.${currentUser.id}`,
    },
    (payload) => {
      showToast(payload.new.message)
      updateNotificationBadge()
    }
  )
  .subscribe()

// 2. Presence - 온라인 사용자 표시
const presenceChannel = supabase.channel('online-users')
presenceChannel
  .on('presence', { event: 'sync' }, () => {
    const state = presenceChannel.presenceState()
    updateOnlineUsers(Object.values(state).flat())
  })
  .subscribe(async (status) => {
    if (status === 'SUBSCRIBED') {
      await presenceChannel.track({
        user_id: currentUser.id,
        name: currentUser.name,
        online_at: new Date().toISOString(),
      })
    }
  })

// 3. Broadcast - 실시간 커서 위치 공유
const cursorChannel = supabase.channel('cursors')
cursorChannel
  .on('broadcast', { event: 'cursor' }, ({ payload }) => {
    renderCursor(payload.userId, payload.x, payload.y)
  })
  .subscribe()

활용 팁

  • Edge Function 최적화: 자주 사용하는 Supabase 클라이언트는 함수 외부에서 생성하면 재사용되어 성능이 향상됩니다. 그러나 요청별로 다른 인증이 필요하면 함수 내부에서 생성하세요.
  • Realtime 필터링: filter 옵션으로 관심 있는 데이터만 구독하면 불필요한 네트워크 트래픽을 줄일 수 있습니다.
  • Broadcast vs Postgres Changes: 클라이언트 간 직접 메시지(커서, 타이핑 상태)는 Broadcast를, DB 변경 감지는 Postgres Changes를 사용하세요. Broadcast가 레이턴시가 더 낮습니다.
  • 에러 처리: Edge Function에서 에러가 발생해도 Database Webhook은 재시도합니다. 멱등성(Idempotency)을 보장하는 설계가 중요합니다.
  • 로컬 개발: supabase functions serve로 로컬에서 Edge Function을 테스트할 수 있습니다. --env-file로 환경변수를 전달하세요.

마무리

Supabase Edge Functions와 Realtime의 조합은 실시간 애플리케이션 개발의 진입 장벽을 크게 낮춰줍니다. WebSocket 서버 관리, 스케일링, 인프라 운영 없이도 프로덕션 수준의 실시간 기능을 구축할 수 있습니다. Next.js와 함께 사용하면 풀스택 실시간 애플리케이션을 빠르게 개발할 수 있습니다.