본문 바로가기
개발/프로젝트-식당 웨이팅 앱 FOOD LINE

230620 실전프로젝트18 [Nest.js] Jmeter로 부하테스트하기2

by 코딩하는짱구 2023. 6. 20.
반응형

✅오늘 학습  Keyword

2023.06.15 - [실전프로젝트] - 230616 실전프로젝트15 [Nest.js] Jmeter로 부하테스트하기

 

230616 실전프로젝트15 [Nest.js] Jmeter로 부하테스트하기

✅오늘 학습 Keyword 애플리케이션 통합 완료 후 JMeter를 이용하여 부하테스트를 해보기로 했다. JMeter란 Apache에서 만든 자바로 만들어진 웹 어플리케이션 성능 테스트 오픈소스이고, GUI, CLI 방법을

veritas-crystal.tistory.com

JMeter 사용법을 숙지한 후에 기능별로 기준을 나누어 테스트를 진행했다. 

 

 

✅JMeter로 애플리케이션 성능테스트 해보기

테스트 기준과 결과는 아래와 같다. 

 

 

✅오늘 알게된 점 

caching intercepter를 적용한 검색 부분은 정말 경이로울 정도로 속도가 개선되는 것을 알 수 있었다.  

그러나 팀원들의 의견은 좌표를 이용한 위치기반 식당 조회 부분에서는 요청 좌표의 아주 사소한 부분만 바뀌어도 결과를 전부 캐싱해야하는 점이 비효율 적이고 메모리 관리가 어렵다고 판단되어 캐싱이 아닌 indexing 을 사용한 elastic search를 사용하기로 했다.

 

내 생각엔 TTL을 약 하루정도로 설정하여 caching을 사용하는 것도 충분히 효율적이고 속도 또한 보장을 할 수 있을 것 같고, ㄴ특정 유저는 대체로 하루동안은 비슷한 지역에서 식당을 조회할것이기에  빠르게 결과를 조회할 수 있을 것이다. 만약 caching이 indexing보다 월등하게 빨랐다면 caching을 사용하는 쪽으로 진행 했겠지만 그건 아니니까~ 또 식당에는 실시간으로 변하는 waiting list가 존재하기에 TTL을 오래 설정하기엔 무리가 있을지도 .. 

 

**Nestjs 캐시 무효화

TTL을 길게 설정하여 캐시가 더 오래 남아 활용되도록 구성하면, 반복적인 비즈니스 로직의 실행을 더 많이 줄여 자원을 아낄 수 있고 더 빠른 접근을 가능케 할 수 있습니다. 그러나 모든 캐시의 만료기간을 길게 설정하는 문제를 초래할 수 있습니다. 오랜 기간 변경되지 않아도 되는 데이터가 있는 반면, 짧은 변경 주기를 가지는 데이터도 있습니다. ‘캐시를 얼마나 오래 유지해야 하는가?‘의 문제는 언제나 답이 없습니다. 데이터의 성격과 상황에 따라 알맞게 설정해야 할 것입니다.

만료기간이 긴 경우 캐시 데이터가 오래된 데이터일 가능성이 높아지며, 만료기간이 짧은 경우 캐시 효율성이 낮아집니다. 물론 캐시처리 하지 않는 것보다는 짧은 유효기간의 캐시라도 두는 것이 성능 향상에 있어 도움이 될 것은 분명합니다.

 

**caching intercepter

Nest.js에서 Interceptor란?

https://docs.nestjs.com/interceptors#interceptors

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea

docs.nestjs.com

 

 

 

//프로젝트 내에서 interceptor 설정

import {
  CACHE_KEY_METADATA,
  CACHE_MANAGER,
  CACHE_TTL_METADATA,
} from '@nestjs/cache-manager';
import {
  Injectable,
  ExecutionContext,
  Optional,
  Inject,
  CallHandler,
  HttpServer,
  NestInterceptor,
} from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
const HTTP_ADAPTER_HOST = 'HttpAdapterHost';
const REFLECTOR = 'Reflector';

export interface HttpAdapterHost<T extends HttpServer = any> {
  httpAdapter: T;
}

@Injectable()
export class CacheInterceptor implements NestInterceptor {
  @Optional()
  @Inject(HTTP_ADAPTER_HOST)
  protected readonly httpAdapterHost: HttpAdapterHost;

  protected allowedMethods = ['GET'];
  constructor(
    @Inject(CACHE_MANAGER) protected readonly cacheManager: any,
    @Inject(REFLECTOR) protected readonly reflector: any,
  ) {}

  async intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Promise<Observable<any>> {
    const key = this.trackBy(context);
    const ttlValueOrFactory =
      this.reflector.get(CACHE_TTL_METADATA, context.getHandler()) ?? null;

    if (!key) {
      return next.handle();
    }
    console.log('key:', key);
    try {
      const value = await this.cacheManager.get(key);
      console.log('value:', value);
      if (value !== undefined && value !== null) {
        return of(value);
      }
      // const ttl =
      //   typeof ttlValueOrFactory === 'function'
      //     ? await ttlValueOrFactory(context)
      //     : ttlValueOrFactory;
      const ttl = 5;
      console.log('ttl:', ttl);
      return next.handle().pipe(
        tap((response) => {
          console.log('response:', response);
          const args =
            ttl === undefined ? [key, response] : [key, response, ttl];
          this.cacheManager.set(...args);
        }),
      );
    } catch {
      return next.handle();
    }
  }

  protected trackBy(context: ExecutionContext): string | undefined {
    const httpAdapter = this.httpAdapterHost.httpAdapter;
    const isHttpApp = httpAdapter && !!httpAdapter.getRequestMethod;
    const cacheMetadata = this.reflector.get(
      CACHE_KEY_METADATA,
      context.getHandler(),
    );

    if (!isHttpApp || cacheMetadata) {
      return cacheMetadata;
    }

    const request = context.getArgByIndex(0);
    if (!this.isRequestCacheable(context)) {
      return undefined;
    }
    return httpAdapter.getRequestUrl(request);
  }

  protected isRequestCacheable(context: ExecutionContext): boolean {
    const req = context.switchToHttp().getRequest();
    return this.allowedMethods.includes(req.method);
  }
}

 

반응형