IT관련/Nodejs

Nodejs에서 sql += "" 방식과 배열(push & join) 방식을 V8 엔진의 메모리 할당 및 GC 관점에서 비교

파란하늘999 2026. 3. 20. 11:17

Nodejs에서 sql += "" 방식과 배열(push & join) 방식을 V8 엔진의 메모리 할당 및 GC 관점에서 비교

 

1. 메모리 할당 구조 비교

sql += "" (문자열 결합)

  • 불변성(Immutability): 매 연산마다 새로운 문자열 객체가 생성됩니다.
  • ConsString 트리: V8은 내부적으로 ConsString이라는 이진 트리 구조를 만듭니다. +=를 할 때마다 트리의 깊이가 깊어집니다.
  • 복사 비용: 나중에 이 문자열을 DB로 보낼 때(Flattening), 깊게 쌓인 트리를 순회하며 하나의 커다란 연속된 메모리 공간으로 전체 복사가 일어납니다.

const parts = [] (배열 방식)

  • 가변성(Mutability): 배열은 요소가 추가되어도 배열 객체 자체의 메모리 주소가 바뀌지 않습니다.
  • 참조 저장: parts.push("string")은 문자열의 실제 데이터를 복사하는 게 아니라, 해당 문자열이 위치한 **메모리 주소(포인터)**만 배열에 차곡차곡 쌓는 방식입니다.
  • 연속적 할당: 배열은 내부에 요소가 늘어날 것을 대비해 메모리 공간을 미리 넉넉하게 예약(Over-allocation)하므로 재할당 횟수가 훨씬 적습니다.

2. GC(가비지 컬렉터) 관점의 차이

비교 항목 sql += "" (문자열 더하기) parts.join(" ") (배열 조인)
중간 객체 생성 매우 많음. 연산 횟수만큼 중간 단계의 문자열/ConsString 노드가 생성됨. 거의 없음. 기존 문자열들의 포인터만 배열에 담음.
Scavenge GC 부하 New Space에 단기 생존 객체가 범람하여 Minor GC가 빈번해짐. 배열 객체 하나만 관리하면 되므로 GC 부하가 현저히 낮음.
메모리 파편화 크기가 제각각인 문자열 객체들이 생성/삭제되며 파편화 유발. 고정된 배열 크기 내에서 관리되므로 메모리 배치가 효율적임.
최종 메모리 복사 트리 구조를 풀면서 거대한 단일 메모리를 새로 할당함. join() 시점에 딱 한 번만 최종 문자열을 위한 메모리를 할당함.

3. 코드 실행 흐름 시각화

문자열 더하기 방식 (sql +=)

  1. "SELECT" 생성
  2. "SELECT" + " * " = 새로운 객체 A 생성 (기존 "SELECT"는 쓰레기)
  3. 객체 A + " FROM users" = 새로운 객체 B 생성 (객체 A는 쓰레기)
  4. ... 무한 반복 (중간에 생성된 A, B 등은 모두 GC가 치워야 할 짐)

배열 방식 (push & join)

  1. [] 배열 생성
  2. "SELECT", " * ", " FROM users" 주소값을 배열에 기록 (데이터 복사 없음)
  3. join() 호출 시점에 딱 한 번: [주소1, 주소2, 주소3]을 순회하며 최종 결과물 하나만 생성.

4. 실무적인 결론

Node.js 환경에서 동적으로 아주 긴 쿼리를 생성해야 한다면 배열에 push 하고 마지막에 join(' ') 하는 것이 훨씬 "GC 친화적"입니다.

  • 성능: 수천 번 이상의 결합이 일어날 경우 배열 방식이 압도적으로 빠릅니다.
  • 예측 가능성: GC가 언제 터질지 모르는 불안 요소(Stop-the-world)를 줄여줍니다.
반응형