dependency-graph

2 개의 포스트

figma

레이어 패널의 성능 개선 | Figma 블로그 (새 탭에서 열림)

Figma는 대규모 파일에서 레이어 패널의 느린 상호작용을 해결하기 위해 아키텍처를 전면 재설계했다. 행 목록 계산과 노드 상세 정보 계산을 분리하고, 파생 속성(derived properties)을 활용해 변경된 부분만 다시 계산한 결과, 가장 복잡한 파일에서 일부 상호작용이 30~50% 빨라졌다. 핵심은 보이지 않는 레이어까지 계산하지 않고, 필요한 데이터만 지연 계산·캐싱하는 것이다. ## 레이어 패널 성능 저하의 원인 - 레이어 패널은 Figma 파일의 노드 트리를 중첩된 목록으로 표현한다. - 초기 아키텍처는 파일의 각 노드에 대해 이름, 아이콘, 표시 여부, 잠금 상태 등 UI에 필요한 데이터를 하나의 큰 JavaScript 객체로 구성했다. - 레이어가 변경될 때마다 확장된 모든 노드에 대해 데이터를 처음부터 다시 계산했다. - 파일이 수만 개의 레이어를 포함하게 되면서 두 가지 문제가 발생했다. - **계산량 과다:** 화면에 실제로 보이는 행은 보통 20~30개뿐인데, 확장된 모든 노드의 데이터를 계산했다. - **재계산 빈도 과다:** 레이어 하나를 확장하거나 변경해도 전체 데이터를 다시 생성했다. ## 1단계: 두 번에 나누어 계산하기 - 데이터 수집 과정을 두 단계로 분리했다. - 첫 번째 단계에서는 패널에 표시될 **행 ID의 순서**만 계산한다. - 이 과정도 단순하지 않으며 다음과 같은 제품 규칙을 반영해야 한다. - 오토 레이아웃 프레임의 자식은 역순으로 표시될 수 있다. - 위젯이나 FigJam 스티키처럼 자식 노드를 패널에 표시하지 않는 노드가 있다. - 프로토타이핑 프레임의 고정 헤더와 스크롤 헤더는 자식을 두 영역으로 나눈다. - 최상위 프레임과 컴포넌트는 고정된 위치에 표시된다. - 두 번째 단계에서는 첫 단계에서 얻은 행 ID 중 현재 화면에 보이는 노드만 대상으로 상세 데이터를 계산한다. - 기존에도 화면 밖 항목을 렌더링하지 않는 윈도잉(windowing)을 사용했지만, 데이터 계산 자체는 모든 확장 노드에 대해 수행되고 있었다. - 새 구조에서는 이름, 아이콘, 잠금·표시 상태, 선택 상태 등을 실제로 필요한 수십 개 행에 대해서만 계산한다. - 그 결과 수십만 개 노드의 데이터를 계산하던 작업을 화면 주변의 작은 범위로 줄였다. ## 2단계: 파생 데이터 캐싱 - 변경되지 않은 레이어를 다시 그리거나 재계산하지 않고, 바뀐 부분만 갱신하도록 개선했다. - 이를 위해 Figma가 플랫폼 차원에서 개발한 **파생 속성(derived properties)** 기능을 사용했다. - Figma 노드는 직접 저장·조회할 수 있는 필드(fields)를 가진다. - 하지만 일부 속성은 직접 저장하지 않고 다른 필드와 속성으로부터 계산된다. - 예를 들어 노드는 부모 기준의 상대 위치만 저장하고, 절대 위치는 다음과 같이 계산할 수 있다. ```text Self.AbsolutePosition = Parent.AbsolutePosition + Self.RelativePosition ``` - 파생 속성 시스템은 이런 계산 관계를 명시적으로 선언하고, 의존하는 값이 바뀌면 최신 상태를 유지하도록 한다. - 시스템의 장점은 다음과 같다. - 각 파생 속성이 어떤 필드와 다른 파생 속성에 의존하는지 추적한다. - 의존 관계가 최적화된 그래프 형태로 컴파일된다. - 속도와 메모리 사용량 사이의 선택을 고려한 여러 캐싱 정책을 제공한다. - 기본적으로 지연 계산(lazy evaluation) 방식이어서 실제로 읽을 때만 계산한다. - 레이어 패널은 부모-자식 트리 구조를 갖기 때문에 파생 속성과 자연스럽게 결합된다. - 이전에는 트리 어디에서든 변경이 발생하면 노드 데이터를 전체적으로 다시 계산했지만, 새 구조에서는 의존 관계를 바탕으로 영향을 받은 데이터만 갱신할 수 있다. ## 성능 개선의 의미 - 대규모 Figma 파일에서 레이어 패널의 스크롤, 확장·축소, 선택 등의 상호작용이 더 빨라졌다. - 레이어 패널의 계산 비용이 줄어들면서 드래그나 텍스트 입력 같은 에디터의 다른 작업에도 영향을 주던 지연이 완화됐다. - 윈도잉만 적용하는 것보다, **계산 대상 자체를 가시 영역으로 제한하는 것**이 중요하다는 점을 보여준다. - UI 성능을 높이려면 다음 두 문제를 별도로 해결해야 한다. - 불필요한 항목까지 계산하는 문제 - 같은 항목을 반복해서 계산하는 문제 ## 실용적인 결론 대규모 트리형 UI에서는 렌더링만 가상화하는 것으로 충분하지 않다. 먼저 화면에 필요한 항목의 ID나 구조만 계산한 뒤, 실제 표시되는 항목의 상세 데이터만 지연 계산하고, 의존성이 변한 부분만 캐시를 무효화하는 방식이 효과적이다.

figma

파일 로드 속도 개선: (새 탭에서 열림)

Figma는 파일 전체를 한꺼번에 불러오는 대신 사용자가 현재 보고 있는 페이지만 동적으로 로드해 파일 로딩 속도를 개선했다. 파일이 커져도 실제 사용자가 접근하는 콘텐츠의 복잡도에 비례해 로드하도록 설계한 결과, 가장 느린 5%의 페이지 로드 시간이 33% 감소했다. 핵심 과제는 페이지 간 컴포넌트·스타일·변수 참조 같은 의존성을 정확히 추적하면서 필요한 데이터만 전송하는 것이었다. ## 사용자 체감 복잡도에 맞춘 로딩 - Figma 파일은 여러 페이지, 컴포넌트, 라이브러리, 프로토타입 화면을 포함해 매우 커질 수 있다. - 하지만 사용자는 한 세션에서 파일의 모든 페이지를 탐색하지 않는 경우가 많다. - 따라서 파일 크기 전체가 아니라 현재 열어 본 페이지의 복잡도에 따라 로딩 시간이 결정되어야 한다. - 선택한 페이지만 먼저 표시하고, 다른 페이지는 사용자가 접근할 때 불러오면 초기 로딩 시간과 메모리 사용량을 줄일 수 있다. - Figma는 파일이 계속 커지더라도 로딩 성능은 지속적으로 개선되는 것을 목표로 삼았다. ## 페이지 간 읽기 의존성 - Figma 파일은 각 레이어와 속성을 가진 노드들의 트리 구조로 구성된다. - 노드는 다른 페이지에 있는 노드를 참조할 수 있으며, 이를 **읽기 의존성(read dependency)**이라고 부른다. - 예를 들어 인스턴스는 다른 페이지에 있는 원본 컴포넌트를 가리키므로, 올바르게 렌더링하려면 해당 컴포넌트 노드도 먼저 받아야 한다. - 스타일 역시 내부적으로 노드로 구현된다. - 특정 프레임이 `BrandPrimary` 색상 스타일을 사용하면 해당 스타일 노드를 로드해야 실제 색상값을 해석할 수 있다. - 변수도 같은 방식으로 동작한다. - 텍스트 크기에 `text-subheader` 변수를 적용했다면, 클라이언트는 변수 노드를 조회해 실제 값인 `16` 같은 원시 값을 확인해야 한다. - 따라서 단순히 현재 페이지만 로드해서는 부족하며, 렌더링에 필요한 다른 페이지의 참조 데이터까지 함께 찾아야 한다. ## QueryGraph 기반 동적 로딩 - Figma는 읽기 의존성을 메모리 내 그래프로 관리하는 **QueryGraph**를 구축했다. - QueryGraph는 의존 노드 간 연결을 추적하고, Figma의 멀티플레이어 시스템이 클라이언트에 어떤 데이터를 전송할지 결정하는 기반이 됐다. - 이 구조는 기존에 보기 전용 파일과 프로토타입의 동적 로딩을 구현하는 데 활용됐다. - 동적으로 불러오는 단위는 콘텐츠 유형에 따라 달라진다. - **Figma 캔버스:** 선택한 페이지를 먼저 로드하고, 추가 페이지는 필요할 때 요청한다. - **프로토타입 뷰어:** 한 번에 하나의 프레임을 표시하고, 사용자가 이동할 가능성이 있는 인접 프레임을 미리 로드한다. - 페이지를 로드할 때는 해당 페이지 자체뿐 아니라 다른 페이지에 존재하는 필수 컴포넌트·스타일·변수 노드도 QueryGraph를 통해 함께 전송한다. ## 동적 로딩의 효과 - 모든 페이지와 노드를 초기화하는 방식보다 불필요한 데이터 전송을 줄일 수 있다. - 사용자가 실제로 접근하지 않는 페이지를 메모리에 올리지 않아 메모리 사용량도 감소한다. - 큰 파일이라도 현재 작업 중인 페이지가 단순하다면 빠르게 캔버스를 표시할 수 있다. - 가장 느린 5%의 페이지 로드 시간이 33% 줄어드는 성과를 거뒀다. ## 실용적인 시사점 대규모 웹 애플리케이션에서는 전체 데이터를 일괄 로드하기보다 사용자의 현재 화면과 실제 접근 경로를 기준으로 로딩 단위를 정하는 것이 효과적이다. 다만 페이지 단위 지연 로딩을 적용할 때는 컴포넌트, 스타일, 변수처럼 화면 밖에 존재하는 참조 데이터까지 의존성 그래프로 추적해야 하며, 필요한 의존성만 정확히 함께 로드하는 설계가 중요하다.