[Open WebUI] ChatItem 사이드바 메모리 누수 수정
PR 링크: open-webui/open-webui#23209 상태: Merged | 변경: +21 / -24
들어가며
Open WebUI의 사이드바에서 각 채팅 항목(ChatItem)마다 이벤트 리스너 정리가 제대로 되지 않아 메모리 누수가 발생하고 있었습니다. 또한 각 ChatItem 인스턴스마다 1x1 투명 드래그 이미지(Image 객체)를 개별 생성하고 있어 사이드바에 채팅이 많으면 불필요한 메모리를 사용했습니다.
핵심 코드 분석
Before: onMount/onDestroy 분리 + 개별 드래그 이미지
<script lang="ts">
const dragImage = new Image();
dragImage.src = 'data:image/png;base64,...';
onMount(() => {
if (itemElement) {
document.addEventListener('click', onClickOutside, true);
itemElement.addEventListener('dragstart', onDragStart);
itemElement.addEventListener('drag', onDrag);
itemElement.addEventListener('dragend', onDragEndHandler);
}
});
onDestroy(() => {
if (itemElement) {
document.removeEventListener('click', onClickOutside, true);
itemElement.removeEventListener('dragstart', onDragStart);
// ...
}
});
</script>
두 가지 문제가 있습니다:
onMount에서 등록한 리스너를onDestroy에서 해제하지만,itemElement참조가 달라질 수 있어 해제가 누락될 수 있습니다.- 각 ChatItem 인스턴스마다
new Image()를 생성합니다.
After: onMount 반환 함수로 정리 + 모듈 레벨 공유 이미지
<script context="module" lang="ts">
/** Shared 1x1 transparent drag preview */
const invisibleDragImage = new Image();
invisibleDragImage.src = 'data:image/png;base64,...';
</script>
<script lang="ts">
onMount(() => {
const el = itemElement;
if (!el) return;
document.addEventListener('click', onClickOutside, true);
el.addEventListener('dragstart', onDragStart);
el.addEventListener('drag', onDrag);
el.addEventListener('dragend', onDragEndHandler);
return () => {
document.removeEventListener('click', onClickOutside, true);
el.removeEventListener('dragstart', onDragStart);
el.removeEventListener('drag', onDrag);
el.removeEventListener('dragend', onDragEndHandler);
};
});
</script>
핵심 변경 사항:
onMount에서el을 로컬 변수로 캡처하고, 반환 함수에서 같은 참조로 리스너를 해제합니다.- 드래그 이미지를
context="module"블록으로 이동하여 모든 ChatItem 인스턴스가 하나의 Image를 공유합니다.
왜 이게 좋은가
- 메모리 누수 완전 해결:
onMount의 반환 함수는 Svelte가 컴포넌트 파괴 시 자동으로 호출하므로, 등록과 해제의 대칭이 보장됩니다. 클로저가 동일한el참조를 캡처하므로 DOM 엘리먼트 불일치 문제도 없습니다. - 메모리 사용량 감소: 사이드바에 100개의 채팅이 있으면 100개의 Image 객체 대신 1개만 생성됩니다.
- 코드 단순화:
onDestroyimport가 제거되고, 등록/해제 로직이 한 곳에 모여 유지보수가 쉬워집니다.
Svelte에서 onMount의 반환 함수를 정리(cleanup)에 사용하는 것은 React의 useEffect 정리 패턴과 유사하며, 컴포넌트 라이프사이클 관리의 모범 사례입니다.
참고 자료
관련 포스트
PR Analysis 의 다른글
- 이전글 [Open WebUI] DOMParser 대신 html-entities로 HTML 디코딩 최적화
- 현재글 : [Open WebUI] ChatItem 사이드바 메모리 누수 수정
- 다음글 [Loki] Kafka 파티션 불필요한 Shuffle Sharding 제거
댓글