AWS 기술 블로그

대규모 이벤트를 위한 AWS 기반 사용자 Virtual Waiting Room 구현하기

고객이 대규모 이벤트를 진행할 경우 특정 시점에 트래픽이 폭주하는 경향이 발생될 수 있으며 이런 유형의 트래픽에 맞추어 서비스를 자동으로 확장하는 데는 한계가 있을 수 있습니다. 이는 결과적으로 고객 경험과 인프라스트럭처에 부정적인 영향을 미치게 됩니다. 이에 트래픽 폭주시에도 안정적으로 서비스할 수 있는 AWS 기반 사용자 Virtual Waiting Room 솔루션을 소개하고자 합니다.

Virtual Waiting Room 개요

AWS는 AWS 서비스, 코드 및 구성을 조합하여 바로 배포할 수 있는 다양한 AWS 솔루션을 제공하고 있습니다. Virtual Waiting Room은 AWS솔루션 중 하나로써 대량 트래픽 발생시 웹 사이트로 수신되는 사용자 요청을 버퍼링하고 처리 용량이 충분할 때 트래픽이 통과되어 처리 할 수 있는 기능을 제공하는 오픈소스입니다.

웹사이트에 트래픽 급증을 발생시키는 대규모 이벤트에는 다음과 같은 경우가 있습니다.

  • 콘서트 또는 스포츠 행사 티켓 판매 시작
  • 블랙 프라이데이와 같은 특별 할인 또는 대량 소매 할인 행사
  • 대형 마케팅 발표가 동반된 신규 제품 출시
  • 온라인 시험 및 수업의 시험 접속 및 수업 출석
  • COVID-19 백신 접종 예약

Virtual Waiting Room 아키텍처

Virtual Waiting Room은 크게 세 가지 주요 컴포넌트로 구성되어 있습니다.

  • Core API
    • 배포된 주요 리소스들은 두 개의 Amazon API Gateway, 한 개의 VPC, 다수의 AWS Lambda 함수, Amazon DynamoDB 테이블 및 Amazon ElastiCache 클러스터입니다. 이 API는 Waiting room에 들어온 사용자를 추적하기 위한 기본 메커니즘을 제공합니다. 그리고 프로세스 진행 상태와 보호되는 타켓 사이트로 들어가기 위한 인증 토큰을 요청합니다.
  • Waiting room 프론트엔드 웹사이트
    • Waiting room프론트엔드 페이지에서는 사용자의 현재 대기 순서를 제공합니다. 그리고 구성된 간격으로 처리된 위치와 사용자의 위치를 동적으로 업데이트 합니다. 해당 페이지는 HTML, CSS 및 JavaScript를 적용할 사이트의 프런트엔드 스타일 및 테마에 맞게 커스터마이징할 수 있습니다.
  • 타겟 사이트 보호를 위한Lambda authorizer
    • Lambda Authorizer는 타겟 사이트의 API를 감싸고 보호합니다. 이렇게 하면 모든 사용자 호출이 Waiting room  Core API를 통해 발급된 시간 제한이 있는 유효한 토큰을 전달 받습니다. 따라서 사용자가 Waiting room을 우회하는 것을 방지합니다.

  1. 클라이언트를 위한 퍼블릭 API 호출을 전달하기 위한 Amazon CloudFront 배포
  2. Virtual Waiting Room에서의 대기열 요청을 처리하고 대기열 위치를 추적하며 대상 웹사이트로의 액세스를 허용하는 토큰의 검증을 지원하는 Amazon API Gateway 퍼블릭 API 리소스
  3. 대기열 메시지를 처리할 AWS Lambda 함수로 향하는 트래픽을 규제하는 Amazon Simple Queue Service(Amazon SQS) 대기열. 각 요청에 대해 Lambda 함수를 호출하는 것이 아니라 SQS 대기열이 수신되는 요청 급증을 배치 처리
  4. 관리 기능을 지원하는 API Gateway 프라이빗 API 리소스
  5. 퍼블릭 및 프라이빗 API 요청을 검증 및 처리하고 적합한 응답을 반환할 Lambda 함수
  6. Amazon ElastiCache for Redis 클러스터와 직접 상호 작용하는 Lambda 함수를 호스팅할 Amazon Virtual Private Cloud(VPC). VPC 엔드포인트는 VPC 내의 Lambda 함수가 솔루션 내의 서비스와 프라이빗 통신하도록 함
  7. 사용자 지정 Amazon EventBridge 버스와 연동하여 정기적으로 상태 업데이트를 브로드캐스트하는 Lambda 함수를 호출하는 Amazon CloudWatch 규칙
  8. 토큰 데이터를 저장할 Amazon DynamoDB 테이블
  9. 토큰 작업용 키 및 기타 민감한 데이터를 저장할 AWS Secrets Manager
  10. (선택 사항) AWS Identity and Access Management(IAM) 역할 및 Lambda 함수로 구성되어 API 호출에 대한 서명을 검증하는 권한 부여자 구성 요소. 권한 부여자가 사용자의 API를 보호하기 위해서는 API Gateway를 사용할 수만 있으면 됨
  11. (선택 사항) 두 종류의 인렛 전략을 지원하는 Amazon Simple Notification Service(Amazon SNS), CloudWatch, 및 Lambda 함수
  12. (선택 사항) API Gateway 및 Lambda 함수가 포함되어 OpenID 제공자가 웹사이트에 대해 사용자를 인증할 수 있도록 하는 OpenID 어댑터 구성 요소. 이 구성 요소에 대한 대기실 페이지용 Amazon Simple Storage Service(Amazon S3) 버킷이 포함된 CloudFront 배포
  13. (선택 사항) 선택적 대기실 샘플 웹 애플리케이션용 Amazon S3 원본 버킷이 포함된 CloudFront 배포

Virtual Waiting Room 배포

Virtual Waiting Room을 사용하기 위해서는 CloudFormation 서비스로 Getting Started 스택을 배포해야 합니다. 이 스택에는 Core API 스택, Authorizers 스택 그리고 샘플코드 스택이 포함되어 있습니다.

본 블로그에서는 서울 리전에 배포를 진행하도록 하겠습니다. 만약 다른 리전에 배포를 원할 경우는 AWS Management Console 메뉴 바에서 배포할 리전을 변경하면 됩니다.

단계1: API Gateway을 위한 CloudWatch logging 활성화

  1. CloudFormation 서비스에서 Create stack을 선택합니다.
  2. Create stack 페이지에서 Amazon S3 URL 텍스트 박스에 배포할 aws-virtual-waiting-room-api-gateway-cw-logs-role.template의 S3 URL을 입력하고 Next 버튼을 선택합니다.
    S3 URL: https://solutions-reference.s3.amazonaws.com/virtual-waiting-room-on-aws/latest/virtual-waiting-room-on-aws-api-gateway-cw-logs-role.template

  1. Specify stack details 페이지에서 Stack name 텍스트 박스에 원하는 이름을 입력하고 Next 버튼을 선택합니다.

  1. Configure stack options 페이지에서 Next 버튼을 선택합니다.
  2. Review 페이지에서 “I acknowledge that AWS CloudFormation might create IAM resources”을 체크 후 Create stack 버튼을 선택하면 설치가 시작됩니다.
  3. 성공적으로 설치되면 Resources 탭에서 배포된 리소스를 확인 할 수 있습니다.

단계2:  Getting Started 스택 배포

  1. Create stack 페이지에서 Amazon S3 URL 텍스트 박스에 배포할 virtual-waiting-room-on-aws-getting-started.template의 S3 URL을 입력하고 Next 버튼을 선택합니다.
    S3 URL: https://solutions-reference.s3.amazonaws.com/virtual-waiting-room-on-aws/latest/virtual-waiting-room-on-aws-getting-started.template

  1. Specify stack details 페이지에서 Stack name 텍스트 박스에 원하는 이름을 입력하고, 파라미터들은 기본 값을 사용하고자 합니다. Next 버튼을 선택합니다. Stack명은 24자 이내로 입력해야 합니다. 초과할 경우 설치 과정에서 Simple Notification Service(SNS) queue 명이 80자를 초과하여 에러가 발생됩니다.

  1. Configure stack options 페이지에서 Next 버튼을 선택합니다.
  2. Review 페이지의 Capabilities 탭 내용들을 체크 후 Create stack 버튼을 선택하면 설치가 시작됩니다.

  1. AWS CloudFormation 콘솔의 왼쪽 스택 리스트에서 스택의 진행 상태를 볼 수 있습니다. 약 30분 내에 스택의 상태가 CREATE_COMPLETE가 되면 성공적으로 설치가 완료된 것입니다. Outputs 탭에서 테스트에서 사용할 ControlPanelURL과 WaitingRoomURL 정보를 조회 할 수 있습니다.

Virtual Waiting Room 테스트

Sample 스택에 포함된 어플리케이션을 이용하여 Virtual Waiting Room을 테스트하고 동작 원리를 이해하고자 합니다.

단계 1 : AWS access key 생성

  1. Waiting Room Private REST API을 호출하여 Waiting room을 관리하고 상태를 조회하기 위한 Control Panel UI을 사용하기 위해서는 권한이 부여된 사용자의 access key가 필요합니다. IAM 콘솔에서 신규 IAM 사용자를 생성하거나 기존 사용자에 Virtual Waiting Room 배포에서 생성된 그룹을 추가합니다.

  1. Security credentials 탭에서 Create access key 버튼을 선택하여 access key를 생성합니다.

단계 2 : Virtual Waiting Room 구성

  1. 브라우저에서 ControlPanelURL을 입력하여 Control Panel UI 페이지를 호출합니다. 신규 생성한 Access key ID와 secret access key을 입력 후 Use 버튼을 선택합니다.

  1. Virtual Waiting Room에 관한 여러 메트릭의 상태가 Connected로 변경됨을 확인할 수 있습니다.

단계 3 : Sample application 테스트

  1. 브라우저에서 WaitingRoomURL을 입력하여 Waiting room UI 페이지를 호출합니다.

  1. 해당 페이지에서는 다음과 같은 데이터를 얻기 위해서 Waiting Room Public Rest API(API Gateway)을 호출합니다.
    • Request ID
      assign_queue_num을 호출하고 Request ID을 리턴 받음
      https://d4w5*******.cloudfront.net/assign_num?event_id=Sample
    • My Position
      queue_num을 호출하고 My Position을 리턴 받음
      https://d4w5*******.cloudfront.net/queue_num?event_id=Sample&request_id=f2ef8fc4-d3cd-4367-a92e-a9e177c59683
    • Serving Position
      주기적으로 serving_num을 호출하고 Serving Position을 리턴 받음
      https://d4w5*******.cloudfront.net/serving_num?event_id=Sample
    • Waiting Room Size
      주기적으로 waiting_num을 호출하고 Waiting Room Size을 리턴 받음
      https://d4w5*******.cloudfront.net/waiting_num?event_id=Sample
  1. 브라우저에서 마우스 오른쪽 버튼을 클릭 후 Inspect 선택하고 Network 탭을 선택하면 다음과 같이 API가 호출되는 내용을 확인 할 수 있습니다.

  1. Control Panel UI 페이지에서 수동으로 Serving Counter을 10 증가 시킵니다. 10개의 요청이 Waiting room에서 나와 타겟 사이트로 이동할 수 있습니다.

  1. Waiting room UI 페이지가 Serving Position이 10으로 변경되었고 상태가 Waiting for line to advance에서 Check out now로 변경되면서 버튼이 활성화 되었습니다. My Position이 Serving Position과 같거나 작으면 serving_num과 waiting_num API 호출이 중단되고 상태가 변경됩니다. 버튼을 선택하면 타겟 사이트로 이동하게 됩니다.

  1. JWT(JSON Web Tokens) 토큰이 발급되었고, 타겟 사이트의 비즈니스가 호출되었습니다.

  1. 위 페이지에서 토큰을 발급 받기 위해서 Waiting Room Public Rest API(API Gateway)가 호출됩니다. 발근된 토큰은 https://jwt.io 사이트에서 디코딩하여 상세 내역을 확인할 수 있습니다.
    • JWT
      generate_token을 호출하고 JWT를 리턴 받음
      https://d4w5*******.cloudfront.net/generate_token

  1. 발급된 토큰은 Amazon DynamoDB 테이블에 저장되어 관리됩니다.

  1. Purchase now 버튼을 선택하면 타겟 사이트의 비즈니스 API가 호출되고 수행됩니다.

  1. 비즈니스 API을 호출 할 때 발급된 토큰을 전달하여 Waiting room 통과 여부를 체크하게 됩니다. 만약 토큰이 없거나 유효하지 않는 토큰을 사용하면 다음과 같이 인증 에러가 발생됩니다.

  1. 다음 코드는 타겟 사이트의 호출된 API(Lambda) snippet입니다.
from chalice import Chalice, CustomAuthorizer, CORSConfig, Response

app = Chalice(app_name='core-api-authorizers-sample')

WAITING_ROOM_AUTH = CustomAuthorizer('WaitingRoomAuthorizer', 
                                     header='Authorization', 
                                     authorizer_uri='PLACEHOLDER')

CORS_CONFIG = CORSConfig(allow_origin='*', 
                         allow_headers=['*'], 
                         max_age=600, 
                         expose_headers=['*'], 
                         allow_credentials=True)

RESPONSE_HEADERS = {'Content-Type': 'application/json',
                    'Access-Control-Allow-Origin': '*'}

@app.route('/checkout', 
           methods=['GET'], 
           authorizer=WAITING_ROOM_AUTH, 
           cors=CORS_CONFIG)


def checkout(): 
    """
    This function represents a /checkout API endpoint. 
    """ 
    return Response(status_code=200, 
                    body={"result": "Success"},
                    headers=RESPONSE_HEADERS)

Inlet strategy 개요

타겟 사이트의 동시 처리량을 조절하기 위하여 Virtual Waiting Room의 Serving counter에 대하여 증가시점과 증가량을 결정할 때 사용되는 전략입니다. Virtual Waiting Room에서는 inlet strategy로 Periodic과 MaxSize 2가지 예제를 제공하고 있습니다.

Periodic:

CloudWatch 룰이 1분 주기로 PeriodicInlet 람다 함수를 호출하여 지정된 수만큼 Serving counter을 증가시키는 전략입니다.

  • 파라미터
    • event start time, end time: 람다 함수가 실행되는 시간 범위
    • increment ammount: Serving count 증가 단위
    • CloudWatch custorm alarm (option): 설정된 alarm이 ok일 때 람다 함수 동작

MaxSize:

타겟 사이트가 동시에 수용할 수 있는 최대 사용자 수를 제어하는 전략입니다. SNS 토픽(stack-name}-inlet-strategy-MaxSizeInletSns-*)에 퍼플리시되면 MaxSizeInlet 람다 함수가 호출됩니다. 람다 함수는 아래 메시지를 이용하여 얼마만큼 Serving counter을 증가시킬지 결정합니다.

  • SNS(Simple Notification Service) 토픽 메시지
    • completed: list of request IDs to be marked completed
    • abandoned: list of request IDs to be marked abandoned
    • exited: number of transactions that have completed

Inlet strategy을 이해하기 위해서는 ElastiCache for Redis에서 관리하는 Serving counter와 Queue counter의 의미를 알아야 합니다. 각 Counter의 상한 값은 9,223,372,036,854,775,807이며 상한 값에 도달했다면 리셋을 해야 합니다.

  • Serving counter: 타겟 사이트에서 제공할 수 있는 요청 수
  • Queue counter: 타겟 사이트에서 제공된 요청 수
  • 서비스 가능한 요청 수 = Serving counter – Queue counter

Periodic strategy 배포

  1. CloudFormation 콘솔에서 aws-virtual-waiting-room-sample-inlet-stragegy.template을 배포합니다.
    S3 URL: https://solutions-reference.s3.amazonaws.com/aws-virtual-waiting-room/latest/aws-virtual-waiting-room-sample-inlet-strategy.template
  1. 적절한 스택명과 파라미터를 입력합니다.
    • EventId와 PrivateCoreAPIEndPoint
      • 설치된 Virtual Waiting Room core stack의 parameter와 output 값을 참조
    • Inlet strategy는 Periodic을 선택
    • IncrementBy 파라미터
      • 1분 주기로 Serving counter을 증가시킬 값으로 타겟 사이트의 동시 처리량을 고려해서 적절한 값을 입력
    • StartTime, EndTime
      • Periodic strategy가 수행될 시작 시간과 종료 시간, 디폴트 값을 사용해도 됨

  1. CloudFormation 스택이 배포한 리소스는 다음과 같습니다.

  1. 배포된 EventBridge 룰로 PeriodicInlet 람다 함수를 1분 주기로 호출하도록 설정되어 있으며 주기는 변경할 수 있습니다.

  1. 람다가 실행 시 참조하는 Serving counter는 INCREMENT_BY 환경변수로 전달되며, 해당 변수를 수정하면 람다 함수가 실행 시 바로 참조됩니다.

Periodic strategy 동작 확인

CloudFormation SampleModule stack에서 설치한 Control Panel UI에 접속 후 Serving Position 값을 살펴보면 1분 단위로 10씩 증가됨을 확인할 수 있습니다.

Periodic strategy 고려사항

Sample Periodic strategy은 특정 조건에서 의도하지 않게 트래픽이 폭증 되어 타겟 시스템으로 전달 될 수 있습니다. 예를 들어 Servingcounter가 1분 단위로 10씩 증가하고 주기마다 요청이 10보다 적었던 상황에서 18:13분에 40개 요청이 발생되면 30개 요청이 Waiting room에서 빠져나가 타겟 시스템으로 요청을 하게 됩니다.

Periodic stragegy에 추가로 다음 고려사항을 적용하면 문제점을 개선할 있습니다.

  • 주기 단축
    • EventBridge rule에서 PeriodicInlet 람다 함수 호출 주기를 1분에서 적용할 타겟 시스템의 상황에 맞게 단축
  •  증가량 로직 변경
    • PeriodicInlet 람다 함수에서 주기마다 INCREMENT_BY 만큼 증가하는 로직을 변경
      • 기존: Serving count + INCREMENT_BY
      • 개선: Serving count + NEW_INCREMENT_BY
        NEW_INCREMENT_BY = INCREMENT_BY – (Serving counter -Queue counter)
        0 <= NEW_INCREMENT_BY <= INCREMENT_BY

Virtual Waiting Room 솔루션에서는 Queue counter을 조회하는 Private API를 제공하지 않기 때문에 추가로 개발해야 합니다. 해당 값은 ElastiCache for Redis에서 조회할 수 있습니다.

ElastiCache for Redis에 접근하기 위해서는 ElastiCache 엔드포인트와 Redis 암호가 필요합니다.

설치된 ElastiCache 클러스터명은 CloudFormation {stack-name}-CoreModuleStack의 리소스 탭에서 조회합니다

ElastiCache 클러스터 엔드포인트(호스트) 명은 ElasitCache 콘솔에서 조회합니다.

Redis 암호는 Securets Manager 서비스의 Secret value 탭에서 조회합니다.

다음은 구성된 ElastiCache 엔드포인트와 Redis 암호로 redis-cli 명령어를 사용하여 queue_counter와 serving_counter 값은 조회하는 과정입니다.

$ wget https://redismodules.s3.amazonaws.com/redis-stack/redis-stack-server-6.2.2-v4.rhel7.x86_64.tar.gz
...
$ tar xzf redis-stack-server-6.2.2-v4.rhel7.x86_64.tar.gz
$ sudo cp redis-stack-server-6.2.2-v4/bin/redis-cli /usr/bin
...
$ export REDISCLI_AUTH='Mpz5xAdJ3V$v$HvUn8W2WW2IK>>lA7nIc>^&$l!RDV!mN$-c95Ek!F>Xl>iGdULOk^<XC9lBYl1R5u79iase<5i&fy^QVbYqQJ52-#KB4Cd$J9tLAmoCfj275pPAZaNR'
$ redis-cli -h master.awr13mgas2awt4tf.odcoyh.apn2.cache.amazonaws.com -p 1785 --tls
master.awr13mgas2awt4tf.odcoyh.apn2.cache.amazonaws.com:1785> get queue_counter
“61”
master.awr13mgas2awt4tf.odcoyh.apn2.cache.amazonaws.com:1785> get serving_counter
"51"
master.awr13mgas2awt4tf.odcoyh.apn2.cache.amazonaws.com:1785>

MaxSize strategy 배포

CloudFormation에서 aws-virtual-waiting-room-sample-inlet-stragegy.template을 이미 배포하였기 때문에 MaxSize strategy는 업데이트를 통해 배포하게 됩니다.

  1. 기 배포한 스택에 대하여 Update을 선택합니다.

  1. Periodic 값을 MaxSize로 변경합니다.

  1. 타겟 시스템에서 동시 처리할 수 있는 사용자 수를 입력합니다.

  1. 변경 사항을 확인하고 Update stack을 선택하면 설치가 진행됩니다.

  1. 성공적으로 업데이트가 완료되면 배포된 리소스를 확인 합니다.

  1. 배포된 MaxSizeInlet 람다 함수는 아래와 같습니다. 람다가 실행 시 참조하는 MaxSize 값은 환경변수에 정의되어 있으며 변경 가능합니다.

MaxSize strategy 동작 확인

  1. Control panel UI 페이지에서 Increment Serving counter를 이용하여 Serving counter 값이 설정한 MaxSize와 같게 변경합니다.
  1. Waiting room UI 페이지를 이용하여 2명의 사용자가 요청합니다.

  1. Check out now을 선택하면 Waiting room에서 나오게 되고 JWT 토큰이 발급 됩니다. DynamoDB에서 각 요청에 대한 request_id와 JWT 토큰을 조회할 수 있습니다.

  1. MaxSize를 3으로 설정하였기 때문에 추가 요청이 발생될 경우 1개 요청은 Waiting room에서 타겟 시스템으로 이동하게 되고,나머지는 Waiting room에서 대기하게 됩니다.
  2. SNS topic에 Waiting room에서 타겟 시스템으로 이동한 2명의 사용자에 대하여 완료 처리하는 이벤트를 전달하면 {stack-name}-inlet-stragegy-MaxSizeInlet-* 람다 함수가 호출되어 발급된 JWT 토큰을 만료 시키고 Serving counter을 증가시킵니다.

  1. CloudWatch에서 람다 함수의 로그를 조회하면 Serving counter가 3에서 5로 증가하였음을 확인할 수 있습니다. 다시 최대 3명 사용자를 타겟 시스템에서 처리할 수 있게 되었습니다.

  1. DynamoDB에서 발급되었던 JWT 토큰의 세션 상태는 만료로 변경되었으며, Active Token은 2에서 0으로 변경됨을 확인 할 수 있습니다.

MaxSize strategy 고려사항

타겟 시스템의 동시 처리량이 정확하게 유지되도록 아래의 사항들을 반영해야 합니다.

  • 클라이언트의 세션 사용이 완료되면 SNS에 Completed 메시지를 전달해서 세션에 발급된 토큰을 사용 중지
  • 클라이언트가 세션 완료 로직을 호출할 수 없을 경우를 고려하여, 적절한 세션 타임아웃을 설정

Virtual Waiting Room 리소스 정리

이 블로그에서 사용했던 리소스는 향후 불필요한 과금을 방지하기 위해 삭제하여야 합니다. CloudFormation 콘솔에서 설치한 스택을 삭제하면 사용하였던 모든 리소스들이 자동으로 삭제 됩니다.

참고 사이트

YooSung Jeon

YooSung Jeon

전유성 솔루션즈 아키텍트는 통신/공공 산업군에서 데이터 분석과 다양한 오픈소스 활용 경험을 바탕으로 DNB(Digital Native Business) 고객을 대상으로 고객의 비즈니스 성과를 달성하도록 최적의 아키텍처를 구성하는 역할을 수행하고 있습니다.