참고: https://www.youtube.com/watch?v=xmacMfbrG28 


(Asynchronous)

Poller: 큐에서 이벤트 가져와서 데이터 처리

  • event
    • SQS에서 얻은 페이로드를 전달하여 Lambda Function을 동기 호출
    • 장애 발생 시 재시도 관리
    • 정기적으로 하트비트 보내기
    • Lambda Function 실행 결과 데이터를 Destination configuration에 지정된 AWS 서비스로 전송
  • stream
    • 샤드를 구독하고 대상 Lambda Function의 메타데이터, 배치 크기 변경을 모니터링
    • 스트리밍 소스에서 데이터를 수신하고 배치 규칙(배치 크기, 배치 창 설정)에 따라 Lambda Function을 시작
    • 스로틀링이 발생하면 재시도
    • 정기적으로 하트비트 보내기
    • 스트리밍 소스가 비활성화되거나 삭제될 때 Poller 할당 삭제

State Manager / Stream Tracker: Scaling 관리(Poller 및 event/streaming source)

Leasing Service: 이벤트 소스 또는 스트리밍 소스에서 작동하도록 Poller 할당(Poller unhealty 표시되면 새로 할당)

 

Event Processing - Handling Asynchronous and Event Based Invokes

  • 사용자가 ALB에 Lambda 비동기 실행 (InvocationType = Event 지정)을 요청
  • ALB가 Front End Invoke에 요청을 전달
  • Front End Invoke가 SQS에 메시지 보내기(SendMessage)
  • 대상 대기열을 폴링하는 Poller가 SQS에서 메시지를 수신 (ReceiveMessage)
  • Poller가 Front End Invoke에 Lambda의 동기화 호출을 요청
  • 이 후의 흐름은 동기 호출과 같다

 

Event Destinations - Providing Callbacks After Processing

  • 사용자가 ALB에 요청을 발행 ~ Poller가 동기 호출을 할 때까지 이전의 비동기 호출 시퀀스와 유사
  • 동기 호출의 결과 (성공 / 실패)에 따라 Event Destinations에 지정된 리소스에 대해 Poller가 메시지를 전달
  • Poller는 SQS에서 메시지를 삭제 (DeleteMessage)

 

Scaling Up and Down - Incoming Event Volume

State Manager - Leasing Service를 활용

State Manager는 대기열과 관련 Poller를 확장하여 수신된 이벤트를 Lambda Functions에 설정된 concurrency 까지 처리할 책임이 있음

  • State Manager가 SQS의 메시지를 확인
  • State Manager가 SQS 변경을 감지하고 Lambda Function의 동시 실행 수가 큰 경우 Leasing Service에 Poller 할당을 요청하여 새로운 큐 생성
  • Lambda Function의 동시 실행 수가 작 으면 기존 큐를 그대로 사용
  • Leasing Service는 DynamoDB에 Poller 할당 정보 작성
  • State Manager는 지정된 리소스에 여러 개의 Poller를 할당하여 중복성을 보장
  • Poller는 DynamoDB에서 할당 정보를 읽고 대기열 폴링을 시작

 

Handling Errors

Leasing Service: 정기적으로 Poller의 상태확인을 수행, heartbeat 안보낸 poller 감지 후 새 poller 할당

  • 이벤트 소스를 처리하도록 할당된 Poller가 중지됨
  • Leasing Service는 한 점 기간 내에 Poller에서 하트비트가 전송되는지 확인
  • 하트비트가 전송되지 않은 경우 Leasing Service는 새로운 Poller를 할당
  • 새 Poller가 할당 정보를 읽고 이벤트 소스 처리를 시작

 

Retry Policies

이벤트 수명: 0초 ~ 6시간

재시도 횟수 0 ~ 2 번

ex) retry 1번, DLQ 설정 O

  • 사용자가 ALB에 요청을 발행 ~ Poller가 동기 호출을 할 때까지는 위의 동기 호출 순서와 유사
  • Front End Invoke에서 오류가 반환됨
  • Poller는 대기 시간이 지나면 다시 동기화 호출을 시도
  • Front End Invoke에서 오류가 반환됨
  • Poller는 DLQ에 메시지 보냄 (SendMessage)
  • Poller는 SQS에서 메시지 삭제 (DeleteMessage)

(Stream: Poll-based)

Stream Processing

  • 사용자가 Kinesis에 레코드 추가
  • Kinesis의 샤드에 할당 된 Poller가 레코드를 얻는다
  • Front End Invoke에 Lambda의 동기화 호출

 

  • Stream Tracker가 이벤트 소스 (Kinesis 샤드 등)의 상태를 읽는다
  • Stream Tracker가 Leasing Service에 Poller 할당을 요청
  • 할당된 Poller는 이벤트 소스 샤드 구독을 시작

 

Scaling Up and Down - Incoming Event Volume

스트리밍 소스가 활성화된 경우 활성 샤드 수에서 필요한 Poller 수 계산/결정

Stream Tracker는 스트림을 처리하는 데 필요한 동시 실행 수를 계산하고 Leasing Service에 추가 Poller 요청

https://aws.amazon.com/jp/blogs/compute/new-aws-lambda-scaling-controls-for-kinesis-and-dynamodb-event-sources/

  • Stream Tracker가 listShardsAPI를 호출하여 샤드 수 변경을 감지
  • Stream Tracker는 Leasing Service를 통해 Poller 수를 조정
  • Leasing Service는 DynamoDB에 Poller 할당 정보를 쓴다
  • Poller는 DynamoDB에서 새 할당 정보를 읽고 샤드 구독을 시작

 

Handling Errors - Stream

DLQ, Exponential Backoff 지원

스트리밍 소스를 순서대로 처리하고 처리에 실패한 배치는 레코드가 만료될 때 까지 처리를 재시도

  • 사용자가 Kinesis에 레코드 추가
  • Kinesis의 샤드에 할당 된 Poller가 레코드를 얻는다
  • Front End Invoke에 Lambda의 동기화 호출
  • 처리가 실패하고 Front End Invoke가 Poller에 오류를 반환
  • Poller는 실패한 배치를 두 개의 배치로 분할하고 다시 Front End Invoke에 Lambda의 동기 호출을 수행
  • 두 배치 중 하나가 성공하면
    • Front End Invoke가 Poller에게 성공을 반환
    • Front End Invoke가 Poller에 오류를 반환
  • Poller는 재시도 후 오류가 반환된 배치를 DLQ에 보낸다

Lambda - Scale Out(Provisioned Concurrency)

왜 Provisioned Capacity가 아닌가?

  • 용량은 하드웨어에 의존한다.
    • 특정 워크로드에 필요한 용량은 하드웨어 성능에 따라 다르다. 하드웨어가 새로운 CPU, 더 빠른 메모리로 변경되면 그에 따라 필요한 용량도 변경됨.
    • 이는 고객이 하드웨어 성능에 대한 이해가 필요하다. → 서버리스의 의미에서 벗어남
  • 내결함성
    • 용량 모델은 호스트 및 AZ 장애를 고려하여 AZ 장애가 발생했을 때에도 충분한 용량을 얻을 수 있도록 설계해야함
  • 용량 모델에서 비즈니스 로직 변경이 용량 사용 효율에 영향을 줄 수 있다.
    • 비즈니스 로직 구현 팀이 기능을 추가하면 프로비저닝 된 용량에서 얻은 처리량이 변경된다.

Scale Up

  • 실행 환경이 확장되면 cold start 발생 → 지연 시간 발생(EC2나 컨테이너 기반)
  • Lambda의 경우 concurrency 기반으로 scale-up

 

우수한 웹 서비스 구축하려면..

  • 부하 제어: 처리량이 떨어지기 시작하는 전환점을 초과하지 않도록 제어
  • 빠른 Scale: 빠르게 확장 및 용량 추가가 가능해야한다.
  • 사전 프로비저닝 가능: Provisioned Concurrency 사용

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

[AWS] GPU 인스턴스 유형(EC2)  (0) 2022.11.12
[AWS] Lambda에 X-Ray 적용하기  (0) 2022.11.06
[AWS] Serverless Service - Lambda 편 (2)  (0) 2022.11.05
[AWS] Serverless Service - Lambda 편 (1)  (0) 2022.11.05
[AWS] API Gateway - Websocket API  (0) 2022.11.01

참고: https://www.youtube.com/watch?v=0PRjqEQ2J3g&t=56s 

https://www.slideshare.net/awskorea/aws-lambda-aws-aws-summit-seoul-2019


Front End Invoke: 실제 API call을 받는 역할(동기 / 비동기 호출 모두 관장)

(Synchronous Inoke)

Counting Service: 사용자가 얼마나 많은 API 요청을 하는지 모니터링하고 제한 기능 제공

Worker Manager: 실제 Container의 상태를 관리하고 API 요청을 가용 가능한 Container로 중계

Worker: 고객 함수(코드)가 안전하게 실행되는 실제 Container 환경

Placement Service: Worker에 Sandbox 구성을 자원 활용률이 높고, 고객 서비스 영향이 없도록 관리

 

Load Balancing & Auto Scaling

Load Balancing(Worker 배정 과정)
Auto Scaling(Worker Node 추가)

(Worker가 있는 건 알겠는데 얼마나 바쁜지 일을 더 줘도 되는지 Mgr가 Placement에게 물어본다.)

(필요한 Function이 일하고 사라지는 개념, 일을 다하면 리소스에 대한 return → 이 역할도 Placement 담당)

 

Handling Failures

멀티 AZ로 관리 되기 때문에 한 AZ에서 장애 발생해도 상관 없다!

 

Isolation

고객의 Workload = function = code는 항상 안전한 환경에서 실행한다.

Firecracker (하나의 host 내에서 수백개 ~ 수천개 단위의 vm 돌아갈 수 있음)

→ 하나의 코드가 그냥 독립되어서 돌아간다고 생각하면 됨

https://github.com/firecracker-microvm/firecracker

이렇게 되면 굉장히 많은 vm이 동시에 제한된 하드웨어 리소스를 공유하는 건데 충돌 발생 안하나???

→ Guest OS에서 Host OS로 접근할 때의 Driver를 개선! (firecracker device 안에서 직접적으로 하드웨어를 액세스)

(VirtIO: 게스트 커널의 디바이스 드라이버와 협력해 게스트 커널을 부팅하는데 필용한 하드웨어를 가상으로 제공)

  • Guest OS가 VirtIO 드라이버에 쓰기 요청
  • VirtIO 드라이버가 커널 링 버퍼의 공유 메모리에 요청을 PUSH
  • Firecracker가 쓰기 요청을 풀고 물리 디스크에 쓰기

Utilization

"Lambda의 경우 사용한 만큼 요금을 지불하기에 주어진 시간 내 최대한 시스템을 바쁘게 돌려야 좋다."

→ 균형 잡힌 분배가 아닌 몰빵

동일한 Workload를 한 곳에 담게 되면 자원에 대한 충돌이 일어날 확률이 높다.

→ 각 각 다른 타이밍에 다른 자원을 사용하는 멀티태스킹을 해라!

Worker 외부의 RemoteNAT 컴포넌트로 ENI와의 NAT 처리 진행

https://aws.amazon.com/ko/blogs/compute/announcing-improved-vpc-networking-for-aws-lambda-functions/

  • 시간이 많이 걸리는 처리는 Lambda Function의 스케일 시가 아닌 Lambda Function 작성 시에 실행되므로 스케일이 빨라진다
  • VPC Lambda의 대기 시간이 낮아지고 대기 시간이 예측하기 쉬워진다
  • ENI 및 IP 주소 범위에 제한을 두지 않고 VPC Lambda를 실행할 수 있다
  • 하나의 ENI를 많은 Worker에서 공유하고 이용하기 때문에 IP 주소의 소비 수가 줄어 NW 관리가 간소화된다

Lambda 와 RDS/RDBMS 접근

전형적인 DB 접근

  • 여러 가용 영역 내 Subnet에 ENI 사용: AZ 레벨의 이벤트 또는 IP 소모 문제 피할 수 있음
  • Lambda는 VPC 내 ENI로 접근: 가용 IP에 따른 확장성의 제약을 고려해야한다, ENI 신규 구성은 시간이 소모됨.
  • Public host name DNS 쿼리를 피할 수록 좋음
  • 기본적으로 VPC의 Lambda는 인터넷 접근 불가능: NAT 추가하고 Routing Table 구성 필요

 

DB 커넥션 관리는 어떻게 해야할까?

(수많은 Lambda가 필요할 때)

  • Connection Pooling과 Lamdba
    • Container 당 하나의 connection 만 사용: Connection Pool Size = 1
    • Handler 밖에 Global section 에 DB connect 객체를 생성, 재활용

 

위처럼 하면 생기는 문제점

  • Lambda container가 사라지는 지 인지할 수 없음: Connection을 명시적으로 닫을 수 없음(Database TTL에 의지)
  • Lambda container의 생성 삭제를 조정할 수 없음: Idle(대기) connection이 많이  생성될 수 있음
  • 여러 Lambda 함수 실행은 여러 다른 Container에서 실행될 수 있음: Connection 재사용 보장 못함

 

해결 방법 1: Concurrency limit

  • 계정 Concurrency 제한 / 함수 Concurrency 제한
    • AWS support - Account 제한 요청
  • Lambda는 호출 제한이 있을 경우 retry 수행/관리

"Concurrency limit: 본인의 전체 서비스에 문제가 없도록 보호하기 위해 존재, 필요에 따라 조절 가능"

  • Account level: 계정 내 모든 lambda에 적용되어 DB 접근 함수 제한이 간단하지 않음
  • Function level: 어떤 Lambda 함수가 DB 접근이 필요한지 확인, Peak 발생하기 전에 알고 App 레벨의 이해 필요

 

해결 방법 2: 동적 Connection 관리

  • 확장 가능한 구조 구현
  • Connection 수를 관리 가능, Lambda 함수 개수와 무관
  • 고려 사항: 관리 리소스 증가, Connection 재사용 불가, 약간의 Latency 증가
더보기

❗ 그래서 결론적으로 뭔 소리냐...? ❗

(Lambda + RDS의 경우 문제점)

1) lambda와 rds가 connection을 맺었는데 lambda가 명시적으로 connection 종료를 하지 않은 채 없어졌는데, 또다른 요청이 들어왔을 경우 새로운 connection을 맺게 됨...(max_connection 문제)

2) lambda가 과도하게 invoke, 실행되는 동안 모든 lambda가 connection을 맺게 되면 또 max_connection 초과

 

(해결 방안)

1) Connection 관리: handler 하나에 1 Connection / globel scope으로 connection 관리

2) RDS Proxy: Connection Pool은 Proxy가 가지고 있고, Lambda는 프록시를 통해 대신 Connection을 사용

3) API 호출 최적화: lambda가 동시에 적게 실행 + Connection이 빠르게 반환되게

 

그래도 가능하다면 DynamoDB나 AWS Aurora와 같이 Scale Out이 가능한 DB를 사용하는 것이 좋다.

 

참고: https://velog.io/@jdd04026/Lambda

 

Tip. Lambda 함수 트레이싱

  • 필요한 이유("lambda는 실행 후 사라지는 개념..")
    • 서버리스 아키텍처/애플리케이션 개발 시 Debug 편의성
    • 서버리스 아키텍처/애플리케이션 동작 시 성능 문제 해결의 편의성
  • 방법
    • CloudWatch를 통한 Metrics 모니터링
    • CloudWatch log 통한 log 분석
    • log와 metric으로 부족한 경우:
      • 전체 요청 처리 시 서버리스 함수 간의 관계 파악 확인
      • 요청 처리 시 다른 리소스 접근에 대한 관계 파악 확인
    • X-ray 서비스 활용: 서버리스 아키텍처를 만들고 람다로 서비스 개발 시 전체 흐름 확인

 

Tip. Lambda 커스텀 런타임 사용

  • Layer 기능을 활용하자!

 

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

[AWS] Lambda에 X-Ray 적용하기  (0) 2022.11.06
[AWS] Serverless Service - Lambda 편 (3)  (0) 2022.11.06
[AWS] Serverless Service - Lambda 편 (1)  (0) 2022.11.05
[AWS] API Gateway - Websocket API  (0) 2022.11.01
[AWS] Lambda Warm Start / Cold Start  (0) 2022.10.28

 Managed Level - Console

  • 구성
    • 일반구성: 기본 설정 관련
      • 메모리: 메모리에 비례하는 CPU가 함수에 할당/Compute Optimizer 옵트인 가능
      • 임시 스토리지: /tmp 디렉터리, 최대 10GB까지 대기 시간이 짧은 안전한 임시 파일 시스템 액세스
      • 제한 시간, 실행 역할
    •  트리거
      • 소스: Alexa, Apache Kafka, API Gateway, ALB, CW Logs, CodeCommit, Cognito Sync Trigger, DynamoDB, EventBridge, Kinesis, MQ, MSK, S3, SNS , SQS 등..
    • 권한
      • Lambda 실행 역할 / 태그 기반 액세스 제어 / 리소스 기반 정책 등..
    • 대상
      • 소스가 비동기식 호출: SNS, SQS, Lambda, EventBridge
      • 소스가 스트림 호출: Kinesis, DynamoDB
    • 함수 URL: Lambda에 고유한 HTTP(S) 엔드포인트 추가
      • 언제 사용할까?
    • 환경 변수: key-value
    • 태그
    • VPC: (default로 퍼블릭으로 액세스 가능한 리소스로 deploy)
    • 모니터링 및 운영 도구: CW, X-Ray, CW Lambda Insights
    • 동시성: 함수에 대한 최대 동시 실행 횟수 설정
    • 비동기식 호출: 처리되지 않은 이벤트 처리
      • lambda가 자체적으로 재시도를 처리(최대 2번)
      • 그래도 스로틀 발생, 오류 반환 시 대기열에 보관(최대 6시간)
      • 보관 이후에 삭제하는데 이를 SQS나 SNS로 전송(배달 못한 편지 대기열)
    • 코드 서명: 신뢰할 수 있는 코드만 함수에서 실행되도록
    • 데이터베이스 프록시
      • 데이터베이스 연결 풀 관리
    • 파일시스템: EFS 연결
    • 상태 머신: Step Functions
  • 별칭: 함수 버전을 매핑
  • 버전

Code Level(Python 기준)

  • Lambda Handler Function:
    • Lambda 콘솔의 기본 이름: lambda_function.lambda_handler
      • lambda_handler: 함수 이름(핸들러 이름 바꾸고 싶으면 여길 바꾸면 됨)
      • lambda_function: 핸들러 코드가 저장된 파일
##Global Scope

def lambda_handler(event, context):
	## business logic here
    return response
  • event: 
    • Lambda가 Invoke 될 때 다른 서비스에서 이벤트 (혹은 데이터) 를 전달 받는다.
    • 어떤 서비스로부터 Invoke 되었는지에 따라 event의 구조가 달라진다.
    • Invoke 방식에는 Event / RequestResponse 로 나뉨.
      • RequestResponse: API Gateway → Lambda
      • Event: Response를 바라진 않음. 이벤트 전달이 목적!(Asynchronous)
    • Lambda가 Event 방식으로 호출된 경우, 실행 결과로 response 값이 있으면 이 데이터는 Invoke를 요청한 클라이언트에게 다시 전달될까? → NO!
import os

def lambda_handler(event, context):
    print('## ENVIRONMENT VARIABLES')
    print(os.environ)
    print('## EVENT')
    print(event)
더보기

 여기서 착각했던게 있는데...

이 테스트 이벤트의 경우에는 테스트를 위한거고 실제적으론 함수를 invoke 하는 서비스의 이벤트를 받는 것!

테스트 용으로 람다 함수가 잘 작동하는지 확인하고 해당 서비스와 트리거를 하던 연결을 하던 해서 사용하면 될듯..

(아마 서비스마다 들어오는 이벤트 형식이 지정되어있는 것 같다... 테스트 이벤트 템플릿 보면 겁네 많음)

  • context:
    • Lambda의 Runtime 정보를 핸들러에게 제공
      • function_name, function_version, invoked_function_arn, memory_limit_in_mb, aws_request_id, log_group_name 등..(로깅 등의 용도로 사용할 수 있는 메타데이터)
      • 오랫동안 실행되는 Lambda의 경우 get_remaining_time_in_millis() 가 쓸모 있을지도..
import time

def lambda_handler(event, context):   
    print("Lambda function ARN:", context.invoked_function_arn)
    print("CloudWatch log stream name:", context.log_stream_name)
    print("CloudWatch log group name:",  context.log_group_name)
    print("Lambda Request ID:", context.aws_request_id)
    print("Lambda function memory limits in MB:", context.memory_limit_in_mb)
    # We have added a 1 second delay so you can see the time remaining in get_remaining_time_in_millis.
    time.sleep(1) 
    print("Lambda time remaining in MS:", context.get_remaining_time_in_millis())
  • Global Scope:
    • 핸들러 바깥의 영역으로 init(cold start 시)에만 실행되는 부분
    • Worker 노드가 종료되지 않으면 메모리에 남아있음(캐시처럼 사용할 수 있는 트릭)
    • 모놀리식 구조로 코드 작성했을 때 RDS 커넥션 추가해도 괜찮다..
      • but, 함수를 기능별로 나눴을 경우 RDS 커넥션 해두고 끊지 않으면 DB 쪽에서 max_connection에 걸릴 수도.. → Global Scope에 연결하는 것보다 사용할 때마다 연결 하고 끊는게 좋다
    • init은 했는데 terminate을 따로 안해주니까 이런 경우에 시스템이 어떻게 동작할까에 대한 고민을 해라!
  • 로깅:
  • 오류:
    • Lambda invoke → 호출 요청 수신 → 검증 과정
      • 실행 역할 권한 검증 → 이벤트 문서가 유효한 JSON 문서인지 확인 → 파라미터 값 검사
    • 요청이 검증을 통과하면 Lambda가 함수 인스턴스로 요청을 보냄
      • Lambda 런타임 환경의 경우 이벤트 문서를 객체로 변환한 후 함수 핸들러에 전달
    • 오류 발생 반환: 예외 유형(오류의 원인), 메시지 및 HTTP 상태 코드

Infra Level

  • Lambda Invoke:
    • Invocation Type: Invoke 시 어떤 방식으로 실행할지 결정 가능
      • Event: Asynchronous 방식
        • Lambda 함수가 성공했는지 여부를 알 수 없음
        • Invoke Event를 잘 수신했다(Accept의 의미): "StatusCode: 2XX"
        • → 코드가 잘 동작했는지 확인할 수 있는 방법이 없어 DLQ가 필수다!
        • S3, SNS, EventBridge (CloudWatch Events)
      • RequestResponse(default): Synchronous 방식
        • "StatusCode: 200" : 성공적으로 잘 수행했다
        • Kinesis, DynamoDB Streams, SQS, ALB, API Gateway
      • DryRun: 파라미터 및 권한을 검사해서 실행 가능한지에 대한 체크(실제 Invoke는 아님)
    • Asynchronous invocation:
      • event를 queue(SQS)에 넣는다 : Lambda 내부적으로 queue를 가지고 있음
        • lambda 함수가 적절한 타이밍에 큐에 쌓인 이벤트를 처리를 못하면 이벤트가 손실 될 수 있음
        • 따라서, 반드시 수행되어야하는 태스크는 별도의 고객 소유 SQS를 통해 Invoke 시켜야함
          • lambda + sqs + lambda 의 구조가 best practice
      • 하나의 이벤트를 중복처리하는 경우도 존재:
        • Event-driven 방식으로 동작하는 함수는 반드시 Idempotency(멱등성)를 고려하여 작성
      • 함수 실패의 경우 Dead Letter Queue를 통해 알림 받기 가능
    • Invocation Type에 따른 Lambda 트러블 슈팅:
      • 대량의 데이터를 SQS에 넣었다. Lambda를 Invoke 시키는 시나리오에서 Lambda 실패, 데이터는 어디로 갈까? Lambda 함수는 기다리고 있으면 다시 또 실행될까?
        • SQS는 RequestResponse(Synchronous) 방식: retry를 몇 번 시도 후 그래도 실패하면 실패 이벤트 처리로 넘어감. → 실패 시 고객이 SQS의 처리상태를 통해 직접 알 수 있음
      • 대량의 파일을 S3에 업로드해서 Lambda를 Invoke 시키는 시나리오에서 실패했다, 이벤트는 어디로 갈까? Lambda 함수는 기다리고 있으면 다시 또 실행될까?
        • S3 는 Event(Asynchronous) 방식: retry 몇 번 시도 후 실패시 이벤트 손실이 생길 수 있음 → DLQ를 지정하는 방법밖에 없음(Lambda 내부 문제)

 

Lambda 내부 아키텍처

  • 컴포넌트들이 달성하고자 하는 목표?
    • Load Balancing
    • Scaling Up and Down
    • Handling Failures
    • Predictable Low Latency
    • Security Isolation
    • Managing Utilization
    • ...

 

  • Synchronous Invoke:
    • Front End Invoke: IAM Policy, Role 확인(authentication) / Concurrency 더 수행할 수 있는지 / 메타데이터 확인 등
    • Counting Service: concurrency limit을 넘지 않는지
      • 근데 concurrency를 초과해서 실행될 수 있음 → limit에 도달하자마자 막으면 고객들이 maximum concurrency를 누릴 수 없음
    • Worker Manager: Front End와 함께 AutoScaling
    • Worker: sandbox 구성(해당 cpu, memory limit 설정, 고객 코드, runtime, 실행환경 초기화 등..)
    • Placement Service

(Alb(우리한텐 안보임)가 받아 Front End로 Front End가 Worker Mgr에게 sandbox 가져오라고 명령

sandbox 있으면 Worker 가져오고,

없으면 Placement 에게 sandbox 만들 수 있는 Worker 달라고 요청, Worker를 Worker Mgr에게 전달, Worker Mgr가 sandbox에 init command 날리고 Front End에게 전달 Front End가 코드 invoke 명령어 내리고

결과가 Alb를 통해 나간다.)

→ Warm Sandbox 존재: Claim Worker 과정과, init 과정이 필요 없음! (빠르게 invoke 가능) ⇒ Warm Start

Warm Sandbox 없음: Claim Worker 과정과 init 과정 필요 (지연이 있음) ⇒ Cold Start

 

  • Asynchronous Invoke:
    • Poller: 큐에 저장된 이벤트 꺼내와서 처리되는 과정을 전부 관리
    • State Manager/Stream Tracker: Poller 서비스를 스케일링, 이벤트 소스 스트림을 관리하거나 등..
    • Leasing Service: 어떤 Poller 서비스가 어떤 이벤트를 담당할건지

(synchronous와 Front End까진 같다, Font End에서 Worker Mgr로 가면 sync / SQS로 가면 async

Front End에서 SQS로 넣게 되면 함수 결과값으로 StatusCode: 2XX 던져주고 끝.

SQS에서 Poller가 이벤트 가져와야하는데 어떤 Poller가 어떤 이벤트 담당할지 결정하는게 Leasing Service(그림엔 없음)

SQS에서 메시지를 가져온 Poller가 새로운 클라이언트가 되어서 Front End를 호출할 때 RequestResponse 방식으로 호출 - 이 뒤로는 sync와 똑같이 이루어진다.)

 

Firecracker

 

간단하게(?) 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

+ Recent posts