차트에 데이터가 많아지면 결국 부딪히는 게 성능입니다. 그 처리를 어디서, 어떻게 하느냐를 두고 예전에 전 회사에서 같이 일했던 형님과 얘기한 적이 있는데, 접근이 정반대라 재미있었습니다. 지금 오픈소스로 유지 중인 @keneth80/k-chart에 다운샘플링을 넣게 된 계기이기도 합니다.
같이 일하던 형님이 대용량 데이터 처리를 어떻게 했는지 얘기해줬습니다. 그 형님은 프론트 기술을 잘 몰라서, 서버에서 데이터를 이미지로 그려서 내려주는 방식을 썼습니다. 몇백만 건이든 서버가 그림 한 장으로 만들어 보내니, 브라우저는 이미지만 띄우면 되죠.
성능도 좋고 똑똑한 방법이긴 한데, 한계가 분명했습니다.
결국 사용자가 뭔가 할 때마다 서버를 왔다 갔다 해야 합니다. 이미지라 인터랙션이 안 되니까요.
저는 대용량을 프론트에서 전부 처리하고 싶었습니다. 대용량 처리를 프론트에서 전부 하는 것. 그러면 줌이든 포인트 조회든 서버 왕복 없이 브라우저 안에서 바로 됩니다. 문제는, 그 많은 데이터를 프론트가 어떻게 감당하느냐였습니다.
형님이 이미지 차트에 적용한 방법중에 데이터를 축소하는 방법을 썼다는 얘기가 떠올랐습니다.
그런데 전 이 말만 듣고는 어떻게 줄이지? 라는 생각이 들었습니다.
어떻게 줄여야 비슷하게 보일까?
화면 폭은 정해져 있습니다. 몇백만 개 점을 다 그려도, 화면 픽셀 수보다 점이 많으면 어차피 겹쳐서 안 보입니다. 그럼 눈에 보이는 만큼만 그리고, 나머지는 안 그려도 되지 않을까.
그래서 실제로 대충 데이터를 10분의 1 정도로 줄여서 그려봤더니, 원본과 아주 비슷하게 보였습니다. 눈으로는 차이를 거의 못 느꼈고, 대신 그리는 양이 줄었으니 속도는 빨라졌습니다. 프론트에서 대용량을 감당할 수 있게 된 거죠.
문제는 "어떻게 줄이냐"였습니다.
처음엔 아주 단순하게 지시했습니다. "10개마다 하나씩 뽑아라." 데이터가 10분의 1로 주니 당연히 빨라졌고, 얼핏 보면 비슷했습니다.
그런데 테스트하다 보니 문제가 나왔습니다. 순간적인 Peak(뾰족하게 튀는 값)를 놓치는 겁니다. 10개마다 하나씩 뽑으니, 그 사이에 있던 급등·급락 지점이 우연히 안 뽑히면 그래프에서 사라집니다. 데이터는 줄었는데 중요한 순간이 뭉개진 거죠. 시계열 데이터에서 그 피크가 가장 중요한 데이터이고 제일 먼저 봐야 할 지점일 때가 많은데 말입니다.
그래서 LTTB(Largest Triangle Three Buckets) 알고리즘을 적용했습니다. 이건 그냥 일정 간격으로 뽑는 게 아니라, 시각적으로 중요한 점(모양을 결정하는 점)을 골라서 남깁니다. 그래서 데이터를 줄여도 그래프의 모양이 크게 안 망가지고, 뾰족한 피크도 살아남습니다. 이 LTTB는 AI가 저에게 제안해준 내용입니다. 이런 점은 아주 똑똑해요.
그러나 LTTB로 바꾸니 Peak 문제는 풀렸는데, 이번엔 다른 게 걸렸습니다.
LTTB를 적용하면서 이런저런 함수를 붙이다 보니, 줄이는 계산 자체가 무거워져서 퍼포먼스가 떨어졌습니다. 데이터를 줄여서 그리기를 빠르게 하려던 건데, 줄이는 과정이 느려지니 배보다 배꼽이 커진 셈이죠. 왜 알게 되었냐면 솔직히 사람이 체감하기엔 확 느겨지지 않아요. 제가 kchart를 다른 차트들과 성능측정을 하는데 여기서 알게된겁니다.
특히 데이터가 대규모로 커지면 다운샘플링 과정 자체가 병목이 됩니다. 그리기 전에 데이터를 줄이는 그 단계에서 시간을 다 쓰는 거죠. 그래서 LTTB 구현 자체를 최적화했습니다. 숫자형 데이터에 대해서는 numeric field 전용 fast path를 따로 만들었습니다.
핵심은 accessor 함수 호출을 없앤 겁니다. 보통 LTTB는 각 점에서 x·y 값을 꺼낼 때 point => point.value 같은 접근 함수를 부르는데, 데이터가 몇백만 개면 그 함수 호출이 몇백만 번 일어납니다. 숫자 필드일 때는 함수를 거치지 않고 point[xField]로 바로 읽게 했더니, 그 오버헤드가 사라졌습니다. 줄이는 것도 결국 성능이 걸리는 지점이라, 거기까지 파고들어야 했습니다.
결국 핵심은 이거였습니다.
그리고 원본 데이터는 그대로 둡니다. 줄인 건 어디까지나 그리기용이고, 상세 조회나 다른 계산엔 원본을 씁니다.
서버에서 이미지로 그리는 방식과 프론트에서 다 처리하는 방식, 둘 다 맞는 상황이 있습니다. 인터랙션이 거의 없고 데이터가 극단적으로 크면 서버 이미지가 나을 수도 있죠. 다만 줌이나 포인트 조회 같은 인터랙션이 중요하다면, 프론트에서 처리하고 데이터를 똑똑하게 줄이는 쪽이 사용자 경험이 훨씬 낫습니다. 다운샘플링 적용하는 아이디어와 차트의 전체적인 설계를 제가 담당은 했지만, LTTB 적용하고 그 병목까지 해결할 수 있었던 것은 AI의 도움이 있었습니다. 솔직히 제가 테스트 했으면 성능 상관없이 넘어갔을거예요. 이제는 AI를 잘 활용해야하는 시대가 온것은 분명한 것 같습니다.
회사에서 데이터 시각화를 담당하며 만들기 시작해 지금은 오픈소스로 유지 중인 @keneth80/k-chart 이야기입니다. 요즘은 관심사 커뮤니티(galaxyconstellate.com)를 만들며 이런 개발 기록을 남기고 있습니다.