BackEND/Java

실제 프로젝트에서 QueryDSL과 캐싱을 활용한 최적화 전략

mingmingIT 2025. 4. 5. 10:43

QueryDSL과 캐싱을 활용하면 데이터 조회 성능을 극대화할 수 있습니다. 이번 포스팅에서는 실제 프로젝트에서 QueryDSL과 Redis 캐싱을 적용하는 방법을 다루며, 최적화를 위한 다양한 전략을 소개합니다.


1. QueryDSL 기반의 고성능 데이터 조회 적용

1) 대량 데이터 조회 시 Streaming 사용

JPA는 대량 데이터를 한 번에 조회하면 메모리 문제를 유발할 수 있습니다. 이를 해결하기 위해 stream()을 사용할 수 있습니다.

Stream<Member> memberStream = queryFactory
    .selectFrom(member)
    .where(member.age.gt(20))
    .fetch().stream();

memberStream.forEach(System.out::println);

💡 stream()을 활용하면 대량 데이터를 한 번에 로딩하지 않고 순차적으로 처리할 수 있습니다.

2) 페이지네이션 최적화

페이징 조회 시 fetch()count()를 분리하여 불필요한 데이터 로딩을 줄일 수 있습니다.

public Page<MemberDto> findMembers(Pageable pageable) {
    List<MemberDto> content = queryFactory
        .select(Projections.constructor(MemberDto.class, member.name, member.age))
        .from(member)
        .offset(pageable.getOffset())
        .limit(pageable.getPageSize())
        .fetch();

    long total = queryFactory
        .select(member.count())
        .from(member)
        .fetchOne();

    return new PageImpl<>(content, pageable, total);
}

💡 데이터 수를 조회하는 쿼리를 별도로 실행하면 성능을 개선할 수 있습니다.

3) 인덱스 활용 및 튜닝

데이터 조회 속도를 향상시키기 위해 적절한 인덱스를 적용하는 것이 중요합니다.

@Entity
@Table(indexes = {@Index(name = "idx_member_name", columnList = "name")})
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String name;
}

💡 인덱스를 추가하면 조회 성능이 크게 개선될 수 있습니다.


2. Redis 캐싱을 활용한 조회 성능 최적화

1) Spring Boot에서 Redis 설정

Redis는 높은 성능을 제공하는 인메모리 데이터 저장소로, 자주 조회하는 데이터를 캐싱하는 데 유용합니다.

spring.redis.host=localhost
spring.redis.port=6379

2) 캐시 적용 (@Cacheable)

Redis를 활용하여 자주 조회하는 데이터를 캐싱하면 DB 부하를 줄일 수 있습니다.

@Cacheable(value = "memberCache", key = "#id")
public MemberDto getMember(Long id) {
    return queryFactory
        .select(Projections.constructor(MemberDto.class, member.name, member.age))
        .from(member)
        .where(member.id.eq(id))
        .fetchOne();
}

💡 @Cacheable을 적용하면 동일한 요청에 대해 Redis에서 데이터를 조회하여 성능을 개선할 수 있습니다.

3) 캐시 무효화 (@CacheEvict)

데이터가 변경될 경우 기존 캐시를 무효화하여 최신 데이터를 유지할 수 있습니다.

@CacheEvict(value = "memberCache", key = "#id")
public void updateMember(Long id, String name, int age) {
    queryFactory.update(member)
        .set(member.name, name)
        .set(member.age, age)
        .where(member.id.eq(id))
        .execute();
}

💡 @CacheEvict를 사용하면 데이터가 변경될 때 자동으로 캐시를 삭제할 수 있습니다.

4) 캐시 만료 시간 설정

캐싱된 데이터가 오래 유지되지 않도록 TTL(Time-To-Live) 값을 설정할 수 있습니다.

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(10));
    return RedisCacheManager.builder(redisConnectionFactory)
        .cacheDefaults(config)
        .build();
}

💡 TTL을 설정하면 오래된 데이터가 자동으로 삭제되어 최신 데이터를 유지할 수 있습니다.

5) 캐싱을 활용한 API 성능 개선

API 응답 속도를 높이기 위해 Redis 캐싱을 적용하면 빠른 데이터 제공이 가능합니다.

@GetMapping("/members/{id}")
public ResponseEntity<MemberDto> getMember(@PathVariable Long id) {
    MemberDto member = memberService.getMember(id);
    return ResponseEntity.ok(member);
}

💡 DB를 직접 조회하는 대신 캐싱된 데이터를 반환하면 API 성능이 크게 향상됩니다.


3. QueryDSL과 캐싱을 활용한 최적화 전략 정리

대량 데이터 조회 시 Streaming을 활용하여 메모리 사용 최적화
페이지네이션 최적화를 통해 불필요한 데이터 로딩 방지
적절한 인덱스를 추가하여 검색 성능 향상
Redis 캐싱을 적용하여 반복적인 조회 성능 향상
캐시 무효화 및 TTL 설정으로 최신 데이터 유지
API 응답 속도를 높이기 위해 캐싱을 적극 활용