ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SpringBoot] N+1 ์„ ๊ณ ๋ คํ•˜์—ฌ ํŽ˜์ด์ง• ์ฟผ๋ฆฌ ์ž‘์„ฑํ•˜๊ธฐ
    SpringBoot 2024. 1. 1. 13:43

    ๐Ÿ’ญ ๋“ค์–ด๊ฐ€๋ฉฐ

    ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๊ฒŒ ๋๋‹ค. ํ”„๋กœ์ ํŠธ๋Š” ๊ต๋‚ด์—์„œ ์—ด๋ฆฌ๋Š” ์Šคํฌ์ธ  ๊ฒฝ๊ธฐ๋“ค์˜ ๊ฒฐ๊ณผ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ์„œ๋น„์Šค์ด๋‹ค.

    ๊ฒฝ๊ธฐ๋“ค์˜ ๋ชฉ๋ก์„ ์กฐํšŒํ•  ๋•Œ, QueryDSL ์„ ์‚ฌ์šฉํ•˜์—ฌ ํŽ˜์ด์ง• ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

    ํ”„๋กœ์ ํŠธ์—์„œ์˜ ์ปจ๋ฒค์…˜ ์ƒ, ๊ฒฝ๊ธฐ์— ์ฐธ์—ฌํ•˜๋Š” ํŒ€๋“ค์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆœ์ •๋ ฌํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ–ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

    List<Game> games = gameDynamicRepository.findAllByLeagueAndStateAndSports(leagueId, state, sportIds,
                pageRequest);
    
    return games.stream()
            .map(game -> new GameResponseDto(game,
                    gameTeamRepository.findAllByGameWithTeam(game).stream()
                            .sorted(comparingLong(GameTeam::getId)).toList(),
                    game.getSport()))
            .toList();

     

    ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋ฉ˜ํŠธ๋ฅผ ๋ฐ›์•˜๋‹ค.

    "์ด๋ ‡๊ฒŒ ๋˜๋ฉด N+1 ์ด ๋ฐœ์ƒํ•˜๊ฒ ๊ตฐ์š” ์Šนํฌ๋‹˜.."

     

    N+1 ๋ฌธ์ œ์— ๋Œ€ํ•ด์„œ๋Š” ์ตํžˆ ์•Œ๊ณ  ์žˆ์—ˆ์ง€๋งŒ, ์ •์ž‘ ๋‚ด๊ฐ€ ์ฝ”๋“œ๋ฅผ ์งค ๋•Œ๋Š” ๋ณ„๋‹ค๋ฅธ ์˜์‹์„ ํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋ฌธ์ œ ์ง€์ ์„ ์ฐพ์ง€ ๋ชปํ•œ๋‹ค๋Š” ์ƒ๊ฐ์„ ํ•˜๊ฒŒ ๋˜์–ด ์ด ๊ธฐํšŒ์— ์ œ๋Œ€๋กœ ์žก์•„๋ณด์ž! ํ•˜๊ณ  ์ƒ๊ฐํ•˜๊ฒŒ ๋๋‹ค.

    โœ… ํ˜„์žฌ ์š”๊ตฌ์‚ฌํ•ญ

    - games: ๊ฒฝ๊ธฐ

    - game_teams : games, teams ์„ ์—ฐ๊ฒฐํ•˜๋Š” ์ค‘๊ฐœ ํ…Œ์ด๋ธ”

    - teams : ํŒ€์˜ ์ •๋ณด

     

    ์กฐ๊ฑด์— ํ•ด๋‹นํ•˜๋Š” ๊ฒฝ๊ธฐ๋ฅผ

    1. ๊ฒฝ๊ธฐ์— ์ฐธ์—ฌํ•˜๋Š” ํŒ€๋“ค์€ id ์ˆœ์œผ๋กœ

    2. ๊ฒฝ๊ธฐ๋“ค์€ ํŽ˜์ด์ง•ํ•˜์—ฌ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค.

     

    ๐Ÿ“ N+1 ๋ฌธ์ œ๋Š” ๋ฌด์—‡์ผ๊นŒ?

     

    ์šฐ์„ , games ์™€ game_teams ๋Š” ์œ„์™€ ๊ฐ™๋‹ค.

    games : game_teams = 1:N ์˜ ๊ด€๊ณ„๋ฅผ ๋งบ๊ณ  ์žˆ์œผ๋ฉฐ games ์ชฝ์—์„œ๋Š” game_teams ๋ฅผ ์ฐธ์กฐํ•˜์ง€ ์•Š๊ณ  ์žˆ๋‹ค.

    ๋˜ํ•œ, game_teams ๋Š” teams ์™€ games ๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ์ค‘๊ฐœํ…Œ์ด๋ธ”์ด๋‹ค. ํŒ€์˜ ์ •๋ณด๋Š” teams ์— ๋‹ด๊ฒจ ์žˆ๋‹ค.

     

    List<Game> games = gameDynamicRepository.findAllByLeagueAndStateAndSports(leagueId, state, sportIds,
                pageRequest);
    
    return games.stream()
            .map(game -> new GameResponseDto(game,
                    gameTeamRepository.findAllByGameWithTeam(game).stream()
                            .sorted(comparingLong(GameTeam::getId)).toList(),
                    game.getSport()))
            .toList();

     

    ์•ž์„œ ์–ธ๊ธ‰ํ•œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•œ ์ฟผ๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์•˜๋‹ค.

     

     

    ๋‚ด๊ฐ€ ์˜ˆ์ƒํ–ˆ๋˜ ์ƒํ™ฉ์€ ๊ฒฝ๊ธฐ ์ค‘ ์กฐ๊ฑด์— ๋งž๋Š” ๊ฒฝ๊ธฐ๋“ค์„ ์ฐพ๋Š” ์ฟผ๋ฆฌ ํ•˜๋‚˜๊ฐ€ ๋‚˜๊ฐ€๋Š” ๊ฒƒ์ด์—ˆ๋‹ค. 

    ๊ทธ๋Ÿฌ๋‚˜, ์‹ค์ œ๋กœ ํ™•์ธํ•ด๋ณด๋‹ˆ GameTeam ๊ฐ์ฒด๋ฅผ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ๊ฐ€ ์ˆ˜์‹ญ๊ฐœ๊ฐ€ ๋‚˜๊ฐ„ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๐Ÿคฏ๐Ÿคฏ๐Ÿคฏ

     

    N+1 ๋ฌธ์ œ๋Š” ์ด์™€ ๊ฐ™์ด ์˜๋„ํ•˜์ง€ ์•Š์€ ์ฟผ๋ฆฌ๊ฐ€ ์ˆ˜์‹ญ๊ฐœ๊ฐ€ ๋‚˜๊ฐ€๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. 

    ์ด๋Š” 1:N ์กฐ์ธ ์ƒํ™ฉ์—์„œ 1 ์ชฝ์—์„œ ์กฐํšŒ๋ฅผ ํ•  ๊ฒฝ์šฐ, ์˜๋„ํ•œ ์ฟผ๋ฆฌ ํ•œ๊ฐœ(์‹ค์ œ๋กœ ๊ฐ์ฒด๋ฅผ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ)์™€ N ๊ฐœ๋งŒํผ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€๋Š” ๊ฒƒ์ด๋‹ค.

    ๋‚˜์˜ ๊ฒฝ์šฐ๋ฅผ ์˜ˆ์‹œ๋กœ ๋“ค๋ฉด, ๊ฒŒ์ž„์˜ ๊ฐœ์ˆ˜๋งŒํผ ํŒ€์„ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ๊ฐ€ N๊ฐœ๋งŒํผ ๋ฐœ์ƒํ•œ๋‹ค.

    ์ง‘์‚ฌ์™€ ๋ฐ˜๋ ค๋™๋ฌผ๊ณผ์˜ ๊ด€๊ณ„๋กœ ์˜ˆ๋ฅผ ๋“ค์ž๋ฉด ์ง‘์‚ฌ์˜ ๋ชจ๋“  ๋ฐ˜๋ ค๋™๋ฌผ์„ ์กฐํšŒํ•˜๋ ค๊ณ  ํ–ˆ๋”๋‹ˆ N๊ฐœ๋งŒํผ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€๋Š” ๊ฒƒ์ด๋‹ค.

     

    ์ง€๊ธˆ ์ด ๋ฌธ์ œ๋Š” ์™œ ๋ฐœ์ƒํ•˜๊ณ  ์žˆ์„๊นŒ?

    ๐Ÿ“ N+1 ์ด ๋ฐœ์ƒํ•˜๋Š” ์ด์œ 

    return games.stream()
            .map(game -> new GameResponseDto(game,
                    gameTeamRepository.findAllByGameWithTeam(game).stream()
                            .sorted(comparingLong(GameTeam::getId)).toList(),
                    game.getSport()))
            .toList();

     

    ๐Ÿ‘€ ์™œ ์ด ์ฝ”๋“œ์—์„œ๋Š” N+1 ์ด ๋ฐœ์ƒํ•˜๋Š”๊ฐ€?

    ์œ„์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด, GameTeam ์˜ id ๋ฅผ ์กฐํšŒํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

    ํ˜„์žฌ games, ์ฆ‰ ๊ฒฝ๊ธฐ๋“ค์— ๋Œ€ํ•ด์„œ ๋ฐ˜๋ณต์ ์œผ๋กœ ํ•ด๋‹น ๊ฒฝ๊ธฐ์— ์ฐธ์—ฌํ•˜๊ณ  ์žˆ๋Š” ํŒ€๋“ค์„ ์ฐพ์•„์„œ id ์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜์—ฌ dto ์˜ ์ธ์ž์— ๋„ฃ๊ณ  ์žˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ GameTeam ์˜ id ๋ฅผ ์กฐํšŒํ•˜๊ฒŒ ๋˜๋‹ˆ, ๊ฒฝ๊ธฐ ํ•˜๋‚˜ ๋‹น (1) ๊ฒฝ๊ธฐ์— ์ฐธ์—ฌํ•˜๋Š” ํŒ€์˜ ๊ฐœ์ˆ˜๋งŒํผ (N) ์ฟผ๋ฆฌ๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ ๋‚˜๊ฐ€๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

     

    games ๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ game_teams ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , ๊ฒฝ๊ธฐ ๋ชฉ๋ก(games)์„ ์กฐํšŒํ•  ๋•Œ๋Š” ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ์‹ค์ œ๋กœ game_teams ๊ฐ์ฒด์— ์ ‘๊ทผํ•˜์—ฌ id ๋ฅผ ์กฐํšŒํ•˜๋Š” ์ˆœ๊ฐ„ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

     

    ๐Ÿ‘€ N+1 ๋ฌธ์ œ๋Š” ๊ทผ๋ณธ์ ์œผ๋กœ ์™œ ๋ฐœ์ƒํ•˜๋Š”๊ฐ€?

    ์ด๋ ‡๊ฒŒ ์šฐ๋ฆฌ์˜ ์˜ˆ์ƒ๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€๋Š” ์ด์œ ๋Š” ๊ฐ์ฒด์ง€ํ–ฅ๊ณผ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ„์˜ ํŒจ๋Ÿฌ๋‹ค์ž„ ์ฐจ์ด๋กœ ์ธํ•ด์„œ์ด๋‹ค.

    ๊ฐ์ฒด๋Š” A ๊ฐ€ B ๋ฅผ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋‹ค๋ฉด ์ง์ ‘์ ์œผ๋กœ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, GameTeam ์ด Team ์„ ์ฐธ์กฐํ•˜๊ณ  ์žˆ์œผ๋‹ˆ GameTeam.team ์„ ํ†ตํ•ด ๋ฐ”๋กœ team ์œผ๋กœ์˜ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ๋ ˆํผ๋Ÿฐ์Šค๋งŒ ๊ฐ–๊ณ  ์žˆ๋‹ค๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋‚ด์—์„œ Random Access ๋ฅผ ํ†ตํ•ด ๋ฐ”๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

    ๊ทธ๋Ÿฌ๋‚˜ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๊ฒฝ์šฐ์—๋Š” SQL ์ฟผ๋ฆฌ๋งŒ์„ ํ†ตํ•ด์„œ ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค.

    ๐Ÿ“ N+1 ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•

    fetch join ์„ ์‚ฌ์šฉํ•œ๋‹ค.

    fetch join ์€ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋‚˜ ์ปฌ๋ ‰์…˜๋“ค์„ ํ•œ๋ฒˆ์— ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก JPQL ์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

    ์˜ˆ๋ฅผ ๋“ค์–ด์„œ, game ์„ ์กฐํšŒํ•  ๋•Œ ์—ฐ๊ด€๋œ game_teams ๊ฐ์ฒด๋“ค๋„ ํ•˜๋‚˜์˜ ์ฟผ๋ฆฌ๋กœ ์กฐํšŒํ•ด์˜ค๋Š” ๊ฒƒ์ด๋‹ค.

    SELECT g FROM Game g JOIN FETCH g.gameTeams WHERE g.id = :gameId

     

     

     

    ๊ทธ๋Ÿฌ๋‚˜ fetch join ์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋ช‡ ๊ฐ€์ง€์˜ ์ฃผ์˜ํ•  ์ ๋“ค์ด ์กด์žฌํ•œ๋‹ค.

    Distinct ์ ˆ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

    ๋ ˆ์ฝ”๋“œ Game GameTeam
    1 Game1 GameTeam1
    2 Game1 GameTeam2
    3 Game2 GameTeam3

     

    fetch join ์„ ์‹คํ–‰ํ•œ ๊ฒฐ๊ณผ์˜ ์˜ˆ์‹œ๋Š” ์œ„์™€ ๊ฐ™๋‹ค. ์ด๋Š” ์ค‘๋ณต๋˜์–ด์„œ ์กด์žฌํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฐ˜๋“œ์‹œ distinct ๋ฅผ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

    SQL ์—์„œ์˜ distinct ๋Š” join ๋˜์–ด์„œ ๋ฐœ์ƒํ•œ ๊ฒฐ๊ณผ์— ๋Œ€ํ•ด ์ค‘๋ณต์„ ์ œ๊ฑฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์šฐ๋ฆฌ๊ฐ€ ์˜๋„ํ•œ ๋Œ€๋กœ ์ค‘๋ณต์ด ์ œ๊ฑฐ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค. SQL ์ƒ์œผ๋กœ๋Š” Game1 + GameTeam1 ์ด ์žˆ๋Š” row ์™€ Game1๊ณผ GameTeam2 ๊ฐ€ ์žˆ๋Š” row ๋Š” ๋™์ผํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

    ๊ทธ๋Ÿฌ๋‚˜ JPQL ์ƒ์œผ๋กœ์˜ distinct ๋Š” ๊ฐ์ฒด ์ž์ฒด์— ๋Œ€ํ•œ ์ค‘๋ณต์„ ์ œ๊ฑฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์šฐ๋ฆฌ๊ฐ€ ์˜๋„ํ•œ ๋Œ€๋กœ ์ค‘๋ณต์„ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค.

    Paging ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

    fetch join ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด, ๋ฉ”๋ชจ๋ฆฌ ์ƒ์— ๋ชจ๋“  ๊ฐ์ฒด๊ฐ€ ์กด์žฌํ•˜๊ฒŒ ๋˜๊ณ  ๊ทธ ์ƒํƒœ์—์„œ ํŽ˜์ด์ง•์„ ์‹คํ–‰ํ•˜๊ฒŒ ๋œ๋‹ค.

    ๋งŒ์•ฝ, fetch join ์„ ์‚ฌ์šฉํ•˜์—ฌ Game ์„ ๊ธฐ์ค€์œผ๋กœ ํŽ˜์ด์ง•์„ ํ•˜๊ฒŒ ๋˜๋ฉด, ํ…Œ์ด๋ธ”์— ์กด์žฌํ•˜๋Š” ๋ชจ๋“  ์—ฐ๊ด€๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ๋กœ ๋กœ๋”ฉํ•ด์•ผ ํ•œ๋‹ค. 

    ์ด๋กœ ์ธํ•ด ๋ฉ”๋ชจ๋ฆฌ ๋‚ด๋ถ€์—์„œ ํŽ˜์ด์ง•์ด ์ผ์–ด๋‚˜๊ฒŒ ๋˜๊ณ , ์ด๋Š” ๋ฉ”๋ชจ๋ฆฌ ๊ณผ๋ถ€ํ•˜๋กœ ์ด์–ด์ ธ ์žฅ์•  ์š”์ธ์ด ๋  ์ˆ˜ ์žˆ๋‹ค.

     

    โŒ ์„ ํƒํ•˜์ง€ ์•Š์€ ์ด์œ 

    1. ํŽ˜์ด์ง•์ด ํ•„์š”ํ•œ ํ˜„์žฌ ์ƒํ™ฉ์—์„œ ์ ํ•ฉํ•˜์ง€ ์•Š๋‹ค.

    2. ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ๋งบ์–ด์ง„ ๊ฒฝ์šฐ์—๋งŒ ๊ฐ€๋Šฅํ•˜๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋งบ์ง€ ์•Š๊ธฐ๋กœ ๋‚ด๋ถ€์ ์œผ๋กœ ๋…ผ์˜ํ–ˆ๊ธฐ์— ์œ„์˜ ๋ฐฉ๋ฒ• ์—ญ์‹œ๋„ ์ œ์™ธํ–ˆ๋‹ค. ์™œ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋งบ์ง€ ์•Š๊ธฐ๋กœ ํ–ˆ๋Š”์ง€๋Š” ๋’ค์˜ ์„ ํƒ์ง€์—์„œ ๋” ์ž์„ธํžˆ ์„ค๋ช…ํ•˜๊ฒ ๋‹ค.

    BatchSize ๋ฅผ ํ™œ์šฉํ•œ๋‹ค.

    @BatchSize(size = 100)
    @OneToMany(mappedBy = "game")
    private List<GameTeam> gameTeams = new ArrayList<>();

     

    BatchSize ๋Š” ์œ„์™€ ๊ฐ™์ด ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Š” Game (1) ์ชฝ์—์„œ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋งบ์œผ๋ฉด ์ •ํ•ด๋‘” ๊ฐœ์ˆ˜(size) ๋งŒํผ in ์ ˆ๋กœ GameTeam(N) ์˜ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

     

    spring:
      jpa:
        properties:
            default_batch_fetch_size: 100

    ์ด๋Š” ์œ„์ฒ˜๋Ÿผ ์ง์ ‘ ์—”ํ‹ฐํ‹ฐ์— ์‚ฌ์ด์ฆˆ๋ฅผ ์ •์˜ํ•  ์ˆ˜๋„ ์žˆ๊ณ , ์ „์—ญ์ ์œผ๋กœ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด ์œ„์ฒ˜๋Ÿผ yaml ์— ๊ธฐ์ž…ํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

     

    ์œ„์˜ ๋ฐฉ๋ฒ•์„ ์ ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ์ฟผ๋ฆฌ๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‚˜๊ฐˆ ๊ฒƒ์ด๋‹ค.

    // games ์กฐํšŒ ์ฟผ๋ฆฌ
    
    select * from game_teams where game_teams.game IN (games ์กฐํšŒ ์ฟผ๋ฆฌ๋กœ ์•Œ๊ฒŒ ๋œ game ์˜ id)

     

    BatchSize ์™€ fetch join ๋น„๊ต

    BatchSize ๋Š” ์„ค์ •ํ•ด๋†“์€ ์‚ฌ์ด์ฆˆ์— ๋น„ํ•ด ์กฐํšŒ๋˜์–ด์•ผ ํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ์˜ ๊ฐœ์ˆ˜๊ฐ€ ๋” ๋งŽ๋‹ค๋ฉด, ์ฟผ๋ฆฌ๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ๊ฐ€ ๋‚˜๊ฐˆ ์ˆ˜ ์žˆ๋‹ค. ์ด๋กœ ์ธํ•ด ์ฟผ๋ฆฌ ๊ฐœ์ˆ˜์˜ ๊ด€์ ์—์„œ๋Š” fetch join ์ด ์œ ๋ฆฌํ•˜๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋งŒ์•ฝ batch size ๊ฐ€ 10์ธ๋ฐ ์กฐํšŒ๋˜์–ด์•ผ ํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ์˜ ๊ฐœ์ˆ˜๊ฐ€ 100๊ฐœ๋ผ๋ฉด 10๋ฒˆ์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€์•ผ ํ•œ๋‹ค.

    ๊ทธ๋Ÿฌ๋‚˜, fetch join ์€ join ์„ ๋จผ์ € ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ๋•Œ๋ฌธ์— ์ค‘๋ณต๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€๋งŒ batch size ์˜ ๊ฒฝ์šฐ์—๋Š” join ์„ ํ•˜์ง€ ์•Š๊ณ  ์กฐํšŒํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ค‘๋ณต๋˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ ๋ฐ์ดํ„ฐ ์ „์†ก๋Ÿ‰์˜ ๊ด€์ ์—์„œ๋Š” BatchSize ๊ฐ€ ์œ ๋ฆฌํ•˜๋‹ค.

     

    โŒ ์„ ํƒํ•˜์ง€ ์•Š์€ ์ด์œ 

    ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์ด์ „์—, ํŒ€ ๋‚ด์—์„œ API ์ŠคํŽ™ ์ƒ์œผ๋กœ ํ•„์š”ํ•œ ์‘๋‹ต ํ˜•ํƒœ์— ๋งž์ถฐ์„œ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์–‘๋ฐฉํ–ฅ ๋งคํ•‘์„ ๋งบ์ง€ ์•Š๊ธฐ๋กœ ๋…ผ์˜๋ฅผ ๋งˆ์นœ ์ƒํƒœ์˜€๋‹ค. ์ด๋Š” API ์ŠคํŽ™ ์ƒ์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด (ex. ๊ฒฝ๊ธฐ์˜ ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜ํ•  ๋•Œ ๊ฒฝ๊ธฐ์— ์ฐธ์—ฌํ•˜๋Š” ํŒ€์˜ ๋ชฉ๋ก์€ ๋ฐ˜ํ™˜ํ•  ํ•„์š”๊ฐ€ ์—†์–ด์งˆ ๊ฒฝ์šฐ) ์—ฐ๊ด€๊ด€๊ณ„์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ๊ฒƒ์€ ์˜ณ์ง€ ์•Š์€ ์˜์กด๊ด€๊ณ„๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด์—ˆ๋‹ค.

    ๋น„์ง€๋‹ˆ์Šค ๋กœ์ง ์ƒ์œผ๋กœ ํ•ด๋‹น ๊ฐ์ฒด๊ฐ€ ๋ถ„๋ช…ํžˆ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋งบ๊ณ  ์žˆ๋‹ค๊ฑฐ๋‚˜, ์ƒ๋ช… ์ฃผ๊ธฐ๋ฅผ ๊ฐ™์ด ํ•ด์„œ ๊ฐ์ฒด๋ฅผ ๋™์‹œ์— ๊ด€๋ฆฌํ•  ํ•„์š”์„ฑ์ด ๋Š๊ปด์ง€๋Š” ๊ฒฝ์šฐ์—๋งŒ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋งบ๋„๋ก ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

    Projection ์„ ์ด์šฉํ•ด dto ๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ

    select new ํŒจํ‚ค์ง€ ๊ฒฝ๋กœ.GameResponseDto(์›ํ•˜๋Š” ํ•„๋“œ)
    from Game g
    join g.game_teams gt
    where gt.game_id = g.id

    ์œ„์™€ ๊ฐ™์ด ์ง์ ‘์ ์œผ๋กœ ํ•„์š”ํ•œ ํ•„๋“œ๋“ค์„ ๋ช…์‹œํ•˜์—ฌ ๋ฐ”๋กœ dto ๋กœ ๋ณ€ํ™˜ํ•˜๊ฒŒ ๋˜๋ฉด, ํ•„์š”ํ•œ ์ปฌ๋Ÿผ๋งŒ ๋ช…์‹œํ•˜์—ฌ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค.

    ๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฐ€ ๋™์‹œ์— ์˜๋ฏธํ•˜๋Š” ๊ฒƒ์€, ํ•„์š”ํ•œ ์ปฌ๋Ÿผ์„ ๋ชจ๋‘ ๋ช…์‹œํ•ด์•ผ ํ•˜๊ธฐ์— API ์ŠคํŽ™์ด ๋ฐ”๋€Œ๊ฒŒ ๋˜๋ฉด ์œ„์˜ ์ฝ”๋“œ๋„ ์ˆ˜์ •๋˜์–ด์•ผ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

    ์ฆ‰, DTO ์˜ ๋ณ€๊ฒฝ์ด DAO ๊นŒ์ง€ ๋ณ€๊ฒฝ์„ ์ „ํŒŒํ•˜๊ฒŒ ๋˜์–ด ์ข‹์ง€ ์•Š์€ ๋ฐฉ๋ฒ•์ด ๋  ์ˆ˜ ์žˆ๋‹ค.

    ์ด๋ฅผ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”, DTO ์˜ ๋ณ€๊ฒฝ์ด DAO ๊นŒ์ง€ ๋ณ€๊ฒฝ์„ ์ „ํŒŒํ•˜๊ฒŒ ๋˜๋Š” ์ฟผ๋ฆฌ์™€ ๊ทธ๋ ‡์ง€ ์•Š์€ ์ฟผ๋ฆฌ๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ด€๋ฆฌํ•ด์•ผ ํ•œ๋‹ค.

     

    โŒ ์„ ํƒํ•˜์ง€ ์•Š์€ ์ด์œ 

    ์œ„์˜ ๋ฐฉ๋ฒ• ์—ญ์‹œ๋„, ์ฟผ๋ฆฌ๋ฅผ ๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋งบ์–ด์•ผ ํ•œ๋‹ค.

    ๊ทธ๋ ‡๊ธฐ์— ์ด ๋ฐฉ๋ฒ• ์—ญ์‹œ๋„ ์„ ํƒํ•˜์ง€ ์•Š์•˜๋‹ค.

    N+1 ๋ฌธ์ œ๋ฅผ ๊ณ ๋ คํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์งœ๊ธฐ

    List<Game> games = gameDynamicRepository.findAllByLeagueAndStateAndSports(queryRequestDto, pageRequest);
            List<GameTeam> gameTeams = gameTeamQueryRepository.findAllByGameIds(
                    games.stream()
                            .map(Game::getId)
                            .toList()
            );
    
    Map<Game, List<GameTeam>> groupedByGame = gameTeams.stream()
            .collect(groupingBy(GameTeam::getGame));
    
    return games.stream()
            .map(game -> new GameResponseDto(game, groupedByGame.getOrDefault(game, new ArrayList<>()),
                    game.getSport()))
            .toList();

     

    fetch join ์€ ํŽ˜์ด์ง•์œผ๋กœ ์ธํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ณ , BatchSize ๋Š” ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„๋ฅผ ๋งบ์ง€ ์•Š์•„ ํ™œ์šฉํ•  ์ˆ˜ ์—†๊ธฐ์— ๊ฒฐ๊ตญ N+1 ๋ฌธ์ œ๋ฅผ ๊ณ ๋ คํ•ด ์ฝ”๋“œ๋ฅผ ์งœ๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค. ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ๋ฐฉ๋ฒ•๋“ค์€ ๋ชจ๋‘ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋งบ์–ด์•ผ ๊ฐ€๋Šฅํ•œ ๋ฐฉ๋ฒ•๋“ค์ด์—ˆ๊ธฐ์— ํ•ด๋‹น ๋ฌธ์ œ๋ฅผ ์—ผ๋‘์— ๋‘๊ณ  ์ฝ”๋“œ๋ฅผ ์งœ๋Š” ๊ฒƒ์„ ํ†ตํ•ด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

     

    ์šฐ์„ , ๊ฒฝ๊ธฐ ์ „์ฒด๋ฅผ ์กฐํšŒํ•ด games ์— ๋‹ด๋Š”๋‹ค. ๊ทธ๋ฆฌ๊ณ  GameTeam ์ „์ฒด ์ค‘์— ํ•ด๋‹น ๊ฒฝ๊ธฐ์— ์ฐธ์—ฌํ•˜๊ณ  ์žˆ๋Š” ํŒ€๋“ค์„ ์ „๋ถ€ ์กฐํšŒํ•ด์˜จ๋‹ค. ์ด๋•Œ ๊ฒฝ๊ธฐ์˜ id ๋“ค์€ stream ์„ ์ด์šฉํ•ด ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด, ์กฐ๊ฑด์— ๋งž๋Š” ๊ฒฝ๊ธฐ ๊ฐ์ฒด๋“ค๊ณผ ๊ฒฝ๊ธฐ์— ์ฐธ์—ฌํ•˜๋Š” ํŒ€ ๊ฐ์ฒด๋“ค์€ ์ „๋ถ€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ 1์ฐจ ์บ์‹œ์— ๋‹ด๊ธฐ๊ฒŒ ๋˜์—ˆ๋‹ค. ์ฆ‰, ๋” ์ด์ƒ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐˆ ์ผ์€ ์—†๋‹ค. ์ดํ›„ groupedByGame ์— ๊ฒฝ๊ธฐ์— ์ฐธ์—ฌํ•˜๋Š” ํŒ€๋“ค์„ ๊ฒฝ๊ธฐ๋ฅผ key ๋กœ ํ•˜์—ฌ ๊ทธ๋ฃนํ™”ํ•œ๋‹ค. 

     

    ์ด๋ฅผ ํ†ตํ•ด ์ฟผ๋ฆฌ๋ฅผ ๋‘๋ฒˆ๊นŒ์ง€ ์ค„์ผ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

     

    gameTeamQueryRepository.findAllByGameIds ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ์˜ ์ฟผ๋ฆฌ

    ๐ŸŒ ์ฐธ๊ณ  ์ž๋ฃŒ

    https://velog.io/@xogml951/JPA-N1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EC%B4%9D%EC%A0%95%EB%A6%AC

     

    JPA N+1 ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ๋ฒ• ์ด์ •๋ฆฌ

    JPA๋กœ ๊ฐœ๋ฐœ ๊ณผ์ •์—์„œ N+1๋ฌธ์ œ๋ฅผ ๋งŒ๋‚˜๋ฉด์„œ ํ•ด๋งธ๋˜ ๋ถ€๋ถ„๋“ค๊ณผ ์‹ค์ œ ํ”„๋กœ๋•์…˜์—์„œ ์ด๋ฅผ ์ œ๋Œ€๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ๋ชปํ•˜๋ฉด ์„ฑ๋Šฅ์ ์ธ ์ €ํ•˜๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์žฅ์• ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋ถ€๋ถ„์„ ๊นจ๋‹ซ๊ณ  ์ •๋ฆฌ ํ•ด๋ณด๊ณ ์ž ํ•ฉ

    velog.io

     

Designed by Tistory.