[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줄 수정으로 리스크가 매우 낮습니다.
참고 자료
관련 포스트
- [Open WebUI] CodespanToken에서 JS 트랜지션을 CSS 애니메이션으로 교체하여 메인 스레드 부하 제거
- [Open WebUI] TTS 문장 파싱을 showCallOverlay 가드로 감싸 불필요한 O(n^2) 연산 제거
- [Open WebUI] CodeEditor에서 EditorView 미해제로 인한 메모리 누수 수정
- [Open WebUI] UserMessage에서 JSON 직렬화 대신 structuredClone과 빠른 경로 비교 적용
- [Open WebUI] JSON.parse(JSON.stringify()) 를 structuredClone으로 교체
PR Analysis 의 다른글
- 이전글 [Open WebUI] JSON.parse(JSON.stringify()) 대신 structuredClone으로 딥 카피 최적화
- 현재글 : [Open WebUI] 스트리밍 중 scrollToBottom을 rAF로 배치 처리하여 불필요한 리플로우 제거
- 다음글 [Open WebUI] 모델 생성 페이지 메모리 누수 수정: 이벤트 리스너 해제
댓글