본문으로 건너뛰기

[Open WebUI] 스트리밍 중 scrollToBottom을 rAF로 배치 처리하여 불필요한 리플로우 제거

PR 링크: open-webui/open-webui#21946 상태: Merged | 변경: +12 / -2

들어가며

Open WebUI에서 LLM 스트리밍 응답을 표시할 때, 토큰이 도착할 때마다 scrollToBottom()이 호출됩니다. 이 함수는 내부적으로 await tick()scrollHeight 읽기를 수행하여 매번 레이아웃 리플로우를 강제합니다. 토큰은 보통 60fps보다 빠르게 도착하기 때문에, 대부분의 리플로우는 화면에 반영되지도 않는 낭비된 연산입니다. 이 PR은 requestAnimationFrame 가드를 적용하여 프레임당 최대 한 번만 스크롤하도록 변경합니다.

핵심 코드 분석

Before: 토큰마다 즉시 스크롤

if (autoScroll) {
    scrollToBottom();
}

매 토큰 도착 시 scrollToBottom()이 직접 호출되어 불필요한 레이아웃 리플로우가 반복됩니다.

After: rAF 가드로 프레임당 1회 제한

let scrollRAF = null;
const scheduleScrollToBottom = () => {
    if (!scrollRAF) {
        scrollRAF = requestAnimationFrame(async () => {
            scrollRAF = null;
            await scrollToBottom();
        });
    }
};

// 스트리밍 콜백에서
if (autoScroll) {
    scheduleScrollToBottom();
}

scrollRAF 변수가 이미 예약된 프레임이 있는지 추적합니다. 프레임이 아직 실행되지 않았다면 새로운 요청은 무시되고, 실행 후에야 다음 요청이 예약됩니다.

왜 이게 좋은가

  • 레이아웃 리플로우 최소화: scrollHeight 읽기는 브라우저에게 레이아웃 재계산을 강제합니다. 초당 수백 번 발생하던 것을 최대 60회로 제한합니다.
  • 이미 검증된 패턴: 같은 프로젝트의 Markdown.svelte에서 토큰 파싱에 동일한 rAF 패턴을 사용하고 있었습니다.
  • 비스트리밍 경로 유지: 스트리밍이 아닌 일반 스크롤 호출은 기존 그대로 즉시 실행됩니다.
  • 최소 변경: 12줄 추가, 2줄 수정으로 리스크가 매우 낮습니다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글