[TIL] Dock 개발 여정 (27) - 대용량 document workspace의 초기 비용 줄이기

RE_BROTHER·2026년 9월 1일

dev-dock

목록 보기
28/28

지난 포스팅에서는 실제 문서 작업 중 발생한 Explorer와 상태 보존 문제를 다뤘다. 그 과정을 진행하면서 기능이 정상적으로 동작하는지와 별개로, 큰 저장소를 열었을 때 앱이 얼마나 빨리 반응하는지도 제품 품질의 일부라는 사실이 분명해졌다.

작은 테스트 폴더에서는 보이지 않던 문제가 Git 저장소를 선택했을 때 나타났다.

  • 초기 화면이 늦게 나타난다.
  • 파일과 폴더를 읽는 동안 UI가 버벅인다.
  • 앱 메모리가 평소 약 200MB에서 1~2GB 수준까지 증가한다.

이번에는 이 문제를 한 번에 해결했다고 주장하지 않고, 어떤 비용이 어디에서 발생하는지 나누어 측정할 수 있는 첫 단위를 만들었다.

.gitignore를 그대로 Explorer 규칙으로 쓰지 않은 이유

처음에는 사용자의 .gitignore를 읽어 필요 없는 파일을 숨기면 된다고 생각할 수 있다. 하지만 .gitignore는 Git이 추적하지 않을 파일을 정하는 정책이고, Dock의 Explorer는 사용자가 확인하고 편집할 document workspace의 파일을 보여 주는 기능이다.

Git에서 무시하는 파일이라고 해서 사용자가 절대 열어 보지 않는 것은 아니다. 반대로 저장소에서 추적되는 파일이어도 문서 Explorer에 보일 필요가 없을 수 있다. 그래서 첫 단계에서는 모든 규칙을 가져오지 않고, 생성물과 의존성처럼 제품 관점에서 명확히 제외할 수 있는 기본 디렉터리만 공통 규칙으로 관리했다.

export const DEFAULT_IGNORED_DIRECTORIES = new Set([
  '.git',
  'node_modules',
  'dist',
  'out',
  'coverage',
  'test-results',
  'playwright-report',
]);

이 목록은 Explorer, Markdown 목록, 이미지 자산 스캔이 공유한다. 기능마다 서로 다른 제외 목록을 갖게 되면 한 화면에는 보이는 파일이 다른 화면에서는 사라지는 정합성 문제가 생기기 때문이다.

watcher도 플랫폼별로 비용이 달라진다

Dock은 운영체제 밖에서 파일이 변경되는 상황을 감지해야 한다. Windows와 macOS에서는 재귀 fs.watch를 우선 사용하고, 해당 방식이 지원되지 않거나 오류가 발생하면 Linux에서 디렉터리별 watcher를 유지하는 fallback을 사용한다.

모든 변경 이벤트마다 전체 디렉터리를 다시 읽으면 이벤트가 연속해서 발생할 때 비용이 커진다. watcher는 이벤트를 짧게 debounce하고, 내부에서 발생시킨 이름 변경·이동 이벤트는 다시 처리하지 않도록 구분한다. 다만 현재 구조는 초기 workspace 목록과 Markdown 참조를 여전히 재귀적으로 수집하므로 watcher 개선만으로 메모리 급증을 해결할 수는 없다.

화면에 필요한 만큼만 Explorer를 펼치기

기존 Explorer는 전체 entry 목록을 받은 뒤 모든 폴더를 자동으로 펼쳤다. 사용자가 아직 열어 보지 않은 하위 파일까지 React element와 이벤트 핸들러를 만들기 때문에, 큰 workspace에서는 데이터 조회 비용과 별개로 Renderer DOM 비용이 커진다.

const next = new Set(
  [...current].filter(
    (path) => path === '' || directories.has(path),
  ),
);
next.add('');

빈 문자열은 workspace 루트다. 초기에는 루트 항목만 표시하고, 사용자가 폴더를 펼친 뒤에 하위 트리를 렌더링한다. 폴더를 닫으면 해당 하위 DOM도 다시 화면에서 제거된다.

이 변경은 workspace:list-entries IPC를 당장 바꾸지 않았다. 현재 IPC를 list-children 방식으로 바꾸면 파일 목록, 검색, backlinks, watcher 갱신까지 동시에 영향을 받는다. 먼저 Renderer 렌더링 비용을 줄인 뒤, 실제 대규모 workspace에서 Main·Renderer 메모리와 초기 응답 시간을 측정해야 다음 구조 변경을 판단할 수 있다.

이미지 자산 스캔도 같은 규칙을 사용해야 한다

이미지 진단 화면은 assets 아래의 이미지를 찾아 문서에서 사용 중인지 확인한다. 기존 이미지 수집기가 assets 하위 모든 디렉터리를 재귀 방문하면, assets/node_modules나 assets/dist처럼 관심 대상이 아닌 경로까지 읽을 수 있다.

if (entry.isDirectory()) {
  if (DEFAULT_IGNORED_DIRECTORIES.has(entry.name)) continue;
  assets.push(...(await collectImageAssets(root, absolutePath)));
  continue;
}

이 검사는 이미지 파일을 읽기 전에 디렉터리 진입 자체를 막는다. 실제 자산을 읽는 단계에서는 별도로 assets 경계, 지원 MIME, 매직 바이트, 최대 파일 크기를 검증한다. 탐색 비용을 줄였다고 해서 외부 파일을 신뢰하는 것은 아니다.

먼저 기준을 기록해야 최적화라고 부를 수 있다

감으로 코드를 바꾸면 개선 여부를 판단할 수 없다. 그래서 workspace를 변경하지 않는 기준 측정 스크립트를 추가했다.

node scripts/measure-workspace-baseline.mjs D:\dev\git\jarvis-dock

스크립트는 다음 값을 JSON으로 출력한다.

  • 디렉터리 수와 파일 수
  • Markdown 파일 수
  • 기본 제외 디렉터리 수
  • 최대 깊이와 무시 디렉터리 이름별 분포
  • 현재 플랫폼 watcher 전략과 추정 watcher 수
  • 스캔 시간
  • 측정 프로세스의 RSS와 V8 heap 변화량

현재 작은 fixture에서는 7개 디렉터리·24개 파일·18개 Markdown, 최대 깊이 3, Windows 재귀 watcher 추정 1개가 측정됐다. 저장소에서는 18개 디렉터리·174개 파일·86개 Markdown, 최대 깊이 4, 기본 제외 디렉터리 4개, Windows 재귀 watcher 추정 1개가 측정됐다. RSS 변화량은 각각 약 1.01MB와 1.85MB였다. 이 결과는 사용자가 보고한 1~2GB 메모리 증가를 재현한 것이 아니며, 최적화가 끝났다는 의미도 아니다. 현재 환경에서 재현 대상이 충분히 크지 않다는 사실을 확인한 기준값이다.

watcher 수는 현재 운영체제에서 Dock이 선택할 watcher 전략을 기준으로 계산한 추정치다. 측정 스크립트 자체의 메모리는 Main·Renderer가 함께 실행되는 실제 앱의 메모리와 다르므로, 대규모 workspace의 packaged 앱 측정이 별도로 필요하다.

테스트 결과와 남은 한계

이번 단위에서는 동작 변경과 성능 구조 변경을 섞지 않기 위해 기존 IPC 권한 경계와 watcher 계약을 유지했다.

  • 이미지 자산 스캔 단위 테스트 4개 통과
  • npm run check 통과: 33개 test file·161개 테스트
  • packaged Electron E2E 21개 통과
  • Windows smoke 통과
  • 이미지 자산 스캔 변경 커밋 aae2b7f의 Windows·Linux·macOS 패키징 및 Windows Electron runtime regression 성공
  • 최신 측정 도구 보강 커밋 0a45d46에서 측정 기준을 확장하고, npm run check를 다시 통과

아직 남아 있는 비용은 명확하다.

  1. workspace:list-entries가 전체 트리를 한 번에 수집한다.
  2. 검색·backlinks를 위해 Markdown 전체 목록과 내용을 읽는 흐름이 있다.
  3. 운영체제별 watcher 수와 실제 Main·Renderer 메모리를 측정하지 않았다.
  4. 사용자가 보고한 대규모 저장소에서의 초기 지연을 아직 동일 조건으로 재현하지 못했다.

다음 단계에서는 실제 대용량 document workspace를 기준으로 초기 선택부터 Explorer 표시까지의 시간, 전체 entry 수, watcher 수, Main·Renderer 메모리를 측정한다. 그 결과가 확인되면 폴더를 펼칠 때 해당 하위 entry만 조회하는 지연 로딩 IPC와 변경된 경로만 반영하는 증분 갱신을 별도의 설계·ADR 단위로 검토한다.

마무리

대용량 workspace 최적화는 .gitignore를 읽어 파일을 숨기는 한 줄짜리 문제가 아니었다. 무엇을 사용자에게 보여 줄지, 어떤 파일 변경을 감시할지, 언제 하위 트리를 렌더링할지, 이미지 진단이 어느 경로까지 탐색할지를 같은 정책과 측정 기준 안에서 분리해야 했다.

이번 작업은 최종 해답이 아니라 다음 판단을 위한 기반이다. 측정 없이 IPC를 바꾸지 않고, 화면에서 보이지 않는다고 파일 시스템에서도 무조건 제외하지 않으며, 최적화 전후의 차이를 숫자로 확인하는 흐름을 만들었다.

profile
will be better

0개의 댓글