간단하게(?) WebSocket API, Lambda, DynamoDB를 사용해 서버리스 채팅 앱을 구축해보자..

https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/websocket-api-chat-app.html

해당 링크에서 cloudformation 템플릿으로 작업 후에 하나씩 살펴보자..


  • CloudFormation 생성

CloudFormation 템플릿

 

  • WebSocket API 생성

라우팅 선택 표현식 값으로 표현된 속성은 클라이언트가 API 호출할 때마다 전송되는 모든 메시지에 포함되어야 한다.

$request.body.action 의 경우, 클라이언트 요청의 body 내에 "action" 키에 mapping 된 값으로 route 한다는 의미!

 

다음으로 넘어가 $connect / $disconnect / $default 다 추가하기.

(요청과 일치하는 다른 경로가 없을 때 $default 경로를 호출)

통합 연결에서 Lambda 선택 후 각 각 맞는 함수를 넣어준다.

 

  • Test the chat API

wscat 유틸을 사용해 테스트 해보자

 

터미널 환경을 위해 cloud9을 열어주자... 2개를 만들 것!(메시지를 던지는 서버 1 / 메시지를 받는 서버 2)

wscat을 사용하기 위해선 npm이 필요!

npm -v
npm install -g wscat

publish 된 API endpoint에 연결

wscat -c wss://{YOUR-API-ID}.execute-api.{YOUR-REGION}.amazonaws.com/{STAGE}

api gateway의 대시보드를 보면 websocket url 알 수 있음..

위의 명령어 입력 시 connected 가 뜨면 연결 된 것..

DynamoDB에 connect Id 가 들어간 것을 볼 수 있음

sendMessage 함수 테스트를 위해 아래와 같은 JSON 메시지 전달 (cloud9 -1)

던진 메시지를 보기 위한 것..(cloud9 -2)

두 개의 터미널(cloud9 -1,2)에서 연결을 종료 시킨 뒤 다시 dynamoDB를 확인해보면...

 

이제 하나씩 다시 살펴보자!


일단 Lambda 함수들 먼저 보면

Connect / Disconnect / Default / sendMessage 이렇게 4개의 함수가 생성되어 있다.

(물론 모든 함수들은 트리거로 위에서 생성했던 API Gateway 잡혀있음!!)

  • ConnectHandler
const AWS = require('aws-sdk');
      const ddb = new AWS.DynamoDB.DocumentClient();
      exports.handler = async function (event, context) {
        try {
          await ddb
            .put({
              TableName: process.env.table,
              Item: {
                connectionId: event.requestContext.connectionId,
              },
            })
            .promise();
        } catch (err) {
          return {
            statusCode: 500,
          };
        }
        return {
          statusCode: 200,
        };
      };

DynamoDB 테이블에 connectId 값을 넣는 작업

 

  • DisconnectHandler
const AWS = require('aws-sdk');
      const ddb = new AWS.DynamoDB.DocumentClient();
      
      exports.handler = async function (event, context) {
        await ddb
          .delete({
            TableName: process.env.table,
            Key: {
              connectionId: event.requestContext.connectionId,
            },
          })
          .promise();
        return {
          statusCode: 200,
        };
      };

DynamoDB 테이블에 connectId 값에 해당되는 record 삭제

(따라서 위 두 함수의 역할에는 기본적인 Lambda + DynamoDB 에 접근할 수 있는 권한이 필요!)

 

  • DefaultHandler
const AWS = require('aws-sdk');

      exports.handler = async function (event, context) {
        let connectionInfo;
        let connectionId = event.requestContext.connectionId;
      
        const callbackAPI = new AWS.ApiGatewayManagementApi({
          apiVersion: '2018-11-29',
          endpoint:
            event.requestContext.domainName + '/' + event.requestContext.stage,
        });
      
        try {
          connectionInfo = await callbackAPI
            .getConnection({ ConnectionId: event.requestContext.connectionId })
            .promise();
        } catch (e) {
          console.log(e);
        }
      
        connectionInfo.connectionID = connectionId;
      
        await callbackAPI
          .postToConnection({
            ConnectionId: event.requestContext.connectionId,
            Data:
              'Use the sendmessage route to send a message. Your info:' +
              JSON.stringify(connectionInfo),
          })
          .promise();
      
        return {
          statusCode: 200,
        };
      };

잘못된 경로(?)로 들어오면... 들어오는 방법 설명!

 

  • sendMessageHandler
const AWS = require('aws-sdk');
      const ddb = new AWS.DynamoDB.DocumentClient();
      
      exports.handler = async function (event, context) {
        let connections;
        try {
          connections = await ddb.scan({ TableName: process.env.table }).promise();
        } catch (err) {
          return {
            statusCode: 500,
          };
        }
        const callbackAPI = new AWS.ApiGatewayManagementApi({
          apiVersion: '2018-11-29',
          endpoint:
            event.requestContext.domainName + '/' + event.requestContext.stage,
        });
      
        const message = JSON.parse(event.body).message;
      
        const sendMessages = connections.Items.map(async ({ connectionId }) => {
          if (connectionId !== event.requestContext.connectionId) {
            try {
              await callbackAPI
                .postToConnection({ ConnectionId: connectionId, Data: message })
                .promise();
            } catch (e) {
              console.log(e);
            }
          }
        });
      
        try {
          await Promise.all(sendMessages);
        } catch (e) {
          console.log(e);
          return {
            statusCode: 500,
          };
        }
      
        return { statusCode: 200 };
      };

메시지 주고 받게...

(위 두 함수들은 연결된 클라이언트들 호출을 위한 "execute-api:ManageConnections" 권한이 필요하다.)

 

❗ 근데.. 위는 정말 간단하게 메시지 주고 받는 거고... 실제로 사용할 땐 어떤 식으로 사용하는지 궁금하다... ❗

그리고 웹소켓 연결 후 주고받는 메시지들은 어떻게 관리가 되는지, 유실은 없는지 등의 궁금증이 생기네...?

'Cloud > AWS' 카테고리의 다른 글

[AWS] Serverless Service - Lambda 편 (2)  (0) 2022.11.05
[AWS] Serverless Service - Lambda 편 (1)  (0) 2022.11.05
[AWS] Lambda Warm Start / Cold Start  (0) 2022.10.28
[AWS] Fan-Out 시나리오 -1(easy)  (0) 2022.10.11
[AWS] Event-Driven Architecture  (0) 2022.10.11

제일 중요한(뇌피셜) 개념을 다뤄보려 한다..

사실 내가 이걸 먼저 보려고 했었는데...😂


Warm Start / Cold Start

Lambda 동작 순서

lambda code 작성 & 배포 → Load Code as .zip → Download to container → Bootstrap code(compile, load code to container) → run code

https://velog.io/@milkcoke/Lambda-Cold-start

(Bootstrap: 특정 언어에 대해 컴파일하여 기록하는 것)

 

Warm Start

  • 이미 실행 준비가 완료된 상태

 

Cold Start

  • 배포 패키지의 크기와 코드 실행 시간 및 코드의 초기화 시간에 따라 새 실행 환경으로 호출을 라우팅할 때 지연 시간이 발생
  • 쉽게 얘기하면 잠시 절전 모드의 컴퓨터를 키는 느낌 이랄까...
  • 발생 원인
    • 인스턴스 자체가 떠있지 않은 상황:
      • 컨테이너를 실행시키기 위해선 인스턴스가 활성화되어 있어야하는데 요청과 요청 사이의 간격이 너무 크거나, 최초의 요청이여서 인스턴스가 내려가있을 수 있다.
    • ENI 적용 시간:
      • Lambda도 결국엔 서버이다. 통신을 위해선 ENI가 컨테이너에 붙어야 한다. 특히, VPC를 사용하는 Lambda의 경우 반드시 적용된다.
      • 예를 들면, DB와 연결할 때, DB는 내부 서버에게만 열어야하기 때문에..
      • 이를 해결하려면 VPC + ENI의 성능을 향상시켜야 한다!
    • Dwell Time:
      • lambda 구조에서도 봤듯이 요청이 들어오면 컨테이너로 바로 들어가는게 아니라 큐에 쌓이게 된다.
      • 큐에서 요청을 꺼내 컨테이너로 보내는 시간: Dwell Time
      • 이 시간은 언제 길어질까?
        • 한 계정에 동시에 띄울 수 있는 최대 컨테이너 수(Max Concurrent Executions)는 제한되어 있음
        • 요청이 엄청 들어왔다고 가정했을 때, 컨테이너가 어떻게 증가할까?
          • 아마 컨테이너가 하나씩 선형적으로 증가하는 그림은 아닐듯
        • 한 계정에는 하나의 서비스를 만들기 위해 여러 종류의 Lambda를 사용하게 된다. 각 Lambda에서 생성될 수 있는 컨테이너 수를 제한하는데(Reserved Concurrent Executions) 이 설정보다 많은 요청이 동시에 들어오면 Throttle 발생!

 

Cold Start 해결 방법

  • Lambda 메모리 늘리기:
    • 메모리를 올리면 인스턴스의 사양(cpu)이 올라가 처리 속도 자체가 빨라질 수 있다고 한다.
    • but, 비용 고민: (메모리별 가격) * (요청이 처리되는 시간) * (요청 수)
    • 메모리가 올라가 요청이 처리되는 시간이 줄어들 수도 있고 그냥 메모리만 올라가면 음... 계산기 잘 두드려라
  • lambda 컨테이너 재사용:
    • 한번 호출되고, 다음 호출이 5분 이내인 경우 그 후속 호출에 컨테이너 재사용
    • 자체적인 Warm Start 구성이라고 생각해도 될듯?
    • 여기서 꼼수를 쓰자.. 요청 없어도 5분마다 강제 실행을 시키는 것!
      • CW 활용, Route 53 HealthCheck를 람다 API Gateway Endpoint 설정 등...
      • but, 쓸데 없는 지출이 증가됨
    • 시간이 오래 걸리는 코드는 global로 선언하는 게 좋다
      • 재사용할 때 돌아가지 않도록!
      • 예를들면 db_connection과 같은 작업?
더보기

warm-cold-start-test func

let isWarmStart = false;

exports.handler = async () => {
  let message;
 
  if (isWarmStart) {
    message = "warm start";
  } else {
    message = "cold start";
    isWarmStart = true;
  }
 
  return {
    statusCode: 200,
    body: JSON.stringify({ message }),
  };
};

그냥 아주 단순한 코드.. global로 isWarmStart 변수 선언하고 handler 실행되면 true로 값 바꿈.

처음에 실행한 결과값

그 이후 두번째 실행한 결과값

 

일정 시간 뒤에 다시 실행해보면 cold start를 다시 출력할 것이다..

 

'Cloud > AWS' 카테고리의 다른 글

[AWS] Serverless Service - Lambda 편 (1)  (0) 2022.11.05
[AWS] API Gateway - Websocket API  (0) 2022.11.01
[AWS] Fan-Out 시나리오 -1(easy)  (0) 2022.10.11
[AWS] Event-Driven Architecture  (0) 2022.10.11
[AWS] Lambda Layer  (0) 2022.10.07

SNS topic에 게시된 메시지가 복제되어
Kinesis Data Firehose 전송 스트림, SQS 대기열, HTTP(S) 엔드포인트 및 Lambda 함수와 같은 여러 엔드포인트로 푸시되는 경우

https://docs.aws.amazon.com/ko_kr/sns/latest/dg/sns-common-scenarios.html

 

이 중에서 나는 SNS - SQS 팬아웃 구조를 실습할 것이다!


(상황 설정: 온라인 상점에 주문이 접수될 때마다 주제에 대한 SNS 메시지를 전송하는 클라우드 네이티브 앱 개발 중

→ 해당 주제를 구독하는 SQS 대기열은 새 주문에 대해 각각 동일한 알림을 수신할 것)

 

  • SNS Topic 생성

 

  • SQS 대기열 생성

각각 이름이 Orders-for-Inventory / Orders-for-Analytics 인 Queue 생성

 

  • 대기열에서 주제 구독

 

  • 주제에 메시지 게시

 

  • 구독 확인

각 각 대기열 들어가서 폴링 하기

 

❗ 진짜 간단하게 SNS + SQS 구조를 나타낸 것... ❗

 

'Cloud > AWS' 카테고리의 다른 글

[AWS] API Gateway - Websocket API  (0) 2022.11.01
[AWS] Lambda Warm Start / Cold Start  (0) 2022.10.28
[AWS] Event-Driven Architecture  (0) 2022.10.11
[AWS] Lambda Layer  (0) 2022.10.07
[AWS] Amazon Rekognition 개념  (1) 2022.10.05

+ Recent posts