Published on
👁️

JPA N+1 문제 해결에 따른 성능 변화 분석 - 5. 테스트 결과 비교

Authors
  • avatar
    Name
    River
    Twitter

목차 페이지로 이동

데이터 규모에 따른 JPA N+1 문제 진단과 해결 전략 비교
(Fetch Join, Entity Graph, Batch Fetching)

5. 테스트 결과 비교
5.1. 테스트 결과 데이터 요약

(1)테스트 케이스 및 버전 설명

데이터 규모

  • 소규모 데이터 : 조회 엔티티 200개 + 연관엔티티 100,000개 (조회엔티티당 500개)
  • 대규모 데이터 : 조회 엔티티 1000개 + 연관엔티티 1,000,000개 (조회엔티티당 1000개)

테스트 케이스

  • Case 1 : 소규모 데이터 환경
  • Case 2 : 대규모 데이터 환경
  • Case 3 : 대규모 데이터 + Batch Fetching 환경

API 버전

  • Pure : 해결 전략 없는 상태 (N+1 문제 존재)
  • Optimized : Fetch Join 적용
  • Graph : Entity Graph 적용
  • Original : 프로젝트 끝날 때 상태 (일부 Fetch Join 적용됨)

케이스별 쿼리 수

  • Case 1 쿼리 수

    • original(606개) > pure(105개) > optimized(4개) = graph(4개)
  • Case 2 쿼리 수

    • original(1506개) > pure(505개) > optimized(4개) = graph(4개)
  • Case 3 쿼리 수

    • original(1012개) > pure(= batch fetching)(10개) > optimized(4개) = graph(4개)

(2)Case 1 평균 데이터

GC 성능 지표

항목originalpureoptimizedgraph
GC 발생 횟수150.33157.33186.67188.67
평균 GC 시간(ms)6.435.794.254.35
최대 GC 시간(ms)13.1015.266.9511.71
총 회수 메모리량(MB)39502.6742272.3350428.0050996.33
평균 메모리 회수량(MB)262.77268.61270.15270.30

API 성능 지표

항목originalpureoptimizedgraph
API 요청 수1302.004655.336803.336845.33
평균 API 응답 시간(ms)3332.53427.8449.5042.38
최소 API 응답 시간(ms)388.0070.3328.0024.33
최대 API 응답 시간(ms)9415.331451.00140.00112.00
API TPS9.0533.2648.6748.95
API P90 응답 시간(ms)6460.93799.3366.0056.67
API P95 응답 시간(ms)7048.97861.6772.0061.00
API P99 응답 시간(ms)7813.91978.9686.6771.00

GC-API 상관관계 지표

항목originalpureoptimizedgraph
API 호출 중 발생한 GC 수149.67156.33186.00188.00
GC 발생 API 요청 수1217.332684.00874.67778.33
GC 미발생 API 요청 수84.671971.335928.676067.00
GC 발생 API 평균 응답 시간(ms)3522.68582.4758.7150.39
GC 미발생 API 평균 응답 시간(ms)598.44185.7648.1441.35
GC 발생 API 비율(%)93.5058.4512.8611.37
GC 영향 비율(%)488.62205.7421.9721.87

K6 성능 지표

항목originalpureoptimizedgraph
K6 TPS8.9832.9948.2148.55
가상 사용자 최대100.00100.00100.00100.00
K6 평균 응답 시간(ms)4835.72573.5354.7746.95
K6 최소 응답 시간(ms)85.4674.6830.8026.97
K6 중간값 응답 시간(ms)4832.00507.2352.8045.84
K6 최대 응답 시간(ms)12842.801707.85181.42121.74
K6 P90 응답 시간(ms)8617.501078.0272.4661.88
K6 P95 응답 시간(ms)8741.841126.7380.0566.39
K6 P99 응답 시간(ms)8998.991234.28104.3478.98
실패율(%)0.000.000.000.00

효율성 지표

항목originalpureoptimizedgraph
요청당 GC 발생 비율0.120.030.030.03
요청당 메모리 회수량(MB)30.349.087.417.45
총 실행 시간 대비 GC 소요 시간 비율(%)0.020.050.240.28

(3)Case 2 평균 데이터

GC 성능 지표

항목originalpureoptimizedgraph
GC 발생 횟수129.00126.33200.00178.67
평균 GC 시간(ms)7.637.085.866.35
최대 GC 시간(ms)21.2315.8315.3315.01
총 회수 메모리량(MB)66077.6765678.3395987.00104139.00
평균 메모리 회수량(MB)512.23519.88479.93582.87

API 성능 지표

항목originalpureoptimizedgraph
API 요청 수856.331900.335838.336311.67
평균 API 응답 시간(ms)5542.222094.56161.18115.98
최소 API 응답 시간(ms)897.00330.0056.0048.00
최대 API 응답 시간(ms)15307.005222.00495.00376.00
API TPS5.7013.5241.7545.12
API P90 응답 시간(ms)11171.473887.53272.33183.00
API P95 응답 시간(ms)12470.974135.30307.00212.33
API P99 응답 시간(ms)13831.474482.05375.06271.48

GC-API 상관관계 지표

항목originalpureoptimizedgraph
API 호출 중 발생한 GC 수129.00126.33199.67178.00
GC 발생 API 요청 수835.331621.332165.331574.67
GC 미발생 API 요청 수21.00279.003673.004737.00
GC 발생 API 평균 응답 시간(ms)5655.942371.61209.72147.23
GC 미발생 API 평균 응답 시간(ms)1006.74485.23132.52105.57
GC 발생 API 비율(%)97.5585.3237.0324.95
GC 영향 비율(%)462.36388.7858.4939.42

K6 성능 지표

항목originalpureoptimizedgraph
K6 TPS5.6713.4141.4044.70
가상 사용자 최대100.00100.00100.00100.00
K6 평균 응답 시간(ms)8303.222896.15202.79132.37
K6 최소 응답 시간(ms)78.6684.9859.6551.69
K6 중간값 응답 시간(ms)8158.442935.91169.84116.48
K6 최대 응답 시간(ms)23090.907473.611180.61460.52
K6 P90 응답 시간(ms)14824.495131.80365.67222.96
K6 P95 응답 시간(ms)15056.755220.48413.24259.64
K6 P99 응답 시간(ms)15311.565445.12557.78339.48
실패율(%)0.000.000.000.00

효율성 지표

항목originalpureoptimizedgraph
요청당 GC 발생 비율0.150.070.030.03
요청당 메모리 회수량(MB)77.1634.5616.4416.50
총 실행 시간 대비 GC 소요 시간 비율(%)0.020.020.130.16

(4)Case 3 평균 데이터

GC 성능 지표

항목originalpureoptimizedgraph
GC 발생 횟수122.33167.00188.00196.33
평균 GC 시간(ms)6.755.816.086.03
최대 GC 시간(ms)18.4918.0712.7213.62
총 회수 메모리량(MB)64762.6789288.3395443.00102642.67
평균 메모리 회수량(MB)529.40534.68508.33522.80

API 성능 지표

항목originalpureoptimizedgraph
API 요청 수1145.336421.005793.336225.00
평균 API 응답 시간(ms)3724.5694.94166.42126.02
최소 API 응답 시간(ms)632.0042.6756.3348.33
최대 API 응답 시간(ms)10065.33434.33663.33467.33
API TPS7.8845.8841.4144.48
API P90 응답 시간(ms)7542.77138.33293.00206.33
API P95 응답 시간(ms)8516.22167.67336.67235.35
API P99 응답 시간(ms)9373.94254.32431.99302.49

GC-API 상관관계 지표

항목originalpureoptimizedgraph
API 호출 중 발생한 GC 수122.33165.67187.00196.00
GC 발생 API 요청 수1077.671243.001971.671847.67
GC 미발생 API 요청 수67.675178.003821.674377.33
GC 발생 API 평균 응답 시간(ms)3909.49121.62221.94161.50
GC 미발생 API 평균 응답 시간(ms)788.8688.42137.43111.01
GC 발생 API 비율(%)94.0919.3734.0729.68
GC 영향 비율(%)395.4737.0961.0745.49

K6 성능 지표

항목originalpureoptimizedgraph
K6 TPS7.8245.4641.0744.04
가상 사용자 최대100.00100.00100.00100.00
K6 평균 응답 시간(ms)5709.16106.41212.02147.00
K6 최소 응답 시간(ms)74.4645.4159.7751.66
K6 중간값 응답 시간(ms)5802.0494.94166.82125.26
K6 최대 응답 시간(ms)16107.93513.29989.49606.94
K6 P90 응답 시간(ms)10087.13166.38398.17254.18
K6 P95 응답 시간(ms)10202.74208.30461.59291.76
K6 P99 응답 시간(ms)10397.46332.47607.00381.65
실패율(%)0.000.000.000.00

효율성 지표

항목originalpureoptimizedgraph
요청당 GC 발생 비율0.110.030.030.03
요청당 메모리 회수량(MB)56.5413.9116.4716.49
총 실행 시간 대비 GC 소요 시간 비율(%)0.020.160.120.15
5.2. 전략별 메모리 효율성

(1)GC 발생 유무에 따른 API 응답 시간 비교

  • GC 발생 API 평균 응답 시간은 메모리 회수 과정에서의 정지 현상으로 인한 지연이 반영된 것

    ⇒ GC 없는 API 평균 응답 시간과의 차이로 GC가 성능에 미치는 영향을 확인 할 수 있다.

Case 1 (소규모 데이터)

항목originalpureoptimizedgraph
GC 발생 API 평균 응답 시간(ms)3522.68582.4758.7150.39
GC 미발생 API 평균 응답 시간(ms)598.44185.7648.1441.35
차이(ms)2924.24396.7110.589.04
비율(%)488.64213.5621.9721.87
  • 비율 (%) = 차이 / GC 미발생 API 평균 응답 시간
  • 최적화 전략(optimized, graph)을 적용할 경우
    • 소규모 데이터에서는 10 ms (약 21%) 정도의 지연 발생
  • N+1 문제가 존재하는 경우 지연 시간은 약 200%, 500% 정도로 엄청나다.

Case 2 (대규모 데이터)

항목originalpureoptimizedgraph
GC 발생 API 평균 응답 시간(ms)5655.942371.61209.72147.23
GC 미발생 API 평균 응답 시간(ms)1006.74485.23132.52105.57
차이(ms)4649.201886.3877.1941.66
비율(%)461.81388.7658.2539.46
  • N+1 문제가 있던 없던 대규모 데이터에 따른 GC의 영향이 증가함을 알 수 있다.

Case 3 (대규모 데이터 + Batch Fetching 적용)

항목originalpureoptimizedgraph
GC 발생 API 평균 응답 시간(ms)3909.49121.62221.94161.50
GC 미발생 API 평균 응답 시간(ms)788.8688.42137.43111.01
차이(ms)3120.6333.2084.5150.50
비율(%)395.5937.5561.4945.49
  • Pure 버전의 Case 2와 Case 3 비교하면 엄청난 차이를 볼 수 있다.

    이는 개발 시 우리가 반드시 Batch Fetching과 같은 해결 전략을 통해 반드시 N+1 문제를 해결해야 함을 알 수 있다.

(2)메모리 효율성 비교

Case 1 (소규모 데이터)

항목originalpureoptimizedgraph
요청당 메모리 회수량(MB)30.349.087.417.45
요청당 GC 발생 비율0.120.030.030.03
평균 GC 시간(ms)6.435.794.254.35
API P90 응답 시간(ms)6460.93799.3366.0056.67
API P95 응답 시간(ms)7048.97861.6772.0061.00
API P99 응답 시간(ms)7813.91978.9686.6771.00
  • API 요청당 메모리 사용량(MB) = 총 회수 메모리량(MB) / API 요청 수

  • 소규모 데이터에서는 N+1 문제를 해결한 전략(optimized, graph)이 메모리 효율성이 뚜렷하게 향상됨
    • API 요청 당 메모리 사용량 (pure 대비)
      • optimized : 18.4% 감소
      • graph : 18.0% 감소
    • 요청당 GC 발생 비율 개선율 (pure 대비)
      • 미세한 차이, 거의 동일함

  • 메모리 효율성 향상과 응답 시간 개선은 직접 이어짐을 확인 가능
    • optimized는 p95 응답시간이 pure 대비 92% 감소 (861.67ms → 72.00ms)
    • graph는 p95 응답시간이 pure 대비 93% 감소 (861.67 → 61.00ms)

Case 2 (대규모 데이터)

항목originalpureoptimizedgraph
요청당 메모리 회수량(MB)77.1634.5616.4416.50
요청당 GC 발생 비율0.150.070.030.03
평균 GC 시간(ms)7.637.085.866.35
API P90 응답 시간(ms)11171.473887.53272.33183.00
API P95 응답 시간(ms)12470.974135.30307.00212.33
API P99 응답 시간(ms)13831.474482.05375.06271.48
  • 대규모 데이터에서도 N+1 문제를 해결한 전략(optimized, graph)의 메모리 효율성이 압도적으로 우수

    • API 요청 당 메모리 사용량 (pure 대비)
      • optimized : 52.4% 감소
      • graph : 52.2% 감소
    • 요청당 GC 발생 비율 개선율 (pure 대비)
      • optimized : 57.1% 감소
      • graph : 57.1% 감소

    ⇒ “연관 데이터가 많으면 많을수록 N+1 문제의 영향이 더 크게 나타난다.”


  • 응답 시간에서도 극적인 개선 효과를 보인다.
    • optimized의 p95 응답시간이 pure 대비 92.6% 감소 (4135.30 ms → 307.00 ms)
    • graph의 p95 응답시간이 pure 대비 94.9% 감소 (4135.30 ms → 212.33 ms)

Case 3 (대규모 데이터 + Batch Fetching 적용)

항목originalpureoptimizedgraph
요청당 메모리 회수량(MB)56.5413.9116.4716.49
요청당 GC 발생 비율0.110.030.030.03
평균 GC 시간(ms)6.755.816.086.03
API P90 응답 시간(ms)7542.77138.33293.00206.33
API P95 응답 시간(ms)8516.22167.67336.67235.35
API P99 응답 시간(ms)9373.94254.32431.99302.49
  • Case3의 결과에서는 Batch Fetching(Pure)이 가장 좋은 결과를 나타냄을 확인할 수 있었다.
5.3. 데이터 크기 증가에 따른 영향 분석

(1)Graph 버전 데이터 규모에 따른 변화

성능 지표

항목소규모 데이터대규모 데이터차이증가율
GC 발생 횟수188.67178.67-10.00-5.30%
평균 GC 시간(ms)4.356.352.00+45.87%
최대 GC 시간(ms)11.7115.013.31+28.25%
평균 API 응답 시간(ms)42.38115.9873.61+173.70%
최소 API 응답 시간(ms)24.3348.0023.67+97.26%
API P95 응답 시간(ms)61.00212.33151.33+248.09%
API P99 응답 시간(ms)71.00271.48200.48+282.36%
API TPS48.9545.12-3.83-7.82%
GC 발생 API 평균 응답 시간(ms)50.39147.2396.84+192.17%
GC 미발생 API 평균 응답 시간(ms)41.35105.5764.22+155.32%
GC 영향 비율(%)21.8739.4217.55+80.22%
  • GC 영향 비율 = (GC발생 API 평균응답시간 - GC미발생 API 평균응답시간) * 100 / GC미발생 API 평균응답시간
    • 이 값이 높을수록 GC가 API 응답 시간 증가에 큰 영향을 미쳤다는 의미

  • 응답 성능
    • 데이터 규모가 증가함에 따라 P95, P99 응답 시간이 크게 증가했으나 다른 버전에 비해 상대적으로 양호한 편이다.
    • 특히 TPS 감소율이 -7.82%로 가장 적어 처리량 측면에서 안정성이 높다.
    • 응답 시간 증가는 메모리 사용량 증가와 GC 간섭 증가가 주 원인으로 보인다.

  • GC 영향
    • 평균 GC 시간은 45.87% 증가했다는 점에서 GC가 더 많은 객체를 처리해야 하기 때문에 발생하는 현상으로 보인다.
    • GC 영향 비율 80.22% 증가는 가비지 컬렉션(GC)이 성능에 더 큰 영향을 미치고 있음을 뜻한다.
    • 그러나 다른 버전에 비해 GC 영향 비율 증가폭이 작은 편이다.

메모리 지표

항목소규모 데이터대규모 데이터차이증가율
총 회수 메모리량(MB)50996.33104139.0053142.67+104.21%
평균 메모리 회수량(MB)270.30582.87312.57+115.64%
GC 발생 API 요청 수778.331574.67796.33+102.31%
GC 미발생 API 요청 수6067.004737.00-1330.00-21.92%
요청당 GC 발생 비율0.030.030.00+2.71%
요청당 메모리 회수량(MB)7.4516.509.05+121.58%
  • 메모리 사용 패턴
    • 요청당 메모리 회수량으로 소규모(7.45)에서 대규모(16.5)로 120% 증가했다.

      • 총 회수 메모리량과 평균 메모리 회수량이 2배 이상 증가하여 메모리 사용량이 데이터 규모에 비례하여 증가했다.
      • 이것은 연관 데이터가 늘어남에 따른 객체 생성이 많아짐을 의미하고 정상적이다.
    • 요청당 GC 발생 비율은 단 2.71% 증가로 다른 버전들에 비해 가장 안정적인 메모리 관리를 보여준다.

      ⇒ 대규모 데이터 환경에서도 메모리 관리 측면에서 안정적인 성능

    • 이는 Entity Graph가 내부적으로 객체 그래프를 구성하는 방식과 JPA Hibernate의 최적화가 Fetch Join과 미세하게 다를 수 있기 때문으로 추정된다

(2)Optimized 버전 데이터 규모에 따른 변화

성능 지표

항목소규모 데이터대규모 데이터차이증가율
GC 발생 횟수186.67200.0013.33+7.14%
평균 GC 시간(ms)4.255.861.61+37.91%
최대 GC 시간(ms)6.9515.338.37+120.42%
평균 API 응답 시간(ms)49.50161.18111.68+225.61%
최소 API 응답 시간(ms)28.0056.0028.00+100.00%
API P95 응답 시간(ms)72.00307.00235.00+326.39%
API P99 응답 시간(ms)86.67375.06288.40+332.77%
API TPS48.6741.75-6.92-14.22%
GC 발생 API 평균 응답 시간(ms)58.71209.72151.00+257.19%
GC 미발생 API 평균 응답 시간(ms)48.14132.5284.39+175.31%
GC 영향 비율(%)21.9758.4936.52+166.19%
  • 응답 성능
    • P95, P99 응답 시간이 각각 326.39%, 332.77% 증가하여 graph 버전보다 더 큰 성능 저하를 보였다.
    • TPS 감소율(-14.22%) 역시 마찬가지로 graph 버전(-7.82%)보다 크게 나왔다.
    • 결과적으로, 데이터 크기 증가에 따른 응답 시간 증가폭이 커서 확장성 측면에서 graph 버전보다 불리하게 측정되었다.

  • GC 영향
    • graph와 마찬가지로 평균 GC 시간은 37.91% 증가했다.
    • GC 영향 비율이 166.19% 증가하여 graph 버전(80.22% 증가)보다 GC의 영향이 더 크게 증가하여 응답 속도가 더욱 감소하게 원인으로 작용하는 것 같다.
    • 위의 응답 성능과 종합하면 Fetch Join()이 EntityGraph보다 전반적으로 데이터 크기 증가에 불리한 것처럼 보인다.

메모리 지표

항목소규모 데이터대규모 데이터차이증가율
총 회수 메모리량(MB)50428.0095987.0045559.00+90.34%
평균 메모리 회수량(MB)270.15479.93209.78+77.65%
GC 발생 API 요청 수874.672165.331290.67+147.56%
GC 미발생 API 요청 수5928.673673.00-2255.67-38.05%
요청당 GC 발생 비율0.030.030.01+24.86%
요청당 메모리 회수량(MB)7.4116.449.03+121.76%
  • 메모리 사용 패턴
    • 요청당 메모리 회수량은 소규모(7.41)에서 대규모(16.44)로 증가했고 이는 graph 버전과 거의 동일하므로 메모리 회수 효율은 graph와 optimized가 비슷하다고 볼 수 있다.
    • 요청당 GC 발생 비율이 24.86% 증가하여 graph 버전(2.71%)보다 메모리 관리 효율이 떨어진다.
      • 다만, 대규모 데이터에서 EntityGraph(16.5 MB)와 Fetch Join(16.44 MB) 사이의 차이가 크지 않고

        두 전략 모두 N+1 문제가 있는 것보다 메모리 측면에서 훨씬 뛰어나다.

(3)Pure 버전 데이터 규모에 따른 변화

성능 지표

항목소규모 데이터대규모 데이터차이증가율
GC 발생 횟수157.33126.33-31.00-19.70%
평균 GC 시간(ms)5.797.081.29+22.27%
최대 GC 시간(ms)15.2615.830.57+3.74%
평균 API 응답 시간(ms)427.842094.561666.73+389.57%
최소 API 응답 시간(ms)70.33330.00259.67+369.19%
API P95 응답 시간(ms)861.674135.303273.63+379.92%
API P99 응답 시간(ms)978.964482.053503.09+357.84%
API TPS33.2613.52-19.74-59.36%
GC 발생 API 평균 응답 시간(ms)582.472371.611789.15+307.17%
GC 미발생 API 평균 응답 시간(ms)185.76485.23299.47+161.21%
GC 영향 비율(%)205.74388.78183.04+88.97%
  • 응답 성능
    • 평균 API 응답 시간이 389.57% 증가하여 모든 버전 중 가장 큰 성능 저하를 보였다.
    • P95, P99 응답 시간도 각각 379.92%, 357.84% 증가하여 심각한 성능 저하를 나타낸다.
    • TPS가 59.36% 감소하여 처리량 측면에서도 가장 큰 폭의 감소를 보인다.
    • N+1 문제로 인한 과도한 쿼리 발생이 주요 성능 저하 원인으로 판단된다.

  • GC 영향
    • 평균 GC 시간은 22.27% 증가했다.
    • GC 영향 비율이 88.97% 증가하여 graph 버전(80.22%)보다 약간 더 높은 증가율을 보인다.
    • 이는 N+1 문제로 인한 많은 쿼리가 메모리 사용 패턴에 큰 영향을 미치는 것으로 보인다.

메모리 지표

항목소규모 데이터대규모 데이터차이증가율
총 회수 메모리량(MB)42272.3365678.3323406.00+55.37%
평균 메모리 회수량(MB)268.61519.88251.27+93.54%
GC 발생 API 요청 수2684.001621.33-1062.67-39.59%
GC 미발생 API 요청 수1971.33279.00-1692.33-85.85%
요청당 GC 발생 비율0.030.070.03+96.60%
요청당 메모리 회수량(MB)9.0834.5625.48+280.48%
  • 메모리 사용 패턴
    • 요청당 메모리 회수량이 280% 증가하여 메모리 회수 효율이 매우 나빠졌음을 알 수 있다.
      • 소규모 데이터일 때 메모리 회수 효율은 N+1 해결 전략 버전들과 큰 차이가 없지만 데이터가 많아질수록 급격하게 나빠졌다.
    • 요청당 GC 발생 비율이 96.60% 증가하여 모든 버전 중 가장 높은 증가율을 보인다.
      • N+1 문제가 메모리 관리 측면에서 얼마나 안 좋은지 알 수 있다.
    • N+1 문제로 인한 많은 쿼리 발생이 메모리 관리 효율성을 크게 저하시키는 것은 original과 마찬가지지만 소규모일 때 속도가 생각보다 좋았기에 차이가 많이 발생한 것 같다.

(4)Original 버전 데이터 규모에 따른 변화

성능 지표

항목소규모 데이터대규모 데이터차이증가율
GC 발생 횟수150.33129.00-21.33-14.19%
평균 GC 시간(ms)6.437.631.20+18.67%
최대 GC 시간(ms)13.1021.238.13+62.05%
평균 API 응답 시간(ms)3332.535542.222209.69+66.31%
최소 API 응답 시간(ms)388.00897.00509.00+131.19%
API P95 응답 시간(ms)7048.9712470.975422.00+76.92%
API P99 응답 시간(ms)7813.9113831.476017.56+77.01%
API TPS9.055.70-3.35-37.04%
GC 발생 API 평균 응답 시간(ms)3522.685655.942133.26+60.56%
GC 미발생 API 평균 응답 시간(ms)598.441006.74408.29+68.23%
GC 영향 비율(%)488.62462.36-26.25-5.37%
  • 응답 성능
    • 평균 API 응답 시간이 66.31% 증가하여 모든 버전 중 가장 낮은 증가율을 보였다.

      ⇒ 소규모일 때 너무 느렸기 때문에 오히려 증가폭이 낮게 나온 것 같다.

    • P95, P99 응답 시간도 각각 76.92%, 77.01% 증가로 다른 버전에 비해 가장 낮은 증가율이다.

    • 그러나 절대적인 응답 시간(대규모: 5542.22ms)은 모든 버전 중 가장 높은 수준이다.

    • TPS가 37.04% 감소하여 pure 버전 다음으로 큰 폭의 감소를 보인다.


  • GC 영향
    • 평균 GC 시간은 18.67% 증가했다.
    • GC 영향 비율이 5.37% 감소하여 모든 버전 중 유일하게 개선되었다.
    • 이미 매우 높은 GC 영향 비율(462.36%)을 가지고 있기도 하지만, GC는 상황에 따라 변동이 많기 때문에 정확하지 않다.

메모리 지표

항목소규모 데이터대규모 데이터차이증가율
총 회수 메모리량(MB)39502.6766077.6726575.00+67.27%
평균 메모리 회수량(MB)262.77512.23249.46+94.94%
GC 발생 API 요청 수1217.33835.33-382.00-31.38%
GC 미발생 API 요청 수84.6721.00-63.67-75.20%
요청당 GC 발생 비율0.120.150.04+30.47%
요청당 메모리 회수량(MB)30.3477.1646.83+154.36%
  • 메모리 사용 패턴
    • 요청당 메모리 회수량이 graph와 optimized와 비슷하게 증가했지만, 소규모 일 때 절대적인 수치가 30이었으므로 원래 나빴지만 훨씬 안 좋아졌다.
    • GC 발생 API 요청 수가 31.38% 감소하였는데 이것 역시 측정할 때 다르게 나올 수 있는 부분이다.
    • 요청당 GC 발생 비율이 30.47% 증가하였지만 소규모일 때 역시 높았기에 크게 증가하지 않는 듯하다.
      • 소규모일 때도 N+1 문제가 있고 최적화가 이루어지지 않아 안 좋았다.

(5)데이터 크기 증가에 따른 증가율 종합

성능 지표

항목optimizedgraphpureoriginal
GC 발생 횟수+7.14%-5.30%-19.70%-14.19%
평균 GC 시간(ms)+37.91%+45.87%+22.27%+18.67%
최대 GC 시간(ms)+120.42%+28.25%+3.74%+62.05%
평균 API 응답 시간(ms)+225.61%+173.70%+389.57%+66.31%
최소 API 응답 시간(ms)+100.00%+97.26%+369.19%+131.19%
API P95 응답 시간(ms)+326.39%+248.09%+379.92%+76.92%
API P99 응답 시간(ms)+332.77%+282.36%+357.84%+77.01%
API TPS-14.22%-7.82%-59.36%-37.04%
GC 발생 API 평균 응답 시간(ms)+257.19%+192.17%+307.17%+60.56%
GC 미발생 API 평균 응답 시간(ms)+175.31%+155.32%+161.21%+68.23%
GC 영향 비율(%)+166.19%+80.22%+88.97%-5.37%
  • P95, P99 응답 시간
    • 전체 API 요청 중 95%와 99% 이내의 응답 시간을 나타내는 지표로, 낮을 수록 요청 처리가 빠르다는 의미
  • TPS
    • 초당 처리할 수 있는 API 요청 수로, 높을 수록 시스템이 더 많은 요청을 처리할 능력이 있다는 의미
  • 평균 GC 시간
    • 가비지 컬렉션 작업의 평균 소요 시간으로 낮을 수록 GC가 빠르게 처리되어 중단 시간이 짧음을 의미
  • GC 영향 비율(%)
    • GC가 미발생할 때의 API 평균 응답 시간에서 발생할 때의 차이와의 비율로, 낮을 수록 GC가 API 응답 시간에 미치는 영향이 적다는 의미

메모리 지표

항목optimizedgraphpureoriginal
총 회수 메모리량(MB)+90.34%+104.21%+55.37%+67.27%
평균 메모리 회수량(MB)+77.65%+115.64%+93.54%+94.94%
GC 발생 API 요청 수+147.56%+102.31%-39.59%-31.38%
GC 미발생 API 요청 수-38.05%-21.92%-85.85%-75.20%
요청당 GC 발생 비율+24.86%+2.71%+96.60%+30.47%
요청당 메모리 회수량(MB)+121.76%+121.58%+280.48%+154.36%
  • 요청당 메모리 회수량

    • 총 회수 메모리량 / API 요청 수로, 많을 수록 많은 객체가 생성되고 제거되었음을 의미한다.

      ⇒ 즉, 객체 생성/소멸이 활발하다는 것이다.

    • 하지만, 낮을 수록 꼭 좋은 것은 아니다. static으로 참조되었거나 전역 캐시 등으로 살아남은 Old 객체가 많아서 그런 것일 수 있기 때문이다.

    • 다만, 테스트 결과의 GC 로그결과를 보면 대부분 Young GC만 발생했기에 시스템이 조기에 적은 비용으로 자주 처리하고 있어서 건강하다는 것이고, 동일한 부하 조건에서 요청당 메모리 회수량이 적다면 객체를 덜 만드는 것으로 볼 수 있다.

      ⇒ 즉, 메모리 효율이 높다는 의미가 될 수 있다.

  • 요청당 GC 발생 비율

    • GC 발생 횟수 / API 요청 수로, 낮을 수록 메모리 관리가 효율적으로 이루어져 적은 수의 GC로 더 많은 쵸엉을 처리할 수 있음을 의미
    • 요청당 GC 발생 비율이 낮고, 요청당 메모리 회수량도 낮다면 이상적인 GC 성능이다.

데이터 크기 증가에 따른 영향

  • 객체 생명주기와 가비지 컬렉션
    • API를 호출하면서 생성되는 Java 객체는 API 응답 완료 시 가비지가 되며, 다른 API 호출에서 재사용되지 않으므로 가비지 컬렉터의 제거 대상이 된다.
    • 대규모 데이터에서는 더 많은 Java 객체가 생성되어 GC가 소모하는 시간이 증가한다. 이는 모든 버전에서 평균 GC 시간과 최대 GC 시간이 증가하는 현상으로 확인할 수 있다.

  • 메모리와 성능의 상관관계
    • 가비지가 늘어나면 가비지 컬렉터가 관여하는 시간과 비율이 증가하고, 이는 API 응답 시간 증가로 이어진다.

  • 최적의 전략
    • 데이터 규모 증가에 가장 효과적으로 대응하는 방식은 EntityGraph 버전으로, 메모리 관리 효율성(요청당 GC 발생 비율 +2.71%)과 TPS 유지율(-7.82%)이 가장 우수했다. 다만, Fetch Join과 큰 차이는 없으므로 상황에 기호에 맞게 선택하여 적용하면 된다.

  • 측정 오류
    • Original 버전에서 GC 영향 비율이 오히려 감소(-5.37%)한 것은 소규모 데이터에서 이미 매우 높은 값(488.62%)을 보였기 때문으로, 상대적 개선이 아닌 측정의 특성으로 판단된다.

결론

EntityGraph 기반 graph 버전은 데이터 규모 증가 시에도 상대적으로 안정적인 응답 성능과 메모리 사용 효율을 유지하였고 optimized 버전 역시 마찬가지다. 반면 pure 버전은 N+1 문제로 인해 심각한 성능 저하가 발생했음을 알 수 있다.

5.4. Batch Fetching의 영향

(1)pure 버전의 Batch Fetching 영향

  • N+1 미해결 (Case 2의 Pure 버전)
  • Batch Fetching (Case 3의 Pure 버전)

항목N+1 미해결Batch Fetching차이증가율
GC 발생 횟수126.33167.0040.67+32.19%
요청당 GC 발생 비율0.070.030.04-57.14%
평균 GC 시간(ms)7.085.81-1.27-17.94%
최대 GC 시간(ms)15.8318.072.24+14.15%
총 회수 메모리량(MB)65678.3389288.3323610.00+35.95%
요청당 메모리 회수량(MB)34.5613.91-20.65-59.75%
API TPS13.5245.8832.36+239.35%
API P95 응답 시간(ms)4135.30167.67-3967.63-95.95%
API P99 응답 시간(ms)4482.05254.32-4227.73-94.33%
GC 영향 비율(%)388.7837.09-351.69-90.46%
  • 요청당 GC 발생 비율 = GC 발생 횟수 / API 요청 수
    • 낮을수록 효율적으로 메모리를 사용한다는 의미이며 GC가 적게 발생하여 높은 처리량(TPS)을 나타낼 수 있다.

  • Pure 버전에 Batch Fetching을 적용한 결과
    • P95, P99 응답 시간 개선율은 각각 95.95%, 94.33% 빨라졌으며 TPS 역시 239% 증가하였다.

      ⇒ 성능 측면에서 엄청난 개선을 보인다.

    • GC 발생 횟수가 늘어난 것은 성능이 개선됨에 따라 API 요청 수가 대폭 증가한 탓이다.

      ⇒ 요청당 GC 발생 비율이 57% 줄어들었다. 이는 메모리 측면에서 다른 전략과 비슷하게 올라온 것

    • GC 영향 비율(%) 역시 90.46% 줄어서 영향 비율이 37%로 줄어들었다.

      ⇒ 이는 가비지 컬렉션(GC)이 성능에 영향을 훨씬 더 적게 미치고 있음을 뜻한다.

    • 총 회수 메모리량(MB) 증가는 TPS 증가에 따른 자연스러운 증가이다.


  • 이는 N+1 문제가 존재하는 환경에서 Batch Fetching이 매우 효과적인 최적화 전략임을 알 수 있다.

(2)Batch Fetching 제약 조건

  • Batch Fetching의 경우 Fetch Join이나 EntityGraph가 적용되었을 경우 Batch 효과가 무시된다.

optimized 성능 지표

항목Case 2 OptimizedCase 3 Optimized차이증가율
GC 발생 횟수200.00188.00-12.00-6.00%
평균 GC 시간(ms)5.866.080.22+3.75%
최대 GC 시간(ms)15.3312.72-2.61-17.03%
평균 API 응답 시간(ms)161.18166.425.24+3.25%
최소 API 응답 시간(ms)56.0056.330.33+0.59%
API P95 응답 시간(ms)307.00336.6729.67+9.66%
API P99 응답 시간(ms)375.06431.9956.93+15.18%
API TPS41.7541.41-0.34-0.81%
GC 발생 API 평균 응답 시간(ms)209.72221.9412.22+5.83%
GC 미발생 API 평균 응답 시간(ms)132.52137.434.91+3.71%
GC 영향 비율(%)58.4961.072.58+4.41%
  • 증가율 차이가 대부분 10% 미만이며 아닌 것들도 측정 오차일 뿐이다.
    • 절대적인 값 차이가 크지 않다.

optimized 메모리 지표

항목Case 2 OptimizedCase 3 Optimized차이증가율
총 회수 메모리량(MB)95987.0095443.00-544.00-0.57%
평균 메모리 회수량(MB)479.93508.3328.40+5.92%
GC 발생 API 요청 수2165.331971.67-193.66-8.94%
GC 미발생 API 요청 수3673.003821.67148.67+4.05%
요청당 GC 발생 비율0.030.030.000.00% (동일)
요청당 메모리 회수량(MB)16.4416.470.03+0.18%
  • 증가율 차이가 대부분 10% 미만이며 아닌 것들도 측정 오차일 뿐이다.
    • 절대적인 값 차이가 크지 않다.

graph 성능 지표

항목Case 2 GraphCase 3 Graph차이증가율
GC 발생 횟수178.67196.3317.67+9.89%
평균 GC 시간(ms)6.356.03-0.32-5.04%
최대 GC 시간(ms)15.0113.62-1.39-9.26%
평균 API 응답 시간(ms)115.98126.0210.04+8.66%
최소 API 응답 시간(ms)48.0048.330.33+0.69%
API P95 응답 시간(ms)212.33235.3523.02+10.84%
API P99 응답 시간(ms)271.48302.4931.01+11.42%
API TPS45.1244.48-0.64-1.42%
GC 발생 API 평균 응답 시간(ms)147.23161.5014.27+9.69%
GC 미발생 API 평균 응답 시간(ms)105.57111.015.44+5.15%
GC 영향 비율(%)39.4245.496.07+15.40%
  • 증가율 차이가 대부분 10% 미만이며 아닌 것들도 측정 오차일 뿐이다.
    • 절대적인 값 차이가 크지 않다.

graph 메모리 지표

항목Case 2 GraphCase 3 Graph차이증가율
총 회수 메모리량(MB)104139.00102642.67-1496.33-1.44%
평균 메모리 회수량(MB)582.87522.80-60.07-10.31%
GC 발생 API 요청 수1574.671847.67273.00+17.34%
GC 미발생 API 요청 수4737.004377.33-359.67-7.59%
요청당 GC 발생 비율0.030.030.000.00% (동일)
요청당 메모리 회수량(MB)16.5016.49-0.01-0.06%
  • 증가율 차이가 대부분 10% 미만이며 아닌 것들도 측정 오차일 뿐이다.
    • 절대적인 값 차이가 크지 않다.

결론

  • Fetch Join과 EntityGraph를 적용한 경우 Batch Fetching의 효과가 무시됨을 결과를 통해 증명했다.

  • 이렇게 Batch Fetching이 무시되는 이유는 Fetch Join과 EntityGraph의 경우 한 번의 쿼리로 조인해서 가져오기 때문에 지연 로딩이 발생할 일이 없다.

    ⇒ Batch Fetching은 지연 로딩인 쿼리를 Batch로 큰 덩어리로 가져오는 기술이다. 지연 로딩이 없으면 효과가 없다.

5.5. 3가지 전략 분석 (Fetch Join vs EntityGraph vs Batch Fetching)

(1)N+1 미해결 vs 해결 전략

  • Case 2의 Pure 버전은 N+1 문제를 미해결 상태지만 Case 3의 Pure 버전은 Batch Fetching 버전이다.

    ⇒ 비교를 통해 Batch Fetching, EntityGraph, Fetch Join 전략별 성능 및 메모리 사용량 분석

성능 지표

항목N+1 미해결Fetch JoinEntityGraphBatch Fetching
GC 발생 횟수126.33188.00 (48.81%)196.33 (55.41%)167.00 (32.19%)
평균 GC 시간(ms)7.086.08 (-14.16%)6.03 (-14.82%)5.81 (-17.98%)
최대 GC 시간(ms)15.8312.72 (-19.67%)13.62 (-13.98%)18.07 (14.13%)
평균 API 응답 시간(ms)2094.56166.42 (-92.05%)126.02 (-93.98%)94.94 (-95.47%)
최소 API 응답 시간(ms)330.0056.33 (-82.93%)48.33 (-85.35%)42.67 (-87.07%)
API P95 응답 시간(ms)4135.30336.67 (-91.86%)235.35 (-94.31%)167.67 (-95.95%)
API P99 응답 시간(ms)4482.05431.99 (-90.36%)302.49 (-93.25%)254.32 (-94.33%)
API TPS13.5241.41 (206.39%)44.48 (229.05%)45.88 (239.41%)
GC 발생 API 평균 응답 시간2371.61221.94 (-90.64%)161.50 (-93.19%)121.62 (-94.87%)
GC 미발생 API 평균 응답 시간485.23137.43 (-71.68%)111.01 (-77.12%)88.42 (-81.78%)
GC 영향 비율(%)388.7861.07 (-84.29%)45.49 (-88.30%)37.09 (-90.46%)

메모리 지표

항목N+1 미해결Fetch JoinEntityGraphBatch Fetching
총 회수 메모리량(MB)65678.3395443.00 (45.32%)102642.67 (56.28%)89288.33 (35.95%)
평균 메모리 회수량(MB)519.88508.33 (-2.22%)522.80 (0.56%)534.68 (2.85%)
GC 발생 API 요청 수1621.331971.67 (21.61%)1847.67 (13.96%)1243.00 (-23.33%)
GC 미발생 API 요청 수279.003821.67 (1269.77%)4377.33 (1468.94%)5178.00 (1755.91%)
요청당 GC 발생 비율0.070.03 (-51.16%)0.03 (-52.56%)0.03 (-60.87%)
요청당 메모리 회수량34.5616.47 (-52.33%)16.49 (-52.29%)13.91 (-59.76%)

(2)각 전략별 결과 분석

공통 특성

  • 3가지 전략 모두 P95, P99, TPS가 각각 최소 91%, 90%, 206% 이상 개선되었다.

  • 3가지 전략 모두 요청당 GC 발생 비율이 최소 50% 이상 줄었고, 요청당 메모리 회수량 또한 최소 52% 이상 줄어들었다.

    ⇒ 가비지 객체가 적고, GC도 발생 빈도도 훨씬 줄어들었음을 의미한다.

    ⇒ 그에 따라 API 응답속도에서 GC 영향이 대폭 줄었다. (GC 영향 비율(%)이 최소 84%이상 감소)

  • 결론적으로, 해결 전략 모두에서 응답시간, TPS, 메모리량과 효율성이 전부 개선되었음을 의미한다.

개별 특성

  • Fetch Join (Optimized 버전)
    • 성능 측면이나 메모리 측면에서 3가지 전략 중 제일 안 좋게 나왔다.
    • Batch Fetching과 EntityGraph에 비해 GC 영향 비율 감소가 상대적으로 적으며 절대적인 수치 또한 61.07%로 GC 영향이 아직 상당히 존재하고 있다.
    • 이는 요청당 GC 발생 비율과 요청당 메모리 회수량이 적게 개선됨에 따른 것으로 볼 수 있을 것이다.

  • EntityGraph (Graph 버전)
    • GC 미발생 API 요청 수가 많아 안정적인 성능을 보여줌 (1468.94% 증가)
    • Fetch Join보다 더 나은 응답 시간 개선 효과 (API P95 응답 시간 94.31% 개선)
    • GC 발생 횟수가 가장 많이 증가함 (55.41% 증가)

  • Batch Fetching (Case 3의 Pure 버전)
    • 세 전략 중 가장 좋은 응답 시간 개선 효과 (API P95 응답 시간 95.95% 개선)
    • 가장 높은 TPS 개선 (239.41% 증가)
    • GC 미발생 API 요청 수가 가장 많음 (1755.91% 증가)
    • 유일하게 GC 발생 API 요청 수가 감소함 (23.33% 감소)
    • GC 영향 비율 감소가 가장 큼 (90.46% 개선)

결론

  • 모든 N+1 문제 해결 전략은 미해결 상태에 비해 압도적인 성능 개선을 보여주지만, 각 전략 간에도 미묘한 차이가 있다.
  • 데이터 분석 결과 Batch Fetching이 대부분의 성능 지표에서 가장 좋은 결과를 보여주었다.
  • 다만, 3가지 중 무조건 Batch Fetching 이 좋다고 할 수는 없다. 실시한 테스트의 측정 한계가 존재한다.
    • Case당 버전별 3회씩 측정하여 측정 표본이 많지 않은 점
    • GC 측정이 컴퓨터의 상태에 따라 결과가 다르게 나올 수 있는 점
    • 최대한 방지한다고 했지만 Java 애플리케이션 워밍업이 작용했을 수 있다는 점

다음 페이지로 이동 (6. 결론 및 Insight)