Spring Boot · 2026-03-20

Spring Boot에서 GraphQL DataLoader로 N+1 해결

Spring Boot와 GraphQL DataLoader를 사용해 N+1 문제의 원리와 구현 방법, 검증 절차를 단계별로 설명

작성일 : 2026-03-20 ㆍ 작성자 : 관리자
post
목차

개요

GraphQL을 도입한 애플리케이션에서 흔히 마주하는 성능 문제 중 하나가 N+1 문제다. 이 문제는 클라이언트에서 하나의 쿼리로 여러 연관 데이터를 요청할 때, 데이터베이스에 불필요하게 많은 쿼리가 발생하는 현상을 뜻한다. Spring Boot 환경에서 GraphQL DataLoader를 활용하면 이 문제를 효과적으로 완화할 수 있다. 아래에서는 N+1 문제의 개념부터 DataLoader 적용 예시, 검증 방법까지 실무 관점에서 차근차근 설명한다.

N+1 문제 이해

무슨 일이 벌어지는가

예를 들어 게시글 목록을 요청하면 각 게시글의 작성자 정보를 함께 반환하는 경우가 많다. 게시글 10개와 함께 작성자 정보가 필요하면, 게시글을 가져오는 1번 쿼리와 각 작성자 정보를 가져오는 추가 10번의 쿼리가 발생할 수 있다. 이렇게 총 N+1번의 쿼리가 실행되면 응답 지연과 DB 부하가 커진다.

왜 문제가 되는가

쿼리 수가 늘어나면 네트워크 왕복, DB 커넥션 사용, 쿼리 파싱 비용 등이 누적된다. 트래픽이 늘어나면 지연이 크게 증가하고, 스케일링 비용도 커진다. 따라서 동일한 데이터를 모아서 한 번의 배치 쿼리로 처리하는 것이 중요하다.

DataLoader 개념

DataLoader는 여러 요청을 모아서(batch) 한 번에 처리하고 결과를 요청자에게 분배하는 패턴을 제공한다. 요청을 지연시키지 않으면서 같은 이벤트 루프 또는 요청 컨텍스트 내에서 발생한 데이터 접근을 묶어 쿼리 수를 줄인다. GraphQL과 자연스럽게 결합되어 각 요청별로 DataLoader 인스턴스를 생성해 사용한다.

Spring Boot에서의 구현 흐름

구현의 핵심은 다음과 같다.

  • DataLoader 등록: 각 HTTP 요청별로 DataLoaderRegistry를 생성
  • Resolver에서 DataLoader 사용: 연관 데이터를 직접 조회하지 않고 DataLoader에게 위임
  • BatchLoader 구현: 여러 키를 받아 한 번에 DB 조회를 수행
  • 검증: 로그나 쿼리 모니터링으로 실제 쿼리 수 감소 확인

설정 및 코드 예시

다음은 간단한 설정과 Resolver 예시다. 게시글(Post)과 사용자(User)가 있고, Post에서 작성자 정보를 DataLoader로 조회한다고 가정한다.

BatchLoader 구현

package com.example.graphql;

import org.dataloader.BatchLoader;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

public class UserBatchLoader implements BatchLoader<Long, User> {
  private final UserRepository userRepository;

  public UserBatchLoader(UserRepository userRepository) {
    this.userRepository = userRepository;
  }

  @Override
  public CompletableFuture<List<User>> load(List<Long> ids) {
    return CompletableFuture.supplyAsync(() -> {
      List<User> users = userRepository.findAllById(ids);
      Map<Long, User> map = users.stream()
        .collect(Collectors.toMap(User::getId, u -> u));
      return ids.stream().map(id -> map.get(id)).collect(Collectors.toList());
    });
  }
}

DataLoaderRegistry 등록

package com.example.graphql;

import org.dataloader.DataLoader;
import org.dataloader.DataLoaderRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DataLoaderConfig {

  @Bean
  public DataLoaderRegistry dataLoaderRegistry(UserRepository userRepository) {
    DataLoaderRegistry registry = new DataLoaderRegistry();
    registry.register("userLoader",
      DataLoader.newDataLoader(new UserBatchLoader(userRepository)));
    return registry;
  }
}

Resolver에서 DataLoader 사용

package com.example.graphql;

import graphql.schema.DataFetchingEnvironment;
import org.dataloader.DataLoader;
import java.util.concurrent.CompletableFuture;

public class PostResolver {
  public CompletableFuture<User> author(Post post, DataFetchingEnvironment env) {
    DataLoader<Long, User> loader = env.getDataLoader("userLoader");
    return loader.load(post.getAuthorId());
  }
}

검증 방법

실제 적용 후에는 다음 항목을 점검한다.

  • 쿼리 로그: N+1 발생 전후의 쿼리 수 비교
  • 응답 시간: 평균 응답 시간과 p95 지표 확인
  • 동시성 테스트: 동시에 많은 요청을 보냈을 때 DB 부하 변화

주의사항

  • DataLoader는 요청 범위(Request-scoped)로 생성해야 데이터 누수 방지
  • 캐싱 전략을 적절히 설정하면 동일 요청 내 불필요한 중복 조회를 줄일 수 있음
  • 복잡한 연관 관계에서는 배치 쿼리의 결과 정렬과 매핑에 유의

마무리

Spring Boot에서 GraphQL DataLoader를 도입하면 N+1 문제로 인한 불필요한 쿼리 폭증을 효과적으로 줄일 수 있다. 핵심은 각 요청마다 DataLoader를 생성해 BatchLoader에 위임하고, 결과를 클라이언트에 분배하는 구조를 만드는 것이다. 적용 후에는 로그와 성능 지표를 통해 실제 개선 효과를 검증하는 것이 중요하다. 이 과정을 통해 시스템의 응답 속도와 확장성을 개선할 수 있다.

graphql dataloader spring boot n+1 graphql spring boot 해결 spring boot graphql n+1 문제 graphql dataloader spring boot n+1 문제 graphql batching