Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags more
Archives
Today
Total
관리 메뉴

ultra_dev

Amazon SQS와 Lambda를 활용한 서버리스 활용 및 성능 개선기(feat. 람다에서 비동기 코드 사용시 주의하기) 본문

SPRING&JAVA

Amazon SQS와 Lambda를 활용한 서버리스 활용 및 성능 개선기(feat. 람다에서 비동기 코드 사용시 주의하기)

ultra_dev 2024. 4. 5. 03:02

Aws Lambda란?

:서버리스 기반의 플랫폼으로 필요할 때만 함수가 호출되는 서비스이다. 아래와 같은 장점이 있다.

  1. 확장성: 서버리스 아키텍처이기 때문에 필요한 만큼의 리소스를 동적으로 확장하여 사용할 수 있음
  2. 비용 절감: AWS Lambda는 실행 시간만큼만 과금되므로 사용한 만큼의 비용만 지불하면 됨
  3. 간편한 관리: 서버를 관리할 필요가 없고 수정 및 배포가 간편함

 

 

사용 이유

: 위의 장점을 이유로 스프링 배치에서 구현했던 메일 전송 기능을 Aws Lambda를 활용하는 방식으로 바꾸기로 했다.

고려 사항 

: 위의 부분이 장점이라면 백엔드 서버 상에서 스케줄러를 돌리면 되는데 굳이 람다를 쓰는 이유는?

 

-> 현재 Aws ECS 클라우드 컨테이너를 사용해서 배포하는 방법으로 마이그레이션을 진행 했는데, 트래픽이나 메모리 사용량에 따라 백엔드 서버가 여러 개 띄워질 경우에 스케줄러가 원하는 방식으로 작동하지 않을 것이기 때문(중복 호출 등)

 

 


 

스프링 프레임워크는 사용하지 않고 순수 자바 코드에 Gradle을 통해 차트 관련 라이브러리나 DB 관련 라이브러리에 대한 의존성을 주입해주며 개발했다. (그래도 용량이 10mb가 넘더라..)

 

Jar파일을 람다 함수에 업로드 하는 방식으로 개발했다.

주입한 라이브러리들도 람다 함수에서 인식할 수 있게 하기 위해 Jar파일을 아래와 같은 방식으로 생성했다.

 

task buildZip(type: Zip) {
    archiveFileName = 'test-lambda.zip'
    from compileJava
    from processResources
    into('lib') {
        from configurations.runtimeClasspath
    }
}

 

이후 아래와 같은 방식으로 파일 업로드 및 Handler를 지정해주는 방식으로 진행했다.

(참고로 Handler 위치 변경과 파일 업로드는 동시에 진행하지 못하기 때문에 수정 시, 각각 따로 작업을 진행해야 한다.)

 

org.test.handler는 해당 클래스가 속한 패키지, 그리고 SQSProducerHandler는 대상 클래스 이름,

뒤에 handlerRequest는 메소드 이름이다.

 

SQS를 사용한 이유에 대해서는 후술하겠다.

 

 

 

 

 

참고로 handleRequest 메소드는 AWS Lambda에서 요청 핸들러 함수로 사용되며, RequestHandler 인터페이스를 구현할 때 필수적으로 포함해야 하는 메소드이다.

 

즉, 이 메소드는 Lambda 함수가 호출될 때 실행되는 메인 핸들러 함수이다.

 

public class SQSProducerHandler implements RequestHandler<SQSEvent, String> {

    public String handleRequest(SQSEvent sqsEvent, Context context) {

 

 

 


 

다시 비즈니스 로직으로 돌아가자면, 처음 구상안은 아래와 같았다.

 

1. 매 시간마다 이벤트 브릿지에서 이벤트 발동

2. DB에서 해당 시간에 메일 알람 요청을 한 유저 관련 데이터 조회

3. 지정 데이터 차트 생성

4. 메일 발송

 

 

따라서 처음에는 위의 handlerRequest 메소드 하나에 위의 로직이 돌아가도록 만들었다.

 

 

문제 발생

 

이때 문득 대량 작업 처리에 관한 의문 사항이 들었다.

 

1.

고객들이 정각에 메일발송 알림을 신청했는데 이후 고객이 증가하게 된다면,

위의 람다 함수 1개에서 대량의 작업이 실행될 것이다.

 

2. 

DB조회 후, 여러 차트를 생성해서 메일로 전송하는 가볍지 않은 작업인데 이거를 위의 람다 함수 1개에서 처리한다면 

메일이 과연 제 시간에 도착할 수 있을까?

 

결론 : 이 구조는 확장성 측면에서 좋지 않은 구조이다.


 해결 방안

: SQS 메시지 큐를 사용하자! 

 

1. 매 시간 이벤트 브릿지 발동

2. Producer 람다 함수에서 SQS에 관련 의뢰서 정보를 쏴준다.

2. SQS에 의뢰서 정보가 들어오면 Consumer 람다 함수를 호출한다.

 

 

이를 통해 여러 람다 함수가 실행되며 다량의 고객이 생기더라도 불편함이 없도록 구조를 개선했다.

 

최종 구조는 아래와 같다.

 

 

 


 

추가 사항 ) Lambda에서 비동기 코드를 구현할 때 조심하자

 

: 처음 이메일 전송 코드를 구현할 때는 스프링 배치에서 썼던 것처럼 비동기 방식으로 구현했다.

메일 전송은 네트워크를 통한 외부 서비스 호출이므로 비동기로 구현하는 것이 효율적이라고 생각했기 때문이다.

 

문제는 Aws Lambda에 같은 방식을 적용했을 때 나타났다.

 

분명 코드상에서는 문제가 없고, 람다 함수가 아닌 java 프로젝트의 main함수로 돌렸을 때는 메일에 차트가 담겨서 잘 전송이 되는데,

람다 함수를 통해 호출하면 메일 전송이 안됐는데도 불구하고, Dead Letter Queue에 담기지도 않는 것이다.

 

원인을 도저히 찾을 수가 없는데 답답해서 새벽까지 고군분투했던 것 같다.

 

계속 코드를 수정하다가 차트를 보내지 않고 단순 메일을 보내는 경우는 전송 확률이 높고,

여러 메일을 보내는 경우에는 초기 메일들은 전송이 되는 점에 주목했다.

 

혹시 비동기 방식 때문에 람다 함수가 먼저 끝나 버리는 것 아닐까..?

 

아니나 다를까 동기 방식으로 코드를 수정하니 제대로 메일 전송이 되는 걸 보고 깨달았다.

 

비동기로 실행한 작업들이 완료되기 전에 람다 함수가 먼저 종료되버리는 거구나..

 

기존 스프링 배치의 경우 계속해서 서버가 돌아가고 있으니 문제가 없었지만, Aws Lambda의 경우 메소드가 호출되고 끝나면 종료가 되는 구조이다보니까 이런 문제가 생겼던 것이다.

 

 

이번 케이스를 통해서 관습적으로 뭔가를 사용하지 말고, 구동 원리에 대해 한번 더 생각해보고 코드를 짜야겠다는 생각을 하게 됐다.

 

 

 

 

 

 

 

 

Comments