본문으로 건너뛰기

[Open WebUI] 스트리밍 중 메시지 리스트 재구성을 프레임당 1회로 제한

PR #21885 - perf: throttle message list rebuild to once per animation frame during streaming

들어가며

Open WebUI의 Messages.sveltehistory.messages가 변경될 때마다 부모 체인을 순회하며 메시지 리스트를 재구성합니다. 스트리밍 중에는 매 토큰마다 이 작업이 실행되어 초당 수백 번의 리빌드가 발생했지만, 실제로 각 ResponseMessage는 자체 반응형 바인딩으로 콘텐츠를 업데이트하므로 리빌드의 대부분은 불필요했습니다.

핵심 코드 분석

Before

$: if (history.currentId) {
    let _messages = [];
    // 부모 체인 순회하며 메시지 배열 구성
    // ...
    messages = _messages;
}

history.messages의 모든 변경에 즉시 반응하여 리빌드합니다.

After

let pendingRebuild = null;
let lastCurrentId = null;

const buildMessages = () => {
    // 기존 리빌드 로직
};

$: if (history.currentId) {
    const currentIdChanged = history.currentId !== lastCurrentId;
    lastCurrentId = history.currentId;

    if (currentIdChanged) {
        // 구조적 변경: 즉시 리빌드
        cancelAnimationFrame(pendingRebuild);
        pendingRebuild = null;
        buildMessages();
    } else if (history.messages) {
        // 콘텐츠 업데이트(스트리밍): 프레임당 1회로 쓰로틀링
        if (!pendingRebuild) {
            pendingRebuild = requestAnimationFrame(() => {
                pendingRebuild = null;
                buildMessages();
            });
        }
    }
}

onDestroy에서 정리 로직도 추가되었습니다:

onDestroy(() => {
    cancelAnimationFrame(pendingRebuild);
});

왜 이게 좋은가

  1. 완전히 보이지 않는 변경: PR 작성자가 "free lunch"라고 표현할 만큼, 사용자 경험에 전혀 영향 없이 순수한 성능 개선입니다.
  2. 구조적 변경과 콘텐츠 변경 분리: 채팅 전환이나 새 메시지 같은 구조적 변경은 즉시 반영하고, 스트리밍 중 콘텐츠 업데이트만 쓰로틀링합니다.
  3. 리소스 정리: onDestroy에서 대기 중인 requestAnimationFrame을 취소하여 메모리 누수를 방지합니다.
  4. ~60Hz로 제한: 초당 수백 번의 리빌드가 ~60번으로 줄어들어 CPU 사용량이 크게 감소합니다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글