아래는 라미레포트 실제 대화내역입니다.
안녕하세요, 저는 당신의 전문 글쓰기 도우미 라미입니다. 제안서, 보고서, 칼럼 등 전문적인 문서 작성을 도와드립니다.
여러분의 생각을 최대한 자세히 들려주세요. 짧은 답변보다는 여러분의 의도와 맥락을 충분히 설명해주실수록 더 좋은 문서가 완성됩니다. 평소 대화하듯 편하게, 하지만 최대한 구체적으로 이야기해주시면 좋겠어요.
전문 튜터의 체계적인 지도와 함께 개요 작성부터 제목 선정, 본문 작성까지 전 과정을 도와드리겠습니다. 시작부터 끝까지 함께 완성해 나가는 것이 좋은 글의 비결입니다.
"글 작성을 시작하겠습니다" 라고 답변해 주시면 바로 시작하겠습니다.
글 작성을 시작하겠습니다
현재 진행 상황은 아이디어 구체화 단계입니다. 주제, 목적, 독자를 정한 뒤 다음 단계로 넘어갈 수 있습니다. 글의 주제나 목적, 또는 독자에 대해 어떤 생각이 있으신지 공유해 주시겠습니까?
현재 진행 상황
▶️ 1. 아이디어 구체화 (1/2)
❌ 2. 아이디어 구체화 (2/2)
❌ 3. 개요 작성
❌ 4. 본문 작성
❌ 5. 퇴고 과정
❌ 6. 제목 작성
다음 단계
아이디어 구체화 (1/2)
제목: 내가 오픈소를 개발하며 배운 성능 최적화 방법
주제: 프레임워크를 개발하면서 알게된 다양한 성능 최적화 방법
타깃: 성능 최적화에 관심있는 개발자들, 커리어 향상을 위해 깊은 지식을 원하는 프론트엔드
서론:
-프론트엔드 개발에서 가장 골치 아픈 문제 중 하나는 바로 화면이 '버벅'거리거나 '렉'이 걸리는 상황입니다.
-특히 프로젝트 규모가 커질수록 점점 더 빈번하게 발생하여, 어떤 부분에서 병목현상이 일어나는지 파악하기가 어려워집니다.
-저 역시 Flitter라는 렌더링 엔진 프레임워크를 개발하면서 동일한 성능저하 문제를 경헙했습니다. Flitter는 SVG나 Canvas를 선언적으로 조작하면서, Flutter의 인터페이스를 자바스크립트로 구현한 프로젝트입니다.
-이 글에서는 프레임워크를 개발하면서 겪은 성능 최적화 여정을 공유하려고 합니다. 여러분도 분명 '아, 이런 방법이 있었구나!' 하는 인사이트를 얻으실 수 있길 바랍니다.
분론:
1. SVG 이벤트 처리 최적화
-Flitter에서는 SVG 요소들과 사용자 인터랙션을 처리해야 했습니다.
-처음에는 각 SVG 요소마다 직접 이벤트 핸들러를 부착했는데, 많은 돔 요소에 직접 이벤트 핸들러를 붙이는 것은 심각한 렌더링 성능 저하를 가져왔습니다.
-해결책으로 이벤트 위임 패턴을 도입했습니다. 최상위 SVG 요소에만 이벤트 핸들러를 연결하고, 프레임워크 내부에서 이벤트 버블링을 구현했습니다.
-이를 통해 DOM 접근을 최소화하면서도 동일한 인터랙션을 처리할 수 있게 되었습니다.
렌더링 파이프라인 최적화
-렌더링 엔진에서 애니메이션이나 상태 업데이트를 처리할 때 requestAnimationFrame을 사용했습니다.
-이때 중요한 발견은 requestAnimationFrame 자체가 매크로 테스크를 할당하는 비용이 무시할 수 없다는 점이었습니다.
-반복되는 매크로 테스크 할당은 브라우저에 상당한 부하를 주기 때문에, 호출 횟수를 최소화하는 것이 중요했습니다.
-Flutter의 스케줄러에서 영감을 받아, 여러 업데이트 요청을 배치로 모아서 처리하는 방식을 도입했습니다.
-또한 Vsync 클래스를 만들고 싱글톤으로 관리해, 불필요한 requestAnimationFrame을 줄이고 전반적인 성능을 개선할 수 있었습니다.
성능 측정 자동화
-Playwright와 크롬 데브툴 프로토콜을 활용해 성능 측정 시스템을 구축했습니다.
-특히 렌더링 엔진의 핵심 함수들이 얼마나 자주 호출되는지, 각각의 실행 시간은 어떻게 되는지 자동으로 측정했습니다.
-이 데이터를 바탕으로 병목 지점을 정확히 파악하고 최적화할 수 있었습니다.
-프레임워크의 성능 개선 여부를 객관적으로 확인할 수 있는 기반이 되었습니다.
최적화 결과
-위 최적화들을 적용한 결과, SVG 다이어그램 드래그 시의 프레임 드랍이 크게 감소했습니다.
-특히 복잡한 다이어그램을 조작할 때도 버벅임 없이 부드러운 인터랙션이 가능해졌습니다.
-Flitter로 만든 다이어그램 서비스 Easyrd에서 노드를 드래그할때 자연스럽게 움직이는걸 확인할 수 있습니다.
결론:
-렌더링 엔진 프레임워크를 개발하면서 성능 최적화의 중요성을 다시 한번 실감했습니다.
-특히 이벤트 처리나 렌더링 파이프라인같은 로우 레벨에서의 최적화가 전체 성능에 큰 영향을 미친다는 것을 배웠습니다.
-여러분도 분명 나만의 방식으로 성능 최적화를 시도해보신 경험이 있으실 텐데요, 어떤 방법을 사용하셨나요?
-댓글로 여러분의 성능 최적화 경험을 공유해주시면 정말 좋을 것 같습니다. 위 모든 예시는 https://github.com/meursyphus/flitter 에서 확인해 볼 수 있습니다.
현재 진행 상황은 아이디어 구체화 1단계에 있습니다. 개요 작성을 원하시면 일단 아이디어 구체화 2단계를 마치고, 개요 작성 단계로 넘어갈 수 있습니다. 아이디어 구체화를 좀 더 발전시키시겠습니까?
현재 진행 상황
▶️ 1. 아이디어 구체화 (1/2)
❌ 2. 아이디어 구체화 (2/2)
❌ 3. 개요 작성
❌ 4. 본문 작성
❌ 5. 퇴고 과정
❌ 6. 제목 작성
다음 단계
아이디어 구체화 (1/2)
아뇨 바료 개요로 가요. 전 앞단계를 다른곳에서 이미 수행했다구요
사용자가 초록을 작성해두었습니다. 이를 바탕으로 개요 작성을 진행해주세요.
현재 진행 상황
▶️ 1. 아이디어 구체화 (1/2)
❌ 2. 아이디어 구체화 (2/2)
❌ 3. 개요 작성
❌ 4. 본문 작성
❌ 5. 퇴고 과정
❌ 6. 제목 작성
다음 단계
아이디어 구체화 (1/2)
[개요 작성]
읽는 사람과의 공감
1.1 프론트엔드 개발자에게 흔히 발생하는 화면 버벅거림 문제
1.2 프로젝트 규모 증가에 따라 빈번해지는 렉 현상
1.3 사용자 친화적인 UI 개발의 어려움
문제 제기와 해결 조건
2.1 성능 저하 문제의 근본 원인 분석
2.2 이벤트 처리와 렌더링 파이프라인에서의 병목 현상
2.3 성능 최적화를 위해 고려해야 할 주요 조건
구체적 해결책
3.1 SVG 요소의 이벤트 처리 최적화
3.2 requestAnimationFrame 관리와 매크로 테스크 최적화
3.3 성능 측정 자동화를 통한 지속적 개선
베네핏 소구
4.1 SVG 다이어그램 드래그 시 프레임 드랍 감소
4.2 복잡한 인터랙션 만족도 증가
4.3 Flitter 프로젝트를 통한 성능 개선 사례
클로징과 행동 유도
5.1 개인의 성능 최적화 방법 사례 공유 권유
5.2 Flitter 프로젝트 리뷰를 통한 학습 유도
5.3 직접 최적화 도전과 성공 경험 제안
이 개요가 사용자 경험에 잘 맞는지, 혹시 수정할 부분이 있는지 알려주세요!
넵 좋아요. 이걸 바탕으로 초안 작성해게 본문 작성 ㄱㄱ
[개요]
제목: 내가 오픈소를 개발하며 배운 성능 최적화 방법
주제: 프레임워크를 개발하면서 알게된 다양한 성능 최적화 방법
타깃: 성능 최적화에 관심있는 개발자들, 커리어 향상을 위해 깊은 지식을 원하는 프론트엔드
서론:
-프론트엔드 개발에서 가장 골치 아픈 문제 중 하나는 바로 화면이 '버벅' 거리거나 '렉'이 걸리는 상황입니다.
-특히 프로젝트 규모가 커질수록 점점 더 빈번하게 발생하여, 어떤 부분에서 병목현상이 일어나는지 파악하기가 어려워집니다.
-저 역시 Flitter라는 렌더링 엔진 프레임워크를 개발하면서 동일한 성능저하 문제를 경헙했습니다. Flitter는 SVG나 Canvas를 선언적으로 조작하면서, Flutter의 인터페이스를 자바스크립트로 구현한 프로젝트입니다.
-이 글에서는 프레임워크를 개발하면서 겪은 성능 최적화 여정을 공유하려고 합니다. 여러분도 분명 '아, 이런 방법이 있었구나!' 하는 인사이트를 얻으실 수 있길 바랍니다.
분론:
1. SVG 이벤트 처리 최적화
-Flitter에서는 SVG 요소들과 사용자 인터랙션을 처리해야 했습니다.
-처음에는 각 SVG 요소마다 직접 이벤트 핸들러를 부착했는데, 많은 돔 요소에 직접 이벤트 핸들러를 붙이는 것은 심각한 렌더링 성능 저하를 가져왔습니다.
-해결책으로 이벤트 위임 패턴을 도입했습니다. 최상위 SVG 요소에만 이벤트 핸들러를 연결하고, 프레임워크 내부에서 이벤트 버블링을 구현했습니다.
-이를 통해 DOM 접근을 최소화하면서도 동일한 인터랙션을 처리할 수 있게 되었습니다.
렌더링 파이프라인 최적화
-렌더링 엔진에서 애니메이션이나 상태 업데이트를 처리할 때 requestAnimationFrame을 사용했습니다.
-이때 중요한 발견은 requestAnimationFrame 자체가 매크로 테스크를 할당하는 비용이 무시할 수 없다는 점이었습니다.
-반복되는 매크로 테스크 할당은 브라우저에 상당한 부하를 주기 때문에, 호출 횟수를 최소화하는 것이 중요했습니다.
-Flutter의 스케줄러에서 영감을 받아, 여러 업데이트 요청을 배치로 모아서 처리하는 방식을 도입했습니다.
-또한 Vsync 클래스를 만들고 싱글톤으로 관리해, 불필요한 requestAnimationFrame을 줄이고 전반적인 성능을 개선할 수 있었습니다.
성능 측정 자동화
-Playwright와 크롬 데브툴 프로토콜을 활용해 성능 측정 시스템을 구축했습니다.
-특히 렌더링 엔진의 핵심 함수들이 얼마나 자주 호출되는지, 각각의 실행 시간은 어떻게 되는지 자동으로 측정했습니다.
-이 데이터를 바탕으로 병목 지점을 정확히 파악하고 최적화할 수 있었습니다.
-프레임워크의 성능 개선 여부를 객관적으로 확인할 수 있는 기반이 되었습니다.
최적화 결과
-위 최적화들을 적용한 결과, SVG 다이어그램 드래그 시의 프레임 드랍이 크게 감소했습니다.
-특히 복잡한 다이어그램을 조작할 때도 버벅임 없이 부드러운 인터랙션이 가능해졌습니다.
-Flitter로 만든 다이어그램 서비스 Easyrd에서 노드를 드래그할때 자연스럽게 움직이는걸 확인할 수 있습니다.
결론:
-렌더링 엔진 프레임워크를 개발하면서 성능 최적화의 중요성을 다시 한번 실감했습니다.
-특히 이벤트 처리나 렌더링 파이프라인같은 로우 레벨에서의 최적화가 전체 성능에 큰 영향을 미친다는 것을 배웠습니다.
-여러분도 분명 나만의 방식으로 성능 최적화를 시도해보신 경험이 있으실 텐데요, 어떤 방법을 사용하셨나요?
-댓글로 여러분의 성능 최적화 경험을 공유해주시면 정말 좋을 것 같습니다. 위 모든 예시는 https://github.com/meursyphus/flitter 에서 확인해 볼 수 있습니다.
사용자가 초록을 바탕으로 개요 작성을 마쳤고, 본문 작성으로 진행하고자 합니다.
사용자가 제공한 개요를 바탕으로 본문을 작성해주세요.
현재 진행 상황
▶️ 1. 아이디어 구체화 (1/2)
❌ 2. 아이디어 구체화 (2/2)
✅ 3. 개요 작성
▶️ 4. 본문 작성
❌ 5. 퇴고 과정
❌ 6. 제목 작성
다음 단계
아이디어 구체화 (1/2)
프론트엔드 개발을 진행하다 보면, 언젠가는 반드시 화면 버벅거림이라는 벽을 마주치게 됩니다. 특히 프로젝트 규모가 커질수록 렉 현상도 비례하여 늘어나곤 하죠. 저 역시 Flitter라는 렌더링 엔진 프레임워크를 개발하면서 이 성능 저하의 골칫거리를 뼈저리게 느꼈습니다. Flitter는 SVG나 Canvas를 선언적으로 조작하면서 Flutter의 인터페이스를 자바스크립트로 구현한 프로젝트인데, 초반에는 프레임워크가 커지면서 SVG 처리와 렌더링 파이프라인에서 병목이 발생해 사용자가 체감할 수 있을 정도의 성능 저하가 나타났습니다. 이 글에서는 그 문제를 해결하기 위해 시도했던 과정을 나누고자 합니다. 혹시 비슷한 문제를 겪고 계신다면, 실마리를 얻어 가시면 좋겠습니다.
SVG 이벤트 처리 최적화
처음 구현할 때는 각 SVG 요소마다 이벤트 핸들러를 직접 등록했습니다. 작은 규모에서는 문제가 없었지만, 점점 요소가 늘어나면서 브라우저가 마치 “UE(사용자 경험) 지옥”에 빠진 것처럼 느려지는 상황이 잦아졌습니다. 결국 이벤트 위임 패턴을 적용했는데, 최상위 SVG 요소 하나에만 이벤트 핸들러를 걸고 이벤트가 하위 요소로 버블링되도록 한 것입니다. 이 방식은 DOM 노드 접근을 현저히 줄여 주고, 코드를 훨씬 간결하게 만드는 장점이 있었습니다.
렌더링 파이프라인 최적화
렌더링 엔진의 업데이트 루틴에서 requestAnimationFrame을 사용했으나, 이 호출 자체가 매크로 테스크를 생성한다는 사실에 주목하게 되었습니다. 자주 반복되는 매크로 테스크는 브라우저가 처리해야 할 일감을 기하급수적으로 증가시키므로, 호출 횟수를 최소화해야 했습니다.
Flutter의 스케줄러 개념을 참조해 여러 렌더링 업데이트 요청을 일정 시점까지 모아 두었다가 한 번에 처리했습니다. 또, Vsync 클래스를 만들고 싱글톤으로 관리하면서 “필요할 때만” requestAnimationFrame을 호출하도록 바꿔 주었습니다. 그 결과, 동시다발적인 업데이트로 인한 프레임 드랍을 눈에 띄게 줄일 수 있었습니다.
성능 측정 자동화
직접 최적화한 내용이 정말 효과가 있는지 확인하기 위해선 데이터가 필요했습니다. Playwright와 크롬 데브툴 프로토콜을 이용해 핵심 함수가 얼마나 자주 호출되는지, 실행 시간이 어느 정도인지 자동으로 측정할 수 있는 시스템을 구축했습니다. 측정된 데이터를 바탕으로 병목 지점을 알아내고, 수정 후에 실제 퍼포먼스가 얼마나 개선되었는지 수치화해 나갔습니다. 이는 엔진의 성능을 안정적으로 유지하고, 더 나은 설계를 적용할 때 근거 자료로 활용하기에 유용했습니다.
최적화 결과
SVG 다이어그램을 드래그하며 화면을 막 움직일 때, 이전에는 미세한 버벅거림이 느껴졌지만 이제는 눈에 띄게 줄어들었습니다. 복잡한 노드나 연산이 많은 상태에서도 가능한 한 부드럽게 프레임을 유지하게 되었고, 실제로 Easyrd라는 다이어그램 서비스에서 노드를 자유롭게 드래그할 때 UI가 매끄럽게 반응하는 모습을 직접 확인할 수 있었습니다.
“어떻게 하면 코드가 이 정도까지 매끄럽게 돌아갈까?”를 고민하는 과정이 조금은 번거롭지만, 최종 사용자에게 제공되는 경험의 질이 크게 달라진다는 점에서 그 가치가 충분하다고 생각합니다.
결론
렌더링 엔진을 직접 만들어 보면서, 이벤트 처리와 렌더링 파이프라인처럼 로우 레벨에서의 최적화가 전체 퍼포먼스에 얼마나 중요한지 더욱 뼈저리게 배웠습니다. 여러분도 분명 비슷한 고민을 하시거나, 혹은 나름의 해결책을 시도해보신 경험이 있을 텐데요. 각자 발견하신 최적화 방법과 결과를 함께 공유한다면, 더 빠르고 부드러운 사용자 경험을 제공하는 데 큰 도움이 될 것 같습니다.
혹시 더 자세한 내용이나 코드가 궁금하시다면, 제 오픈소스 프로젝트인 Flitter(https://github.com/meursyphus/flitter)를 살펴봐 주시면 감사하겠습니다. 그리고 어떤 방식으로든 직접 최적화를 시도하고, 그 성과를 체감해 보시면 좋겠습니다. 성능 개선은 결코 단번에 완성되지 않지만, 그 작은 변화가 세밀하게 모여 더 나은 사용자 경험을 만들어 나갑니다.
현재 문서조각은 개요의 '서론' 부분에 해당하며, 사용자가 겪은 성능 저하 문제와 Flitter 프로젝트를 소개하고 그에 따라 성능 최적화 방법을 공유하는 목적으로 작성되었습니다. 이제 문서의 '서론'을 확장하기 위해 필요한 정보를 질문해보겠습니다.
서론의 첫 부분에서는 프론트엔드에서 화면 버벅거림을 일으키는 주된 원인이나 구체적인 상황을 설명해 주실 수 있을까요?
Flitter 프로젝트 소개 부분에서는 더 구체적으로 이 프로젝트가 해결하고자 하는 문제나 주된 사용 사례가 무엇인지 설명해주실 수 있을까요?
Flitter를 개발하면서 겪은 성능 문제는 초기 예상과 어떻게 달랐는지, 그리고 이러한 차이를 통해 배운 점이 있다면 공유해주실 수 있을까요?
이 질문들을 바탕으로 내용을 더욱 구체화하여 '서론' 부분을 풍성하게 만들 수 있습니다. 관련된 코드나 문서, 참고 자료가 있다면 공유해주시면 좋습니다.
음 flitter로 easyrd라는 다이어그램 서비스 만들었는데, 이거 다이어그램 노드를 드래그하거나, 초점이동하는게 그 노드가 많아지니까 엄청 버벅거렸어요 한 프레임 16ms를 넘는 함수호출시간이 문제였던 거죠
그리고 초기에는 svg만 다뤘을때인데, 다이어그램의 첫 로딩속도도 좀 느렸거든요. api 네트워크 문제가 아니라, 그냥 그리는 속도가 느렸던건데 svg 자식별로 이벤트 핸들러를 다는동작이 있었는데, 이거 돔 접근하는것도 시간이 꽤 걸렸었죠. 그것도 모르고 다이어그램 엄한데 고치고 있기도 했답니다
그래서 성능을 측정하는 과정과 이를 개선하는과정에 대해 프론트엔드 쪽에서 , 현업에서 자주 접하지 못할 이야기를 해서 다들 인사이트 같이 공유했으면 해요
서론을 더욱 풍부하게 만들기 위한 내용 확장에 대한 정보를 함께 제공해주셨습니다. 추가적인 내용이 본문 초안에 포함되어 더욱 명확하게 전달될 수 있을 것입니다. 다음과 같은 내용이 서론에 자연스럽게 포함되도록 본문을 구체화해보겠습니다.
화면 버벅거림의 주된 원인 및 사용자 시나리오:
Flitter 프로젝트의 성능 최적화 필요성:
초기 예상과의 차이 및 배운 점:
앞서 주신 이 정보를 바탕으로 본문 초안을 더욱 구체화하겠습니다. 지금까지 주신 피드백에 따라 서론을 보강하는 작업을 시작해보겠습니다.
네 ㄱㄱ
서론 확장을 위해 사용자로부터 제공된 정보와 피드백을 기반으로 본문 초안을 구체화하려고 합니다. 서론에서는 Easyrd의 다이어그램 서비스에서 발생한 성능 문제가 노드 드래그와 초점 이동에서 두드러졌고, SVG 자식별로 이벤트 핸들러를 부착하면서 발생한 DOM 접근 지연이 문제였음을 강조하겠습니다. 더불어, Flitter 프로젝트를 처음 진행할 당시 예상과 달리 성능 문제가 심각하여 다이어그램 구조 수정에 시간을 낭비했던 경험을 바탕으로 성능 최적화 과정의 중요성과 그로 인해 얻은 인사이트를 다른 프론트엔드 개발자와 공유할 수 있는 기회를 제공하고자 합니다. 이 내용을 기반으로 본문을 작성해 주시기 바랍니다.
[진행률: 1/4]
프론트엔드를 개발하다 보면, 화면이 ‘버벅’거리거나 심각하게 ‘렉’이 걸리는 문제가 자주 발생합니다. 특히 애플리케이션 규모가 커질수록 이런 문제는 더욱 빈번해지고, 어디에서 병목 현상이 일어나는지 찾기가 생각보다 쉽지 않습니다. 렌더링에 관여하는 요소가 많아질수록, 작은 코드 변경만으로도 전체 성능이 급격히 떨어지거나 의도치 않은 부분에서 부하가 발생하기도 합니다.
저 역시 “Flitter”라는 렌더링 엔진 프레임워크를 개발하다가 비슷한 난관에 부딪혔습니다. 이 프레임워크는 SVG나 Canvas를 선언적으로 조작해 Flutter와 유사한 인터페이스를 자바스크립트로 구현한 오픈소스 프로젝트인데, 복잡한 동작을 점차 추가할수록 렌더링이 급격히 느려졌습니다. 특히 다이어그램 서비스인 Easyrd를 구축하면서, 노드를 드래그하거나 초점을 옮길 때마다 프레임 드랍이 두드러져 사용자 경험이 심각하게 훼손되는 문제가 있었죠.
노드가 적을 때는 큰 문제가 없어 보였지만, 노드가 많아질수록 16ms 한 프레임을 훌쩍 넘는 함수 호출 시간이 쌓이기 시작했습니다. 화면에서 노드를 빠르게 이동시키거나 확대·축소를 반복할 경우 이벤트가 몰려들어 브라우저가 이를 처리하지 못해 ‘버벅’거리는 현상이 눈에 띄게 늘어났습니다. 사용자 입장에서는 노드가 툭툭 끊기거나 예상치 못한 곳으로 튀는 듯한 느낌을 받았고, 이는 곧 서비스 사용성을 심각하게 떨어뜨리는 요인으로 이어졌습니다.
Flitter를 처음 설계할 당시에는 단순히 SVG 요소들을 선언적으로 그려주기만 하면 큰 문제가 없을 것이라 생각했습니다. 그러나 “다이어그램의 첫 로딩 속도가 왜 이렇게 느리지?”라는 의문이 들면서부터 문제가 확대되었죠. 네트워크 지연 때문이 아니라, 정작 브라우저가 SVG를 그리는 과정 자체에서 시간을 많이 소비하고 있었습니다. 자세히 확인해 보니, SVG 자식 요소마다 개별적으로 이벤트 핸들러를 달아 DOM에 접근하는 빈도가 과도하게 높았던 것이 원인이었습니다.
성능 문제가 예상보다 심각하다 보니, 초기에 다이어그램 구조를 잘못된 방향으로 수정하기도 했습니다. DOM 접근을 줄이기는커녕 오히려 더 복잡한 로직을 추가하면서 렌더링 부담이 가중되는 악순환도 겪었죠. 이 과정을 통해 “성능을 측정하고, 병목 지점을 정확히 파악한 뒤에 개선을 시도해야 한다”는 사실을 절감했습니다. 많은 프론트엔드 개발 환경에서 이런 실수를 범하기 쉽고, 제대로 된 측정 없이 대책을 세우면 시간과 자원을 낭비하기도 쉽습니다.
결국 성능 최적화는 단순한 코드 리팩터링이 아니라, 정확한 측정과 문제 지점을 구체적으로 밝히는 작업에서부터 시작되었습니다. 코드 어디를 손봐야 좋은 결과가 나타날지 모르는 상태에서 무작정 수정만 반복하는 것은 피해야 했죠. 이 글에서는 저와 같은 상황에 놓인 분들, 특히 대규모 SVG 렌더링과 사용자 인터랙션 처리로 골머리를 앓는 프론트엔드 개발자분들께 “이런 수고로움이 있었고, 이렇게 해결했다”라는 이야기를 드리고 싶습니다. 현업에서 흔히 접하지 못하는 로우 레벨 최적화 과정과 그로 인해 얻은 인사이트가, 조금이라도 개발 방향을 잡는 데 도움이 되길 바랍니다.
현재 문서조각의 키워드와 개요 매칭 부분입니다:
타이틀: 프레임워크 개발 과정에서 발견한 다양한 성능 최적화 방법
해당 섹션:
현재 문서조각을 더 풍성하게 만들기 위해 각 부분별로 여쭤보고 싶습니다:
[첫 번째 부분: SVG 이벤트 처리 최적화]
[두 번째 부분: 렌더링 파이프라인 최적화]
아무 언급해주시면 제가 정리하도록 하겠습니다. 관련 코드나 문서, 참고자료가 있다면 함께 공유해 주시면 좋겠습니다.
일단 첫번째는 flitter가 svg만 지원했었을때 GestureDetector라는 위젯에서 이벤트 핸들러를 달면 해당 요소에 직접 svg 요소에 이벤트 핸들러를 달아주는거였는데요. 이게 위젯트리가 많아지니까 붙이는데에도 시간이 걸리더라구요. 그래서 이걸 어떻게 해야하지 고민했었는데, 아예 부모 svg에만 이벤트 핸들러를 부착하고, 자식 위젯들에 붙은 이벤트 핸들러는 위젯트리에서만 부착하는거고, 실제로는 부모 svg 이벤트 핸들러의 콜백 리스트에 넣어두기 때문에 이런 위임정책을 하는건데 리액트도 보니까 가상돔에만 이벤트 핸들러를 다는거고, 실제돔은 루트돔에만 달더라구요. 이런 이벤트 위임이 성능문제를 해결한거죠
그리고 두번째는 랜더링 파이프라인은 저도 몰랐었는데 requestAnimationFrame이 (매크로 테스크 큐 아님) 애니메이션 루프를 위핸 테스크 처리에 콜백을 할당해주는데, 이 큐에 콜백을 넣는행위 자체가 비용이 크더라구요. 보니까 setTimeout 이런 매크로 테스크에 큐 넣는 행위도 비슷한 시간 (30~60us) 걸렸던거 같아요. 제 맥 기준에서요! 그래서 아 콜백의 비용과 상관없이 저 함수들을 호출하는것도 시간이 길다는걸 그 크롬 dev tool을 통해 알게 되어가지고,
import { assert } from "../utils";
import type RenderFrameDispatcher from "./RenderFrameDispatcher";
enum SchedulerPhase {
idle,
persistenceCallbacks,
postFrameCallbacks,
}
class Scheduler {
phase: SchedulerPhase;
private persistenceCallbacks: (() => void)[];
private postFrameCallbacks: (() => void)[];
private renderFrameDispatcher: RenderFrameDispatcher;
constructor({
renderFrameDispatcher,
}: {
renderFrameDispatcher: RenderFrameDispatcher;
}) {
this.phase = SchedulerPhase.idle;
this.persistenceCallbacks = [];
this.postFrameCallbacks = [];
this.renderFrameDispatcher = renderFrameDispatcher;
renderFrameDispatcher.setOnFrame(() => this.handleDrawFrame());
}
flushPostCallbacks() {
this.postFrameCallbacks.forEach(callback => {
callback();
});
this.postFrameCallbacks = [];
}
ensureVisualUpdate() {
switch (this.phase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
this.schedule();
break;
case SchedulerPhase.persistenceCallbacks:
break;
}
}
private hasScheduledFrame = false;
private schedule() {
if (this.hasScheduledFrame) return;
this.renderFrameDispatcher.dispatch();
this.hasScheduledFrame = true;
}
private handleDrawFrame() {
assert(
this.phase === SchedulerPhase.idle,
"Scheduler should be idle on beginning frame",
);
this.hasScheduledFrame = false;
this.phase = SchedulerPhase.persistenceCallbacks;
this.flushPersistenceCallbacks();
this.phase = SchedulerPhase.postFrameCallbacks;
this.flushPostCallbacks();
this.phase = SchedulerPhase.idle;
}
private flushPersistenceCallbacks() {
this.persistenceCallbacks.forEach(callback => {
callback();
});
}
addPersistenceCallbacks(callback: () => void) {
this.persistenceCallbacks.push(() => callback());
}
addPostFrameCallbacks(callback: () => void) {
this.postFrameCallbacks.push(() => callback());
}
}
export default Scheduler;
현재 사용자가 제공한 피드백을 바탕으로 본문의 2번 SVG 이벤트 처리 최적화와 3번 렌더링 파이프라인 최적화에 대한 부분을 확장합니다. SVG 이벤트 처리에서는 Flitter가 SVG만 지원했을 때, GestureDetector 위젯에서 이벤트 핸들러를 달아 요소에 직접 핸들러 추가하는 방식으로 위젯 트리가 많아질수록 시간이 소모되었던 경험과, 이벤트 위임 패턴 도입을 통해 성능을 개선한 사례를 포함시킵니다. 렌더링 파이프라인 최적화에 관한 부분에서는 requestAnimationFrame의 콜백 자체가 매크로 테스크 큐에 넣어지는 행위가 비용을 수반했음을 설명하고, 이를 해결하기 위해 Flutter의 배치 처리 방식을 참조하여 단일 프레임 내에서 한 번만 호출 되도록 리팩터링한 사례와, 실제 코드 구현 예시를 활용하여 구체적으로 설명합니다.
[진행률: 2/4]
프로젝트 초기에 Flitter가 SVG만 지원하던 시절, 가장 먼저 마주한 문제는 모든 SVG 요소에 직접 이벤트 핸들러를 부착하는 방식이었습니다. 대표적으로 GestureDetector라는 위젯에 클릭, 드래그 등의 이벤트 핸들러를 달면, 내부적으로 실제 SVG 요소에 핸들러가 추가되는 구조였지요. 이렇게 자식 요소가 하나씩 늘어날 때마다 DOM 접근이 급격히 증가했고, 결국 수많은 요소에 핸들러가 연결되면서 화면 초기 로딩부터 인터랙션까지 버벅임이 두드러졌습니다.
노드가 여러 개만 있을 때는 문제가 크게 눈에 띄지 않았지만, 점차 다이어그램 노드나 UI 위젯이 복잡해지면서 트리 구조가 깊어졌습니다. 이때마다 SVG 요소마다 이벤트 핸들러를 붙이는 작업 자체가 병목 지점이 되었습니다. DOM 접근이 누적되어 브라우저가 한 화면을 완전히 구성하거나 변경하는 데 필요한 시간이 눈에 띄게 길어졌고, 사용자 입장에서는 “화면이 생각보다 늦게 뜬다”라는 인상을 받게 되었습니다.
이 문제를 해결하기 위해 도입된 것이 이벤트 위임(Event Delegation) 패턴입니다. 최상위 SVG 요소 하나에만 핸들러를 등록하고, 실제로는 이벤트 버블링을 통해 내부 이벤트를 캐치하도록 설계한 것이 핵심입니다. 자식 위젯에 연결된 핸들러는 실제로는 ‘위젯트리’ 내부에서만 관리되며, 최상위 SVG에서 발생하는 이벤트가 적절한 콜백 목록으로 분배되는 구조가 만들어졌습니다. 이렇게 루트 요소 하나에만 이벤트를 집중시키면 DOM 접근이 획기적으로 줄어들어, 노드가 아무리 많아져도 각 자식마다 직접 핸들러를 다는 방식에 비해 훨씬 가벼운 인터랙션 응답 속도를 얻을 수 있습니다.
이벤트 위임은 사실 React 등 다른 라이브러리에서도 널리 사용되는 기법입니다. React 역시 ‘가상 DOM’에 이벤트 핸들러를 관리하고, 실제 DOM에는 루트 하나에만 이벤트를 등록함으로써 중복 작업을 최소화합니다. Flitter 역시 이를 벤치마킹하여 “부모 SVG에만 이벤트 핸들러를 부착하는 정책”을 채택했습니다. 그 결과 대규모 다이어그램에서도 일관된 인터랙션 성능을 유지할 수 있었고, 요소 개수에 비례해서 늘어났던 DOM 접근이 현저히 줄어들었습니다.
이벤트 위임 패턴을 도입하면 성능 개선뿐 아니라 코드 유지보수에도 도움이 됩니다. 한두 곳에서만 핵심 핸들러를 관리하면 되므로, 새로운 이벤트 유형을 추가하거나 로직을 수정할 때도 개별 요소를 전부 수정할 필요가 없습니다. 또한 코드를 간결하게 정리할 수 있어, 협업 시 충돌이나 실수를 줄이는 데도 유리합니다.
인터랙션을 부드럽게 만들기 위해서는 애니메이션 프레임마다 필요한 업데이트를 수행해야 합니다. 이를 위해 자바스크립트 환경에서는 보통 requestAnimationFrame 함수를 사용합니다. 그런데 의외로 이 함수 호출 자체가 브라우저 내부의 특정 큐에 콜백을 넣어야 하기 때문에 일정 비용이 발생한다는 점이 관찰되었습니다. 크롬 DevTools로 분석해 보니, setTimeout 같은 매크로 태스크를 등록하는 행위와 마찬가지로 requestAnimationFrame 콜백을 스케줄링하는 과정에서도 30~60μs 정도의 시간이 소모된다는 사실이 드러났습니다. 작은 수치 같아 보이지만, 이를 여러 군데에서 남발하면 점차 프레임 드랍이 누적되는 결과로 이어졌습니다.
노드 드래그나 애니메이션이 복잡한 경우, 컴포넌트가 여러 번 업데이트 요청을 보낼 수 있습니다. 변화가 감지될 때마다 바로 requestAnimationFrame을 호출하면, 실제 실행되는 콜백의 개수도 기하급수적으로 늘어날 수 있습니다. 브라우저는 프레임마다 모든 콜백을 처리하려고 시도하므로, 불필요한 반복 호출이 성능을 소모하게 되는 것이죠. 이는 곧 브라우저 전체 리소스 사용량 증가로 이어지고, 대규모 애플리케이션에서는 화면이 프리징에 가까운 상태로 느려지기도 합니다.
이를 해결하기 위해 Flutter 엔진에서 쓰이던 ‘배치 처리’ 아이디어를 참고했습니다. Flutter는 변경 사항이 있을 때마다 매번 Paint 과정을 호출하는 대신, 지연시켜 모았다가 한꺼번에 처리해 버리는 구조를 사용합니다. Flitter에서는 이를 본떠 “한 프레임 내에서 딱 한 번만 업데이트를 스케줄링”하고, 그 사이 여러 위젯이 수정되어도 이후에 한꺼번에 렌더링 과정을 실행하도록 리팩터링했습니다. 예를 들어 한 프레임에 여러 요청이 들어오면 전부 ‘dirty 위젯’ 목록에 쌓아 두고, 다음 requestAnimationFrame 콜백이 실행될 때 몰아서 처리함으로써 콜백 호출 횟수를 최소화하는 식입니다.
이 로직을 좀 더 깔끔하게 관리하기 위해 Scheduler라는 싱글톤 클래스를 구현했습니다. 내부에 ‘persistenceCallbacks’와 ‘postFrameCallbacks’ 같은 콜백 목록을 두고, 한 번 프레임이 시작될 때 모아 둔 작업을 단번에 실행하는 구조입니다. 이렇게 하면 불필요한 requestAnimationFrame 호출이 줄어들어 매번 새로운 큐에 콜백을 등록하는 비용이 크게 절감됩니다. 실제로 코드 분석 결과, 동일한 애니메이션에서 호출되는 requestAnimationFrame 횟수가 크게 줄어들었고, 이를 통해 드래그나 화면 전환 시 끊김 현상이 현저히 줄어드는 효과를 얻었습니다.
배치 처리 방식 도입 후 가장 큰 장점은, 복잡한 그래프나 다이어그램 애니메이션에서도 속도가 안정적으로 유지된다는 점입니다. 이전에는 노드가 서로 겹치거나 급격하게 이동할 때 프레임 레이트가 급락하곤 했습니다. 그러나 변경 요청을 모아서 한꺼번에 렌더링하는 방식으로 개선한 뒤에는, 애니메이션 전반에 걸쳐 일정한 프레임을 확보하여 끊김 없는 경험을 제공할 수 있게 되었습니다. 이는 특히 마우스 드래그와 같은 사용자 인터랙션이 빈번하게 일어나는 시나리오에서 체감이 더 크게 다가왔습니다.
개요 매칭:
현재 문서조각은 개요의 "3. 성능 측정 자동화"와 "4. 최적화 결과" 부분에 해당합니다.
질문:
[성능 측정 자동화에 대해]
[최적화 결과에 대해]
관련 코드나 문서, 참고자료가 있다면 함께 공유해 주시면 좋겠습니다.
성능 측정과 관련해선 이거 . 글참고해서 의의나 목적 왜했는지를 가져오면 될듯 이전에 쓴글임. 코드 예시도 일부가져와주면 좋고,
최적화 결과는 이제 easyrd에서 드래그나 초점 이동하는게 놀라울정도로 부드러워졌다는거죠.
프론트엔드 개발자라면 누구나 겪는 상황입니다.
"이 부분 최적화했는데 확실히 빨라진 것 같아요!"
"음... 제 환경에선 별로 차이를 못 느끼겠는데요?"
Chrome DevTools를 열고 성능 측정을 시도해보지만:
결과적으로, 우리는 "느낌상" 성능이 좋아졌다고 이야기할 수밖에 없습니다.
하지만 진정한 성능 최적화는 객관적인 수치로 입증될 수 있어야 합니다.
이 글에서는 자동화된 성능 측정 방법을 통해:
1. 일관된 환경에서의 객관적인 성능 데이터 수집
2. 시간에 따른 성능 변화의 추적
3. 팀 전체가 공유할 수 있는 성능 지표 확보
방법을 알아보겠습니다.
💡 실전 팁: Chrome DevTools의 Performance 패널은 강력한 도구이지만, 수동 측정의 한계가 있습니다. 자동화된 측정을 통해 이러한 한계를 극복하면서도, DevTools의 분석 기능은 계속 활용할 수 있습니다.
Chrome DevTools의 Performance 패널을 코드로 제어할 수 있다는 사실, 알고 계셨나요?
Playwright를 사용하면 브라우저 자동화를 통해 성능 측정을 자동화할 수 있습니다.
test('Capture performance traces', async ({ page, browser }) => {
// Chrome trace 기록 시작
await browser.startTracing(page, {
path: `./traces/${formatDate(new Date())}.json`
});
// 페이지 로드 및 액션 수행
await page.goto('http://localhost:4173/performance-test');
await page.click('button#start');
// 성능 측정 포인트 기록
await page.evaluate(() => {
window.performance.mark('Perf:Started');
window.performance.mark('Perf:Ended');
window.performance.measure('overall', 'Perf:Started', 'Perf:Ended');
});
// 측정 종료 및 결과 저장
await browser.stopTracing();
});
하지만 이렇게 한 번 측정하는 것으로는 충분하지 않습니다.
💡 물리교육과 시절의 실험실 습관
"자, 여러분! 한 번 측정은 측정이 아닙니다!"물리실험 레포트를 작성하면서 몸에 밴 습관입니다. 😄
측정값의 오차는 반복 측정 횟수의 제곱근에 반비례한다는 사실,
아직도 생생하게 기억납니다.
물리 실험에서 배운 원칙을 웹 성능 측정에 적용해보면:
반복 측정의 중요성
변인 통제의 철저함
test('Scientifically measure performance', async () => {
const results = [];
const REPEAT_COUNT = 50; // 신뢰도를 위한 충분한 반복
for(let i = 0; i < REPEAT_COUNT; i++) {
// 변인 통제: 매번 새로운 브라우저 환경
const browser = await chromium.launch({
args: ['--no-sandbox', '--disable-dev-shm-usage']
});
const page = await browser.newPage();
await browser.startTracing(page, {
path: `./traces/trace_${i}.json`
});
// 성능 측정
const result = await measurePerformance(page);
results.push(result);
await browser.stopTracing();
await browser.close(); // 브라우저 상태 초기화
}
// 통계 처리
const avg = calculateAverage(results);
const standardError = calculateStandardError(results);
console.log(`실행 시간: ${avg}ms ± ${standardError}ms`);
});
다음은 제가 실제로 경험한 흥미로운 사례입니다:
// ❌ 잘못된 측정 방법: 브라우저 재시작 없이 반복 측정
test('Without browser restart', async ({ page }) => {
const results = [];
for(let i = 0; i < 50; i++) {
const startTime = performance.now();
await runPerformanceTest(page);
results.push(performance.now() - startTime);
}
console.log('실행시간 변화:', results);
});
이 코드로 측정한 결과:
왜 이런 현상이 발생할까요?
V8 엔진의 JIT(Just-In-Time) 컴파일러가 실행 패턴을 학습하면서 코드를 최적화하기 때문입니다. 즉, 같은 코드가 반복될수록 실행 속도가 점점 빨라지는 거죠.
// ✅ 올바른 측정 방법: 매번 브라우저 재시작
test('With browser restart', async () => {
const results = [];
for(let i = 0; i < 50; i++) {
const browser = await chromium.launch();
const page = await browser.newPage();
const startTime = performance.now();
await runPerformanceTest(page);
results.push(performance.now() - startTime);
await browser.close();
}
console.log('실행시간 변화:', results);
});
이 방식으로 측정한 결과:
💡 실전 팁:
JIT 컴파일러의 최적화는 프로덕션 환경에서 실제로 발생하는 현상입니다.
하지만 성능 측정의 목적은 "기준값" 을 얻는 것이므로,
이러한 최적화의 영향을 받지 않는 환경에서 측정해야 합니다.
물리교육과 출신이 프론트엔드 개발자가 되어 실험 방법론을 적용하게 될 줄이야...
세상일이란 참 알 수 없네요. 😄 이제 이 기반 위에서 더 깊이 있는 성능 분석을 위해
Chrome Trace를 살펴보도록 하겠습니다.
문제 상황: Flitter의 성능 최적화 도전
저는 시각화 프레임워크 Flitter를 개발하는 중에, 성능 최적화에 대한 필요성을 절실히 느끼게 되었습니다. Flitter는 SVG, Canvas를 활용해 동적 데이터를 시각화하는 라이브러리로, 다양한 애니메이션과 상호작용을 제공합니다. 이러한 기능들은 데이터 표현을 풍부하게 해주지만, 성능 부하를 일으키는 원인이 되기도 했습니다.
예를 들어, 데이터가 바뀔 때마다 새롭게 SVG 요소를 그리기 위해 requestFrameAnimation
이 반복 호출되는 상황이 있었습니다. setTimeout의 콜백함수는 최적화로 한번만 트리거가 되도록 했으나,requestFrameAnimation
을 호출하는 자체도 비용이 있을줄은 몰랐습니다. 평소에는 티가나지 않았으나 매우 복잡한 UI를 반복적으로(마우스 드래깅) 랜더링할때 성능저하가 체감되었습니다.
이 문제를 해결하고자 처음에는 특정 함수의 실행 시간을 추적하고, 애니메이션 루틴을 최적화하려고 시도했습니다. 하지만 주관적인 느낌만으로는 성능 개선의 효과를 확신할 수 없었습니다. 성능 데이터를 수치화해 측정해야 했고, 코드의 변경 사항이 실제로 긍정적인 영향을 미치는지 확인할 필요가 있었습니다.
“자바스크립트의 실행 시간을 체계적으로 기록하고, 최적화 결과를 과학적으로 검증할 방법이 필요하다!”
이 결론에 이르러 Chrome Trace와 Playwright를 활용한 자동화된 성능 측정 방식을 도입하게 되었습니다. 이 글에서는 Chrome Trace Report를 활용해 자바스크립트의 각 함수가 얼마나 많은 리소스를 사용하는지 확인하고, 최적화가 필요한 부분을 찾아가는 과정을 소개하려고 합니다.
Chrome Trace Report는 브라우저가 자바스크립트 코드를 실행하는 동안 발생하는 모든 주요 이벤트를 추적하여, 각 이벤트가 CPU와 메모리를 얼마나 소모하는지를 확인할 수 있게 해줍니다. 특히, 자바스크립트 함수 호출과 실행 시간을 포함한 다양한 성능 지표를 기록하기 때문에, 코드의 성능을 면밀히 분석하고 병목 현상을 정확하게 파악하는 데 매우 유용합니다.
Chrome Trace Report를 활용하는 이유
Chrome Trace Report의 가장 큰 장점은 전체 함수 호출 스택과 각 함수의 실행 시간을 한눈에 볼 수 있다는 점입니다. 이를 통해 성능에 부담을 주는 특정 함수나 반복 호출되는 코드가 무엇인지 빠르게 파악할 수 있습니다. 특히 Flitter와 같이 여러 요소와 상호작용하는 시각화 프레임워크에서는 성능 부하를 일으키는 특정 루틴을 찾는 것이 중요한데, Chrome Trace는 이러한 문제를 해결하는 데 큰 도움을 줍니다.
Playwright로 Chrome Trace 자동화하기
Playwright는 브라우저 환경에서 성능 측정을 자동화하는 데 유용한 도구로, Chrome Trace Report와 연동하여 성능 데이터를 수집할 수 있습니다. Playwright를 사용하면 브라우저에서 발생하는 이벤트들을 추적하는 Trace Report를 손쉽게 저장할 수 있습니다. 아래 예제 코드는 특정 시점부터 성능 데이터를 기록하여 JSON 파일로 저장하는 방법을 보여줍니다.
test('Capture performance traces and save JSON file when diagram is rendered', async ({ page, browser }) => {
// Chrome trace 기록 시작
await browser.startTracing(page, {
path: `./performance-history/${formatDate(new Date())}.json`
});
// 성능 측정하려는 페이지로 이동
await page.goto('http://localhost:4173/performance/diagram');
// 성능 측정 시작 지점 표시
await page.evaluate(() => window.performance.mark('Perf:Started'));
await page.click('button');
await page.waitForSelector('svg');
await page.evaluate(() => window.performance.mark('Perf:Ended'));
await page.evaluate(() => window.performance.measure('overall', 'Perf:Started', 'Perf:Ended'));
// 성능 측정 종료 및 결과 저장
await browser.stopTracing();
});
Playwright와 Chrome Trace를 활용하여 JSON 형식의 Trace Report를 생성했지만, 이 파일을 바로 분석하는 데는 몇 가지 어려움이 있습니다. Trace Report에는 실행 중 발생한 이벤트들이 모두 기록되지만, 각 함수의 실행 시간이나 호출 횟수를 직관적으로 확인하기 어렵기 때문입니다. 수많은 Trace Event가 원시 형태로 기록되어 있어 원하는 데이터를 빠르게 파악할 수 없었고, 모든 데이터를 직접 확인하는 데 한계가 있었습니다.
CpuProfile Event와 전처리의 필요성
Trace Report에서 CpuProfile Event
는 disabled-by-default-v8.cpu_profiler
라는 카테고리로 기록되며, 자바스크립트 함수의 CPU 사용 시간을 추적하는 중요한 데이터입니다. 각 이벤트는 시간 간격으로 기록되고, samples
와 timeDeltas
라는 필드를 포함하고 있어 각 함수의 실행 시간을 유추할 수 있습니다. 이를 통해 각 함수의 총 실행 시간을 구하기 위해서는 전처리 작업이 필요합니다.
전처리를 통해 필요한 데이터 추출하기
각 함수의 총 실행 시간을 구하기 위해서는 samples
와 timeDeltas
필드를 활용해 다음과 같은 과정으로 전처리 작업을 수행해야 합니다.
samples
와 timeDeltas
를 순회하면서 각 함수에 해당하는 노드 ID의 총 실행 시간을 계산합니다.이 전처리 과정은 각 함수가 전체 실행 시간에서 얼마나 많은 자원을 사용하는지 명확하게 보여주며, 어떤 함수가 성능 병목 지점인지 구체적으로 파악할 수 있게 해줍니다.
Trace Report는 다양한 이벤트와 호출 정보를 담고 있지만, 이를 바로 분석하는 것은 어렵습니다. 그래서 ChromeTraceAnalyzer
라는 클래스를 구현하여, Trace 데이터에서 각 함수별 실행 시간을 자동으로 계산하고, 성능 분석에 필요한 정보를 손쉽게 추출할 수 있도록 전처리를 수행하려 합니다.
ChromeTraceAnalyzer 클래스의 주요 기능
ChromeTraceAnalyzer
클래스는 Trace 데이터를 받아 각 함수별 실행 시간을 계산하고, 부모-자식 노드 관계를 구성하여 전체 실행 시간을 정확히 파악할 수 있도록 합니다. 이 클래스는 주로 다음과 같은 기능을 제공합니다.
CpuProfile Event
의 데이터를 정리class ChromeTraceAnalyzer {
nodes: any[];
constructor(trace) {
this.setConfig(trace);
}
// 특정 함수의 실행 시간을 반환 (단위: ms)
getDurationMs(name: string): number {
if (!this.nodes) throw new Error('nodes is not initialized');
const result = this.nodes.find((node) => node.callFrame.functionName === name);
return result ? result.duration / 1000 : 0; // 밀리초 단위로 변환
}
// Trace 데이터를 설정하고, 함수별 실행 시간을 계산
setConfig(trace: any) {
const { traceEvents } = trace;
// 'ProfileChunk' 이벤트 필터링
const profileChunks = traceEvents.filter((entry) => entry.name === 'ProfileChunk');
// CpuProfile의 노드와 샘플 데이터 가져오기
const nodes = profileChunks.map((entry) => entry.args.data.cpuProfile.nodes).flat();
const sampleTimes = {};
// 각 샘플의 실행 시간을 합산
profileChunks.forEach((chunk) => {
const { cpuProfile: { samples }, timeDeltas } = chunk.args.data;
samples.forEach((id, index) => {
const delta = timeDeltas[index];
sampleTimes[id] = (sampleTimes[id] || 0) + delta;
});
});
// 노드를 구성하고 자식 노드의 실행 시간을 부모 노드에 합산
this.nodes = nodes.map((node) => ({
id: node.id,
parent: node.parent,
callFrame: node.callFrame,
children: [],
duration: sampleTimes[node.id] || 0
}));
// 부모-자식 관계 설정 및 실행 시간 합산
const nodesMap = new Map();
this.nodes.forEach((node) => {
nodesMap.set(node.id, node);
});
this.nodes
.sort((a, b) => b.id - a.id)
.forEach((node) => {
if (!node.parent) return;
const parentNode = nodesMap.get(node.parent);
if (parentNode) {
parentNode.children.push(node);
parentNode.duration += node.duration;
}
});
}
}
ChromeTraceAnalyzer를 통해 함수별 실행 시간을 분석할 수 있게 되었지만, 단일 측정만으로 성능 데이터를 평가하기에는 부족합니다. 자바스크립트 실행 성능은 환경 변화에 따라 편차가 발생할 수 있기 때문에, 보다 신뢰도 높은 데이터를 얻으려면 여러 번 반복 측정하여 평균 실행 시간과 표준 편차를 계산하는 것이 중요합니다. 이를 통해 개별 측정 결과의 편차를 줄이고, 최적화의 효과를 더욱 정확히 평가할 수 있습니다.
반복 측정 코드 예시
아래 예제는 주요 함수(runApp, mount, draw, layout, paint)의 실행 시간을 10회 반복하여 측정하고, 각 함수의 평균 실행 시간을 출력하는 코드입니다.
test('Capture analyzed trace when diagram is rendered with multiple runs', async () => {
const COUNT = 10;
const duration = {
timestamp: Date.now(),
runApp: 0,
mount: 0,
draw: 0,
layout: 0,
paint: 0,
};
for (let i = 0; i < COUNT; i++) {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('http://localhost:4173/performance/diagram');
// Trace 시작
await browser.startTracing(page, {});
await page.evaluate(() => window.performance.mark('Perf:Started'));
await page.click('button');
await page.waitForSelector('svg');
await page.evaluate(() => window.performance.mark('Perf:Ended'));
await page.evaluate(() => window.performance.measure('overall', 'Perf:Started', 'Perf:Ended'));
// Trace 데이터 추출 및 분석
const trace = JSON.parse((await browser.stopTracing()).toString('utf8'));
const analyzer = new ChromeTraceAnalyzer(trace);
duration.runApp += analyzer.getDurationMs('runApp') / COUNT;
duration.mount += analyzer.getDurationMs('mount') / COUNT;
duration.draw += analyzer.getDurationMs('draw') / COUNT;
duration.layout += analyzer.getDurationMs('layout') / COUNT;
duration.paint += analyzer.getDurationMs('paint') / COUNT;
await browser.close();
}
console.log('**** Average Execution Time ****');
console.log(`runApp: ${duration.runApp}ms`);
console.log(`mount: ${duration.mount}ms`);
console.log(`draw: ${duration.draw}ms`);
console.log(`layout: ${duration.layout}ms`);
console.log(`paint: ${duration.paint}ms`);
console.log('********************************');
});
반복 측정을 통해 얻은 평균 실행 시간과 표준 편차 데이터를 바탕으로, Flitter를 활용해 성능 최적화 효과를 시각화했습니다. 이렇게 시각화된 차트를 통해 시간에 따른 성능 변화 패턴을 직관적으로 확인할 수 있었고, 최적화 전후의 성능 변화를 쉽게 파악할 수 있었습니다.
Flitter를 활용한 스택 막대 그래프 시각화
Flitter를 사용해 각 측정 날짜별로 주요 함수의 총 실행 시간을 누적한 스택 막대 그래프(Stacked Bar Chart)를 생성했습니다. 각 날짜마다 해당 날짜의 전체 실행 시간을 하나의 막대그래프로 표시하고, 이 막대그래프를 함수별 시간으로 구분해 성능이 개선된 부분을 시각적으로 확인할 수 있도록 했습니다.
그래프 구성
최적화 전후의 성능 변화 확인
이 스택 막대 그래프를 통해 시간에 따라 성능이 어떻게 변화했는지 쉽게 파악할 수 있었습니다.
runApp
함수의 실행 시간이 줄어든 후 안정적으로 유지되고, layout
과 paint
함수도 최적화 이후 일관된 성능을 보여주는 것을 시각적으로 확인할 수 있었습니다.이처럼 Flitter를 활용해 성능 데이터를 시각화하니, 성능 개선 효과를 팀원들과 공유할 때 직관적으로 전달할 수 있었고, 최적화가 필요한 부분을 쉽게 식별할 수 있었습니다.
이번 글에서는 Flitter의 성능 최적화를 진행하며, Playwright와 Chrome Trace를 사용해 성능 데이터를 체계적으로 수집하고 분석하는 과정을 공유했습니다. 수작업이 필요했던 성능 측정을 자동화하고, Flitter를 통해 데이터 시각화를 구현하면서 성능 최적화가 이루어졌는지를 수치와 그래프로 직관적으로 확인할 수 있었습니다. 이를 통해 불필요한 추측에 의존하지 않고, 데이터에 기반한 성능 개선이 가능하다는 점에서 큰 성과를 얻을 수 있었습니다.
현재 Flitter 라이브러리는 성능 최적화가 중요한 상황에서 효율적인 시각화 솔루션으로 활용될 수 있습니다. 관련 코드는 GitHub에서 확인하실 수 있으며, 도움이 되셨다면 GitHub에서 Star와 따봉을 부탁드립니다! 이 프로젝트에 관심을 보여주신다면 앞으로도 성능 개선과 다양한 기능을 지속적으로 개발하고 공유할 계획입니다.
GitHub: https://github.com/meursyphus/flitter/blob/latest/packages/test/tests/tracking-performance.test.ts
Docs: Flitter Docs
감사합니다!
현재 텍스트 블록을 추가적으로 보완하거나 이전 피드백을 반영해 보강하고자 하시는 부분이 있을까요? 아니면 지금까지의 내용을 바탕으로 이번 본문을 더욱 발전시켜 보기를 원하시나요? 추가적인 방향에 대해 알려주시면 다음 단계로 진행할 수 있도록 돕겠습니다.
본문 발전 가야째
사용자가 제공한 피드백에 따라, 성능 측정 자동화 시스템 구축과 "Easyrd" 서비스의 최적화 결과에 대한 부분을 구체적으로 확장합니다. 성능 측정은 객관적인 데이터를 통해 성능 개선 여부를 판단하는 것이 중요하며, Playwright와 Chrome DevTools Protocol을 활용하여 성능 데이터를 자동화로 수집하고 분석했습니다. 또한, 최적화 결과로 Easyrd의 드래그 및 초점 이동이 매우 부드럽게 개선된 점을 강조하여 구체적인 사례와 경험을 포함합니다. 이를 바탕으로 현재 텍스트 블록을 발전시켜 주세요.
[진행률: 3/4]
Playwright와 크롬 데브툴 프로토콜(Chrome DevTools Protocol)을 결합해 만든 자동화된 성능 측정 시스템은, 코드의 특정 부분이 얼마나 자주 실행되는지와 실제로 걸리는 시간을 한 번에 파악할 수 있도록 구성되었습니다. 기존에는 시간이 많이 소요되는 구간을 감각적으로 추정하거나, 로그를 수작업으로 분석해야 했지만, 자동화 도구를 도입하면서 효율이 크게 높아졌습니다. 개발 과정에서 쉽게 놓치기 쉬운 ‘작은 함수’나 특정 케이스가 반복 호출되는 부분도 데이터로 정확히 나타나므로, 사소해 보이던 병목 요소라도 빠짐없이 찾아낼 수 있습니다.
Playwright는 여러 브라우저를 자동으로 구동하고 제어할 수 있는 기능을 제공하므로, 테스트와 측정 환경을 일관성 있게 유지하기 좋습니다. 이때 크롬 데브툴 프로토콜을 통해 브라우저 내부 성능 지표와 실행 중인 스크립트 정보를 얻이면, ‘어느 부분이 CPU나 메모리를 많이 소모하는가?’를 세밀하게 분석할 수 있습니다. 이렇게 얻은 결과를 자동으로 수집하고 시각화하면, 이전 버전 대비 어느 함수가 얼마나 개선되었는지 객관적인 숫자로 비교 가능해집니다.
자동화 시스템을 통해 동일한 시나리오를 여러 차례 반복 측정하면, 단순히 한두 번의 체감 테스트로는 놓치기 쉬운 미세한 변화를 정확히 포착할 수 있습니다. 특히, 병목 지점을 수정했다면 그 전후 데이터를 각각 누적 관리함으로써, “최적화가 실제로 성능을 끌어올렸는가?”를 명확하게 보여주게 됩니다. 이런 방식은 프로젝트 규모가 커질수록 필수적이어서, 팀 단위 협업 중에 “어느 부분에 더 투자해야 하는가”를 쉽게 결정하는 근거로도 활용할 수 있습니다.
이러한 최적화를 거친 뒤로, 복잡한 SVG 다이어그램을 드래그하거나 확대·축소하는 과정에서 발생하던 프레임 드랍이 크게 줄었습니다. 마우스 포인터가 도형보다 훨씬 앞서가거나, 객체 이동 중간에 순간 멈춤 증상이 나타나는 등 이전에는 자주 보이던 현상이 이제는 거의 사라졌습니다. 이는 사용자 인터랙션 흐름이 한층 매끄러워졌다는 의미이며, 곧바로 실제 사용자 만족도 상승에도 이바지했습니다.
Flitter 프레임워크를 활용해 만든 다이어그램 서비스인 “Easyrd”에서는, 노드를 드래그할 때 마우스 움직임을 자연스럽게 따라가는 모습을 체감할 수 있습니다. 노드를 재빨리 움직여도 지연 없이 쫓아오기 때문에, 여러 개 노드를 동시에 이동하거나 복잡한 구조를 미세 조정할 때도 훨씬 안정적인 조작 환경을 제공합니다. 이런 시각적·기능적 개선은 엔진 내부에서 이루어진 최적화가 얼만큼 파급 효과가 큰지를 잘 보여주는 사례입니다.
프레임워크 차원의 이벤트 처리와 렌더링 파이프라인을 일부 손본 것만으로도, 이렇게 직관적인 사용자 경험 변화를 끌어냈다는 점이 주요한 성과입니다. 실무 현장에서 저수준 최적화를 시도하면 초기 단계에서 복잡성이 증가하기도 하지만, 제대로 적용만 된다면 프로젝트 전체 퍼포먼스가 달라질 수 있습니다. 특히, 상대적으로 간단해 보이는 특정 함수 호출 횟수를 줄이거나 이벤트 로직을 재설계하는 것만으로도 큰 차이를 만들어낼 수 있음을 확인했습니다. 레이턴시가 일시에 낮아지면서, 사용자 인터랙션이 자연스럽게 연결되고, 결과적으로 서비스에 대한 만족도까지 향상되는 선순환이 만들어지는 것입니다.
개요와 현재 문서조각을 비교해본 결과, 현재 문서조각은 결론 부분에 해당합니다. 이 결론은 로우 레벨 최적화의 중요성 확인, 이벤트 처리/렌더링 파이프라인 최적화의 효과, 그리고 코드 확인이라는 주제를 다루고 있습니다. 이에 대해 다음과 같은 질문을 드리고 싶습니다:
[첫 번째 부분에 대해 - 로우 레벨 최적화의 중요성 확인]
[두 번째 부분에 대해 - 이벤트 처리/렌더링 파이프라인 최적화의 효과]
[마지막 부분에 대해 - 코드 확인]
이 질문들에 대한 답변은 문서의 결론을 더욱 풍부하게 만드실 때 유용할 것 같습니다. 추가 자료나 설명이 있으면 함께 공유해 주세요!
일단 작은 부분 하나 개선해도 이게 중첩되어 적용되니 효과가 아주 크고요. 제 오픈소스로 만든 차트나 다이어그램 서비스 모두 같이 성능이 좋아져서요. 영향력이 있었죠. 그리고 무엇보다 제가 이런걸 경험하면서 성장했음을 느끼기도 하고요. 음 두번째 부분은 . 걍넘어가고.
마지막은 그냥 액션으로 내 오픈소스 링크 주소거든요. 이러면서 뭔가 여러분들의 최적화 경험은 어떤가요 댓글로 남겨주세요. 저는 이걸로 많이 배웠습니다. 기여도 부탁해요 이런 느낌으로 가면서 겸손하지만 살짝 위트있게 제 오픈소스 참여하라고 독려하는 느낌으로 가죠
본문에서 로우 레벨 최적화의 실무 경험과 영향력을 강조할 수 있도록 발전시키고자 합니다. 작은 부분 하나의 개선으로도 중첩 효과로 인해 큰 성과를 낼 수 있었으며, 개발자로서 이를 통해 성장과 성공을 경험한 내용을 추가합니다.
또한, 오픈소스 프로젝트의 성능 향상이 차트나 다이어그램 서비스 전반에 긍정적 영향을 미쳤음을 설명합니다.
마지막으로, 깃허브 레포지토리 링크를 공유하며, 다른 개발자들에게 경험을 공유하고 프로젝트에 기여해 줄 것을 겸손하면서도 위트 있게 부탁하는 내용을 포함합니다.
[진행률: 4/4]
프레임워크 전체 구조에서 이벤트 처리나 렌더링 파이프라인 같은 저수준 요소를 조금만 조정해도, 사용자 측면에서는 즉각적인 차이를 느낄 수 있다는 점이 인상적입니다. 화면 전환이 부드러워지고, 마우스나 터치 이벤트에 대한 반응 속도가 빨라지면서 인터랙션 과정에서 발생하는 미세한 지연도 눈에 띄게 줄어듭니다. 이런 체감 성능 향상은 곧 사용자 만족도로 이어져, 결과적으로 서비스와 제품에 대한 긍정적인 이미지를 쌓게 해줍니다.
단순히 코드를 미세하게 조정하거나 이벤트 핸들러를 다시 설계하는 것만으로도, 의외로 큰 효과를 기대할 수 있다는 점에서 많은 개발자에게 영감을 주는 사례가 될 수 있습니다. 특히, 프로젝트가 커져갈수록 조그만 최적화 포인트 하나가 누적되어 전반적인 퍼포먼스를 끌어올리는 흐름을 만들기도 합니다. 이때, 서로 다른 기능 모듈이 긴밀하게 상호작용하는 복잡한 시스템일수록 이러한 저수준 최적화가 더욱 가치가 있습니다.
물론, 실제 프로젝트에서 이런 최적화 작업은 생각처럼 단순하지 않습니다. 코드 구조를 개편하다 보면, 기존 기능과 충돌을 일으키거나 예상치 못한 버그가 발생하기도 합니다. 이벤트 핸들링 로직을 바꾸면 바로 반응 속도가 빨라지는 대신, 이전에 달아두었던 종속된 로직들이 정상적으로 작동하지 않는 예측 불가능한 상황을 만들 수 있습니다. 따라서 사전에 영향 범위를 충분히 파악하고, 단계적으로 변경 사항을 검증해 가면서 최적화를 적용하는 접근이 중요합니다.
성능 측정 도구나 자동화된 테스트가 함께 활용된다면, 각 단계에서 변경된 성능 지표를 객관적으로 비교할 수 있어 더욱 안정적입니다. 이러한 테스트 과정은 한번 세팅해두면 앞으로의 모든 업데이트 및 확장에 대해서도 유용한 기준 역할을 하게 됩니다.
혹시 여러분도 성능 최적화를 위해 독특한 기법을 적용하거나, 반대로 예상치 못한 어려움을 겪은 경험이 있으신가요? 어떤 시행착오라도 분명 다른 이들에게 참고가 될 수 있습니다. 아래 댓글 등에 직접 경험이나 노하우를 남겨주시면, 많은 사람들이 함께 배우고 개선할 수 있을 것입니다. ‘나만 겪는 문제’라 생각했던 이슈도, 실은 다른 개발자들에게도 공통된 고민일 수 있으니 부담 없이 이야기를 나누어 보셨으면 합니다.
마찬가지로, 위에서 소개한 방식 중 궁금하신 부분이나, 적용하면서 막혔던 점이 있다면 자유롭게 질문을 남겨주세요. 해당 이슈를 해결한 다른 분들이나 저를 비롯한 프로젝트 참여자 모두가 함께 답변을 드릴 수 있을 것입니다. 작은 정보 공유 하나가 커다란 문제 해결의 실마리가 될 수도 있다는 사실을 기억해 주시면 좋겠습니다.
마지막으로, 더 자세한 구현 방식이나 실제 코드를 직접 살펴보고 싶으신 분들은 깃허브 레포지토리를 참고해 보시길 권장드립니다.
→ GitHub: https://github.com/meursyphus/flitter
코드를 통해 프레임워크 내부 구조와 각종 최적화 아이디어가 어떻게 반영되었는지를 쉽게 파악할 수 있을 것입니다. 구체적인 함수 호출 흐름이나 이벤트 핸들러 등록 방식 등도 확인해 볼 수 있으니, 실제로 구현 시도를 하실 때 도움이 되리라 생각합니다. 거듭 말씀드리지만, 이 레포지토리는 적극적인 피드백과 기여를 언제나 환영하고 있습니다.
이상으로 “Flitter” 최적화 경험을 토대로 한 본문 초안을 마무리합니다. 읽으시면서 아쉬웠던 부분이나 추가로 다루었으면 하는 내용이 있다면 언제든 편하게 제안해 주세요. 전체적으로 주제가 잘 전달되었는지, 혹은 보충해야 할 항목이 있는지도 알려주시면 다음 글을 작성할 때 크게 도움이 될 것입니다. 모든 피드백은 프로젝트와 저에게 큰 자산이 되므로, 언제든 자유롭게 의견 주시기를 부탁드립니다.
[최종 글]
프론트엔드를 개발하다 보면, 화면이 ‘버벅’거리거나 심각하게 ‘렉’이 걸리는 문제가 자주 발생합니다. 특히 애플리케이션 규모가 커질수록 이런 문제는 더욱 빈번해지고, 어디에서 병목 현상이 일어나는지 찾기가 생각보다 쉽지 않습니다. 렌더링에 관여하는 요소가 많아질수록, 작은 코드 변경만으로도 전체 성능이 급격히 떨어지거나 의도치 않은 부분에서 부하가 발생하기도 합니다.
저 역시 “Flitter”라는 렌더링 엔진 프레임워크를 개발하다가 비슷한 난관에 부딪혔습니다. 이 프레임워크는 SVG나 Canvas를 선언적으로 조작해 Flutter와 유사한 인터페이스를 자바스크립트로 구현한 오픈소스 프로젝트인데, 복잡한 동작을 점차 추가할수록 렌더링이 급격히 느려졌습니다. 특히 다이어그램 서비스인 Easyrd를 구축하면서, 노드를 드래그하거나 초점을 옮길 때마다 프레임 드랍이 두드러져 사용자 경험이 심각하게 훼손되는 문제가 있었죠.
노드가 적을 때는 큰 문제가 없어 보였지만, 노드가 많아질수록 16ms 한 프레임을 훌쩍 넘는 함수 호출 시간이 쌓이기 시작했습니다. 화면에서 노드를 빠르게 이동시키거나 확대·축소를 반복할 경우 이벤트가 몰려들어 브라우저가 이를 처리하지 못해 ‘버벅’거리는 현상이 눈에 띄게 늘어났습니다. 사용자 입장에서는 노드가 툭툭 끊기거나 예상치 못한 곳으로 튀는 듯한 느낌을 받았고, 이는 곧 서비스 사용성을 심각하게 떨어뜨리는 요인으로 이어졌습니다.
Flitter를 처음 설계할 당시에는 단순히 SVG 요소들을 선언적으로 그려주기만 하면 큰 문제가 없을 것이라 생각했습니다. 그러나 “다이어그램의 첫 로딩 속도가 왜 이렇게 느리지?”라는 의문이 들면서부터 문제가 확대되었죠. 네트워크 지연 때문이 아니라, 정작 브라우저가 SVG를 그리는 과정 자체에서 시간을 많이 소비하고 있었습니다. 자세히 확인해 보니, SVG 자식 요소마다 개별적으로 이벤트 핸들러를 달아 DOM에 접근하는 빈도가 과도하게 높았던 것이 원인이었습니다.
성능 문제가 예상보다 심각하다 보니, 초기에 다이어그램 구조를 잘못된 방향으로 수정하기도 했습니다. DOM 접근을 줄이기는커녕 오히려 더 복잡한 로직을 추가하면서 렌더링 부담이 가중되는 악순환도 겪었죠. 이 과정을 통해 “성능을 측정하고, 병목 지점을 정확히 파악한 뒤에 개선을 시도해야 한다”는 사실을 절감했습니다. 많은 프론트엔드 개발 환경에서 이런 실수를 범하기 쉽고, 제대로 된 측정 없이 대책을 세우면 시간과 자원을 낭비하기도 쉽습니다.
결국 성능 최적화는 단순한 코드 리팩터링이 아니라, 정확한 측정과 문제 지점을 구체적으로 밝히는 작업에서부터 시작되었습니다. 코드 어디를 손봐야 좋은 결과가 나타날지 모르는 상태에서 무작정 수정만 반복하는 것은 피해야 했죠. 이 글에서는 저와 같은 상황에 놓인 분들, 특히 대규모 SVG 렌더링과 사용자 인터랙션 처리로 골머리를 앓는 프론트엔드 개발자분들께 “이런 수고로움이 있었고, 이렇게 해결했다”라는 이야기를 드리고 싶습니다. 현업에서 흔히 접하지 못하는 로우 레벨 최적화 과정과 그로 인해 얻은 인사이트가, 조금이라도 개발 방향을 잡는 데 도움이 되길 바랍니다.## SVG 이벤트 처리에서 불필요한 DOM 접근 줄이기
프로젝트 초기에 Flitter가 SVG만 지원하던 시절, 가장 먼저 마주한 문제는 모든 SVG 요소에 직접 이벤트 핸들러를 부착하는 방식이었습니다. 대표적으로 GestureDetector라는 위젯에 클릭, 드래그 등의 이벤트 핸들러를 달면, 내부적으로 실제 SVG 요소에 핸들러가 추가되는 구조였지요. 이렇게 자식 요소가 하나씩 늘어날 때마다 DOM 접근이 급격히 증가했고, 결국 수많은 요소에 핸들러가 연결되면서 화면 초기 로딩부터 인터랙션까지 버벅임이 두드러졌습니다.
노드가 여러 개만 있을 때는 문제가 크게 눈에 띄지 않았지만, 점차 다이어그램 노드나 UI 위젯이 복잡해지면서 트리 구조가 깊어졌습니다. 이때마다 SVG 요소마다 이벤트 핸들러를 붙이는 작업 자체가 병목 지점이 되었습니다. DOM 접근이 누적되어 브라우저가 한 화면을 완전히 구성하거나 변경하는 데 필요한 시간이 눈에 띄게 길어졌고, 사용자 입장에서는 “화면이 생각보다 늦게 뜬다”라는 인상을 받게 되었습니다.
이 문제를 해결하기 위해 도입된 것이 이벤트 위임(Event Delegation) 패턴입니다. 최상위 SVG 요소 하나에만 핸들러를 등록하고, 실제로는 이벤트 버블링을 통해 내부 이벤트를 캐치하도록 설계한 것이 핵심입니다. 자식 위젯에 연결된 핸들러는 실제로는 ‘위젯트리’ 내부에서만 관리되며, 최상위 SVG에서 발생하는 이벤트가 적절한 콜백 목록으로 분배되는 구조가 만들어졌습니다. 이렇게 루트 요소 하나에만 이벤트를 집중시키면 DOM 접근이 획기적으로 줄어들어, 노드가 아무리 많아져도 각 자식마다 직접 핸들러를 다는 방식에 비해 훨씬 가벼운 인터랙션 응답 속도를 얻을 수 있습니다.
이벤트 위임은 사실 React 등 다른 라이브러리에서도 널리 사용되는 기법입니다. React 역시 ‘가상 DOM’에 이벤트 핸들러를 관리하고, 실제 DOM에는 루트 하나에만 이벤트를 등록함으로써 중복 작업을 최소화합니다. Flitter 역시 이를 벤치마킹하여 “부모 SVG에만 이벤트 핸들러를 부착하는 정책”을 채택했습니다. 그 결과 대규모 다이어그램에서도 일관된 인터랙션 성능을 유지할 수 있었고, 요소 개수에 비례해서 늘어났던 DOM 접근이 현저히 줄어들었습니다.
이벤트 위임 패턴을 도입하면 성능 개선뿐 아니라 코드 유지보수에도 도움이 됩니다. 한두 곳에서만 핵심 핸들러를 관리하면 되므로, 새로운 이벤트 유형을 추가하거나 로직을 수정할 때도 개별 요소를 전부 수정할 필요가 없습니다. 또한 코드를 간결하게 정리할 수 있어, 협업 시 충돌이나 실수를 줄이는 데도 유리합니다.
인터랙션을 부드럽게 만들기 위해서는 애니메이션 프레임마다 필요한 업데이트를 수행해야 합니다. 이를 위해 자바스크립트 환경에서는 보통 requestAnimationFrame 함수를 사용합니다. 그런데 의외로 이 함수 호출 자체가 브라우저 내부의 특정 큐에 콜백을 넣어야 하기 때문에 일정 비용이 발생한다는 점이 관찰되었습니다. 크롬 DevTools로 분석해 보니, setTimeout 같은 매크로 태스크를 등록하는 행위와 마찬가지로 requestAnimationFrame 콜백을 스케줄링하는 과정에서도 30~60μs 정도의 시간이 소모된다는 사실이 드러났습니다. 작은 수치 같아 보이지만, 이를 여러 군데에서 남발하면 점차 프레임 드랍이 누적되는 결과로 이어졌습니다.
노드 드래그나 애니메이션이 복잡한 경우, 컴포넌트가 여러 번 업데이트 요청을 보낼 수 있습니다. 변화가 감지될 때마다 바로 requestAnimationFrame을 호출하면, 실제 실행되는 콜백의 개수도 기하급수적으로 늘어날 수 있습니다. 브라우저는 프레임마다 모든 콜백을 처리하려고 시도하므로, 불필요한 반복 호출이 성능을 소모하게 되는 것이죠. 이는 곧 브라우저 전체 리소스 사용량 증가로 이어지고, 대규모 애플리케이션에서는 화면이 프리징에 가까운 상태로 느려지기도 합니다.
이를 해결하기 위해 Flutter 엔진에서 쓰이던 ‘배치 처리’ 아이디어를 참고했습니다. Flutter는 변경 사항이 있을 때마다 매번 Paint 과정을 호출하는 대신, 지연시켜 모았다가 한꺼번에 처리해 버리는 구조를 사용합니다. Flitter에서는 이를 본떠 “한 프레임 내에서 딱 한 번만 업데이트를 스케줄링”하고, 그 사이 여러 위젯이 수정되어도 이후에 한꺼번에 렌더링 과정을 실행하도록 리팩터링했습니다. 예를 들어 한 프레임에 여러 요청이 들어오면 전부 ‘dirty 위젯’ 목록에 쌓아 두고, 다음 requestAnimationFrame 콜백이 실행될 때 몰아서 처리함으로써 콜백 호출 횟수를 최소화하는 식입니다.
이 로직을 좀 더 깔끔하게 관리하기 위해 Scheduler라는 싱글톤 클래스를 구현했습니다. 내부에 ‘persistenceCallbacks’와 ‘postFrameCallbacks’ 같은 콜백 목록을 두고, 한 번 프레임이 시작될 때 모아 둔 작업을 단번에 실행하는 구조입니다. 이렇게 하면 불필요한 requestAnimationFrame 호출이 줄어들어 매번 새로운 큐에 콜백을 등록하는 비용이 크게 절감됩니다. 실제로 코드 분석 결과, 동일한 애니메이션에서 호출되는 requestAnimationFrame 횟수가 크게 줄어들었고, 이를 통해 드래그나 화면 전환 시 끊김 현상이 현저히 줄어드는 효과를 얻었습니다.
배치 처리 방식 도입 후 가장 큰 장점은, 복잡한 그래프나 다이어그램 애니메이션에서도 속도가 안정적으로 유지된다는 점입니다. 이전에는 노드가 서로 겹치거나 급격하게 이동할 때 프레임 레이트가 급락하곤 했습니다. 그러나 변경 요청을 모아서 한꺼번에 렌더링하는 방식으로 개선한 뒤에는, 애니메이션 전반에 걸쳐 일정한 프레임을 확보하여 끊김 없는 경험을 제공할 수 있게 되었습니다. 이는 특히 마우스 드래그와 같은 사용자 인터랙션이 빈번하게 일어나는 시나리오에서 체감이 더 크게 다가왔습니다.
Playwright와 크롬 데브툴 프로토콜(Chrome DevTools Protocol)을 결합해 만든 자동화된 성능 측정 시스템은, 코드의 특정 부분이 얼마나 자주 실행되는지와 실제로 걸리는 시간을 한 번에 파악할 수 있도록 구성되었습니다. 기존에는 시간이 많이 소요되는 구간을 감각적으로 추정하거나, 로그를 수작업으로 분석해야 했지만, 자동화 도구를 도입하면서 효율이 크게 높아졌습니다. 개발 과정에서 쉽게 놓치기 쉬운 ‘작은 함수’나 특정 케이스가 반복 호출되는 부분도 데이터로 정확히 나타나므로, 사소해 보이던 병목 요소라도 빠짐없이 찾아낼 수 있습니다.
Playwright는 여러 브라우저를 자동으로 구동하고 제어할 수 있는 기능을 제공하므로, 테스트와 측정 환경을 일관성 있게 유지하기 좋습니다. 이때 크롬 데브툴 프로토콜을 통해 브라우저 내부 성능 지표와 실행 중인 스크립트 정보를 얻이면, ‘어느 부분이 CPU나 메모리를 많이 소모하는가?’를 세밀하게 분석할 수 있습니다. 이렇게 얻은 결과를 자동으로 수집하고 시각화하면, 이전 버전 대비 어느 함수가 얼마나 개선되었는지 객관적인 숫자로 비교 가능해집니다.
자동화 시스템을 통해 동일한 시나리오를 여러 차례 반복 측정하면, 단순히 한두 번의 체감 테스트로는 놓치기 쉬운 미세한 변화를 정확히 포착할 수 있습니다. 특히, 병목 지점을 수정했다면 그 전후 데이터를 각각 누적 관리함으로써, “최적화가 실제로 성능을 끌어올렸는가?”를 명확하게 보여주게 됩니다. 이런 방식은 프로젝트 규모가 커질수록 필수적이어서, 팀 단위 협업 중에 “어느 부분에 더 투자해야 하는가”를 쉽게 결정하는 근거로도 활용할 수 있습니다.
이러한 최적화를 거친 뒤로, 복잡한 SVG 다이어그램을 드래그하거나 확대·축소하는 과정에서 발생하던 프레임 드랍이 크게 줄었습니다. 마우스 포인터가 도형보다 훨씬 앞서가거나, 객체 이동 중간에 순간 멈춤 증상이 나타나는 등 이전에는 자주 보이던 현상이 이제는 거의 사라졌습니다. 이는 사용자 인터랙션 흐름이 한층 매끄러워졌다는 의미이며, 곧바로 실제 사용자 만족도 상승에도 이바지했습니다.
Flitter 프레임워크를 활용해 만든 다이어그램 서비스인 “Easyrd”에서는, 노드를 드래그할 때 마우스 움직임을 자연스럽게 따라가는 모습을 체감할 수 있습니다. 노드를 재빨리 움직여도 지연 없이 쫓아오기 때문에, 여러 개 노드를 동시에 이동하거나 복잡한 구조를 미세 조정할 때도 훨씬 안정적인 조작 환경을 제공합니다. 이런 시각적·기능적 개선은 엔진 내부에서 이루어진 최적화가 얼만큼 파급 효과가 큰지를 잘 보여주는 사례입니다.
프레임워크 차원의 이벤트 처리와 렌더링 파이프라인을 일부 손본 것만으로도, 이렇게 직관적인 사용자 경험 변화를 끌어냈다는 점이 주요한 성과입니다. 실무 현장에서 저수준 최적화를 시도하면 초기 단계에서 복잡성이 증가하기도 하지만, 제대로 적용만 된다면 프로젝트 전체 퍼포먼스가 달라질 수 있습니다. 특히, 상대적으로 간단해 보이는 특정 함수 호출 횟수를 줄이거나 이벤트 로직을 재설계하는 것만으로도 큰 차이를 만들어낼 수 있음을 확인했습니다. 레이턴시가 일시에 낮아지면서, 사용자 인터랙션이 자연스럽게 연결되고, 결과적으로 서비스에 대한 만족도까지 향상되는 선순환이 만들어지는 것입니다.## 프레임워크 차원의 성능 최적화가 가져다주는 실질적 이점
프레임워크 전체 구조에서 이벤트 처리나 렌더링 파이프라인 같은 저수준 요소를 조금만 조정해도, 사용자 측면에서는 즉각적인 차이를 느낄 수 있다는 점이 인상적입니다. 화면 전환이 부드러워지고, 마우스나 터치 이벤트에 대한 반응 속도가 빨라지면서 인터랙션 과정에서 발생하는 미세한 지연도 눈에 띄게 줄어듭니다. 이런 체감 성능 향상은 곧 사용자 만족도로 이어져, 결과적으로 서비스와 제품에 대한 긍정적인 이미지를 쌓게 해줍니다.
단순히 코드를 미세하게 조정하거나 이벤트 핸들러를 다시 설계하는 것만으로도, 의외로 큰 효과를 기대할 수 있다는 점에서 많은 개발자에게 영감을 주는 사례가 될 수 있습니다. 특히, 프로젝트가 커져갈수록 조그만 최적화 포인트 하나가 누적되어 전반적인 퍼포먼스를 끌어올리는 흐름을 만들기도 합니다. 이때, 서로 다른 기능 모듈이 긴밀하게 상호작용하는 복잡한 시스템일수록 이러한 저수준 최적화가 더욱 가치가 있습니다.
물론, 실제 프로젝트에서 이런 최적화 작업은 생각처럼 단순하지 않습니다. 코드 구조를 개편하다 보면, 기존 기능과 충돌을 일으키거나 예상치 못한 버그가 발생하기도 합니다. 이벤트 핸들링 로직을 바꾸면 바로 반응 속도가 빨라지는 대신, 이전에 달아두었던 종속된 로직들이 정상적으로 작동하지 않는 예측 불가능한 상황을 만들 수 있습니다. 따라서 사전에 영향 범위를 충분히 파악하고, 단계적으로 변경 사항을 검증해 가면서 최적화를 적용하는 접근이 중요합니다.
성능 측정 도구나 자동화된 테스트가 함께 활용된다면, 각 단계에서 변경된 성능 지표를 객관적으로 비교할 수 있어 더욱 안정적입니다. 이러한 테스트 과정은 한번 세팅해두면 앞으로의 모든 업데이트 및 확장에 대해서도 유용한 기준 역할을 하게 됩니다.
혹시 여러분도 성능 최적화를 위해 독특한 기법을 적용하거나, 반대로 예상치 못한 어려움을 겪은 경험이 있으신가요? 어떤 시행착오라도 분명 다른 이들에게 참고가 될 수 있습니다. 아래 댓글 등에 직접 경험이나 노하우를 남겨주시면, 많은 사람들이 함께 배우고 개선할 수 있을 것입니다. ‘나만 겪는 문제’라 생각했던 이슈도, 실은 다른 개발자들에게도 공통된 고민일 수 있으니 부담 없이 이야기를 나누어 보셨으면 합니다.
마찬가지로, 위에서 소개한 방식 중 궁금하신 부분이나, 적용하면서 막혔던 점이 있다면 자유롭게 질문을 남겨주세요. 해당 이슈를 해결한 다른 분들이나 저를 비롯한 프로젝트 참여자 모두가 함께 답변을 드릴 수 있을 것입니다. 작은 정보 공유 하나가 커다란 문제 해결의 실마리가 될 수도 있다는 사실을 기억해 주시면 좋겠습니다.
마지막으로, 더 자세한 구현 방식이나 실제 코드를 직접 살펴보고 싶으신 분들은 깃허브 레포지토리를 참고해 보시길 권장드립니다.
→ GitHub: https://github.com/meursyphus/flitter
코드를 통해 프레임워크 내부 구조와 각종 최적화 아이디어가 어떻게 반영되었는지를 쉽게 파악할 수 있을 것입니다. 구체적인 함수 호출 흐름이나 이벤트 핸들러 등록 방식 등도 확인해 볼 수 있으니, 실제로 구현 시도를 하실 때 도움이 되리라 생각합니다. 거듭 말씀드리지만, 이 레포지토리는 적극적인 피드백과 기여를 언제나 환영하고 있습니다.
이상으로 “Flitter” 최적화 경험을 토대로 한 본문 초안을 마무리합니다. 읽으시면서 아쉬웠던 부분이나 추가로 다루었으면 하는 내용이 있다면 언제든 편하게 제안해 주세요. 전체적으로 주제가 잘 전달되었는지, 혹은 보충해야 할 항목이 있는지도 알려주시면 다음 글을 작성할 때 크게 도움이 될 것입니다. 모든 피드백은 프로젝트와 저에게 큰 자산이 되므로, 언제든 자유롭게 의견 주시기를 부탁드립니다.
글 쓰기 퇴고 과정이 모두 완료되었습니다. 추가 피드백이 필요하신 경우 알려주시면 기꺼이 도와드리겠습니다. 다른 궁금한 점이나 도움이 필요한 부분이 있으시면 언제든지 말씀해 주세요.
최종 글에 어울리는 제목을 생성해주세요.
현재 진행 상황
▶️ 1. 아이디어 구체화 (1/2)
❌ 2. 아이디어 구체화 (2/2)
✅ 3. 개요 작성
✅ 4. 본문 작성
✅ 5. 퇴고 과정
▶️ 6. 제목 작성
다음 단계
아이디어 구체화 (1/2)
1번 제목: "작은 최적화, 큰 변화: Flitter 성능 향상 비결"
2번 제목: "성능 저하 문제 해결의 길: Flitter의 최적화 경험"
3번 제목: "SVG 드래그 버벅임, 어떻게 개선했나?"
어떤 제목이 가장 마음에 드시나요? 혹은 고치고 싶은 부분이 있나요?