Intern/Project

[Intern] 캐싱 및 redis 사용 검색 속도 향상 적용

dbfl9911 2024. 12. 12. 21:15
반응형

근무 내용

  • 최종 코드 구현 확인
  • 캐싱 및 redis 사용해 cctp api 사용해 조회시 검색 속도 향상 적용

 

근무 결과

  • 메소드 별 실행 시간 측정
async getTransactionInfoFromRange(txHash: string) {
    const start = Date.now(); // 시작 시간 기록
    const url = '<https://usdc.range.org/usdc/api/transfers>';
    const { data } = await firstValueFrom(
      this.httpService.get(url, {
        params: {
          txHash,
          txnType: 'MAINNET',
          limit: 1,
          direction: 'first',
          source: 'ethereum,base,arbitrum', // 소스 네트워크
          destination: 'ethereum,base,arbitrum', // 목적지 네트워크
          status: '',
          min_usd: '',
          max_usd: ''
        }
      }).pipe(
        catchError((error: AxiosError) => {
          const errMsg = "Failed to fetch transaction info from Range API\\nMessage: " + error.message;
          console.error(errMsg);
          throw new CCTPapiError(errMsg);
        })
      )
    );
    const duration = Date.now() - start; // 실행 시간 계산
    console.log(`getTransactionInfoFromRange 실행 시간: ${duration}ms`);
    return data.resources[0];
  }
getTransactionInfoFromRange 실행 시간: 2874ms

⇒ 속도 개선 방법 : 캐싱을 사용해 검색 성능 향상 시킴

//-- api.service.ts 파일 
import NodeCache from 'node-cache';
import * as http from 'http';

private cache: NodeCache;
  private httpAgent: http.Agent;

  constructor(
    private readonly httpService: HttpService,
    private readonly methodMapperService: MethodMapperService
  ) {
    // 캐싱을 위한 NodeCache 인스턴스 초기화
    this.cache = new NodeCache({ stdTTL: 300 }); // TTL 5분

    // HTTP Keep-Alive를 위한 에이전트 설정
    this.httpAgent = new http.Agent({ keepAlive: true });
  }
  //... 생략 기존 코드와 동일 
  async getTransactionInfoFromRange(txHash: string) {
    const start = Date.now(); // 시작 시간 기록
    // 캐싱된 데이터 확인
    const cachedData = this.cache.get(txHash);
    if (cachedData) {
      console.log("캐시 사용");
      console.log(`getTransactionInfoFromRange 실행 시간 (캐시): ${Date.now() - start}ms`);
      return cachedData;
    }

    const url = '<https://usdc.range.org/usdc/api/transfers>';

     try {
      // HTTP 요청 시작
      const { data } = await firstValueFrom(
        this.httpService.get(url, {
          params: {
            txHash,
            txnType: 'MAINNET',
            limit: 1,
            direction: 'first',
            source: 'ethereum,base,arbitrum',
            destination: 'ethereum,base,arbitrum',
            status: '',
            min_usd: '',
            max_usd: ''
          },
          headers: {
            'Accept-Encoding': 'gzip, deflate, br', // 데이터 압축 요청
          },
          timeout: 3000, // 요청 타임아웃 설정 (3초)
          httpAgent: this.httpAgent, // Keep-Alive 설정
        }).pipe(
          // 에러 처리
          catchError((error: AxiosError) => {
            const errMsg = "Failed to fetch transaction info from Range API\\nMessage: " + error.message;
            console.error(errMsg);
            throw new Error(errMsg);
          })
        )
      );

      // 응답 데이터 캐싱
      this.cache.set(txHash, data.resources[0]);

      console.log(`getTransactionInfoFromRange 실행 시간: ${Date.now() - start}ms`);
      return data.resources[0];
    } catch (error) {
      console.error("getTransactionInfoFromRange 에러:", error.message);
      console.log(`getTransactionInfoFromRange 실패 시간: ${Date.now() - start}ms`);
      throw error;
    }
}
  • 캐싱(NodeCache):
    • 동일한 txHash 요청에 대해 캐싱된 데이터를 반환하여 불필요한 HTTP 요청 방지.
    • TTL(Time To Live)을 5분으로 설정.
  • HTTP Keep-Alive:
    • 지속적인 연결을 유지하여 네트워크 지연 감소.
  • 압축 활성화:
    • Accept-Encoding 헤더를 추가하여 Gzip, Brotli 등의 압축 데이터 요청.
  • 타임아웃 설정:
    • timeout: 3000으로 타임아웃을 설정하여 비정상적인 대기 시간 방지.
  • 성능 로그 추가:
    • 실행 시간을 기록하여 성능을 지속적으로 모니터링.

결과는 아래와 같다.

getTransactionInfoFromRange 실행 시간: 2113ms

기대 효과

  • 캐싱 적용으로 동일한 요청에 대해 실행 시간이 획기적으로 단축.
  • HTTP Keep-Alive로 네트워크 연결 지연 최소화.
  • 데이터 압축과 타임아웃 설정으로 요청 효율성 및 안정성 향상.

더 속도를 올릴 수 없을까 해서 Redis를 사용해보기로 했다.

import { createClient } from 'redis';
//...
  private redisClient: any;

  constructor(
    private readonly httpService: HttpService,
    private readonly methodMapperService: MethodMapperService
  ) {
    // 캐싱을 위한 NodeCache 인스턴스 초기화
    this.cache = new NodeCache({ stdTTL: 300 }); // TTL 5분

    // HTTP Keep-Alive를 위한 에이전트 설정
    this.httpAgent = new http.Agent({ keepAlive: true });

    // Redis 클라이언트 초기화
    this.redisClient = createClient();
    this.redisClient.connect().catch((err) => {
      console.error("Redis 연결 실패:", err);
    });
    
  }

Redis 서버 실행후 프로젝트 실행시켜야 함

redis-server
async getTransactionInfoFromRange(txHash: string) {
    const start = Date.now(); // 시작 시간 기록
    
    // Redis 캐시 확인
    const redisCachedData = await this.redisClient.get(txHash);
    if (redisCachedData) {
      console.log("Redis 캐시 사용");
      console.log(`getTransactionInfoFromRange 실행 시간 (Redis 캐시): ${Date.now() - start}ms`);
      return JSON.parse(redisCachedData);
    }

    // NodeCache 확인
    const nodeCachedData = this.cache.get(txHash);
    if (nodeCachedData) {
      console.log("NodeCache 캐시 사용");
      console.log(`getTransactionInfoFromRange 실행 시간 (NodeCache): ${Date.now() - start}ms`);
      return nodeCachedData;
    }

    // 캐싱된 데이터 확인
    const cachedData = this.cache.get(txHash);
    if (cachedData) {
      console.log("캐시 사용");
      console.log(`getTransactionInfoFromRange 실행 시간 (캐시): ${Date.now() - start}ms`);
      return cachedData;
    }

    const url = '<https://usdc.range.org/usdc/api/transfers>';

     try {
      // HTTP 요청 시작
      const { data } = await firstValueFrom(
        this.httpService.get(url, {
          params: {
            txHash,
            txnType: 'MAINNET',
            limit: 1,
            direction: 'first',
            source: 'ethereum,base,arbitrum',
            destination: 'ethereum,base,arbitrum',
            status: '',
            min_usd: '',
            max_usd: ''
          },
          headers: {
            'Accept-Encoding': 'gzip, deflate, br', // 데이터 압축 요청
          },
          timeout: 3000, // 요청 타임아웃 설정 (3초)
          httpAgent: this.httpAgent, // Keep-Alive 설정
        }).pipe(
          // 에러 처리
          catchError((error: AxiosError) => {
            const errMsg = "Failed to fetch transaction info from Range API\\nMessage: " + error.message;
            console.error(errMsg);
            throw new Error(errMsg);
          })
        )
      );

      // 응답 데이터 Redis와 NodeCache에 캐싱
      const cachedValue = JSON.stringify(data.resources[0]);
      await this.redisClient.set(txHash, cachedValue, { EX: 300 }); // Redis TTL 5분
      this.cache.set(txHash, data.resources[0]); // NodeCache 저장

      console.log(`getTransactionInfoFromRange 실행 시간: ${Date.now() - start}ms`);
      return data.resources[0];
    } catch (error) {
      console.error("getTransactionInfoFromRange 에러:", error.message);
      console.log(`getTransactionInfoFromRange 실패 시간: ${Date.now() - start}ms`);
      throw error;
    }
}
Redis 캐시 사용
getTransactionInfoFromRange 실행 시간 (Redis 캐시): 5ms

추가된 최적화 내용:

  1. Redis 캐싱:
    • Redis를 활용하여 데이터를 저장 및 검색하며, TTL을 설정해 주기적으로 갱신.
    • NodeCache와 함께 사용하여 Redis가 사용 불가능할 경우 로컬 캐싱도 활용 가능.
  2. 병렬 처리:
    • getMultipleTransactions 메서드 추가로 여러 트랜잭션을 동시에 처리.
  3. 캐싱 계층 구조:
    • Redis > NodeCache 순으로 데이터 조회를 시도하여 캐싱 효율성을 극대화.

기대 효과:

  • Redis 캐싱과 Keep-Alive 설정으로 네트워크 및 데이터 처리 속도 개선.
  • 병렬 처리를 통한 다중 요청 효율화.

Redis 캐시를 사용한 성능 개선의 주요 포인트

  1. 원래 소요 시간: 2874ms
    • API 호출, 네트워크 지연, 데이터 처리 등이 포함된 시간.
    • 이 과정이 생략되지 않고 실행되면 오래 걸릴 수밖에 없습니다.
  2. 개선된 소요 시간: 5ms
    • Redis 캐시에서 데이터를 가져오므로 네트워크 요청 및 데이터 처리가 생략됨.
    • 메모리에서 읽기만 수행하므로 매우 빠르게 결과를 반환.

어떻게 개선되었는지 정리

  • Redis 캐싱 적용:
    • 동일한 txHash에 대해 한 번만 API 호출.
    • 이후 요청은 Redis에서 바로 데이터를 가져와 반환.
  • 네트워크 지연 제거:
    • API 호출 과정이 제거되어 네트워크와 서버 응답 시간이 사라짐.
  • 데이터 처리 시간 단축:
    • API로 받은 데이터를 정리하는 단계가 생략됨.
반응형