본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 19. 18:04

MongoDB Change Streams를 이용한 .NET 서버 측 이벤트 (Server-Sent Events)

요약

MongoDB Change Streams와 ASP.NET Core의 Server-Sent Events(SSE)를 결합하여 실시간 브라우저 업데이트를 구현하는 방법을 설명합니다. WebSockets의 대안으로 단방향 스트리밍을 효율적으로 구축하는 튜토리얼입니다.

핵심 포인트

  • MongoDB Change Streams를 통해 DB 변경 사항을 즉각 감지
  • SSE를 활용한 서버에서 클라이언트로의 효율적인 단방향 데이터 푸시
  • ASP.NET Core Minimal APIs를 이용한 간편한 SSE 구현 방법
  • 대시보드, 알림, 활동 피드 등 실시간 UI 구현에 최적화

이 튜토리얼은 Kevin Smith에 의해 작성되었습니다.

거의 별도의 작업 없이 구현하는 실시간 업데이트

ASP.NET Core에서 실시간 브라우저 업데이트를 원한다면, 보통 WebSockets를 가장 먼저 떠올립니다.

하지만 대시보드 스타일이나 피드 스타일의 UI에는 충분한 아주 좋은 절충안이 있습니다:

  • 데이터베이스 변경을 감지하기 위한 MongoDB Change Streams
  • 해당 변경 사항을 브라우저로 푸시하기 위한 Server-Sent Events (SSE)

이를 통해 매우 적은 양의 코드로 서버에서 클라이언트로 향하는 단방향 스트리밍을 구현할 수 있습니다.

이 포스트에서는 실제 데모 앱을 살펴보고, SSE가 빛을 발하는 부분, 부족한 부분, 그리고 언제 SignalR로 전환해야 하는지를 다룹니다.

데모 작동 모습

다음은 작동 중인 브라우저 데모입니다:

Server-Sent Events란 무엇인가?

Server-Sent Events는 단일 HTTP 연결을 통해 텍스트 이벤트 스트림을 수신하기 위해 구축된 브라우저 API (EventSource)입니다.

SSE를 다음과 같이 생각하면 쉽습니다:

  • 지속되는(long-lived) HTTP 응답
  • 서버에 의해 푸시되는 UTF-8 텍스트 메시지
  • 자동 클라이언트 재연결 지원

SSE는 단방향(서버에서 브라우저로)이며, 이는 다음과 같은 경우에 정확히 필요한 기능인 경우가 많습니다:

  • 라이브 타임라인
  • 알림 (notifications)
  • 감사/활동 피드 (audit/activity feeds)
  • 관리자 대시보드

ASP.NET Core Minimal APIs에서는 이제 TypedResults.ServerSentEvents(...)를 사용하여 이를 매우 간단하게 구현할 수 있습니다.

MongoDB Change Streams란 무엇인가?

몇 초마다 MongoDB를 폴링(polling)하는 대신, 실제 삽입(insert)/업데이트(update)/삭제(delete) 작업이 발생하는 즉시 반응할 수 있습니다.

개괄적으로 설명하면:

  • 애플리케이션이 변경 스트림 커서(change stream cursor)를 엽니다.
  • MongoDB가 변경 이벤트(change events)를 방출합니다.
  • 애플리케이션이 각 변경 사항을 클라이언트가 소비할 수 있는 형태로 매핑합니다.

실시간 시스템의 경우, 이를 통해 폴링 루프(polling loops)에서 발생하는 많은 낭비를 제거할 수 있습니다.

ASP.NET Core에서 함께 연결하기

처음부터 직접 구축해 보겠습니다.

1) 앱 생성

dotnet new web -n MongoDbServerSideEvents

cd MongoDbServerSideEvents
...

dotnet new web은 최소한의 ASP.NET Core 앱을 생성하며, 이는 작은 실시간 데모를 구현하기에 완벽합니다.

2) Program.cs 교체

다음 코드를 시작점으로 사용하세요:

using System.Runtime.CompilerServices;

using MongoDB.Bson;
...

이 코드에는 세 가지 중요한 개념이 있습니다:

  1. 일반적인 HTTP POST 엔드포인트를 사용하여 새로운 주문을 작성합니다.
  2. MongoDB 변경 스트림(change stream)을 열고 들어오는 각 변경 사항을 yield(생성)합니다.
  3. 해당 스트림을 동일한 경로(GET /orders)에서 브라우저에 SSE(Server-Sent Events)로 노출합니다.

쓰기(write)와 스트리밍(streaming) 코드 경로 모두 취소 토큰(cancellation tokens)을 사용한다는 점에 주목하세요. 이를 통해 요청이 중단되거나 앱이 종료될 때 작업을 신속하게 중지할 수 있습니다.

2.1) ResumeAfter 및 SSE EventId를 사용한 스트림 재개

MongoDB 변경 스트림(Change Streams)의 매우 유용한 기능 중 하나는 항상 "지금부터" 시작하는 대신 스트림의 알려진 지점부터 계속할 수 있다는 점입니다.

.NET에서 SseItem<T>EventId 필드를 제공합니다. 여기에 MongoDB 재개 토큰(resume token)을 넣고 이벤트 유형을 order로 설정할 수 있습니다.

브라우저가 재연결될 때, EventSourceLast-Event-ID 헤더에 마지막 이벤트 ID를 자동으로 전송합니다. 우리는 해당 값을 디코딩하여 ChangeStreamOptions.ResumeAfter에 전달할 수 있습니다.

실제 구현 단계는 다음과 같습니다:

  1. 처리하는 각 이벤트에 대해 change.ResumeToken을 캡처합니다.
  2. 이를 인코딩하여 SseItem.EventId에 설정합니다.
  3. 재연결 시 Last-Event-ID를 읽어 ResumeAfter에 매핑합니다.

다음은 최소한의 구현 버전입니다:

app.MapGet("/orders", (

    IMongoCollection<Order> orders,
...

핵심은 ResumeAfter가 임의의 ID가 아니라는 점입니다. 이는 MongoDB가 각 변경 이벤트(change event)에 대해 생성하는 불투명한 토큰 (opaque token)입니다.

브라우저를 사용 중이라면, Last-Event-ID를 통해 깔끔한 재연결 (reconnect) 시나리오를 구현할 수 있습니다. 브라우저가 아닌 소비자 (consumer)의 경우에도, 직접 토큰을 저장한 뒤 동일한 방식으로 다시 전달할 수 있습니다.

3) 간단한 브라우저 클라이언트 추가

wwwroot/index.html을 생성하고 다음을 추가합니다:

<!DOCTYPE html>

<html lang="en">
...

4) 실행

dotnet run

앱을 열고 몇 개의 주문 (orders)을 생성하면, 각 변경 사항이 실시간으로 목록에 스트리밍되는 것을 볼 수 있습니다.

SSE vs SignalR: 각각의 적합한 용도

SSE는 서버에서 브라우저로 향하는 가벼운 단방향 푸시 채널 (one-way push channel)이 필요할 때 매우 훌륭합니다. 이해하기 쉽고, 브라우저 네이티브 (browser-native)이며, 일반적으로 전이중 (full duplex) 설정보다 운영이 더 간단합니다.

트레이드오프 (trade-off)는 SSE가 의도적으로 제한적이라는 점입니다. SSE는 텍스트 기반이며, 단방향이고, 앱이 채팅방 (rooms), 지속 연결 (persistent connections)을 통한 클라이언트의 서버 호출, 또는 고급 클라이언트 조정 (client coordination)과 같은 더 풍부한 실시간 상호작용으로 확장될 때는 사용 편의성 (ergonomics)이 떨어집니다.

이 지점이 바로 SignalR이 가치를 발휘하는 곳입니다. SignalR은 더 높은 수준의 실시간 프로그래밍 모델, 자동 전송 협상 (automatic transport negotiation: WebSockets, SSE, Long Polling), 그룹 (groups), 허브 (hubs), 그리고 확장을 위한 성숙한 생태계를 제공합니다.

주제SSESignalR실무 가이드
방향서버 -> 클라이언트서버 <-> 클라이언트UI가 주로 듣기만 한다면 SSE로도 충분한 경우가 많음
...

실무적인 선택 방법은 다음과 같습니다:

  • 빠르고 단순한 서버-브라우저 업데이트가 필요할 때는 SSE로 시작하세요.
  • 양방향 메시징 (bi-directional messaging)이나 더 풍부한 협업 의미론 (collaboration semantics)이 필요할 때는 SignalR로 전환하세요.

또한, 전송 방식의 선택과 관계없이 MongoDB 특유의 주의 사항을 명심해야 합니다: Change Streams를 사용하려면 레플리카 셋 (replica set) 또는 샤디드 클러스터 (sharded cluster)가 필요합니다. 단독 (standalone) MongoDB 서버에서는 작동하지 않습니다.

프로덕션 강화 아이디어

이 패턴을 프로덕션 환경에 적용하려면 다음을 고려하십시오:

  • 전체 문서를 스트리밍하는 대신 경량 DTO (Data Transfer Object)로 이벤트를 투영(project)하십시오.
  • 스트리밍 커서(cursor) 주변에 취소(cancellation) 지원 및 적절한 폐기(disposal) 로직을 포함하십시오.
  • 스트림의 열기/닫기/재연결 동작에 대해 구조화된 로깅(structured logging)을 추가하십시오.
  • 노이즈를 줄이기 위해 가능한 한 빨리 변경 스트림(change streams)을 필터링하십시오.
  • 인증(auth)으로 엔드포인트를 보호하고 데이터 범위에 따라 권한 부여(authorize)를 수행하십시오.
  • 유휴 연결 타임아웃(idle connection timeouts)에 대한 프록시 설정을 주의 깊게 살피십시오.

SignalR으로의 전환

SSE는 매우 강력한 시작점입니다. 하지만 단방향 피드 이상의 기능이 필요할 때는 SignalR로 이동해야 합니다.

다음과 같은 상황에서는 SignalR이 도움이 됩니다:

  • 진정한 양방향 통신 (two-way communication)
  • 프로토콜 협상 (WebSockets 및 폴백(fallback) 지원)
  • 그룹 및 더 풍부한 허브(hub) 기반 상호작용 패턴
  • ASP.NET Core 생태계에서 이미 잘 다져진 확장(scale-out) 패턴

자연스러운 발전 단계는 다음과 같습니다:

  1. 간단한 실시간 업데이트를 위해 SSE + MongoDB Change Streams로 시작합니다.
  2. 클라이언트가 동일한 채널을 통해 실시간 메시지를 다시 보내야 할 때 SignalR로 확장합니다.
  3. 여러 앱 인스턴스로 확장(scale out)할 때 백플레인(backplane)을 추가합니다.

해당 아키텍처에서 MongoDB를 계속 사용하고 싶다면, Kevsoft.AspNetCore.SignalR.MongoDB를 SignalR 백플레인으로 사용할 수 있습니다.

이를 통해 실시간 파이프라인의 일부로 MongoDB를 활용하면서도, SignalR 전송 방식(WebSockets, SSE, Long Polling)의 유연성을 얻을 수 있습니다.

마치며

많은 애플리케이션에서 MongoDB Change Streams와 SSE의 조합은 최적의 지점(sweet spot)입니다:

  • 최소한의 구성 요소
  • 적은 양의 코드
  • 실시간 UX로 가는 빠른 경로

그 후 요구사항이 진화하면, 처음부터 다시 작성하는 대신 SignalR로 넘어가는 것이 자연스러운 다음 단계가 됩니다.

단순하게 시작하여 빠르게 가치를 얻으십시오. 전송 복잡도는 제품이 실제로 필요로 할 때만 높이십시오.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0