[re:Invent 2019 세션 레포트] Architecting and operating resilient serverless systems at scale (SVS407) #reinvent

안녕하세요! 클래스메소드 주식회사의 김태우입니다. AWS re:Invent 2019 가 열린지 어느덧 한달이 다 되어가고, 오늘은 2019년의 마지막날이 되었습니다. 라스베가스에서 얻어온 AWS SWAG 후디 + 수많은 티셔츠 + 개인적으로 쇼핑하면서 사온 바지 몇 벌을 번갈아가며 입다보니 한해가 끝난 느낌입니다..ㅋㅋ 클래스메소드에서는 re:Invent 에 참여하여 아웃풋(성과물)으로써 양질의 블로그 10편이상을 작성하는 사원들에게 re:Invent 참가를 위한 모든 경비를 지원해주는 제도(?)가 있습니다. (제도까지는 아니지만 그냥 매년 그러고 있습니다..ㅎㅎ) 저는 현지에서 7편, 도쿄로 돌아와서 LT 발표 등단 블로그 2편, 그리고 마지막 10편째인 본 블로그를 작성함으로 성과물을 인정받으려고 합니다!!ㅋㅋ (데드라인이 오늘까지...;;) 현지에서 듣고 싶었던 강의인데 만석 세션이었던 관계로 Youtube 로 열심히 공부하며 들었습니다. 본 Youtube 영상을 보신 분이 계시다면 제 글을 가볍게 읽으며 아, 이런 내용이었지 하면서 슥슥 확인해주시면 좋을 것 같습니다 :)
2019.12.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

안녕하세요! 클래스메소드 주식회사 의 김태우입니다.

AWS re:Invent 2019 가 열린지 어느덧 한달이 다 되어가고, 오늘은 2019년의 마지막날이 되었습니다. 라스베가스에서 얻어온 AWS SWAG 후디 + 수많은 티셔츠 + 개인적으로 쇼핑하면서 사온 바지 몇 벌을 번갈아가며 입다보니 한해가 끝난 느낌입니다..ㅋㅋ

클래스메소드에서는 re:Invent 에 참여하여 아웃풋(성과물)으로써 양질의 블로그 10편이상을 작성하는 사원들에게 re:Invent 참가를 위한 모든 경비를 지원해주는 제도(?)가 있습니다. (제도까지는 아니지만 그냥 매년 그러고 있습니다..ㅎㅎ)

저는 현지에서 7편, 도쿄로 돌아와서 LT 발표 등단 블로그 2편, 그리고 마지막 10편째인 본 블로그를 작성함으로 성과물을 인정받으려고 합니다!!ㅋㅋ (데드라인이 오늘까지...;;)

현지에서 듣고 싶었던 강의인데 만석 세션이었던 관계로 Youtube 로 열심히 공부하며 들었습니다. 본 Youtube 영상을 보신 분이 계시다면 제 글을 가볍게 읽으며 아, 이런 내용이었지 하면서 슥슥 확인해주시면 좋을 것 같습니다 :)

목차

세션 소개

세션 Youtube 영상

발표자

AWS Lambda 팀에서 수석 엔지니어로 근무하고 있는 David Yanacek 의 강의입니다.

세션 내용

세션코드가 SVS407, 즉 무려 400대에 해당하는 레벨이므로 입문자나 중급사용자보다는 고급 사용자를 대상으로한 세션입니다. 서버리스 시스템이 어떻게 resiliency 에 도움을 줄 수 있는가, 또 resiliency 를 극대화시킨 아키텍쳐를 위해 고민해야하는 점들은 무엇인가에 대한 세션입니다.

Load shedding

이미 알고 있던 개념이었지만 Load shedding (로드 쉐딩) 이라는 표현은 본 세션에서 저도 처음 들었는데요, 부하를 줄이기 위한 어떤 액션을 의미합니다. 쉽게말해서 서버에 부하가 심하게 걸릴 것 같으면 특정 수만큼의 request 만 받아들이고 그 이후의 request 는 reject 시켜버리는 등의 액션을 취하는 것입니다.

이 부분에서 정말 재밌었던 내용은 Throughput 을 높이기 위해 서버에서 병렬도를 높이게 되면 클라이언트에게 되돌려주는 response 의 평균 latency 가 증가한다는 점이었습니다. 듣고보면 당연한 내용인데, 개발시에 서버의 퍼포먼스에 몰입하다보면 오히려 클라이언트에게 돌아가는 response 의 latency 가 증가해버리는 상황을 겪게된다는 점을 종종 망각했던 것 같습니다.

client-timeout

게다가 위 그래프에서 보는 것 처럼, 클라이언트에서 타임아웃으로 리퀘스트 자체에 대해 아예 신경을 쓰지 않게 되는 타이밍이 되면, 그 특정 타이밍 이후의 모든 서버에서의 처리는 전부다 무용지물이 되는거죠. 일반적으로 프로토콜 상으로 타임아웃을 정의한다던지 하는게 아니기때문에, 서버에서는 클라이언트 쪽에서 타임아웃처리해버려서 더이상 자기가 보낸 리퀘스트에 대해 신경을 쓰고 있는지 안쓰고 있는지 알 방법이 없습니다.

goodput-throughput

결국, 위의 그래프처럼 일정 수준 이상의 부하(동시다발적 리퀘스트 등)를 서버에 걸어주게되면 서버가 Throughput 을 높여도, 클라이언트에게 타임아웃 전에 전달되는 Goodput 이 오히려 0 이 되어버리는 경우가 발생하게됩니다. 즉, 빨간 영역에 해당하는 부분은 전부 리소스 낭비라는 것이죠.

goodput-waste

물론, EC2 Auto Scaling 같은 기능이 있기는 하지만, 정확히 언제 스케일을 해야하는지에 대한 고민은 남아있게 됩니다. 이때 스케일 아웃을 너무 빨리해버리면 이것 역시 리소스 낭비(=돈 낭비)가 되는 것이고, 너무 늦게 스케일 아웃을 하면 위에서 언급한 모든 상황이 그대로 현실화되는 것이죠.

따라서 Load shedding 이라는 개념은 아래 그래프와 같이, 특정 Throughput 을 기준으로 그 이상의 Throughput 을 넘어가지 않도록 그 이후의 리퀘스트를 그냥 reject 시켜버리는 기법입니다.

loadshedding

근데, Lambda 기반의 Serverless 아키텍쳐에서는 좀 더 근본적인 해결책이 있습니다. 바로 Lambda 설정 자체에 Timeout 기능이 있다는 것이죠! Lambda 가 실행될 최대시간을 설정함으로써 클라이언트에서 기대하는 latency 에 부합할 수 있는 것은 물론, 예기치못한 상황으로 인해 무작정 계속해서 실행되는 상황도 근본적으로 방지할 수 있다는 것입니다.

그런데, 이런 경우에는 오히려 "타임아웃을 걸면 더 안좋은거 아니야?" 라고 할 수 있겠죠. 왜냐하면 클라이언트에서 매번 expensive request 를 보낸다거나 하면, 단 하나의 request 도 성공하지 못한채로 전부 타임아웃이 걸려버리게 되는 시나리오입니다. 이런 경우에 대해서 AWS 측에서는 항상 Bounded work 를 정의하고 하나의 Lambda 실행에서 해당 작업만을 수행하도록 하는 프랙티스를 권장합니다.

Bounded Work

  • Input size validation
  • Pagination
  • Checkpointing

여기서 Checkpointing 이라 함은, 작업이 진행됨에 따라 지속적으로 상태값을 어딘가에 저장해놓고, 추가적인 작업을 할 때 그 checkpoint 를 참조하여 다음 작업을 진행하도록 하는 것입니다. 결국 state 가 존재하지 않는 Serverless 의 특성을 보완하기 위해 어딘가에 state 를 기록하고 참조한다는 발상입니다. (너무나 당연하죠.)

이런 접근법을 통해 시스템은 전체적으로 resiliency 를 확보할 수 있게됩니다. 물론, 기존의 server-based 시스템 아키텍쳐와는 굉장히 다른 접근법이기때문에 서버리스 아키텍쳐에서의 다양한 패턴과 use case 를 깊이있게 이해할 때 본 세션의 내용이 정말로 와닿을 수 있는 것 같습니다.

lambda-latency

또한 Lambda 를 사용함으로서 항상 동일하고 예측가능한 Latency 를 확보할 수 있습니다. Lambda 의 아키텍쳐에 대해 좀 더 알고 싶은 분들은 본 세션과 함께 Security Overview Of AWS Lambda 화이트페이퍼를 읽어보시면 도움이 될 것 같습니다. 또한, 올해 한국 서밋에서 AWS Korea 의 김일호 SA님께서 굉장히 좋은 세션 발표를 해주셨는데, 이것도 아직 안보신 분들은 보시면 Lambda 의 내부구조 이해에 굉장히 큰 도움이 될 것 같습니다.

Dependency Isolation

사실상 모든 시스템에는 의존성 관계가 엮이게 마련이죠. 이 때, 내 시스템에는 문제가 없는데 의존성이 걸린 시스템에서 문제가 생겨서 전체적인 시스템 성능 저하를 유발시키는 경우도 흔한 이슈 중에 하나입니다. 이 문제에 대해서 본 세션에서는 concurrency control 을 가지고 풀어나갑니다.

Little's Law 에 의하면 concurrency 는 arrival rate x latency 로 관측될 수 있다고 합니다. (처음 알았습니다만, 아이디어가 정말 기발하네요..)

Lambda 에서는 cold start 의 경우와 warm start 의 내부적 메커니즘이 다르다고 합니다. warm start 는 실행까지 걸리는 시간이 짧기때문에 빠르게 처리할 수 있는 반면, cold start 의 경우 Lambda 를 control 하는 시스템측에 상대적으로 더 부하가 걸리는 작업이 됩니다. 따라서, cold start 와 warm start 시의 워크로드를 분리시켜서 서로가 서로에게 영향을 최소로 주도록 구축했다고 합니다.

warm-start-not-fired-due-to-cold-start

즉, 다시말하면 위의 그림과 같이 cold start 로 인해 warm start 가 실행되지 않는 문제를 아래와 같이 워크로드를 분리시켜서 서로 다른 영역으로 간주해버리는 구성을 취하고 있습니다.

warm-start-separated-1

warm-start-separated-2

이러한 구성을 취하는데 키포인트로 설정했던 개념은 아래와 같다고 합니다. (근데 Lambda 내부구조를 정확히 이해하는게 너무 어려워서 솔직히 봐도 대략적으로 밖에 모르겠네요..ㅎㅎ)

warm-start-cold-start-key-concept

API Gateway Throttling 과 Lambda concurrency control

Lambda 하부구조에서 warm start 와 cold start 의 워크로드를 분리시켜주었지만, 그렇다고 해서 모든 문제가 해결되는 것은 아니죠. 위의 load shedding 과 해결방안으로 겹쳐보이기는 하나, API Gateway 에서 API 단위로 throttling 을 걸어주거나, Lambda 의 concurrency control 설정을 해주면 각각의 비즈니스 플로우에 맞는 워크로드에 대하여 concurrency 의 영향을 받지 않게 될 것입니다.

Avoiding queue backlogs

이 부분은 쉽게 직관적으로 이해할 수 있었던 챕터였습니다. 서버리스 아키텍쳐를 구성하다보면 resiliency 를 확보하기 위해 다양한 종류의 queue 를 굉장히 적극적으로 활용하게 되는데요(AWS 에서는 SQS 나 Kinesis 등), 이때, 클라이언트에서 Queue 에 동시다발적으로 너무 많은 태스크를 넣어버리면, 처리하는 측에서 이를 적시에 처리하지 못해서 처리하는 타이밍에서의 value 를 잃어버리게되는 (즉, 다시말하면 이것도 리소스 낭비가 되죠) 경우가 발생하게 됩니다.

예를들어, 아래 그림과 같이 큐를 활용한 채팅 서버에서, 큐에 너무 많은 메세지가 담겨져 있다고 가정해봅시다.

queue-backlogs

그럼, 아래와 같은 시나리오에서 Bob 은 제때 Alice 에서 메세지를 전달받지 못하기 때문에 미팅에 참석해서 아무도 없는 광경을 보고 어처구니가 없게 되죠. (그나저나 Alice 랑 Bob 은 네트워크를 메인으로 IT분야의 발전에 가장 크게 공헌한 두 인물이라고 생각합니다..ㅎㅎ)

alice-bob

이러한 상황은 기본적으로 FIFO(First In First Out) 를 구현한 큐의 성질때문에 발생하게 되는데요, 위와 같은 경우에서는 LIFO (Last In First Out) 방식으로 큐를 구현할 수 있다면, 해당하는 문제가 상당히 개선되게 됩니다.

fifo-lifo

좀 더 구체적으로 어떻게하면 되는걸까요?

ttl-1

네, 다들 짐작하셨다시피 Queue 를 나눕니다. 그런데, 나누긴 했는데, 메세지의 내용은 모두 High priority 를 가질텐데, 어떻게 Low priority 와 High priority 를 구분할 수 있을까요? 정답은 모든 메세지에 High priority 를 부여하고, TTL 을 설정하여 Low priority queue 로 옮겨 담는 것입니다.

hq-lq

그런 다음, 큐를 처리하는 쪽에서는 High priority queue 의 메세지가 없어졌을 경우에 Low priority queue 의 메세지를 처리하는 식으로 구현하는 거죠. 이와 같은 시나리오는 IoT 디바이스에서 Queue 로 보내는 각종 데이터에도 적용하기 쉽습니다. 보통 기기의 메트릭 등을 많이 수집하게 되는데, 컨텍스트에 따라 조금씩 다른 경우가 존재하겠지만, 5분 이상 경과하면 사실상 이미 의미가 없어진다거나 하는 등의 시나리오에서는 Low prioirty queue 로 보낼 것도 없이 TTL 설정으로 그냥 메세지를 삭제해 버리는 경우가 정답인 경우도 있다고 합니다.

Lambda 에서는 async 설정을 통해 태스크에 실패시 retry 와 dead letter queue 설정도 손쉽게 가능하기때문에 Lambda 를 활용하면 위와 같은 시나리오에서도 resiliency 확보에 큰 도움이 됩니다.

또다른 해결방안으로, 아까와 같이 그냥 들어가는 입구에서 throttle 을 걸어버리는 것입니다.

apigateway-throttle

이런 경우에는 Alice 의 메세지가 Bob 에게 전달되지 않았다는 걸 Alice 가 즉각적으로 알아차릴수 있기때문에 customer experience 에 더 도움이 됩니다.

또는, 아래와 같이 queue 를 나눠서 서로 다른 워크로드를 갖게 하는 것도 가능합니다..!

bothworld-best

여기서 Shuffle-sharding 이라는 개념이 등장하는데요! Lambda async invocation 이 어떻게 동작하는지 소개해줍니다. 이부분은 반드시 직접 영상을 보시면서 확인해주시면 큰 도움이 될 것 같습니다! 큐의 갯수를 N 으로 정하는 것만으로도 알아서 위의 queue backlogs 문제점이 사실상 해결된다는 내용입니다. 또한 큐의 갯수 N 을 정하는 공식도 소개하고 있습니다. 이 부분 너무너무 재미있게 들었던 내용이라서 꼭 강추입니다!!

shuffle-sharding

Operating

마지막으로 operation 에 관한 이야기 입니다. 이론적인 디테일보다는 X-Ray, CloudWatch Insight 등 AWS 서비스를 활용하여 어떻게 문제점을 찾아서 트러블슈팅을 하는지에 대한 고급 꿀팁을 설명해주는 챕터였습니다.

마무리하며

서버리스쪽은 알면 알수록 재밌고 무궁무진한 가능성을 느끼게 되는 것 같습니다. 본 글의 세션 외에도 다른 여러가지 세션들을 다시한번 들으며 공부하면서 다양한 것들을 배워나가고 싶습니다. 이제 몇시간 남지 않은 2019년도 잘 마무리하시고 2020년에도 새해복 많이 받으세요!! :D