본문 바로가기

Tech

[Tech] MSA에서 N+1 문제 해결해보기

0. 들어가며

  • “모놀리식에서 잘 되던 Fetch Join이 MSA에서 왜 갑자기 힘들어지는지”
  • 이번 글에서 다룰 것 : MSA 개념 → Join 문제 → N+1 → Bulk Fetch 조회 해결 

 

1. MSA란?

하나의 큰 애플리케이션을 독립적인 작은 서비스들로 나누어, 각 서비스가 독립적으로 개발, 배포, 확장될 수 있도록 하는 아키텍처

 

2. MSA vs 모놀리식

 
MSA
모놀리식
배포
해당 서비스만
전체
영향
해당 서비스만
전체
배포 시간
5분
30분
위험도
낮음
높음

하지만, 데이터 접근 방식이 달라지면서 새로운 문제가 생긴다.

 

3. 모놀리식에서는 ‘Fetch Join’ 으로 끝났던 이유

@Query("Select p from posts p join fetch p.user")​
  • 같은 DB / 같은 트랜잭션 범위
  • post + user 를 한방 쿼리로 조회가 가능했다

모놀리식은 DB Join으로 관계를 해결하지만, MSA 서비스 경계 때문에 Join을 사용할 수가 없다.

 

4. MSA에서는 Join이 왜 어려운가?

  • 데이터가 서비스마다 분리가 되어있음
  • A 서비스가 B 서비스의 데이터를 원하면 조인이 아닌 API 호출을 해야함
@FeignClient(name = "user-service")
public interface UserClient {

    @GetMapping("/api/users")
    List<UserDto> getAll();

    @GetMapping("/api/users/{id}")
    UserDto getUser(@PathVariable Long id);
}

 

5. 문제 발생: API Join 에서 N+1 터짐

 

1) 모든 게시글 조회하는 API 호출 시

public List<PostResponseDto> getAllPosts() {
        return postRepository.findAll().stream()
                .map(p -> PostResponseDto.from(p, userClientWrapper.getUser(p.getUserId())))
                .toList();
    }

 

2) Post서비스와 User서비스가 나누어져 있어서 User 를 불러오기 위해 N+1 문제가 발생한다.

.map(p -> PostResponseDto.from(p, userClientWrapper.getUser(p.getUserId())))

ex)

  • 게시글 목록 20개 조회 (post-service)
  • 각 게시글 작성자 정보 조회하려고 user-service를 20번 호출 → 총 21번 호출 N+1

 

6. 해결: ID 리스트로 한 번에 조회(Bulk Fetch)로 한 번에 가져와서 매핑

6-1) 해결 방법

  • 1) 게시글 목록에서 userId만 모아서 중복 제거
List<Long> ids = postRepository.findAll().stream()
        .map(r -> r.getUserId())
        .distinct()
        .toList();
 
  • 2) user-service에서 배치 API 로 한 번 호출하기
@GetMapping("/api/users/batch")
List<UserDto> getUsers(@RequestParam List<Long> ids);
 
  • 3) 결과를 Map<Long, UserDto> 으로 매핑하기
Map<Long, UserDto> userMap = users.stream()
        .collect(Collectors.toMap(UserDto -> UserDto.getId(), m -> m));
 
  • 4) List<PostResponseDto> 로 매핑 후 반환하기
return posts.stream()
        .map( p -> PostResponseDto.from(p, userMap.get(p.getUserId())))
        .toList();
 

6-2) 결과

  • 호출 수 : N + 1 → 1 + 1
  • 응답 시간 / 서버 부하 감소

 

7. 배운 점

지금 배우는 입장에서 작은 프로젝트에 MSA를 그대로 적용하는 건 오버엔지니어링이라고 느꼈다.

다만, 이번 경험을 통해 모놀리식에서는 당연했던 Join이 서비스 분리 순간 ‘API 조합’ 문제로 바뀌고, 그 과정에서 API N+1 → Bulk Fetch 를 통해 해결하는 방법을 배웠다. 결론적으로 MSA가 좋다 / 나쁘다가 아닌, 팀 규모, 배포 단위 등 요구가 생길 때 선택해야하는 아키텍처라는 기준도 배우게 되었다.

 


GitHub