Promise.allSettled 비동기 처리 성능 테스트 (feat. AWS WebSocket)

Falcon·2021년 4월 3일
4

javascript

목록 보기
3/28
post-thumbnail

🎯 글의 목적

  1. 웹에서 자주 쓰이는 비동기적 처리의 필요성을 동기적 처리와 성능테스트를 통해 체감한다.
  2. Promise.allSettled() 사용법을 익혀 비동기 처리 병렬처리 코드를 작성해본다.

⚙️ 테스트 환경

Node.js version: 14.10.5
ECMAScript: 2020 // 2020 이상 필수

AWS API Gateway (WebSocket0 + Lambda + SQS + DynamoDB
3,000개의 WebSocket Connection 존재 (동시 접속자 3천명)
AWS Lambda call을 통해 Broadcast Message 전파
이 때 Broadcast가 완료되기 까지의 소요 시간 측정


동기적 처리

[코드 구성]
try catch + if in loop

동시 접속자 한명 한명에게 메시지를 보내는 방식으로, 성공적으로 보내면 다음 connectionId 에 메시지를 송신하고 웹소켓 연결이 stale 상태 (유휴 상태) 면 statusCode 410 에러를 발생시킨다.

메시지 성공/실패여부가 결정된 다음에 다음 사람에게 메시지를 보내는 동기적 처리다.

for (const connectionId of connections) {
  try {
    // 겉보기엔 await 걸어서 비동기로 보이지만
    // 하나씩 수행하면서 에러걸리면 넘기는식으로 동기적 처리 방식.
    await apiGatewayManager.postToConnection({
      ConnectionId: connectionId,
      Data: JSON.stringify({
        message: '~~~',
      })
    }).promise();

  } catch (error) {
    // stale state connection error
    if (error.statusCode === 410) {
      staleConnectionIdArray.push(connectionId);
    } else {
      next(error);
    }
  }
}


비동기 처리

[코드 구성]
Promise.allSettled는 보통의 Promise API와 달리 reject 된 결과또한 catch가 아닌 then으로 받아온다.

동기적 처리와 달리 메시지 송신의 성공/실패 여부와 상관없이 3,000명 모두에게 메시지를 따발총쏘듣 다 쏴버리고 송신이 모두 완료된 다음 결과를 Promise.allSettled().then() 으로 받아온다.

Promise.allSettled().then() 에서 받아오는 객체는 '배열 (iterable)' 이다. 이 배열엔 Promise.resolve() => {status: fulfilled} //성공
Promise.reject() => {status: rejected} //실패
성공, 실패 결과가 모두 배열에 담아져온다.

const broadCastPromises : Promise<any>[] = [];

// Broad cast amen total count
for (const connectionId of connections) {
  // promises 배열에 담기만하고 기다리지 않고 다음 사람에게 메시지 전송
  broadCastPromises.push(apiGatewayManager.postToConnection({
    ConnectionId: connectionId,
    Data: JSON.stringify({
      message: '~~~',
    })
  }).promise()
      .catch((error : AWSError)=>{
    //@ts-ignore
    	error.connectionId = connectionId;
    // 여기서 던진 error 는 Promise.allSettled의 catch 가 아닌 `then`에서 받는다.
   	 throw error;
  })
                        );
}

// 모든 메시지 전송이 완료되면
await Promise.allSettled(broadCastPromises)
  .then((results) => {
  // throw error 로 위에서 던진 에러를 promiseResult.status : rejected 로 받는다.
  const failResults = results.filter(({status}) => status === 'rejected')
  .map((failResult) => {
    //@ts-ignore
    return failResult.reason;
  });

  console.log('===================================');
  failResults.forEach((error: AWSError)=>{
    if (error.code === 'GoneException') {
      //@ts-ignore
      staleConnectionIdArray.push(error.connectionId);
    }
  });
})
  .catch(next);

When to use?

  1. 비동기로 많은 작업을 처리하고
  2. ⭐ 성공-실패 결과 별로 분기 처리가 필요할 때⭐

📝 결론

브로드캐스트 수행 속도만으로 대략 10배 차이를 보였다.
3천명 접속자일 때 이정도면 접속자 수가 3만명 단위로 넘어간다고 생각해보면 동기적 처리는 쓸 수 없을 정도다.
웹에서의 비동기처리는 프론트 백 구분할 것 없이 트랜드가 아니라 기본 소양이 아닌가 싶다.

Reference

MDN Promise.allSettled()

profile
I'm still hungry

0개의 댓글