무심코 쓴 리스트(List) 자료구조, 대용량 트래픽 앞에선 당신의 서버를 죽이는 흉기가 됩니다.

무심코 쓴 리스트(List) 자료구조, 대용량 트래픽 앞에선 당신의 서버를 죽이는 흉기가 됩니다.

Poooling·2026년 1월 7일·4

리스트(List) 자료구조를 사용한 계층 구조 구현이 대규모 트래픽에서 왜 위험한지, 그리고 First Child-Next Sibling 패턴을 통한 최적화 방법을 설명합니다.

스타트업에서 '야생형 개발자'로 구르던 시절, 저에게 자료구조란 그저 기능 구현을 위한 도구에 불과했습니다. 계층 구조가 필요한 기능, 예를 들어 댓글 시스템이나 상품 카테고리, 조직도 같은 것을 구현할 때면 아무런 고민 없이 편한 길을 택하곤 했습니다. 노드 클래스 안에 자식 노드들을 담을 ListVector를 선언하는 방식이었습니다. 코드로 치면 class Node { List<Node> children; } 같은 형태가 되겠군요. 이 방식은 직관적이고 구현이 빠릅니다. "세 번째 자식의 두 번째 자식을 가져와라" 같은 인덱스 기반 접근도 쉬워서 비즈니스 로직을 짜는 데 전혀 무리가 없었기 때문입니다. 하지만 최근 네임드 기업으로 이직해 대규모 트래픽과 레거시 시스템을 마주하며, 이 안일한 습관이 얼마나 무서운 기술 부채였는지를 뼈저리게 느꼈습니다.

우리가 흔히 사용하는 '자식 노드 리스트' 방식은 소규모 서비스에서는 문제가 되지 않지만, 규모가 커지면 치명적인 성능 저하의 원인이 됩니다. 가장 큰 문제는 메모리 할당과 접근 패턴에 있습니다. ListVector 같은 동적 배열은 그 자체로 힙 메모리 할당을 요구합니다. 노드가 수만, 수십만 개로 늘어나는 상황을 상상해 보십시오. 각 노드마다 자식 리스트를 위한 추가적인 메모리 할당이 일어나고, 이는 가비지 컬렉터(GC)에 엄청난 부하를 줍니다. 게다가 리스트 내부의 포인터를 따라가야 하므로 메모리가 파편화되기 쉽고, 이는 CPU 캐시 미스(Cache Miss)를 유발하여 전체적인 처리 속도를 떨어뜨립니다. 스타트업 시절 서버가 이유 없이 헐떡거렸던 건, 단순히 사용자가 늘어서가 아니라 제가 짠 비효율적인 자료구조가 리소스를 낭비하고 있었기 때문일지도 모른다는 생각에 등골이 서늘해졌습니다.

여기서 우리는 조금 더 원론적이고 효율적인 접근법을 고민해봐야 합니다. 바로 '첫 번째 자식(First Child)'과 '다음 형제(Next Sibling)' 포인터만을 사용하는 방식입니다. 노드에 자식 리스트 전체를 담는 대신, 딱 두 개의 포인터만 저장하는 것입니다. 하나는 자신의 첫 번째 자식을 가리키고, 다른 하나는 자신의 바로 다음 형제를 가리키게 합니다. 이렇게 하면 별도의 리스트 객체를 생성하거나 동적으로 크기를 조절할 필요가 전혀 없어집니다. 메모리 할당 횟수가 획기적으로 줄어들고, 데이터 구조가 단순해져 intrusive(침입형) 자료구조로 설계하기도 용이합니다. 실제로 제가 3D 씬 그래프나 복잡한 파서(Parser) 트리를 최적화할 때 이 방식을 적용해 메모리 사용량을 절반 이하로 줄인 경험이 있습니다.

물론 이 방식에도 단점은 존재합니다. "N번째 자식"을 찾으려면 첫째부터 형제들을 타고 넘어가며 순차적으로 탐색해야 하므로, 인덱스 접근이 빈번한 로직에는 적합하지 않습니다. 하지만 곰곰이 생각해보십시오. 우리가 다루는 대부분의 계층형 데이터 비즈니스 로직은 특정 인덱스를 콕 집어 조회하기보다, 트리 전체를 순회하거나 특정 가지(Branch)를 타고 내려가는 작업이 주를 이룹니다. 즉, 우리가 잃는 것은 '잘 쓰지 않는 편의성'이고, 얻는 것은 '확실한 성능과 메모리 효율'입니다. 단순히 코드를 짜는 것이 아니라, 시스템의 리소스가 어떻게 흘러가는지를 이해하는 것이 시니어 개발자로 가는 길목임을 깨닫게 되는 지점입니다.

요즘처럼 Cursor나 Claude 같은 AI 도구가 코드를 대신 짜주는 시대에, 이러한 저수준(Low-level)의 최적화 지식은 더욱 중요해집니다. AI에게 "트리 구조 짜줘"라고 시키면, 십중팔구 가장 일반적인 List 방식을 던져줍니다. 그것이 학습 데이터상 가장 보편적이기 때문입니다. 하지만 개발자가 "메모리 할당을 최소화해야 하니 First Child-Next Sibling 패턴으로 변경해 줘"라고 구체적으로 지시할 수 있다면, 결과물의 퀄리티와 서비스의 안정성은 차원이 달라집니다. 기술의 편의성에 매몰되지 않고, 그 이면의 비용을 계산할 줄 아는 안목. 그것이 바로 제가 8년이라는 시간을 버티며 얻은, 그리고 후배님들과 나누고 싶은 가장 큰 자산입니다.

Poooling
PooolingAuthor

Poooling님의 다른 글

댓글 0

첫 번째 댓글을 남겨보세요!