CodeGym /행동 /ChatGPT Apps /스트림 신뢰성: rate‑limits, backpressure 및 이벤트 모니터링

스트림 신뢰성: rate‑limits, backpressure 및 이벤트 모니터링

ChatGPT Apps
레벨 13 , 레슨 4
사용 가능

1. 왜 스트림은 부하에 특히 민감한가

이전 파트에서 우리는 GiftGenius를 위해 MCP‑이벤트, job.progress/job.completed 상태, async‑job 및 스트리밍 채널(SSE/HTTP-stream)이 어떻게 구성되는지 살펴봤습니다. 이제 이 아키텍처가 실제 부하에서 어떻게 동작하는지 보는 것이 중요합니다.

사용자가 한 명이고 가끔 선물 찾기를 실행할 때는 모든 것이 훌륭해 보입니다. 하지만 GiftGenius가 프로덕션에 올라가고 동시에 “모든 직원에게 단체 선물 추천하기” 같은 요청이 수백 건 들어오는 순간, 다음과 같은 상황을 맞닥뜨리게 됩니다:

  • 서버에 수백 개의 장시간 지속되는 SSE 연결이 열려 있음;
  • 워커가 사소한 변화마다 job.progress를 계속 보냄;
  • 로그가 하루에도 기가바이트 단위로 불어남;
  • “서버가 안 죽는 것처럼 보이는데도” 사용자 UI가 끊기기 시작함.

일반적인 HTTP 요청은 수 밀리초 혹은 수 초 동안만 살아 있습니다. SSE나 HTTP‑stream은 수분, 심지어 수시간 동안 살아 있을 수 있습니다. 이들은 연결, 메모리, 파일 디스크립터를 붙잡아 둡니다. 전송되는 각 event는 JSON 직렬화, 네트워크 복사, GC 작업을 의미합니다. 이를 “백엔드에서 콘솔 로그 하나 더 찍는 것” 정도로 보면, 시스템은 금세 난로가 됩니다.

MCP‑이벤트에는 또 다른 특징이 있습니다. 동일한 작업에 대해 여러 번 생성되는 경우가 잦다는 점입니다. 0.1%마다 진행률을 갱신하는 워커는 하나의 job에 대해 인상적인 수의 이벤트를 만들어 냅니다. 그 결과 “노이즈”, 즉 다음과 같은 수많은 작은 메시지를 얻게 됩니다:

  • 네트워크와 CPU를 부담시키고;
  • 큐와 버퍼를 가득 채우며;
  • 디버깅과 로그 분석을 고통스럽게 만듭니다.

따라서 스트림과 MCP‑이벤트 모두를 데이터베이스 쿼리나 모델 호출만큼이나 신중하게 다뤄야 합니다. 이들은 표준화, 제어, 모니터링이 필요한 고가치 자원입니다.

이를 다루기 위해 다음 세 가지 큰 주제를 염두에 둡니다:

  1. Rate‑limits — 이벤트/스트림을 얼마나 자주, 얼마나 많이 생성·전송할 수 있는지 제한합니다.
  2. Backpressure — 소비자가 생산자를 따라가지 못할 때 대응합니다.
  3. 모니터링과 메트릭 — 상황을 측정하고, 임계에 다다르기 전에 이상 징후를 감지합니다.

2. 스트림과 이벤트의 rate‑limiting

가장 직관적인 것, 즉 제한부터 시작하겠습니다.

스트리밍 시나리오에서는 “위험한 문제 유발자”가 종종 클라이언트가 아니라 서버라는 점을 이해해야 합니다. 일반적인 REST API에서는 사용자가 서버를 DDoS로 만들지 못하도록 요청 수를 제한합니다. MCP와 스트림의 세계에서는 반대 DDoS를 만들기 쉽습니다. 워커나 MCP 서버가 초당 수천 개의 이벤트를 클라이언트로 퍼붓는 식이죠.

어떤 제한이 필요한가

보통 세 가지 축으로 생각합니다.

첫째, 사용자 또는 세션 단위의 제한. 한 사용자가 GiftGenius 마스터 위젯을 서너 개씩 띄워 각자 SSE 스트림을 열게 두어서는 안 됩니다. 합리적인 제한은 세션당 활성 스트림을 몇 개로 제한하고, 특정 사용자나 테넌트에 대해 running 상태인 job의 개수도 제한하는 것입니다.

둘째, 단일 job에 대한 제한. 여기서는 이벤트 빈도가 중요합니다. job.progress는 N 밀리초보다 자주 보내지 않거나, 유의미한 변화가 있을 때만, 예를 들어 진행률이 5%포인트 변할 때만 보내면 충분합니다. 카탈로그의 각 상품을 처리할 때마다 메시지를 보낼 필요는 없습니다. 또한 payload 크기를 제한하는 것도 타당합니다. 진행률 이벤트는 수 MB의 텍스트를 담아서는 안 됩니다.

셋째, IP 또는 조직 단위의 제한. 이는 남용 방지입니다. 누군가 스크립트로 작업을 마구 생성해서 스팸을 보내거나, 앱이 예상치 못하게 인기 급상승할 때를 대비합니다. 여기서는 API 게이트웨이와 프록시의 친숙한 메커니즘을 활용하게 됩니다.

이벤트 빈도 제한의 간단한 구현

GiftGenius의 워커가 백그라운드에서 수신자 목록이 긴 선물 추천을 수행하고, 주기적으로 MCP‑notification event/progress로 진행률을 보낸다고 가정해 봅시다. 우리는 이벤트가 500밀리초보다 자주 전송되지 않고, 진행률이 최소 5포인트 이상 변할 때만 전송되기를 원합니다.

워커용 TS 의사 코드:

// 가령 mcpClient.sendNotification(...) 이 있다고 하자
let lastSentPercent = 0;
let lastSentAt = 0;

function reportProgress(jobId: string, percent: number, message: string) {
  const now = Date.now();
  const percentDelta = percent - lastSentPercent;
  const timeDelta = now - lastSentAt;

  // 경과 시간이 500ms 이상이거나 증가폭이 5% 이상일 때만 전송
  if (percentDelta >= 5 || timeDelta >= 500) {
    mcpClient.sendNotification("event/progress", {
      jobId,
      percent,
      message,
    });
    lastSentPercent = percent;
    lastSentAt = now;
  }
}

이 접근은 트로틀링이라고 합니다. 시간과 값의 변화량 기준으로 이벤트 스트림을 “성긴 간격으로” 만든 것입니다.

작업을 단계로 나누는 경우(“1단계/총 3단계”, “2단계/총 3단계”) 로직은 더 간단합니다. 단계가 바뀔 때만 이벤트를 보내면 됩니다.

동시에 열린 스트림 수 제한

MCP 서버 쪽에는 보통 SSE용 HTTP 핸들러가 있을 것입니다:

// app/api/events/[userId]/route.ts (Next.js 16 App Router)
export async function GET(
  req: Request,
  { params }: { params: { userId: string } },
) {
  const userId = params.userId;

  if (!canOpenMoreStreams(userId)) {
    return new Response("Too many streams", { status: 429 });
  }

  const stream = new ReadableStream({
    start(controller) {
      registerSseClient(userId, controller);
    },
    cancel() {
      unregisterSseClient(userId);
    },
  });

  return new Response(stream, {
    headers: { "Content-Type": "text/event-stream" },
  });
}

함수 canOpenMoreStreams는 해당 사용자의 현재 열린 연결 수를 확인하고 임계값(예: 동시 스트림 최대 3개)과 비교할 수 있습니다. 제한을 초과하면 429를 반환하고, GPT 지침에서는 이런 상황에서 새로운 긴 마스터를 시작하기보다 “이미 활성화된 추천이 있으니 완료까지 기다리자”고 사용자에게 안내하도록 모델에 설명합니다.

소규모 시스템에서는 이러한 검사를 프로세스 메모리에서 구현할 수 있습니다. 보다 진지한 인프라에서는 MCP 게이트웨이나 별도의 rate‑limit 서비스로 분리됩니다.

3. Backpressure: 소비자가 따라가지 못할 때

Rate‑limit은 우리가 얼마나 많은 이벤트를 만들고 싶은지를 제한합니다. 하지만 주의 깊게 제한하더라도, 소비자가 “밀려서” 따라가지 못하는 상황은 충분히 가능합니다. 사용자의 모바일 인터넷이 느리거나, 브라우저 탭이 버벅이거나, 그때 ChatGPT가 과부하일 수 있습니다.

Backpressure는 소비자가 따라가지 못할 때 시스템이 보이는 반응입니다. 무한히 데이터를 쌓다가 OOM으로 죽기보다, 의식적으로 다음을 합니다:

  • 속도를 늦추고;
  • 이벤트를 집계하며;
  • 덜 중요한 것들은 버립니다.

어디에서 압력이 생기는가

GiftGenius의 전형적 시나리오는 이렇습니다. 워커가 이벤트를 큐(예: Redis Streams 또는 단순 DB 테이블)에 기록하고, MCP 서버가 이를 읽어 SSE 채널로 푸시합니다. 클라이언트가 느리면(3G, 오래된 노트북, 탭이 잔뜩 열린 상태) TCP 버퍼가 차기 시작하고, Node 프로세스는 큐를 다 비워내지 못해 결국 메모리에 이벤트를 쌓습니다. 그러다 보면 다음과 같은 익숙한 메시지를 보게 됩니다:

FATAL ERROR: Ineffective mark-compacts near heap limit

네트워크(TCP) 수준의 backpressure는 이미 있지만, 그것은 여러분의 도메인 개념을 알지 못합니다. 그저 “잠깐, 버퍼가 꽉 찼어”라고만 말하죠. 우리의 과제는 이를 MCP‑이벤트 수준에서 해석하는 것입니다.

제한이 있는 버퍼링과 이벤트 드롭

진행률과 상태 이벤트에는 좋은 특징이 하나 있습니다. 모든 이벤트가 동일하게 중요하지는 않다는 겁니다. 사용자에게 중요한 것은 마지막 최신 퍼센트이지, 모든 중간 이력(“51%, 52%, 53%, 54%”)이 아닙니다. 즉, 일부 이벤트를 과감히 드롭하고 마지막 것만 보내도 됩니다.

워커로부터 진행률 이벤트를 받아 각 jobId별로 버퍼에 넣는 레이어가 있다고 해 봅시다:

type ProgressEvent = { jobId: string; percent: number; message: string };

const progressBuffers = new Map<string, ProgressEvent[]>();
const MAX_BUFFER = 10;

function bufferProgress(event: ProgressEvent) {
  const buffer = progressBuffers.get(event.jobId) ?? [];
  buffer.push(event);

  // 버퍼 크기를 제한
  if (buffer.length > MAX_BUFFER) {
    // 마지막 몇 개 이벤트만 남긴다
    progressBuffers.set(event.jobId, buffer.slice(-MAX_BUFFER));
  } else {
    progressBuffers.set(event.jobId, buffer);
  }
}

별도의 타이머가 예를 들어 500ms마다 버퍼를 확인해 마지막 이벤트만 보내고 나머지는 무시합니다:

setInterval(() => {
  for (const [jobId, buffer] of progressBuffers.entries()) {
    if (!buffer.length) continue;

    const last = buffer[buffer.length - 1];
    sendProgressToClient(last); // SSE/MCP notification

    progressBuffers.set(jobId, []); // 비움
  }
}, 500);

이것이 바로 conflation 전술의 예시입니다. 여러 업데이트를 최신 하나로 합치는 것이죠. 진행률에는 최적의 패턴입니다.

“로그”나 partial_result 유형의 이벤트에는 다른 전략이 필요할 수 있습니다. 거기서는 손실이 종종 용납되지 않습니다. 로그 텍스트는 중요하고, 누락된 JSON 청크는 데이터 구조를 깨뜨릴 수 있습니다. 이런 경우에는:

  • 메시지를 집계(여러 로그 줄을 하나의 패킷으로 묶기)하거나;
  • 워커에게 “로그 생성 속도를 늦추라”는 제어 신호를 보낼 수 있습니다.

완전 비동기 시스템에서는 두 번째가 더 어렵지만, 최소한 고려는 해야 합니다.

큐 깊이 제한

Backpressure는 전송 직전의 이벤트 버퍼에만 해당하지 않습니다. 시스템 전반의 모든 큐를 살펴봐야 합니다:

  • 워커를 기다리는 작업 큐;
  • 워커와 MCP 서버 사이의 이벤트 큐;
  • 서버 쪽 스트리밍 라이브러리 내부 버퍼.

각 큐에는 합리적인 최대 깊이를 설정하는 것이 중요합니다. 큐가 넘치면, 클라이언트에게 “시스템이 과부하입니다. 잠시 후 다시 시도하세요.”라고 응답하거나, 덜 중요한 job을 버리거나, 일부 시나리오를 “오프라인 모드”로 전환합니다(예: 보고서를 만들어 나중에 링크를 보내기).

흥미로운 기법 하나는 이벤트 유형의 우선순위화입니다. 과부하에서 job.completedjob.failed만 보내고, job.progress는 우선순위를 낮추거나 아예 끌 수 있습니다.

4. 스트림과 이벤트 모니터링

측정 없이는 rate‑limit과 backpressure가 주술에 가깝습니다. 스트림이 수상할 만큼 많아졌는지, 이벤트가 지연되는지, 클라이언트가 떼로 끊어지는지 보여야 합니다.

스트림은 일반 HTTP 요청과 다르게 동작합니다. 지속 시간이 분·시간 단위일 수 있으므로, “초당 요청 수”나 “평균 지연” 같은 고전 메트릭만으로는 충분치 않습니다.

핵심 메트릭

SSE 또는 HTTP/stream에 대해 다음과 같은 지표 그룹을 보는 것이 유용합니다.

  1. 연결 메트릭. 현재 활성 SSE 스트림이 몇 개인가? 하나의 연결은 평균 얼마나 오래 유지되는가? 오류나 타임아웃으로 끝나는 스트림의 비율은? 활성 연결 급증은 트래픽 폭풍이나 리소스 누수(클라이언트가 연결을 닫지 않음)를 시사합니다. 급감은 대규모 끊김(네트워크 문제 또는 서버의 치명적 버그)을 시사합니다.
  2. 이벤트 메트릭. 모든 스트림에서 초당 몇 개의 이벤트를 보내는가(EPS — events per second)? 이벤트의 평균 크기는? payload 역직렬화나 검증 오류는 얼마나 발생하는가? 이벤트 크기가 갑자기 커진다면 누군가가 job.progress에 짧은 문자열 대신 보고서 전체 텍스트를 보내기 시작했을 수 있습니다.
  3. job 메트릭. 상태 분포(pending, running, completed, failed, canceled), 작업 유형별 평균 수행 시간, retry나 dead‑letter로 가는 job의 비율. 이는 문제가 네트워크 레벨뿐 아니라 워커에도 있음을 보여 줍니다(외부 API가 느려짐, 대규모 오류 발생 등).
  4. backpressure 및 시스템 지표. 구성 요소 간 버퍼와 큐의 깊이, 그리고 소비자가 공간을 비우길 기다리며 스트림이 블록된 시간의 비율을 자주 봅니다. 큐가 거의 항상 꽉 차 있다면 시스템이 한계에 달했다는 명확한 신호입니다. 또한 스트리밍을 담당하는 서버의 CPU·메모리와 네트워크 레벨의 오류/타임아웃도 중요합니다. 때로는 MCP 서버와 ChatGPT 사이 네트워크 대역폭이 병목이 됩니다.

요약하면 이 네 그룹은 세 가지 질문에 답합니다. 지금 살아 있는 스트림이 얼마나 되는지, 데이터를 얼마나 흘려보내는지, job이 어떻게 동작하는지, 그리고 시스템이 어디에서부터 숨이 차기 시작하는지.

무엇을 로깅할 것인가

로그는 관측성의 두 번째 기둥입니다. 나중에 특정 job의 히스토리를 복원할 수 있도록 이벤트와 연결을 로깅하는 것이 중요합니다.

보통 각 이벤트와 스트림에 대해 로그에 다음을 넣습니다:

  • jobId 및/또는 eventId;
  • userIdsessionId(멀티 테넌시가 있다면);
  • 이벤트 유형(progress, completed, failed, resource.updated);
  • 채널 유형(SSE 또는 HTTP/stream);
  • 전송 시점의 timestamp와 가능하다면 워커에서 이벤트가 생성된 timestamp.

이렇게 하면 lag를 계산할 수 있습니다. 즉, 워커가 이벤트를 생성한 시점과 소켓으로 나간 시점의 차이입니다. 이 lag 시간이 커지는 것은 backpressure 문제의 좋은 지표입니다.

로그 자체가 과부하의 원인이 되지 않도록 주의해야 합니다. job.progress처럼 빈도가 높은 이벤트는 매 이벤트마다 로깅하는 것이 합리적이지 않을 수 있습니다. 샘플링을 켜서 N번째 이벤트만 로깅하거나 통계를 집계하세요.

코드로는 간단한 헬퍼처럼 보일 수 있습니다:

function logEvent(event: {
  type: string;
  jobId: string;
  userId?: string;
  channel: "sse" | "http-stream";
  payload: unknown;
}) {
  console.info({
    ...event,
    timestamp: new Date().toISOString(),
  });
}

실제 프로젝트에서는 이를 structured logging 라이브러리로 감싸겠지만, 아이디어는 동일합니다. 각 기록에 가능한 한 많은 유용한 컨텍스트를 담는 것입니다.

5. 알림과 디그레이드 정책

이미 메트릭과 로그가 있다면 다음 단계는 알림을 설정하고, 시스템이 “힘들 때 어떻게 점진적으로 성능을 낮출지”를 구상하는 것입니다. 갑자기 죽는 것보다, 덜 잘 동작하더라도 정직하게 동작하는 편이 낫습니다.

알림 예시

GiftGenius에서는 다음과 같은 전형적인 상황을 관찰하는 것이 합리적입니다.

첫째, 비정상적으로 많은 활성 스트림 수. 평소에는 활성 SSE 연결이 수십 개인데 갑자기 수천 개가 되었다면 무슨 일이 벌어지는지 확인해야 합니다. 정말 인기 폭발일 수도 있고, 연결이 닫히지 않는 버그일 수도 있습니다.

둘째, job이 실제로 끝난 시점과 클라이언트가 job.completed를 받는 시점의 지연. 이 지연이 임계(예: 510초)를 넘기기 시작하면, 워커와 클라이언트 사이 어딘가에서 이벤트가 쌓이거나 연결이 버벅인다는 뜻입니다.

셋째, job.failedjob.canceled의 비율이 성공 대비 높을 때. 원인은 워커(외부 API 장애, 신규 버그)일 수도 있고, 사용자 측이 지연에 더 민감해져 취소를 자주 누르는 것일 수도 있습니다.

마지막으로, 연결 오류와 스트림 끊김이 늘어나는 경우. 비정상 disconnect가 증가한다면 네트워크나 클라이언트 측 문제일 가능성이 있고, 폴백 시나리오를 고민할 때입니다.

디그레이드 패턴

시스템이 과부하일 때는 “리소스 절약 모드”를 켤 수 있습니다. 무작정 500으로 응답하기보다는 이 편이 낫습니다.

가장 흔한 패턴은 적응형 이벤트 빈도입니다. event‑rate(초당 이벤트 수)가 평소의 10배로 치솟고 큐의 지연이 커지기 시작하면, 진행률 이벤트 빈도를 줄이세요. 원래 1%마다였다면 10%마다로, 500ms마다였다면 23초마다로. 사용자는 초정밀 진행률 없이도 충분히 견딥니다. 완전히 멈춘 듯한 UI보다는 낫습니다.

덜 중요한 이벤트—예를 들어 상품 피드의 백그라운드 업데이트 중 resource.updated—는 시스템이 과부하인 동안 일시적으로 전송을 끌 수 있습니다.

또 다른 방법은 일부 시나리오를 스트림에서 주기적 폴링으로 전환하는 것입니다. SSE 채널이 무너질 경우 MCP 서버가 위젯에 system.overloaded 같은 시스템 이벤트를 보내면, 위젯은 “N초마다 REST endpoint로 job 상태를 조회”하는 전략으로 전환할 수 있습니다.

6. GiftGenius를 위한 작은 실전 조각

다음을 이미 갖춘 상태를 가정합시다:

  • MCP‑tool startGiftSearch가 job을 만들고 jobId를 반환함;
  • 워커가 검색을 수행하고 event/progressevent/completed를 전송함;
  • 위젯이 연결하는 SSE endpoint /api/events/[userId](Next.js).

“이벤트 폭풍”에 대한 간단한 방어층과 최소한의 모니터링을 추가해 봅시다.

단계·시간 기반 진행률 제한

워커에는 위에서 논의한 트로틀링과 conflation을 추가합니다. 이제 이벤트는 0.5초보다 자주 전송하지 않고, 5% 이상 변할 때만 전송됩니다.

활성 스트림 카운팅

SSE endpoint에서 사용자별 카운터를 유지합니다:

const activeStreams = new Map<string, number>();
const STREAM_LIMIT = 3;

function canOpenMoreStreams(userId: string) {
  const current = activeStreams.get(userId) ?? 0;
  return current < STREAM_LIMIT;
}

function registerSseClient(userId: string, controller: ReadableStreamDefaultController) {
  const current = activeStreams.get(userId) ?? 0;
  activeStreams.set(userId, current + 1);

  // 나중에 이 스트림으로 이벤트를 쓰기 위해
  // controller를 어떤 자료구조에 저장한다
}

function unregisterSseClient(userId: string) {
  const current = activeStreams.get(userId) ?? 1;
  activeStreams.set(userId, Math.max(0, current - 1));
}

서버는 activeStreams.size 같은 메트릭을 Prometheus/Grafana나 다른 모니터링 시스템으로도 전송할 수 있습니다.

가장 단순한 event‑rate 메트릭

우선은 우리가 얼마나 많은 이벤트를 보내는지 대강이라도 셀 수 있습니다:

let eventsSentLastMinute = 0;

function sendProgressToClient(ev: ProgressEvent) {
  // ... 직렬화 후 SSE 스트림으로 기록
  eventsSentLastMinute++;
}

setInterval(() => {
  console.info({
    metric: "events_per_minute",
    value: eventsSentLastMinute,
    timestamp: new Date().toISOString(),
  });
  eventsSentLastMinute = 0;
}, 60_000);

시간이 지나면 이를 제대로 된 카운터와 알림으로 대체하면 됩니다. 하지만 시작점으로는 충분히 괜찮습니다.

위에서 정리한 제한, backpressure, 메트릭/알림, 그리고 적절한 UX 폴백을 모두 묶으면, GiftGenius는 “데모를 위한 데모”를 넘어 실제 트래픽 폭풍을 견딜 수 있게 됩니다. 다음 모듈에서 게이트웨이, 프로덕션 아키텍처, 온전한 관측성을 다룰 때도 이 패턴들이 유용할 것입니다.

7. 스트림, rate‑limits, 모니터링에서 흔한 실수

오류 №1: 스트림 수와 이벤트 빈도에 대한 제한 부재.
개발팀이 “보여 주기 좋으라”고 SSE를 붙이고, 워커는 처리한 각 객체마다 진행률을 성실히 보냅니다. 데모에서는 그럭저럭 동작합니다. 하지만 실제 사용자가 몰리는 첫 순간, 서버 리소스 대부분은 수천 개의 자잘한 이벤트 직렬화·전송에 쓰이고, ChatGPT의 UI는 슬라이드쇼가 됩니다.

오류 №2: 제한 없이 “모두 한꺼번에” 버퍼링하려는 시도.
코드에 “나가지 못한 이벤트”가 담긴 무제한 배열이 생기고, 클라이언트가 회복할 때까지 계속 커집니다. 스포일러: 클라이언트가 회복하기 전에 서버가 먼저 죽습니다. 모든 버퍼에는 엄격한 최대치가 있어야 하며, 넘쳤을 때의 처리 로직도 명시적이어야 합니다.

오류 №3: 모든 이벤트 유형을 똑같이 대함.
진행률은 집계·드롭할 수 있습니다(마지막 퍼센트가 히스토리보다 중요). 로그와 partial 결과는 그렇게 할 수 없습니다. 청크 하나가 빠지면 데이터가 손상될 수 있으니까요. 시스템을 설계할 때, 이벤트를 중요도별로 미리 그룹화하고 과부하 시 각 그룹의 전략을 정하십시오.

오류 №4: 관측성 부재.
활성 스트림에 대한 메트릭도, event‑rate에 대한 카운팅도, 로그에는 “뭔가 잘못됨” 말고는 없는 상황. 이런 상태에서는 사용자 불만과 CPU 부하 그래프를 보고서야 문제를 압니다. 최소한 jobId, eventId 기반의 기본 메트릭과 로그는 사치가 아니라 필수입니다.

오류 №5: 디그레이드를 고려하지 않는 경직된 UX.
위젯과 GPT 지침은 스트림이 항상 사용 가능하고, 진행률이 “실시간”으로 갱신되며, partial 결과가 시나리오대로 온다고 가정합니다. 첫 네트워크 문제에서 사용자는 멈춰 보이는 진행 바와 어떤 설명도 보지 못합니다. 훨씬 나은 접근은 솔직한 폴백을 UX에 녹이는 것입니다. “지금 라이브 업데이트에 문제가 있지만, 추천은 계속 진행하고 완료되면 알려 드릴게요”라고 안내하고, 더 듬성한 업데이트나 폴링으로 전환합니다.

오류 №6: “우리 사용자는 동시에 많은 작업을 만들지 않을 것”이라는 믿음.
경험상, 병렬 job이나 스트림 수를 제한하지 않으면 누군가 반드시 탭을 다섯 개 열고 각 탭에서 “최대치로” 선물 추천을 돌려 놓고 커피를 마시러 갑니다. 프로덕션에서 “설마 괜찮겠지”는 거의 늘 경보음과 함께 모니터링 시스템과의 급 만남으로 끝납니다.

1
설문조사/퀴즈
알림, 레벨 13, 레슨 4
사용 불가능
알림
알림과 스트리밍 시나리오(MCP 이벤트)
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION