๐Ÿš€ Growth/Troubleshooting

๋ฐ˜๋ณต ์ง‘๊ณ„ ์ฟผ๋ฆฌ ํ†ตํ•ฉ ๋ฐ ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ์ตœ์ ํ™”

devCloud 2026. 8. 5. 18:33
728x90
QUERY OPTIMIZATION

๋ฐ˜๋ณต COUNT ์ฟผ๋ฆฌ์™€ ์ง€์—ฐ ๋กœ๋”ฉ ์ถ”๊ฐ€ ์กฐํšŒ๋ฅผ ์ตœ์ ํ™”ํ•œ ๊ณผ์ •

1. ์ž‘์—… ๋ฐฐ๊ฒฝ

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

๋Œ€์ƒ ๊ธฐ๋Šฅ์€ GET /boards ๊ฒŒ์‹œํŒ ๋ชฉ๋ก ์กฐํšŒ์™€ GET /posts/{postId} ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ ์กฐํšŒ์˜€๋‹ค. ๊ธฐ๋Šฅ ๊ตฌํ˜„ ํ›„ SQL ์‹คํ–‰ ๊ตฌ์กฐ๋ฅผ ์ ๊ฒ€ํ•œ ๊ฒฐ๊ณผ, ์กฐํšŒ ๊ฒฐ๊ณผ๋ฅผ DTO๋กœ ์กฐ๋ฆฝํ•˜๋Š” ๊ณผ์ •์—์„œ ๋™์ผํ•œ ๋ชฉ์ ์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐ˜๋ณต๋˜๊ฑฐ๋‚˜ ์ง€์—ฐ ๋กœ๋”ฉ๋œ ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐœ๋ณ„์ ์œผ๋กœ ์กฐํšŒํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ํ™•์ธ๋๋‹ค.

๊ตฌ์ฒด์ ์œผ๋กœ ๊ฒŒ์‹œํŒ ๋ชฉ๋ก์—์„œ๋Š” ๊ฐ ๊ฒŒ์‹œํŒ์˜ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜๊ธฐ ์œ„ํ•ด ๊ฒŒ์‹œํŒ๋งˆ๋‹ค COUNT ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ ์กฐํšŒ์—์„œ๋Š” Post๋ฅผ ๋จผ์ € ์กฐํšŒํ•œ ๋’ค Board, Category, Author์— ์ ‘๊ทผํ•˜๋ฉด์„œ ์ถ”๊ฐ€ ์กฐํšŒ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. ๋‘ ์กฐํšŒ ๊ธฐ๋Šฅ์˜ ๋ฌธ์ œ ์›์ธ์ด ๋‹ฌ๋ž๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ๊ฐ ์ผ๊ด„ ์ง‘๊ณ„์™€ ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ ๋™์‹œ ์กฐํšŒ ๋ฐฉ์‹์œผ๋กœ ๊ฐœ์„ ํ–ˆ๋‹ค.

๋‘ ์‚ฌ๋ก€๋Š” ๊ฐ™์€ ์กฐํšŒ ์ตœ์ ํ™” ์ž‘์—…์ด์ง€๋งŒ ์„ฑ๊ฒฉ์€ ๋‹ค๋ฅด๋‹ค. ๊ฒŒ์‹œํŒ ๋ชฉ๋ก์€ ๋ฐ์ดํ„ฐ ๊ฐœ์ˆ˜์— ๋”ฐ๋ผ ์ฟผ๋ฆฌ๊ฐ€ ์ฆ๊ฐ€ํ•˜๋Š” ๋ช…ํ™•ํ•œ N+1 ๋ฌธ์ œ์ด๊ณ , ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ ์กฐํšŒ๋Š” ๋‹จ๊ฑด ์กฐํšŒ ๊ณผ์ •์—์„œ ์ง€์—ฐ ๋กœ๋”ฉ์œผ๋กœ ๋ฐœ์ƒํ•œ ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ ์ถ”๊ฐ€ ์กฐํšŒ๋ฅผ ์ค„์ธ ์‚ฌ๋ก€๋‹ค.
๊ธฐ๋ก ๊ธฐ์ค€
์•„๋ž˜ SQL์€ ๋‹น์‹œ ๋‚จ๊ฒจ ๋‘” ์ฟผ๋ฆฌ ์ˆ˜์™€ ์‹คํ–‰ ๊ตฌ์กฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋‹จ์ˆœํ™”ํ•œ ์˜ˆ์‹œ๋‹ค. ์‹ค์ œ Hibernate SQL ๋กœ๊ทธ์˜ ์ „์ฒด ์ปฌ๋Ÿผ๊ณผ ๋ณ„์นญ์„ ๊ทธ๋Œ€๋กœ ์˜ฎ๊ธด ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.

2. ๊ฒŒ์‹œํŒ ๋ชฉ๋ก์˜ COUNT ์ฟผ๋ฆฌ N+1

GET /boards๋Š” ์‚ญ์ œ๋˜์ง€ ์•Š์€ ๊ฒŒ์‹œํŒ ๋ชฉ๋ก๊ณผ ๊ฒŒ์‹œํŒ๋ณ„ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๋ฅผ ํ•จ๊ป˜ ๋ฐ˜ํ™˜ํ•˜๋Š” API๋‹ค. ์ดˆ๊ธฐ ๊ตฌํ˜„์—์„œ๋Š” ๊ฒŒ์‹œํŒ ๋ชฉ๋ก์„ ๋จผ์ € ์กฐํšŒํ•œ ๋’ค, ๊ฐ ๊ฒŒ์‹œํŒ์„ DTO๋กœ ๋ณ€ํ™˜ํ•˜๋ฉด์„œ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๋ฅผ ๊ฐœ๋ณ„ ์กฐํšŒํ–ˆ๋‹ค.

// BoardService
public List<BoardResponse> getAllBoards() {
    return boardRepository.findAllByIsDeletedFalse()
            .stream()
            .map(board -> BoardResponse.from(
                    board,
                    postRepository.countByBoardIdAndIsDeletedFalse(
                            board.getId()
                    )
            ))
            .toList();
}

๊ฒŒ์‹œํŒ์ด 3๊ฐœ๋ผ๋ฉด ๊ฒŒ์‹œํŒ ๋ชฉ๋ก ์กฐํšŒ 1ํšŒ์™€ ๊ฒŒ์‹œํŒ๋ณ„ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜ ์กฐํšŒ 3ํšŒ๊ฐ€ ์‹คํ–‰๋œ๋‹ค. ๊ฒŒ์‹œํŒ ์ˆ˜๋ฅผ N์ด๋ผ๊ณ  ํ•˜๋ฉด ์ „์ฒด ์ฟผ๋ฆฌ ์ˆ˜๋Š” 1 + NํšŒ๊ฐ€ ๋œ๋‹ค.

-- ๊ฒŒ์‹œํŒ ๋ชฉ๋ก ์กฐํšŒ
SELECT *
FROM boards
WHERE is_deleted = false;

-- ๊ฐ ๊ฒŒ์‹œํŒ์˜ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๋ฅผ ๊ฐœ๋ณ„ ์กฐํšŒ
SELECT COUNT(*)
FROM post
WHERE board_id = 1
  AND is_deleted = false;

SELECT COUNT(*)
FROM post
WHERE board_id = 2
  AND is_deleted = false;

SELECT COUNT(*)
FROM post
WHERE board_id = 3
  AND is_deleted = false;
๊ฒŒ์‹œํŒ ์ˆ˜ ๋ชฉ๋ก ์กฐํšŒ COUNT ์กฐํšŒ ์ „์ฒด ์ฟผ๋ฆฌ ์ˆ˜
3๊ฐœ 1ํšŒ 3ํšŒ 4ํšŒ
N๊ฐœ 1ํšŒ NํšŒ 1 + NํšŒ

์ด ๊ตฌ์กฐ๋Š” ๊ฒŒ์‹œํŒ ์ˆ˜๊ฐ€ ์ฆ๊ฐ€ํ• ์ˆ˜๋ก COUNT ์ฟผ๋ฆฌ๋„ ๊ฐ™์€ ๋น„์œจ๋กœ ์ฆ๊ฐ€ํ•œ๋‹ค. ๊ฐœ๋ณ„ ๊ฒŒ์‹œํŒ์˜ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๋ฅผ ์กฐํšŒํ•˜๋Š” ๊ธฐ๋Šฅ ์ž์ฒด์—๋Š” ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ, ๋ชฉ๋ก API์—์„œ ๋ฐ˜๋ณต ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹์ด ๋น„ํšจ์œจ์ ์ด์—ˆ๋‹ค.

3. GROUP BY ๊ธฐ๋ฐ˜ ์ผ๊ด„ ์ง‘๊ณ„๋กœ ๊ฐœ์„ 

๊ฒŒ์‹œํŒ๋งˆ๋‹ค ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๋ฅผ ๋”ฐ๋กœ ๊ณ„์‚ฐํ•˜๋Š” ๋Œ€์‹ , ์ „์ฒด ๊ฒŒ์‹œํŒ์˜ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๋ฅผ ํ•œ ๋ฒˆ์˜ ์ง‘๊ณ„ ์ฟผ๋ฆฌ๋กœ ์กฐํšŒํ•˜๋„๋ก ๋ณ€๊ฒฝํ–ˆ๋‹ค.

// PostRepository
@Query("""
    SELECT p.board.id, COUNT(p)
    FROM Post p
    WHERE p.isDeleted = false
    GROUP BY p.board.id
    """)
List<Object[]> countByBoardGrouped();

Repository๋Š” ๊ฒŒ์‹œํŒ ID์™€ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๋ฅผ ํ•œ ๋ฒˆ์— ๋ฐ˜ํ™˜ํ•œ๋‹ค. Service์—์„œ๋Š” ์ง‘๊ณ„ ๊ฒฐ๊ณผ๋ฅผ Map<Long, Long>์œผ๋กœ ๋ณ€ํ™˜ํ•œ ๋’ค, ๊ฒŒ์‹œํŒ DTO๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ๊ฒŒ์‹œํŒ ID๋กœ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๋ฅผ ์กฐํšŒํ•˜๋„๋ก ๊ตฌ์„ฑํ–ˆ๋‹ค.

// BoardService
public List<BoardResponse> getAllBoards() {
    List<Board> boards =
            boardRepository.findAllByIsDeletedFalse();

    Map<Long, Long> postCountMap =
            postRepository.countByBoardGrouped()
                    .stream()
                    .collect(Collectors.toMap(
                            row -> (Long) row[0],
                            row -> (Long) row[1]
                    ));

    return boards.stream()
            .map(board -> BoardResponse.from(
                    board,
                    postCountMap.getOrDefault(
                            board.getId(),
                            0L
                    )
            ))
            .toList();
}

๋ณ€๊ฒฝ ํ›„์—๋Š” ๊ฒŒ์‹œํŒ ๋ชฉ๋ก ์กฐํšŒ 1ํšŒ์™€ ๊ฒŒ์‹œํŒ๋ณ„ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜ ์ง‘๊ณ„ 1ํšŒ๋งŒ ์‹คํ–‰๋œ๋‹ค. ๊ฒŒ์‹œํŒ ์ˆ˜์™€ ๊ด€๊ณ„์—†์ด ์ฟผ๋ฆฌ ์ˆ˜๊ฐ€ 2ํšŒ๋กœ ๊ณ ์ •๋œ๋‹ค.

-- ๊ฒŒ์‹œํŒ ๋ชฉ๋ก ์กฐํšŒ
SELECT *
FROM boards
WHERE is_deleted = false;

-- ๊ฒŒ์‹œํŒ๋ณ„ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๋ฅผ ํ•œ ๋ฒˆ์— ์ง‘๊ณ„
SELECT board_id, COUNT(id)
FROM post
WHERE is_deleted = false
GROUP BY board_id;
๊ฒŒ์‹œํŒ ๋ชฉ๋ก ์กฐํšŒ ๊ฒฐ๊ณผ
  • ๋ณ€๊ฒฝ ์ „: ๊ฒŒ์‹œํŒ 3๊ฐœ ๊ธฐ์ค€ 4ํšŒ ์กฐํšŒ
  • ๋ณ€๊ฒฝ ํ›„: ๊ฒŒ์‹œํŒ ์ˆ˜์™€ ๊ด€๊ณ„์—†์ด 2ํšŒ ์กฐํšŒ
  • ์ฟผ๋ฆฌ ์ฆ๊ฐ€ ๊ตฌ์กฐ: 1 + NํšŒ์—์„œ ๊ณ ์ • 2ํšŒ๋กœ ๋ณ€๊ฒฝ
  • ๊ฒŒ์‹œ๊ธ€์ด ์—†๋Š” ๊ฒŒ์‹œํŒ์€ getOrDefault(..., 0L)๋กœ ์ฒ˜๋ฆฌ

4. ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ ์กฐํšŒ์˜ ์ง€์—ฐ ๋กœ๋”ฉ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ

GET /posts/{postId}๋Š” ๊ฒŒ์‹œ๊ธ€ ๋ณธ๋ฌธ๋ฟ ์•„๋‹ˆ๋ผ ๊ฒŒ์‹œํŒ, ์นดํ…Œ๊ณ ๋ฆฌ, ์ž‘์„ฑ์ž ์ •๋ณด์™€ ๋Œ“๊ธ€ ๋ชฉ๋ก์„ ํ•จ๊ป˜ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ดˆ๊ธฐ ๊ตฌํ˜„์—์„œ๋Š” findById()๋กœ ๊ฒŒ์‹œ๊ธ€๋งŒ ์กฐํšŒํ•œ ๋’ค DTO ์ƒ์„ฑ ๊ณผ์ •์—์„œ ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ์— ์ ‘๊ทผํ–ˆ๋‹ค.

// PostService
public PostDetailResponseDto getPostById(
        Long postId,
        String email
) {
    Post post = postRepository.findById(postId)
            .orElseThrow(() ->
                    new EntityNotFoundException(
                            "์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒŒ์‹œ๊ธ€์ž…๋‹ˆ๋‹ค."
                    )
            );

    // DTO ์ƒ์„ฑ ๊ณผ์ •์—์„œ board, category, author ์ ‘๊ทผ
    ...
}

board, category, author๊ฐ€ ์ง€์—ฐ ๋กœ๋”ฉ์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด ์‹ค์ œ ์ ‘๊ทผ ์‹œ์ ์— ๋ณ„๋„ ์กฐํšŒ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๋‹น์‹œ ๊ธฐ๋ก์ƒ ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ ์กฐํšŒ๋Š” ์ „์ฒด 5ํšŒ์˜ ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋๋‹ค.

-- Post ์กฐํšŒ
SELECT *
FROM post
WHERE id = 1;

-- Board ์ง€์—ฐ ๋กœ๋”ฉ
SELECT *
FROM boards
WHERE id = 1;

-- Category ์ง€์—ฐ ๋กœ๋”ฉ
SELECT *
FROM category
WHERE id = 1;

-- ๋ฃจํŠธ ๋Œ“๊ธ€ ์กฐํšŒ
SELECT *
FROM comments
WHERE post_id = 1
  AND parent_id IS NULL;

-- ๋Œ€๋Œ“๊ธ€ ์ผ๊ด„ ์กฐํšŒ
SELECT *
FROM comments
WHERE parent_id IN (?, ?, ?);

๊ฒŒ์‹œ๊ธ€ ํ•œ ๊ฑด์„ ์กฐํšŒํ•˜๋Š” API์ด๋ฏ€๋กœ ์ด ์‚ฌ๋ก€๋ฅผ ๋ชฉ๋ก ๊ธฐ๋ฐ˜์˜ ์ „ํ˜•์ ์ธ N+1 ๋ฌธ์ œ๋ผ๊ณ  ํ‘œํ˜„ํ•˜๊ธฐ๋Š” ์–ด๋ ต๋‹ค. ๋‹ค๋งŒ ์ƒ์„ธ ์‘๋‹ต์— ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•œ ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐœ๋ณ„ ์ฟผ๋ฆฌ๋กœ ์กฐํšŒํ•˜๊ณ  ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ํ•œ ๋ฒˆ์˜ ์กฐํšŒ๋กœ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ๋Š” ์ถ”๊ฐ€ ๋น„์šฉ์ด ๋ฐœ์ƒํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

5. EntityGraph๋ฅผ ์ด์šฉํ•œ ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ ๋™์‹œ ์กฐํšŒ

์ƒ์„ธ ์‘๋‹ต์— ํ•ญ์ƒ ํ•„์š”ํ•œ board, category, author๋ฅผ ํ•œ ๋ฒˆ์— ์กฐํšŒํ•˜๊ธฐ ์œ„ํ•ด Repository ๋ฉ”์„œ๋“œ์— @EntityGraph๋ฅผ ์ ์šฉํ–ˆ๋‹ค.

// PostRepository
@EntityGraph(
    attributePaths = {
        "board",
        "category",
        "author"
    }
)
Optional<Post> findWithDetailsById(Long id);

Service์—์„œ๋Š” ๊ธฐ์กด์˜ findById() ๋Œ€์‹  findWithDetailsById()๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ๋ณ€๊ฒฝํ–ˆ๋‹ค.

// PostService
public PostDetailResponseDto getPostById(
        Long postId,
        String email
) {
    Post post =
            postRepository.findWithDetailsById(postId)
                    .orElseThrow(() ->
                            new EntityNotFoundException(
                                    "์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒŒ์‹œ๊ธ€์ž…๋‹ˆ๋‹ค."
                            )
                    );

    ...
}

์ ์šฉ ํ›„์—๋Š” Post, Board, Category, Author๊ฐ€ ์กฐ์ธ๋œ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ์กฐํšŒ๋œ๋‹ค. ๋Œ“๊ธ€ ํŠธ๋ฆฌ ์กฐํšŒ๋Š” ๋ณ„๋„ ์กฐํšŒ ์ „๋žต์„ ์œ ์ง€ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ „์ฒด ์ฟผ๋ฆฌ ์ˆ˜๋Š” 5ํšŒ์—์„œ 3ํšŒ๋กœ ์ค„์—ˆ๋‹ค.

-- Post์™€ ์ƒ์„ธ ์‘๋‹ต์— ํ•„์š”ํ•œ ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ ๋™์‹œ ์กฐํšŒ
SELECT ...
FROM post p
LEFT JOIN users u
       ON u.id = p.author_id
LEFT JOIN boards b
       ON b.id = p.board_id
LEFT JOIN category c
       ON c.id = p.category_id
WHERE p.id = 1;

-- ๋ฃจํŠธ ๋Œ“๊ธ€ ์กฐํšŒ
SELECT *
FROM comments
WHERE post_id = 1
  AND parent_id IS NULL;

-- ๋Œ€๋Œ“๊ธ€ ์ผ๊ด„ ์กฐํšŒ
SELECT *
FROM comments
WHERE parent_id IN (?, ?, ?);
๋Œ“๊ธ€ ์กฐํšŒ๋ฅผ ๋ณ„๋„๋กœ ์œ ์ง€ํ•œ ์ด์œ 
๊ฒŒ์‹œ๊ธ€์˜ ๋‹ค๋Œ€์ผ ์—ฐ๊ด€๊ด€๊ณ„์™€ ๋Œ“๊ธ€ ์ปฌ๋ ‰์…˜์„ ํ•œ ์ฟผ๋ฆฌ์—์„œ ๋ชจ๋‘ ์กฐ์ธํ•˜๋ฉด ๊ฒฐ๊ณผ ํ–‰์ด ์ค‘๋ณต๋  ์ˆ˜ ์žˆ๊ณ , ๋Œ“๊ธ€ ํŠธ๋ฆฌ ๊ตฌ์„ฑ๋„ ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฒˆ ์ž‘์—…์—์„œ๋Š” ๊ฒŒ์‹œ๊ธ€์˜ ๋‹จ๊ฑด ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ๋งŒ ํ†ตํ•ฉํ•˜๊ณ , ๋ฃจํŠธ ๋Œ“๊ธ€๊ณผ ๋Œ€๋Œ“๊ธ€ ์กฐํšŒ๋Š” ๊ธฐ์กด์˜ ๋‘ ๋‹จ๊ณ„ ์กฐํšŒ๋ฅผ ์œ ์ง€ํ–ˆ๋‹ค.

6. ๋ณ€๊ฒฝ ์ „ํ›„ ๊ฒฐ๊ณผ

API ๋ฌธ์ œ ๊ฐœ์„  ๋ฐฉ์‹ ๋ณ€๊ฒฝ ์ „ ๋ณ€๊ฒฝ ํ›„
GET /boards ๊ฒŒ์‹œํŒ๋งˆ๋‹ค ๊ฒŒ์‹œ๊ธ€ ์ˆ˜ ๊ฐœ๋ณ„ ์กฐํšŒ GROUP BY ์ผ๊ด„ ์ง‘๊ณ„ ๊ฒŒ์‹œํŒ 3๊ฐœ ๊ธฐ์ค€ 4ํšŒ 2ํšŒ
GET /posts/1 ์ง€์—ฐ ๋กœ๋”ฉ๋œ ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ ์ถ”๊ฐ€ ์กฐํšŒ @EntityGraph ์ ์šฉ 5ํšŒ 3ํšŒ

๊ฒŒ์‹œํŒ ๋ชฉ๋ก์€ ๋ฐ์ดํ„ฐ ๊ฐœ์ˆ˜์— ๋”ฐ๋ผ ์ฟผ๋ฆฌ๊ฐ€ ์ฆ๊ฐ€ํ•˜๋˜ ๊ตฌ์กฐ๋ฅผ ์ œ๊ฑฐํ–ˆ๋‹ค๋Š” ์ ์ด ํ•ต์‹ฌ์ด๋‹ค. ๊ฒŒ์‹œํŒ์ด ๋Š˜์–ด๋‚˜๋„ ๊ฒŒ์‹œํŒ ๋ชฉ๋ก๊ณผ ์ง‘๊ณ„ ์ฟผ๋ฆฌ ๋‘ ๋ฒˆ๋งŒ ์‹คํ–‰๋œ๋‹ค.

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

์ตœ์ข… ๊ฒฐ๊ณผ
  • ๊ฒŒ์‹œํŒ๋ณ„ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜ ์กฐํšŒ๋ฅผ 1 + NํšŒ์—์„œ ๊ณ ์ • 2ํšŒ๋กœ ์ถ•์†Œํ–ˆ๋‹ค.
  • ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ ์กฐํšŒ๋ฅผ 5ํšŒ์—์„œ 3ํšŒ๋กœ ์ถ•์†Œํ–ˆ๋‹ค.
  • ๋ชฉ๋ก ์ง‘๊ณ„์™€ ๋‹จ๊ฑด ์—ฐ๊ด€ ์กฐํšŒ์˜ ๋ฌธ์ œ ์„ฑ๊ฒฉ์„ ๊ตฌ๋ถ„ํ•ด ๊ฐ๊ฐ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐํ–ˆ๋‹ค.
  • ๋Œ“๊ธ€ ํŠธ๋ฆฌ ์กฐํšŒ๋Š” ๋ณ„๋„ ์ „๋žต์„ ์œ ์ง€ํ•ด ์กฐ์ธ ์ค‘๋ณต๊ณผ ์‘๋‹ต ์กฐ๋ฆฝ ๋ณต์žก๋„๋ฅผ ํ”ผํ–ˆ๋‹ค.

7. ์ •๋ฆฌ ๋ฐ ๋ฐฐ์šด ์ 

์ด๋ฒˆ ์ž‘์—…์„ ํ†ตํ•ด N+1 ๋ฌธ์ œ๋Š” ์—ฐ๊ด€๊ด€๊ณ„ ์ง€์—ฐ ๋กœ๋”ฉ์—์„œ๋งŒ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๋ชฉ๋ก์„ ์ˆœํšŒํ•˜๋ฉด์„œ Repository ๋ฉ”์„œ๋“œ๋ฅผ ๋ฐ˜๋ณต ํ˜ธ์ถœํ•˜๋Š” ์„œ๋น„์Šค ๋กœ์ง์—์„œ๋„ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ํ™•์ธํ–ˆ๋‹ค.

๊ฒŒ์‹œํŒ ๋ชฉ๋ก์—์„œ๋Š” ๊ฐ ๊ฒŒ์‹œํŒ์˜ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๋ฅผ ๊ฐœ๋ณ„ ์กฐํšŒํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์ง์ ‘์ ์ธ ์›์ธ์ด์—ˆ๋‹ค. ๋‹จ์ˆœํžˆ ์—ฐ๊ด€๊ด€๊ณ„์˜ fetch ์ „๋žต์„ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์œผ๋กœ๋Š” ํ•ด๊ฒฐํ•  ์ˆ˜ ์—†์—ˆ๊ณ , ํ•„์š”ํ•œ ์ง‘๊ณ„ ๊ฒฐ๊ณผ๋ฅผ GROUP BY๋กœ ํ•œ ๋ฒˆ์— ์กฐํšŒํ•˜๋Š” ๋ฐฉ์‹์ด ์ ์ ˆํ–ˆ๋‹ค.

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

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

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

728x90