본문으로 건너뛰기

[Open WebUI] 이중 RAF 제거로 스트리밍 표시 지연 32ms에서 16ms로 단축

PR 링크: open-webui/open-webui#23016 상태: Merged | 변경: +1 / -16

들어가며

성능 최적화를 시도하다가 기존 최적화와 중복되어 오히려 지연이 늘어나는 경우가 있습니다. 이전 PR(#22947)에서 scheduleHistoryFlush라는 requestAnimationFrame(RAF) 기반 쓰로틀을 추가했지만, Messages.sveltependingRebuild가 이미 같은 역할을 하고 있었습니다.

핵심 코드 분석

이중 RAF 체인 제거

문제가 된 데이터 흐름:

token → scheduleHistoryFlush (RAF 1, ~16ms)
  → pendingRebuild (RAF 2, ~16ms)
    → buildMessages()

Before:

if (type === 'chat:message:delta' || type === 'message' || type === 'status') {
    scheduleHistoryFlush();
} else {
    cancelAnimationFrame(historyRAF);
    historyRAF = null;
    history.messages[event.message_id] = message;
}

let historyRAF = null;
const scheduleHistoryFlush = () => {
    if (!historyRAF) {
        historyRAF = requestAnimationFrame(() => {
            historyRAF = null;
            history = history;
        });
    }
};

After:

history.messages[event.message_id] = message;

scheduleHistoryFlush와 관련된 15줄의 코드가 모두 제거되고, 단순한 직접 할당으로 복원되었습니다. Svelte의 반응성 시스템이 history 변경을 감지하면 pendingRebuild가 다음 프레임에서 buildMessages()를 호출합니다.

왜 이게 좋은가

  • 스트리밍 토큰이 화면에 표시되기까지의 지연이 약 32ms(RAF 2회)에서 약 16ms(RAF 1회)로 감소합니다
  • 빠른 모델에서 초당 수십 개의 토큰이 들어올 때 체감되는 부드러움이 개선됩니다
  • 불필요한 상태 관리 변수(historyRAF)와 함수(scheduleHistoryFlush)가 제거되어 코드가 단순해졌습니다
  • 이미 동작하는 쓰로틀링 메커니즘을 파악하지 못한 채 중복 최적화를 추가한 실수를 잘 보여주는 사례입니다

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글