IT관련/Nodejs

🛑 Node.js에서 sql += "" 방식이 GC에 치명적인 이유

파란하늘999 2026. 3. 20. 10:53

Node.js(V8 엔진)에서 문자열은 불변(Immutable) 데이터 타입입니다.

 

한 번 생성된 문자열은 메모리상에서 수정될 수 없으며, 수정을 시도할 때마다 새로운 메모리 공간이 할당됩니다.

1. 끊임없는 메모리 할당 (Allocation Overload)

+= 연산자를 사용할 때마다 V8 엔진 내부에서는 다음과 같은 일이 벌어집니다.

  1. 기존 문자열을 복사합니다.
  2. 새로 추가될 문자열을 붙입니다.
  3. 새로운 메모리 주소에 결과물을 저장합니다.
  4. 기존의 문자열은 더 이상 참조되지 않는 **쓰레기(Garbage)**가 되어 Heap 메모리에 남습니다.

2. GC의 "Stop-the-World" 부하

V8 엔진의 GC는 메모리가 부족해지면 불필요한 객체를 정리하기 위해 실행됩니다.

  • Minor GC (Scavenge): 젊은 객체들이 머무는 New Space에서 빈번하게 일어납니다. +=로 생기는 짧은 수명의 문자열들은 여기서 처리됩니다.
  • Major GC (Mark-Sweep-Compact): 문자열이 길어지거나 루프 내에서 계속 생성되면 Old Space로 넘어가게 되는데, 이때 Major GC가 발생하면 **애플리케이션의 실행이 일시적으로 중단(Stop-the-world)**됩니다.

쿼리가 수백 줄로 길어지거나 루프 안에서 SQL을 생성하면, GC가 처리해야 할 "죽은 문자열"이 기하급수적으로 늘어나 CPU 점유율이 치솟고 서비스 응답 속도가 느려집니다.


🏗️ 대안: 더 효율적인 방법들

메모리 단편화와 GC 부하를 줄이기 위해 권장되는 방식입니다.

✅ 1. 배열과 join() 사용

가장 간단하면서도 효과적인 방법입니다. 배열에 담아두었다가 마지막에 한 번만 결합합니다.

const queryParts = [];
queryParts.push("SELECT * FROM users");
queryParts.push("WHERE status = 'active'");
// ... 조건 추가
const sql = queryParts.join(" "); // 마지막에 딱 한 번만 새로운 문자열 생성

✅ 2. 템플릿 리터럴 (Template Literals)

코드 가독성을 높여줄 뿐만 아니라, 중간 단계의 불필요한 문자열 생성을 줄여줍니다.

const sql = `
  SELECT *
  FROM orders
  WHERE id = ${orderId}
    AND date > '${startDate}'
`;

✅ 3. Query Builder (Knex, Prisma 등)

가장 권장되는 방식입니다. 내부적으로 최적화된 방식으로 문자열을 조합하며, 무엇보다 SQL Injection 보안 위협을 원천 차단합니다.


💡 요약: 한 줄 정리

"+= 연산은 기존 메모리를 수정하는 게 아니라, 매번 새로운 메모리를 사고 버리는 과정입니다.
이 '쓰레기'들을 치우느라 바빠진 GC가 당신의 서버를 멈추게 할 수 있습니다!"

반응형