본문으로 건너뛰기

[openclaw] 대규모 세션 카탈로그 업데이트 성능 최적화: 전체 비교에서 부분 비교로

PR 링크: openclaw/openclaw#127819 상태: Merged | 변경: +118 / -10

들어가며

대규모 분산 시스템의 UI를 개발하다 보면, 실시간 이벤트가 빈번하게 발생할 때 메인 스레드가 차단되는 경험을 하게 됩니다. 이번에 분석할 PR은 openclaw 프로젝트에서 호스트 업데이트 이벤트 발생 시, 전체 세션 카탈로그를 매번 직렬화하고 비교하던 비효율적인 로직을 개선하여 UI 성능을 획기적으로 향상시킨 사례입니다. 특히 대규모 카탈로그 환경에서 이벤트 발생 시 발생하는 짧은 UI 스톨(Stall) 문제를 해결했습니다.

코드 분석

1. ui/src/components/app-sidebar-session-catalog-live.ts 변경

기존 코드에서는 모든 이벤트마다 전체 catalogs 배열을 JSON.stringify로 직렬화하여 이전 상태와 비교했습니다. 이는 카탈로그 규모가 커질수록 기하급수적으로 비용이 증가하는 구조였습니다.

Before:

if (JSON.stringify(catalogs) === JSON.stringify(params.catalogs)) {
  return null;
}
const materialChange = sessionCatalogMaterialSnapshot(catalogs) !== sessionCatalogMaterialSnapshot(params.catalogs);

After:

const sameCatalogMetadata = 
  currentCatalog !== undefined &&
  currentCatalog.label === nextCatalog.label &&
  JSON.stringify(currentCatalog.capabilities) === JSON.stringify(nextCatalog.capabilities) &&
  JSON.stringify(currentCatalog.error) === JSON.stringify(nextCatalog.error);

if (sameCatalogMetadata && JSON.stringify(currentHost) === JSON.stringify(nextHost)) {
  return null;
}

const materialChange = 
  !currentCatalog || !currentHost || !nextHost ||
  sessionCatalogMaterialSnapshot([{ ...nextCatalog, hosts: [nextHost] }]) !==
  sessionCatalogMaterialSnapshot([{ ...currentCatalog, hosts: [currentHost] }]);

핵심 변경 사항은 전체 배열을 비교하는 대신, 변경된 특정 host와 카탈로그 메타데이터만 선별적으로 비교하도록 로직을 분리한 것입니다. 이를 통해 불필요한 전체 순회를 방지했습니다.

2. ui/src/components/app-sidebar-session-catalog-live.test.ts 추가

테스트 코드에서는 Object.defineProperty를 사용하여 toJSON 메서드를 강제로 에러를 발생시키도록 설정했습니다. 이는 전체 카탈로그가 직렬화되지 않음을 보장하는 강력한 회귀 테스트입니다.

Object.defineProperty(unrelated, "toJSON", {
  value: () => {
    throw new Error("unrelated catalog serialized");
  },
});

왜 이게 좋은가

이번 최적화의 핵심은 '불변성 유지와 부분 비교(Partial Comparison)'입니다.

  1. 성능 향상: 벤치마크 결과, 기존 63.22ms(Median) 걸리던 작업이 2.94ms로 줄어들어 약 95%의 성능 향상을 보였습니다. 메인 스레드 점유 시간이 획기적으로 줄어들어 사용자 경험이 개선되었습니다.
  2. 교훈: JSON.stringify를 통한 깊은 비교(Deep Comparison)는 매우 편리하지만, 데이터 규모가 커지면 성능의 병목이 됩니다. 데이터의 변경 범위를 추적할 수 있다면, 전체를 비교하지 말고 변경된 부분집합(Subset)만 비교하는 전략이 필수적입니다.
  3. 테스트 전략: 단순히 결과값만 비교하는 것이 아니라, 특정 객체가 직렬화되는지 여부를 테스트 케이스로 작성하여 성능 최적화가 의도치 않게 깨지는 것을 방지한 점이 매우 훌륭합니다.

결론

이번 PR은 복잡한 비즈니스 로직을 건드리지 않고도, 데이터 처리 방식의 최적화만으로도 큰 성능 이득을 볼 수 있음을 보여줍니다. 특히 실시간 데이터가 빈번한 UI 컴포넌트에서는 '무엇을 비교할 것인가'에 대한 고민이 성능의 핵심임을 다시 한번 확인하게 됩니다.

참고 자료

⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.

댓글

관련 포스트

PR Analysis 의 다른글