JOIN FETCH가 N+1보다 느릴 수 있다: 재현 가능한 Doctrine 벤치마크 (+ 엔티티당 1행 JSON 집계)

발행: (2025년 12월 18일 오후 02:54 GMT+9)
3 min read
원문: Dev.to

Source: Dev.to

실제 문제: 다중 OneToMany JOIN이 행을 폭발시킴

여러 OneToMany 관계를 JOIN 하면 SQL 결과 집합이 곱해집니다(카르테시안 곱).

예시

  • 이미지 3개 × 리뷰 5개 = 제품당 15행
  • 제품 2000개 → ~30,000행이 DB에서 전송되고 처리됨

Doctrine의 identity map은 PHP에서 중복을 숨겨 주지만, DB는 여전히 곱해진 행 집합을 반환합니다. 쿼리 수가 적어도 성능에 큰 타격을 줄 수 있습니다.

링크 (패키지 + 재현 가능한 벤치마크)

재현 가능한 벤치마크 (Symfony + PostgreSQL)

필요한 고정 데이터와 CLI 명령을 포함한 독립 실행형 Symfony 벤치마크 프로젝트를 공개했습니다. 누구나 로컬에서 결과를 재현할 수 있습니다:

https://github.com/rgalstyan/doctrine-aggregated-queries-benchmark

샘플 실행 (limit = 2000)

참고: 시간은 머신/DB/캐시 상태에 따라 달라집니다. 중요한 것은 추세입니다.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PRODUCTS PERFORMANCE TEST
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Dataset size: 2000 products

TRADITIONAL DOCTRINE (2000 records)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Time:    327.73ms
Memory:  44559.6 KB (43.52 MB)
Queries: 43
Result:  2000 Product entities

DOCTRINE JOIN FETCH (entities) (2000 records)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Time:    816.37ms
Memory:  39795.9 KB (38.86 MB)
Queries: 2
DB rows: ~30000 (Cartesian product in SQL)
Result:  2000 Product entities

SIMPLE JOINS (naive) (2000 records)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Time:    103.14ms
Memory:  11659.4 KB (11.39 MB)
Queries: 1
DB rows: 30000 (Cartesian product!)
Result:  2000 products (after deduplication)

AGGREGATED QUERIES (2000 records)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Time:    66.18ms
Memory:  13989.1 KB (13.66 MB)
Queries: 1
DB rows: 2000
Result:  2000 products (arrays)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
COMPARISON
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

비교 표

접근 방식반환시간 (ms)메모리 (KB)쿼리 수DB 행 수제품 수
전통적인 Doctrineentities327.7344559.643N/A2000
Doctrine JOIN fetchentities816.3739795.92300002000
단순 JOIN (naive)arrays103.1411659.41300002000
JSON 집계arrays66.1813989.1120002000
Back to Blog

관련 글

더 보기 »