Back to Code | Ep 04: 기계를 잊다 — Big O와 성능 세금
요약
AI가 작성한 깔끔한 코드가 실제 운영 환경에서 N+1 쿼리 문제로 인해 심각한 성능 저하를 일으킨 사례를 분석합니다. 추상화된 코드 뒤에 숨겨진 Big O 복잡도와 데이터베이스 성능 비용을 이해하는 엔지니어링의 중요성을 강조합니다.
핵심 포인트
- AI는 가독성은 높지만 O(N²) 및 N+1 쿼리 문제를 유발할 수 있음
- ORM 사용 시 실제 실행되는 SQL과 데이터베이스 부하를 반드시 확인해야 함
- 성능 최적화를 위해 EXPLAIN ANALYZE 등 기계 수준의 분석 도구 활용 필수
- 추상화된 API 뒤에 숨겨진 알고리즘 복잡도(Big O)를 파악하는 것이 인간의 역할
인공지능이 만들어낸 환상에서 깨어나 실제 엔지니어링으로 돌아가는 기업, LogiFlow의 15주간의 기술적 전투.
이야기
아키텍처(Architecture)는 수정되었습니다. 테스트는 이제 실제 의도를 측정했습니다. 시스템은 논리적으로 결함이 없었습니다. 하지만 그때 고객으로부터 P0(최우선 순위) 불만이 접수되었습니다: "월간 물류 보고서(Monthly Logistics Report) 화면을 로드하는 데 18초나 걸립니다!"
Emre는 AI가 작성한 코드를 신뢰한다고 말했습니다. 코드는 읽기 쉬웠고, map과 filter로 가득 차 있었습니다.
Defne는 데이터베이스에 EXPLAIN ANALYZE를 실행했고 진실을 목격했습니다.
기술적 부검: N+1 쿼리 지옥 (The N+1 Query Inferno)
// AI: O(N²) 및 N+1 지옥
const trucks = await prisma.truck.findMany();
...
코드는 우아해 보였습니다. 읽기 쉽고 깔끔했습니다. 하지만 내부적으로는 데이터베이스의 모든 트럭에 대해 배송(deliveries)을 확인하는 별도의 SELECT 쿼리를 실행하고 있었습니다. 트럭이 1,000대라면, 이는 1번이 아닌 1,001번의 데이터베이스 쿼리를 의미했습니다.
Defne는 EXPLAIN ANALYZE 출력 결과를 띄웠습니다:
"이것 좀 보세요. 1,000개의 순차적 쿼리가 실행되고 있고, 각각의 쿼리가 배송(deliveries) 테이블에 대해 전체 테이블 스캔(full table scan)을 수행하고 있어요. 데이터베이스가 말 그대로 질식하고 있습니다."
인간의 해결책: 데이터베이스 수준에서의 사고
// 단일 JOIN/Count 쿼리
const reports = await prisma.truck.findMany({
include: {
...
단 하나의 쿼리. 단 한 번의 왕복(round trip). 데이터베이스는 자신이 가장 잘하는 일, 즉 단 한 번의 통과(single pass)로 조인(join)과 카운트(count)를 수행합니다.
백 투 더 메탈 (Back to the Metal)
AI는 RAM과 CPU 캐시(Cache)의 물리적 한계를 느끼지 못합니다. 기계 수준에서 생각하는 것 — 즉, Back to the Metal — 은 인간의 몫입니다.
EXPLAIN ANALYZE결과- pprof, JProfiler, async-profiler
- 플레임 그래프 (Flame graphs)
- 캐시 적중률 (Cache hit ratios)
이것들은 선택 사항인 '있으면 좋은 것들'이 아닙니다. 이것들은 외과의사가 수술 중에 읽는 계측기입니다. AI는 _컴파일(compiles)_되는 코드를 작성하지만, 인간은 성능을 내는(performs) 코드를 작성합니다.
"AI는 아름다운 함수형 구문(functional syntax) 뒤에 숨겨진 O(N²) 알고리즘을 생성합니다. 보이지 않는 비용을 찾아내는 것은 인간의 역할입니다."
Big O는 은퇴하지 않았다
현대적인 프레임워크와 ORM (Object-Relational Mapping)은 우아한 API 뒤에 이차 복잡도 (Quadratic complexity)를 숨길 수 있습니다. 하지만 우리가 C 언어를 작성하는 것을 멈췄다고 해서 Big O 표기법 (Big O notation)이 은퇴한 것은 아닙니다.
| AI가 작성하는 것 |
|---|
trucks.map(t => getDeliveries(t)) |
| 실제로 일어나는 일 |
| --- |
| N+1 쿼리, O(N²) I/O |
| ... |
| 추상화는 아름답습니다. 하지만 성능 비용은 실재합니다. |
에피소드 4의 교훈
1. N+1 문제 (N+1 Problem): AI는 각 엔티티(Entity)마다 별도의 쿼리를 실행하는 루프를 작성하는 경향이 있습니다. 항상 ORM이 생성하는 SQL을 확인하십시오.
2. 잊혀지지 않은 Big O: 현대적인 프레임워크와 ORM은 깔끔한 구문 뒤에 O(N²) 알고리즘을 숨길 수 있습니다. 신뢰하기 전에 프로파일링 (Profile) 하십시오.
3. CPU 캐시와 메모리 (CPU Cache and Memory): AI는 캐시 지역성 (Cache locality), 거짓 공유 (False sharing), 또는 가비지 컬렉션 (Garbage collection) 압박을 고려하지 않습니다. 밀리초 (Milliseconds) 단위가 중요할 때는 인간이 프로파일러 (Profiler)를 잡아야 합니다.
4. EXPLAIN ANALYZE는 당신의 X-레이입니다: 실행 계획 (Execution plan)을 확인하지 않고 쿼리 성능을 절대 신뢰하지 마십시오. 데이터베이스는 당신이 묻는다면 진실을 말해줍니다.
이것은 "Back to Code" 시리즈의 에피소드 4입니다. 다음 편: 에피소드 5 — 컨텍스트의 복수: DDD와 AI가 파악할 수 없는 "컨텍스트".
Series: back.to.code · 2026
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기