ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 매장 조회수 적용 api 성능 개선기(promise.all) 성능 4배 올리기
    Project/NYAM 2023. 3. 3. 01:48

    사용 기술 : Nest.js - mongoDB

     

    현제 서비스 중인 어플리케이션은 사용자의 행동들에 따른 log 데이터를 생성하여 특정 형식으로 mongodb 에 저장을 해둡니다.

     

    가게 검색시 가게 조회수별 정렬을 위하여 다음과 같이 store_view 를 추가했습니다.

     

    가게 데이터입니다.

     

    추가로 store_total_view 도 추가를 같이 하였습니다.

    서비스가 커지는 것을 예상을 하여 ,항상 가게들의  모든 조회수를 계산하여 데이터를 업데이트 하는 방식은 서버와 디비에 부담이 될것이므로, 스캐줄러를 이용하여 모든 조회수를 store_total_view 에 저장을 하고 , 특정 기간별 (예 최근 1주일 ) 조회는 store_view 에 넣는 방식으로 구성을 했습니다.

     

    사용자의 모바일 기록 Log 데이터는 다음과 같습니다.

     

     

     

    위의 두 데이터의 대한 기존의 코드입니다. 

     

    async store_set_view(query: StoreSetViewQuery) {
        const stores = await this.storeModel.find(query.query());
        const logItems = await this.logsService.getActionLogListToSetView();
        const storeCount = new Map<string, number>();
        logItems.forEach((logItem) => {
          const storeId = logItem.table_id.toString(); 
          if (!storeCount.has(storeId)) {
            storeCount.set(storeId, 1);
          } else {
            storeCount.set(storeId, storeCount.get(storeId) + 1);
          }
        });
        for (let i = 0; i < stores.length; i++) {
          const storeId = stores[i]._id.toString(); 
          const count = storeCount.get(storeId) ?? 0; 
          stores[i].store_view = count;
          await stores[i].save();
        }
        return true;
      }
    async getActionLogListToSetView() {
        const actionLogsQuery = {};
        actionLogsQuery['action'] = 'Read';
        actionLogsQuery['table_name'] = 'store';
        const logItems = await this.actionModel.find(actionLogsQuery);
        return logItems;
      }

     

    코드 설명 :

    로그데이터에서 action 이 Read 인것과 가게 테이블을 전부 불러와 map 에 갯수 와 같이 저장하고

    store list 를 불러와 id 값을 비교하여 store_view 를 갱신해주는 코드입니다.

     

    기존의 코드로 store-view 를 갱신해 봅니다.

     

    처음 구동시 4초가 걸립니다.답답하죠

     

     

    현제 가게 데이터는 약 360 개 로그 데이터는 약 2000 개 정도 입니다. 

     

    서비스가 커짐에 따라 사용자 로그 데이터가 급격한 속도로 늘어날것을 예상하여 리펙토링을 해줍니다.

     

    promise.all 을 사용한 코드입니다.

    async store_set_view(query: StoreSetViewQuery) {
        const stores = await this.storeModel.find(query.query());
        const logItems = await this.logsService.getActionLogListToSetView();
    
        const storeCount = new Map();
        logItems.forEach(({ table_id }) => {
          storeCount.set(table_id.toString(), (storeCount.get(table_id) || 0) + 1);
        });
    
        await Promise.all(
          stores.map(async (store) => {
            const storeId = store._id.toString();
            const count = storeCount.get(storeId) || 0;
            store.store_view = count;
            await store.save();
          }),
        );
    
        return true;
      }

     

     

    다음과 같이 코드를 변경하니, promise.all 은 모든 비동기 함수가 완료될때까지 기다린 다음 결과를 반환합니다.

     

    각 요소들을 동시에 처리 하였고 for each 로 가독성을 높였습니다.

     

     

    처음 구동시 1225ms 이며 이후에는 평균 400ms 가 나옵니다.

     

     

     

     

    promise.all 을 사용하여 모든 저장작업을 동시에 실행하게 하였고,

    네트워크 지연 시간과 입/출력 작업에 대한 대기 시간을 최소화 하였습니다.

     

     

Designed by Tistory.