프로그래밍/Nest.js

성능 최적화를 위한 캐싱 전략: Nest.js에서의 효과적인 구현

shimdh 2025. 3. 24. 13:36
728x90

캐싱은 현대 웹 개발에서 필수적인 기술로 자리 잡고 있으며, 애플리케이션의 성능을 크게 향상시킬 수 있는 중요한 요소입니다. 특히 Nest.js를 사용하여 애플리케이션을 구축할 때, 효과적인 캐싱 전략을 활용하면 데이터베이스에 대한 요청 수를 줄이고, 사용자에게 더 빠른 응답 시간을 제공하는 데 큰 도움이 됩니다. 이번 포스트에서는 캐싱의 개념과 Nest.js에서의 구현 방법, 그리고 실전 사례를 통해 성능 최적화를 위한 캐싱 전략을 살펴보겠습니다.

캐싱의 개념

캐시는 데이터를 임시로 저장하는 메모리 공간으로, 자주 요청되는 데이터를 신속하게 제공하기 위해 설계되었습니다. 캐싱의 주요 목적은 다음과 같이 두 가지로 나눌 수 있습니다:

  • 성능 향상: 반복적인 데이터 접근 시 발생하는 지연 시간을 최소화하여, 사용자에게 더 나은 경험을 제공합니다. 예를 들어, 웹 페이지 로딩 속도가 빨라지면 사용자 이탈률이 감소하고, 전환율이 증가할 수 있습니다.
  • 비용 절감: 서버 리소스와 데이터베이스 쿼리를 줄임으로써 운영 비용을 절감할 수 있습니다. 이는 특히 대규모 애플리케이션에서 중요한 요소로 작용합니다.

Nest.js에서의 캐싱 구현

Nest.js에서는 다양한 방법으로 캐싱을 구현할 수 있으며, 그 중 몇 가지 대표적인 방법은 다음과 같습니다:

1. 메모리 캐시

가장 간단한 형태로, 서버의 메모리에 데이터를 저장합니다. 이 방식은 빠른 접근 속도를 제공하지만, 서버가 재시작되면 캐시가 사라지는 단점이 있습니다.

  • 예시: 사용자 프로필 정보를 메모리에 저장해두고 이후 동일한 요청이 들어오면 데이터베이스 조회 없이 메모리에서 직접 가져올 수 있습니다. 이를 통해 데이터베이스의 부하를 줄이고, 응답 속도를 개선할 수 있습니다.
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
    private readonly cache = new Map<string, UserProfile>();

    async getUserProfile(userId: string): Promise<UserProfile> {
        if (this.cache.has(userId)) {
            return this.cache.get(userId);
        }

        const userProfile = await this.fetchFromDatabase(userId);
        this.cache.set(userId, userProfile);
        return userProfile;
    }

    private async fetchFromDatabase(userId: string): Promise<UserProfile> {
        // DB에서 사용자 프로필 가져오는 로직
    }
}

2. Redis와 같은 분산 캐시

대규모 애플리케이션에서는 Redis와 같은 외부 캐시 솔루션을 사용하는 것이 좋습니다. Redis는 빠른 속도와 높은 가용성을 제공하여, 여러 서버 간에 데이터를 공유할 수 있는 장점을 가지고 있습니다. 이를 통해 데이터 일관성을 유지하면서도 성능을 극대화할 수 있습니다.

3. HTTP 응답 캐시

클라이언트가 자주 변경되지 않는 API 응답에 대해 HTTP 헤더를 이용하여 브라우저나 프록시에 임시 저장하도록 할 수 있습니다. 이 방식은 네트워크 대역폭을 절약하고, 서버의 부하를 줄이는 데 효과적입니다.

4. 데코레이터 활용

Nest.js의 데코레이터 기능을 통해 특정 메서드에 쉽게 캐시 기능을 추가할 수 있습니다. 이를 통해 코드의 가독성을 높이고, 유지보수를 용이하게 할 수 있습니다.

import { CacheInterceptor } from '@nestjs/common';

@UseInterceptors(CacheInterceptor)
@Get('user/:id')
getUser(@Param('id') id: string) {
    return this.userService.getUserById(id);
}

실전 사례

전자 상거래 플랫폼에서는 제품 목록이나 가격 정보가 자주 조회되지만 변동이 적습니다. 이러한 경우에는 해당 정보를 Redis에 저장하고 일정 시간(예: 5분) 동안 유효하도록 설정함으로써 매번 데이터베이스에 접근하지 않고도 빠른 응답 속도를 유지할 수 있습니다. 이와 같은 캐싱 전략은 사용자에게 더 나은 쇼핑 경험을 제공하고, 서버의 부하를 줄이는 데 기여합니다.

import { Injectable } from '@nestjs/common';
import { InjectRedis } from '@liaoliaots/nestjs-redis';

@Injectable()
export class ProductService {
    constructor(@InjectRedis() private readonly redis) {}

    async getProductList(): Promise<Product[]> {
        const cachedProducts = await this.redis.get('productList');

        if (cachedProducts) {
            return JSON.parse(cachedProducts); // 문자열로 반환된 값을 객체로 변환
        }

        const products = await this.fetchFromDatabase();
        await this.redis.setex('productList', 300, JSON.stringify(products)); // 300초 후 만료

        return products;
    }

    private async fetchFromDatabase(): Promise<Product[]> {
         // DB에서 제품 목록 가져오는 로직
     }
}

결론

성능 최적화를 위한 캐싱 전략은 Nest.js 애플리케이션 개발 시 필수적인 요소입니다. 위에서 소개한 기법들을 통해 효율적으로 데이터를 관리하고 사용자 경험을 개선할 수 있습니다. 각 프로젝트의 요구 사항에 맞춰 적절한 방식으로 적용하는 것이 중요하며, 이를 통해 장기적으로 시스템 성능과 안정성을 높일 수 있을 것입니다. 캐싱을 통해 얻는 이점은 단순히 성능 향상에 그치지 않고, 비즈니스의 성공에도 큰 영향을 미칠 수 있습니다.

728x90