람다, 서버리스의 첫걸음을 통해 AWS 의 Lambda 서비스에 대해 간략하게 소개를 했다. 이번에는 조금은 더 심도 있는 이야기를 할 텐데 람다의 구조와 원리를 파악하고 자연스럽게 그 한계를 깨우치도록 하자. 내부 로직을 어느정도 이해하고 있어야 어떤 상황에 서버리스( Serverless ) 람다가 독이 되는지 알 수 있게된다. 어설프게 이해하고 사용 하다가는 독이 된다는 사실로 시작해보자. 람다는 AWS 에서 서버리스를 대표하는 서비스 중 하나다. 앞선 글에서도 그렇게 밝혔고. 근데 이제와서 서버리스는 없다니 이게 무슨 소리지? 이건 클라우드로 넘어오면서 생긴 개념인데 EC2 와 같은 IaaS 는 이미 사용자에게 서버를 클라우드 상에서 제공하며 IDC 상황에서 겪었어야 했던 수 많은 작업들을 생략할 수 있게 해주었다. 거기에 그치지 않고 정말 작은 단위의 프로그램은 마치 서버조차 없이도 돌릴 수 있도록 제공을 해주게되는데 그게 람다의 첫 시작일 수 있겠다. Function as a service ( FaaS )라고 칭하도록 하자. 아마 crontab 이 적절한 비유가 될 수 있겠는데 한시간 단위로 특정 메일을 발송하는 프로그램이 있다고 해보자. 기존에는 이를 위해 별도의 서버가 필요했다. 초소형 EC2 정도의 서버가 24시간 365일 서비스가 되고 있어야 정상적으로 메일이 발송이 될 수 있겠다. 물론 주기적인 시간이라면 EC2 를 스케쥴 할 수도 있겠지만 그렇지 못한 경우도 있으니까 ( 지정된 시간에 EC2 를 wakeup 시키고, 종료시킬 수 있다 ). 이때 필요한 것이 서버리스 개념인데 그저 우리의 프로그램을 클라우드 어딘가에 올리고 적당한 트리거( trigger ) 만 연결시키면 해당 프로그램은 지정된 환경에서 실행되게 된다. 그 역할을 람다가 해준다. 우리는 그 서버를 볼 수 없다. 아니, 굳이 볼 필요조차 없다. 우리가 원하는 것은 단순히 적당한 이벤트에 따라 프로그램이 정상적으로 동작만 되면 되니까. 조금 더 전문적으로 이야기하자면 지정된 트리거에 따라 람다는 서버리스 환경으로 구동된다고 이야기 할 수 있겠다. 위에서 이야기한 것처럼 어딘가에 우리 코드를 실행시켜주는 서버는 분명 존재한다. 하지만 그 서버가 우리를 위해 항상 활성화되어 있지는 않다. 이것은 컴퓨팅 제공 업체에서 자원을 효율적으로 관리하기 위한 방법인데 일정기간 액티브한 행위가 없는 람다를 위해 굳이 서버를 계속 제공해줄 필요는 없다. 그런 개념으로 접근해보면 잦은 호출이 되는 람다에게 제공되는 서버는 항상 켜져있어야 할 것이고 그렇지 않은 람다의 서버는 꺼져있을 것이다. 꺼져있다고해서 다시 사용하려고 할 때 별도의 사용자 액션이 필요한 것은 아니다. 다만 트리거에 의해 람다가 호출되었을 때 서버가 켜지고 부수적인 세팅이 되기까지 적지 않은 시간이 소요된다. 람다에 지정된 메모리 스펙에 따라 약간씩 차이는 있겠지만 대략 수초~수십초 정도 되겠다. 이 상태가 바로 Cold start 이다. 그런 일련의 과정으로 인해 cold start 는 꽤 느린 response 를 제공한다. 한편 부지런히 일 하는 우리 람다를 위해 항상 켜져있는 서버는 warm 상태이기 때문에 빠른 응답을 할 수 있다. 위에서 간략하게 언급하고 넘어간 부수적인 세팅에 대해서는 re:Invent 자료를 통해 확인할 수 있다 ( 해당 자료는 AWS 사용자 그룹에서 제공해주셨다. ) 아래 그림을 통해 람다의 내부 구조를 살펴보도록 하자. 그림에서 보이는 것처럼 Compute substrate 위에 컨테이너가 올라가고 runtime 언어가 설정되며 끝으로 우리가 작성한 코드가 돌게된다. 그렇다는 이야기는 결국 이 모든 부분들이 Cold start 에 영향을 준다는 것이다. 아래 조금 더 상세한 설명이 제공된다. 람다를 테스트 하는 도중 재밌는 사실을 발견할 수 있었다. 기본적으로 람다는 메모리를 128 MB ~ 3008 MB 까지 사용할 수 있는데 그 이유가 궁금했다. Runtime 을 Node 로 지정해서 아래와 같은 것을 확인할 수 있었는데 이건 아마 다른 Runtime 에도 동일하게 적용될 것으로 기대된다. 사실 위에서 Cold start 와 warm start 를 통해 확인된 내용이지만 다시 한번 정리하자면 우리의 람다는 1 개의 환경에서 실행된다. 두 개의 호출이 동시에 발생한다고 해도 1 개 환경 위에서 마치 두 개의 프로세스가 실행되는 것 처럼 말이다. ( 엄밀히 이야기 하면 프로세스는 아니고 람다에서 표현하는 invoke ) 즉 다시 말하면 아래와 같겠다. foo 라는 람다를 메로리 128 MB 로 설정하고 동시에 6개의 호출이 일어났을 때 내부적으로는 아래와 같이 동작된다. 위에 그림처럼 128 MB 라는 메모리 제한은 한번의 호출에 의한 제한이고 람다 인프라는 4 GB 정도의 메모리를 갖고 있는 컨테이너로 세팅된다. 람다 메모리 변경에 따른 실제 람다 컨테이너의 CPU 와 메모리는 아래와 같은 차이를 보인다. 512 MB 를 기준으로 CPU 와 메모리가 소소하게 변경되며 그 이외에는 다를게 없다. 람다에서 3008 MB 까지 밖에 사용을 못하게 제한 된 것은 여기서 이유를 찾을 수 있다. 4 GB 시스템에서 1 GB 는 커널이 차지하기 때문에 user space 에서 가용 가능한 최대가 결국 3 GB 이기 때문이다. 한편 더 큰 메모리를 사용하게 되면 그건 이미 람다가 아닌 다른 시스템을 고민해봐야 할 것이다. 아무튼 결국 한번 호출될 때 많은 메모리를 사용하고 실행시간이 긴 람다라면 병렬로 호출 되었을 때 분명 성능의 한계에 부딪히게 될 것이다. 아래는 그런 내용을 테스트한 결과이다. 100MB 가까운 더미 Array 를 할당하고 5초 후 종료하는 간단한 람다인데 아래 내용을 보면 메모리 사용량이 천차만별이다. 이 수치는 단순하게 람다 컨테이너 내부에서 free -h 를 실행하고 used 를 가져온 값이다. 이 값이 증가하는 것을 보면 모든 호출이 동일한 람다 컨테이너의 메모리를 사용한다는 것을 유추할 수 있다. 또한 동시에 50개의 람다를 호출하니 끝나는 시간에도 큰 차이를 보인다. 이는 할당할 메모리가 없기 때문에 대기 시간이 발생하기 때문. 아래는 Node 로 실행되는 람다의 내부 명령이다. 메모리 제한 등은 노드의 옵션으로 제어하며 우리가 작성한 코드는 아래 보이는 index.js 안에서 invoke 되어 실행된다. 그런 이유로 또 몇 가지 새로운 사실을 발견할 수 있게 되는데 이 내용은 다음 포스팅에 추가로 공유하도록 한다.