728x90

람다 SnapStart 를 적용하며 

람다에 자바로 된 함수를 띄었다. 자바 파일은 아무래도 node.js에 비해 사이즈가 컸다. 
기존에 회사 넥서스에 올라가 있는 라이브러리를 사용하려다 보니 자바로 어쩔 수 없이 해야 했던 상황이다. (다시 js로 구현 해도 되지만 알림톡쪽은 급하게 개선 해야 하는 비즈니스 로직이라 그건 추후로 미뤘다)
그래서 50mb 정도 되는 자바 코드를 그대로 올려서 cold start 로 실행 시켰다.
그러다 보니. 람다 자체내에서 코드를 다운로드 하고, 컨테이너를 띄우고, 전역 코드를 실행 후 내부 함수를 실행하는 순서 까지가 일반적인 방식으로 40000~50000ms(4~50s) 정도가 걸렸다.

처음에 ColdStart로 40초 정도 걸리니 방법을 모색 해보았다. 처음에는 다음과 같은 방법을 찾았다.
- 타임아웃 늘리기
- node.js 로 다시 구현
- 프로비저닝 동시성 구성
- 해당 함수 실행 예상 시간 전에 health check를 통해 warm up 해놓기

 

타임아웃 늘리기

근본적인 해결책은 아닌 것 같았다. 해당 함수가 띄어질 때 까지 40~50초 가량 걸리는데, 빌드될 때 까지 기다리고 처리될 때 까지 함수가 내려지지 않을 정도의 시간을 주는 것이었다.


node.js 로 다시 구현

위에서 말했듯이 다시 js로 구현 해도 되지만 알림톡쪽은 급하게 개선 해야 하는 비즈니스 로직이라 그건 추후로 미뤘다. 그리고 해당 라이브러리에 은근 로직들이 많아 새로 다른 언어로 로직을 만드는데 리스크를 안아야 하는점도 있었다.


프로비저닝 동시성 구성

항상 함수를 띄어 놓으면 그만큼 과금이 된다. 하루에 특정 시간대에 한 두번 도는 함수인데, 함수가 무거워서 컨테이너가 올라갈 때 시간이 많이 걸린다는 이유만으로 해당 금액을 지불하기엔 돈이 아까웠다.


해당 함수 실행 예상 시간 전에 health check를 통해 warm up 해놓기

해당 로직을 처리 해야 하는 예상 시간을 추측 해서 5분 마다 헬스체크 하면서 warm up 시켜놓는 방식도 괜찮았지만, 근본적인 해결책이라고는 볼 수 없었다. 헬스체크를 보내야 하는데, 이를 보내는 주체에 의존하는 모양이 되기 때문에, 주체에 문제가 생기면 warm up이 안되기 때문이다.


지금까지의 고민을 SnapStart이 모든것을 해결해 주었다.

 

SnapStart 란 해당 버전의 빌드 파일의 스냅샷을 따서 캐싱 하고(스냅샷은 Amazon EBS 에 저장), Cold Start를 할 때 이를 활용하여 실행하는 기능이다. 그래서 람다 컨테이너를 올릴 때 아무리 큰 함수라도 빠르게 올릴 수 있다.

 

스냅샷은 구체적으로 함수 코드가 로드되고 초기화된 상태를 저장한다.

 

메모리상태
- Java 런타임에 의해 로드되고 초기화된 모든 클래스
- 정적 변수
- 초기화된 객체
- JIT 컴파일된 코드
- 의존성
- 캐시된 데이터
- 연결 풀
- 스레드 상태


디스크 상태
- 함수 코드와 의존성 라이브러리들이 로드된 상태
- 런타임 환경에 의해 생성된 파일 시스템 구조


그러나 SnapStart에 제약사항은 존재한다.


런타임 제약사항

- 자바 전용이며 Java 11, 17을 지원.
- x86 아키텍처만 동작.
- 프로비저닝된 동시성 불가.
- EFS(Amazon Elastic File System) 연동 불가
- 512MB 이상의 임시 스토리지 불가
- 배포시간 증가 - 개발 생산성에 영향을 줌 (배포 파이프라인에 영향)
- 고유성에 영향을 줌 - 글로벌 변수에 난수 생성은 스냅샷에 포함되어 동일한 값이 사용되게 되기 때문이다. (함수에서 생성하거나 hook을 통해 처리 해야 함)

 

참고자료

 

728x90

 

배경

이 포스팅에서는 DLQ로 SQS의 fifo queue로 채택을 했다. 해당 DLQ는 source queue와 lambda를 바라보고 있다. 해당 DLQ에 이벤트가 쌓이는 케이스는 대표적으로 source queue와 lambda 사이에 통신이 안될 때, lambda에서 처리 로직 중 에러가 발생 했을 때 가 된다고 생각해서 DLQ의 필요성을 느껴서 비치했다.

실패한 이벤트에 대해서 후처리를 해야 하는 거는 분명히 필요하지만, lambda 이후에 fifo DLQ를 비치하는것에 문제가 있었다. SQS fifo 타입의 장점 중 하나인 이벤트 중복 제거를 해친다는 점에서 문제가 있었다.

 

https://aws.amazon.com/ko/blogs/compute/new-for-aws-lambda-sqs-fifo-as-an-event-source

 

 

해결

람다에서 재시도를 할 때 중복 이벤트가 DLQ에 들어갈 수 있다. 여기서 의문이 들 수 있는 부분이 DLQ는 fifo 타입인데 어떻게 중복으로 들어가냐는 말인데 이벤트 content가 아무리 같다고 하더라도 fifo queue에 들어가는 이벤트가 중복인지 아닌지를 판단하는 부분이 GroupId, Deduplication Id으로 판단 한다. 그러나 해당 이벤트의 content를 기반으로 중복 이벤트 처리를 할 수도 있다. Content-based de-duplication 을 활성화 하면 되긴 한다. 그러나 굳이 DLQ가 있어야 할까? 라는 생각이 들었다. 더 cost가 저렴한 RDB/NoSQL 등으로도 처리가 가능하다고 생각했다. 그리고 후처리 까지 써드파티에 의존하기가 꺼려졌다. 그래서 실패한 이벤트에 대한 후처리는 DB로 처리하기로 결정했다.

해당 처리는 복잡한 조인이나 집계 쿼리 같은 것들이 필요가 없고 빠른 읽기/쓰기가 요구되어서 RDB 보다 NoSQL인 MongoDB로 하기로 했다. 데이터의 id를 실패한 이벤트의 messageId 로 설정하며 데이터 정합성을 더했다. 그리고 DLQ의 리드라이브를 대체하여 애플리케이션에서 특정 API 호출 시 dead letter 가 쌓인 MongoDB에 아직 미해결 된 이벤트들을 찾아서 다시 호출 해서 source queue로 다시 넣어 프로세스를 흐름에 편승하게끔 하게 해결했다.

 

AS-IS TO-BE

 

 

결론

최고 50% 가까이 개선 됐다.

배포 전 배포 후


 

(* 비슷한 데이터 처리량을 토대로 비교 했음.)

 

회사에서 또 다른 유의미한 발전을 이루어서 뿌듯했다.

 

+ Recent posts