Server-Sent Events vs. Webhook

실시간 애플리케이션을 개발할 때 서버와 클라이언트 간의 효율적인 통신 방식을 선택하는 것은 매우 중요하다. 서버 전송 이벤트(Server-Sent Events, SSE)와 웹훅(Webhook)은 모두 서버에서 클라이언트로 데이터를 전달하는 방법이지만, 그 작동 방식과 적합한 사용 사례가 크게 다르다.

서버 전송 이벤트(SSE)

기본 개념

서버 전송 이벤트(SSE)는 HTTP 연결을 통해 서버에서 클라이언트로 단방향 실시간 이벤트 스트림을 전송하는 기술이다. HTML5 표준의 일부로, 웹 브라우저에서 EventSource API를 통해 구현된다. SSE는 표준 HTTP 프로토콜 위에서 작동하며, 별도의 프로토콜 전환 없이 실시간 데이터 푸시가 가능하다.

작동 원리

  1. 클라이언트는 EventSource 객체를 사용하여 서버에 HTTP 연결을 요청한다.
  2. 서버는 text/event-stream 콘텐츠 유형으로 응답하고, 연결을 유지한다.
  3. 서버는 필요할 때마다 이벤트 형식의 데이터를 스트림으로 전송한다.
  4. 클라이언트는 수신된 이벤트를 처리하기 위한 이벤트 리스너를 설정한다.
  5. 연결이 끊어지면 클라이언트는 자동으로 재연결을 시도한다.

코드 예시

클라이언트 측 (JavaScript):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// EventSource 객체 생성
const eventSource = new EventSource('/api/events');

// 메시지 수신 이벤트 처리
eventSource.onmessage = function(event) {
    const data = JSON.parse(event.data);
    console.log('새 이벤트 수신:', data);
    updateUI(data);
};

// 특정 이벤트 타입 처리
eventSource.addEventListener('update', function(event) {
    const updateData = JSON.parse(event.data);
    handleUpdate(updateData);
});

// 오류 처리
eventSource.onerror = function(error) {
    console.error('SSE 연결 오류:', error);
};

서버 측 (Node.js):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
app.get('/api/events', (req, res) => {
    // SSE 헤더 설정
    res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
    });
    
    // 주기적으로 이벤트 전송
    const intervalId = setInterval(() => {
        const data = { timestamp: Date.now(), value: Math.random() };
        
        // 일반 메시지 전송
        res.write(`data: ${JSON.stringify(data)}\n\n`);
        
        // 특정 이벤트 타입으로 전송
        res.write(`event: update\ndata: ${JSON.stringify(data)}\n\n`);
    }, 5000);
    
    // 연결 종료 시 정리
    req.on('close', () => {
        clearInterval(intervalId);
    });
});

웹훅(Webhook)

기본 개념

웹훅(Webhook)은 특정 이벤트가 발생했을 때 서버가 다른 외부 서비스(클라이언트)의 URL로 HTTP POST 요청을 보내는 방식이다. 일종의 “역방향 API” 또는 “콜백"으로, 클라이언트가 서버에 지속적으로 요청을 보내는 대신, 서버가 이벤트 발생 시에만 클라이언트에게 알림을 보낸다.

작동 원리

  1. 클라이언트는 자신의 URL(콜백 URL)을 서버에 등록한다.
  2. 서버는 특정 이벤트가 발생하면 해당 URL로 HTTP POST 요청을 보낸다.
  3. 클라이언트는 이 요청을 받아 처리하고, 일반적으로 200 OK 응답을 반환한다.
  4. 서버는 응답 코드에 따라 웹훅 전송 성공 여부를 판단한다.
  5. 실패 시 서버는 보통 재시도 메커니즘을 구현한다.

코드 예시

웹훅 수신자 (웹 서버, Node.js):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const express = require('express');
const app = express();
app.use(express.json());

// 웹훅 엔드포인트
app.post('/webhook', (req, res) => {
    const event = req.body;
    
    console.log('웹훅 수신:', event);
    
    // 이벤트 유형에 따른 처리
    switch(event.type) {
        case 'payment.success':
            processPayment(event.data);
            break;
        case 'user.created':
            onboardUser(event.data);
            break;
        default:
            console.log('처리되지 않은 이벤트 유형:', event.type);
    }
    
    // 성공 응답
    res.status(200).send('Event received');
});

app.listen(3000, () => {
    console.log('웹훅 수신 서버가 포트 3000에서 실행 중입니다.');
});

웹훅 발신자 (이벤트 소스, Node.js):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
async function sendWebhook(event) {
    try {
        // 등록된 웹훅 URL로 POST 요청 전송
        const response = await fetch('https://client-server.com/webhook', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Webhook-Signature': generateSignature(event), // 보안을 위한 서명
            },
            body: JSON.stringify(event)
        });
        
        if (response.ok) {
            console.log('웹훅 전송 성공');
            return true;
        } else {
            console.error('웹훅 전송 실패:', response.status);
            // 필요시 재시도 로직
            return false;
        }
    } catch (error) {
        console.error('웹훅 전송 중 오류:', error);
        // 필요시 재시도 로직
        return false;
    }
}

// 결제 성공 시 웹훅 전송 예시
function onPaymentSuccess(payment) {
    // 비즈니스 로직 처리
    updateDatabase(payment);
    
    // 웹훅 이벤트 전송
    sendWebhook({
        type: 'payment.success',
        timestamp: Date.now(),
        data: {
            paymentId: payment.id,
            amount: payment.amount,
            currency: payment.currency,
            customerId: payment.customerId
        }
    });
}

SSE와 Webhook의 비교 분석

핵심 차이점

두 기술의 가장 근본적인 차이점은 커뮤니케이션 모델에 있다:

  • SSE는 클라이언트가 서버에 연결하여 지속적인 스트림으로 데이터를 받는 푸시 모델이다.
  • 웹훅은 서버가 특정 이벤트 발생 시 클라이언트의 URL로 요청을 보내는 콜백 모델이다.

상세 비교표

특성서버 전송 이벤트(SSE)웹훅(Webhook)
통신 방향서버 → 클라이언트 (단방향)서버 → 클라이언트 (단방향)
연결 유형지속적 연결 (긴 HTTP 연결)일시적 연결 (개별 HTTP 요청)
프로토콜HTTP/HTTPSHTTP/HTTPS
클라이언트 요구사항웹 브라우저 또는 EventSource 지원 클라이언트공개적으로 접근 가능한 HTTP 엔드포인트
표준화HTML5 표준의 일부비공식 패턴 (각 서비스마다 구현 다름)
재연결 메커니즘자동 재연결 내장수동 구현 필요 (재시도 로직)
이벤트 순서순서 보장 (스트림 특성)보장되지 않음 (네트워크 지연 등으로 순서 바뀔 수 있음)
지연 시간매우 낮음 (실시간)상대적으로 높음 (요청-응답 사이클)
메시지 전달 보증연결이 유지되는 동안 보장재시도 로직 구현 필요
보안일반적인 HTTP 보안 매커니즘서명, 토큰, IP 제한 등 추가 보안 구현 필요
부하 분산연결 유지로 인한 부하 발생 가능개별 요청으로 부하 분산 용이
방화벽 문제클라이언트에서 서버로의 접근 필요서버에서 클라이언트로 접근 가능해야 함 (공개 URL)
상태 관리연결 상태 관리 필요상태 비저장(stateless) 방식
브라우저 지원대부분의 현대 브라우저 지원브라우저 직접 수신 불가 (서버 필요)
데이터 형식text/event-stream (주로 JSON)일반적으로 JSON
사용 사례대시보드, 실시간 알림, 주식 시세 등결제 처리, 웹훅 통합, CI/CD 파이프라인 등
확장성다수의 클라이언트 연결 시 확장 어려움상대적으로 더 확장 용이
구현 복잡성비교적 단순중간 정도 (인증, 재시도 로직 등 구현 필요)

세부 비교 분석

통신 모델과 연결 유형

SSE는 클라이언트가 서버에 지속적인 HTTP 연결을 유지하며, 서버는 해당 연결을 통해 이벤트를 지속적으로 스트리밍한다. 이러한 지속적 연결 방식은 실시간성이 높지만, 서버에 더 많은 리소스가 필요하다.

웹훅은 각 이벤트마다 새로운 HTTP 요청을 생성하는 일시적 연결 방식이다. 이는 서버 리소스를 효율적으로 사용할 수 있지만, 지속적인 실시간 업데이트보다는 간헐적인 알림에 더 적합하다.

클라이언트 요구사항

SSE는 클라이언트가 EventSource API를 지원해야 하며, 주로 웹 브라우저 환경에서 사용된다. 모바일 앱이나 다른 환경에서는 별도의 라이브러리가 필요할 수 있다.

웹훅은 클라이언트가 공개적으로 접근 가능한 HTTP 엔드포인트를 가지고 있어야 한다. 이는 주로 서버-서버 통신에 적합하며, 웹 브라우저와 같은 클라이언트 환경에서는 직접 수신이 불가능하다.

이벤트 전달 신뢰성

SSE는 이벤트에 ID를 부여하여 재연결 시 마지막으로 수신한 이벤트 이후의 데이터만 받을 수 있게 한다. 이는 메시지 손실을 방지하고 순서를 보장한다.

웹훅은 기본적으로 이벤트 순서나 전달 보증을 제공하지 않는다. 네트워크 지연이나 오류로 인해 이벤트가 순서대로 도착하지 않거나 손실될 수 있다. 이를 해결하기 위해 별도의 재시도 메커니즘, 이벤트 식별자, 타임스탬프 등을 구현해야 한다.

보안 고려사항

SSE는 일반적인 HTTP 보안 메커니즘(HTTPS, 쿠키, 인증 헤더 등)을 사용한다. 클라이언트가 서버에 연결하는 구조이므로, 서버 측에서 인증 및 권한 부여를 관리할 수 있다.

웹훅은 서버가 클라이언트에게 요청을 보내는 구조이므로, 추가적인 보안 조치가 필요하다.
일반적으로 다음과 같은 방법을 사용한다:

  • 시크릿 토큰을 사용한 요청 서명
  • IP 주소 제한
  • HMAC 기반 메시지 검증
  • 타임스탬프와 난스(nonce) 검증
확장성

SSE는 각 클라이언트마다 서버와의 지속적인 연결을 유지해야 하므로, 대규모 동시 사용자를 처리할 때 확장성 문제가 발생할 수 있다. 이를 해결하기 위해 로드 밸런싱, 메시지 큐 등의 추가 인프라가 필요할 수 있다.

웹훅은 필요할 때만 개별 HTTP 요청을 보내는 방식이므로, 일반적으로 더 나은 확장성을 제공한다. 그러나 많은 수의 웹훅을 빠르게 처리해야 하는 경우, 큐 시스템과 비동기 처리가 필요할 수 있다.

적합한 사용 사례

SSE에 적합한 사용 사례

  1. 실시간 대시보드: 지속적으로 업데이트되는 분석 데이터, 시스템 모니터링 정보 등을 표시하는 대시보드
  2. 라이브 피드: 소셜 미디어 타임라인, 뉴스 피드, 댓글 스트림 등
  3. 주식 시세 및 금융 데이터: 실시간으로 변동되는 시장 데이터
  4. 스포츠 경기 중계: 실시간 점수, 통계, 이벤트 업데이트
  5. 사용자 알림: 브라우저나 웹 애플리케이션에서의 실시간 알림
  6. 채팅 애플리케이션: 단방향 메시지 수신(전송은 별도 API 필요)

웹훅에 적합한 사용 사례

  1. 결제 프로세싱: 결제 완료, 실패, 환불 등의 상태 변경 알림
  2. 외부 서비스 통합: CRM, 이메일 마케팅, 고객 지원 시스템 간 통합
  3. CI/CD 파이프라인: 코드 변경, 빌드 완료, 배포 완료 등의 이벤트 알림
  4. IoT 이벤트 처리: 센서 데이터가 특정 임계값을 초과할 때 알림
  5. 콘텐츠 관리: 새 콘텐츠 게시, 업데이트, 승인 등의 이벤트
  6. 비동기 작업 완료 알림: 장시간 실행되는 작업이 완료되었을 때 알림

하이브리드 접근법

많은 현대적인 시스템에서는 SSE와 웹훅을 상호 보완적으로 사용한다:

  1. 웹훅 + 폴링: 웹훅이 실패할 경우를 대비해 주기적 폴링을 백업으로 구현
  2. 웹훅 → 메시지 큐 → SSE: 웹훅으로 이벤트를 수신하고, 메시지 큐를 통해 SSE로 클라이언트에 전달
  3. 상황별 선택: 실시간성이 중요한 기능은 SSE, 중요 이벤트 알림은 웹훅 사용

용어 정리

용어설명

참고 및 출처