Project/NYAM

매장 조회수 적용 api 성능 개선기(promise.all) 성능 4배 올리기

sung.hyun.1204 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 을 사용하여 모든 저장작업을 동시에 실행하게 하였고,

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