← 포스트 목록
React성능최적화렌더링프론트엔드

"대용량 데이터 다뤄보셨어요?" — 프론트엔드가 부딪힌 건 데이터 크기가 아니라 렌더링이었다

2026-07-19 · 약 15분 소요

요약

문제
무한히 길어지는 리스트와 좌표 위에 겹쳐 쌓이는 지도 마커. 데이터가 늘어나는 만큼 DOM 노드도 그대로 늘어나 초기 렌더링 부하가 비례해 커짐
실행
그려지는 DOM 노드 수를 데이터의 크기에서 떼어낸다는 원칙 아래, 리스트는 가상 스크롤을 먼저 적용했다가 설명되지 않는 버그로 점진적 렌더링으로 전환. 지도는 클러스터링 적용 후 반경·배지 크기를 반복 튜닝
결과
리스트는 항목이 아무리 많아져도 초기 렌더링 부하가 더는 비례해 커지지 않고, 지도는 마커가 겹쳐 클릭도 식별도 안 되던 문제를 해결함. 다만 개선 폭을 숫자로 남기지 못한 건 한계

"대용량 데이터를 다뤄보신 적 있으신가요?"

면접 자리에서 이 질문을 몇 번이나 받았습니다. 그때마다 선뜻 "네"라고 답하기가 어려웠습니다. 제가 다뤄본 데이터는 수십만 건 단위의 배치 처리도, 실시간 스트리밍도 아니었기 때문입니다.

그런데 같은 질문을 반복해서 받다 보니, 면접관이 정말 궁금해하는 건 다뤄본 데이터의 '규모' 자체가 아니라는 걸 알게 됐습니다. 오히려 "그 데이터를 화면에 그리다가 어떤 문제를 만났는지, 그걸 어떻게 분석했고, 그래서 어떻게 풀었는지"를 묻고 있었습니다. 프론트엔드 입장에서 대용량 데이터가 진짜 문제가 되는 지점은 데이터의 크기 그 자체가 아니라, 그 데이터를 화면에 그리는 순간의 렌더링 부하였습니다.

이 글은 성격이 다른 두 화면에서 같은 종류의 문제를 만나 풀어간 이야기입니다. 하나는 세로로 무한히 길어지는 리스트였고, 다른 하나는 좌표 위에 겹쳐 쌓이는 지도 마커였습니다. 둘 다 "데이터가 늘어나는 만큼 DOM 노드가 그대로 늘어난다"는 같은 뿌리를 갖고 있었습니다. 비슷한 고민을 하고 계신 분들께 도움이 되길 바랍니다.

프론트엔드에서 대용량 데이터는 곧 렌더링 부하다

두 화면의 공통점은 이랬습니다. 화면에 그려야 할 항목이 무제한으로 늘어날 수 있는데, 그 항목 하나하나가 실제 DOM 노드였습니다. 리스트는 전체 목록을 .map()으로 순회해 보이지 않는 행까지 전부 노드로 만들었고, 지도는 데이터 수만큼 마커 노드를 얹었습니다. 데이터가 커지면 초기 렌더링 부하가 그대로 비례해서 커지는 구조였습니다.

여기에 각 화면마다 손쉬운 우회를 막는 제약이 하나씩 있었습니다.

그래서 세운 원칙은 하나였습니다. 화면에 실제로 그려지는 DOM 노드 수를, 데이터의 크기에서 떼어내자. 데이터가 아무리 커져도 실제로 그려지는 노드는 "지금 화면에 필요한 만큼"으로 유지하는 것이 목표였습니다.

이 원칙을 구현하는 방법은 크게 세 갈래였습니다.

방식핵심 아이디어성격
가상 스크롤 (windowing)뷰포트 근처 항목만 마운트하고 스크롤에 따라 노드를 교체노드 수가 항상 일정하지만, 좌표를 수동 계산해야 해 구조가 복잡
점진적 렌더링 (chunking)처음 N개만 그리고, 끝에 도달하면 개수를 늘림기존 렌더링 구조를 그대로 두고 얹을 수 있어 단순
클러스터링 (grouping)가까운 항목을 하나로 묶어 그룹으로 그림좌표 기반 데이터에서 "밀집도"에 비례해 노드 수를 줄임

세로 리스트에는 세 번째가 맞지 않고, 좌표가 없는 리스트에는 세 번째가 성립하지 않습니다. 결국 리스트에는 첫째·둘째를, 지도에는 셋째를 적용하게 됐는데, 그 선택 과정에서 겪은 게 이 글의 본론입니다.

첫 번째 사례: 원인을 설명할 수 없는 버그 앞에서 내린 선택

무한히 길어지는 리스트에는 노드 수가 항상 일정하다는 점에서 가상 스크롤이 이론적으로 가장 우아한 해법이었습니다. 그래서 먼저 가상 스크롤을 적용했습니다. 그런데 행 사이 간격이 비정상적으로 벌어지는 버그가 나타났습니다.

원인을 특정하려고 할 수 있는 검증은 다 해봤습니다.

그런데 DOM/CSS 값은 항상 정상인데 화면만 계속 어긋났습니다. 특정 환경에서만 나타나는 문제인지조차 끝내 가려내지 못한 채, 재현은 되지만 설명은 안 되는 상태로 남았습니다.

여기서 방향을 틀었습니다. 원인을 못 찾은 채로 좌표를 수동 계산하는 복잡한 구조를 계속 붙들고 있는 것 자체가 리스크라고 판단하고, 가상 스크롤을 걷어낸 뒤 훨씬 단순한 점진적 렌더링으로 전환했습니다. 처음 일정 개수만 그리고, 리스트 끝에 보이지 않는 감지용 요소를 두어 사용자가 끝에 닿으면 개수를 늘리는 방식입니다. 기존 렌더링 구조를 그대로 두고 그 위에 "몇 개까지 보여줄지"만 얹으면 되니, 좌표 계산에서 비롯된 버그가 끼어들 여지 자체가 없었습니다.

💡 두 방식의 트레이드오프

다만 렌더링 방식을 바꾸면서 지켜야 할 기존 기능이 하나 있었습니다. 특정 항목으로 바로 이동하는 버튼이었는데, 대상이 아직 렌더링되지 않은 범위 밖이면 이동할 노드 자체가 없어 그대로는 동작하지 않았습니다. 이건 렌더링 방식을 바꾸기 전에 미리 확인해둔 제약이라, 처음부터 설계에 반영할 수 있었습니다.

💡 렌더 안 된 항목으로 이동하는 2단계 로직 1단계. 대상이 아직 안 그려진 범위면, 먼저 그 지점까지 렌더링 개수를 확장하고 이동할 위치를 예약해둔다. 2단계. 개수가 늘어나 대상이 실제로 화면에 마운트되는 시점을 감지해, 그제서야 스크롤 이동을 실행한다.

두 번째 사례: "붙이는 순간 끝"이 아니었던 클러스터링

두 번째 화면은 실시간으로 갱신되는 좌표 위에 마커가 쌓이는 지도였습니다. 데이터가 몇십 개만 되어도 줌아웃하면 마커가 서로 겹쳐 클릭도 식별도 안 됐고, 위치가 소켓으로 계속 갱신되니 그 부하가 한 번으로 끝나지 않고 반복됐습니다.

여기서는 가까운 마커를 하나로 묶는 클러스터링을 적용했습니다. 지도에 실제로 그려지는 노드 수가 데이터 수가 아니라 "밀집도"에 비례하게 바뀌는 것이 핵심이었습니다. 일정 반경 안의 마커는 하나의 그룹으로 묶고, 충분히 확대하면 그룹을 풀어 개별 마커를 노출합니다.

문제는 이게 라이브러리를 붙이는 순간 끝나는 작업이 아니었다는 점입니다. 그룹을 얼마나 촘촘하게 묶을지, 그룹 배지를 얼마나 크게 그릴지 같은 값은 정답이 없어서, 실제 데이터 밀집도를 눈으로 확인하며 반복해서 조정해야 했습니다.

정량적으로 "렌더링이 몇 % 빨라졌다"고 말할 수 있는 수치는 남아있지 않습니다. 다만 이렇게 여러 라운드에 걸쳐 크기와 반경을 계속 손봤다는 것 자체가, 실제로 개선이 체감됐기에 다듬을 가치가 있었다는 방증에 가깝습니다.

흥미로웠던 건, 클러스터링을 얹으면서 오히려 손댈 필요가 없었던 부분입니다. 리스트에서 항목을 선택하면 해당 마커가 강조되는 기존 기능이 있었는데, 이 로직이 처음부터 리액트 상태를 거치지 않고 마커 인스턴스의 겉모습만 직접 바꾸는 방식으로 짜여 있었습니다. 덕분에 클러스터링 입장에서는 "묶여있는 마커 중 하나의 겉모습이 바뀐 것"일 뿐이라, 선택할 때마다 그룹 전체가 다시 계산되는 일이 없었습니다.

💡 상태로 바꿀지, 인스턴스를 직접 바꿀지

만약 이 선택 로직이 상태 기반이었다면, 클러스터링을 붙이는 시점에 "선택할 때마다 마커 배열 전체가 리렌더링되어 그룹도 매번 다시 계산되는" 문제를 새로 풀어야 했을 겁니다. 실시간 갱신 때마다 이전 그룹을 정리하는 코드도, 원래 있던 마커 정리 로직 옆에 한 줄 얹는 것으로 끝났습니다. 부가 레이어를 붙일 때 기존 정리 패턴을 그대로 따라간 게, 결과적으로 조용히 누적되는 사고를 처음부터 막아준 셈입니다.

종합 결과

두 화면 모두, 데이터가 늘어나도 실제로 그려지는 DOM 노드 수가 데이터 크기와 분리됐습니다.

정직하게 말하면, 두 작업 모두 "몇 ms에서 몇 ms로 줄었다" 같은 정량 지표는 남기지 못했습니다. 초기 렌더링 부하를 구조적으로 없앤 것과, 겹침·복잡성 같은 정성적 문제를 해소한 것이 실제 결과였습니다.

배운 점

남은 이슈: 사라지지 않은 두 가지 숙제

두 작업 모두 실사용 데이터 기반의 정량 측정과 자동 검증이 빠져 있습니다.

결국 이번 경험에서 제가 얻은 건, "대용량 데이터를 다룬 규모"가 아니라 데이터가 화면과 만나는 지점에서 무엇이 병목이 되고, 어떤 제약 아래서 그걸 어떻게 떼어냈는가라는 이야기였습니다. 여러분의 서비스도 데이터가 늘어날수록 화면이 무거워지고 있나요? 그렇다면 데이터를 줄이기 전에, 화면에 실제로 그려지는 노드 수부터 데이터 크기와 분리할 수 있는지 살펴보시길 권합니다.

한 가지 덧붙이자면, 개선안을 곧바로 적용하기 전에 데이터로 실측할 수 있는 판을 먼저 마련해두시길 권합니다. 이번 작업에서 제가 가장 아쉬웠던 부분이 바로 이 지점입니다. 렌더링 시간이나 노드 수를 측정할 수 있는 계측을 미리 붙여뒀다면, "체감상 빨라진 것 같다"에 기대지 않고 개선 전후를 숫자로 비교하고 튜닝 값도 감이 아니라 근거로 정할 수 있었을 겁니다. 최적화는 측정에서 시작해 측정으로 끝나야 한다는 걸, 측정이 빠진 채로 끝내고 나서야 절감했습니다.