본문으로 건너뛰기

[Open WebUI] JSON.parse(JSON.stringify()) 대신 structuredClone으로 딥 카피 최적화

PR 링크: open-webui/open-webui#21948 상태: Merged | 변경: +5 / -5

들어가며

Open WebUI의 ResponseMessage.svelteMultiResponseMessages.svelte는 스트리밍 중 히스토리 변경을 감지할 때마다 메시지 객체를 딥 카피합니다. 기존에는 JSON.parse(JSON.stringify(...))로 수행했는데, 이는 전체 객체를 JSON 문자열로 직렬화한 후 다시 파싱하는 이중 비용이 있습니다. 이 PR은 structuredClone()으로 교체합니다.

핵심 코드 분석

Before: JSON 라운드트립

let message = JSON.parse(JSON.stringify(history.messages[messageId]));
$: if (history.messages) {
    if (JSON.stringify(message) !== JSON.stringify(history.messages[messageId])) {
        message = JSON.parse(JSON.stringify(history.messages[messageId]));
    }
}

After: structuredClone

let message = structuredClone(history.messages[messageId]);
$: if (history.messages) {
    const source = history.messages[messageId];
    if (source) {
        if (message.content !== source.content || message.done !== source.done) {
            message = structuredClone(source);
        } else if (JSON.stringify(message) !== JSON.stringify(source)) {
            message = structuredClone(source);
        }
    }
}

왜 이게 좋은가

1. 문자열 변환 제거

JSON.parse(JSON.stringify())는 객체를 JSON 문자열로 변환(O(n) 메모리 할당)한 후 다시 파싱(O(n) 파싱)합니다. structuredClone()은 Structured Clone Algorithm을 사용하여 중간 문자열 없이 직접 객체 그래프를 복제합니다.

2. 스트리밍 중 누적 비용

LLM 응답이 길어질수록 메시지 객체의 content 필드가 커집니다. 매 토큰마다 수 KB ~ 수십 KB의 문자열을 생성했다가 바로 GC 대상이 되는 패턴은 V8의 Young Generation GC 압력을 높입니다. structuredClone()은 이 문제를 완화합니다.

3. 타입 보존

JSON.parse(JSON.stringify())Date, Map, Set, undefined 등을 올바르게 처리하지 못합니다. structuredClone()은 이러한 타입을 정확히 복제합니다. 현재 메시지 객체에 이런 타입이 없더라도, 향후 확장 시 잠재적 버그를 예방합니다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글