저의 소중한 HolyBean 프로젝트는 AWS Lambda와 함께 AWS 서비스 중 서버리스 아키텍쳐를 사용하여, 비용 효율적인 프로젝트를 유지하고 있습니다.
과거 ‘파이썬 3.12’ 런타임을 사용하던 백엔드 로직을 과감히 Golang으로 변경하여 그 과정을 기록하고자 합니다.
AWS Lambda는 서버를 직접 관리할 필요 없이 코드를 실행할 수 있는 서버리스(Serverless) 컴퓨팅 서비스입니다. 개발자는 단순히 코드를 업로드하고, 실행 조건(트리거)만 지정하면 AWS가 알아서 필요한 만큼 인프라를 준비하고 확장해 줍니다. 사용한 시간만큼 돈을 내지만 일정 부분 프리티어를 지원해 주기 때문에, 일주일에 3시간 정도 집중적으로 사용되는 프로젝트 특성상 무료로 사용하고 있습니다.
AWS Lambda와 GCP App Engine과 같은 서버리스 컴퓨팅 서비스는 사용할 때만 비용이 지불되는 대신에, 사용하지 않는 시간에는 컴퓨팅 자원을 자동으로 회수해갑니다.
AWS Lambda의 경우에는 5분 정도 사용되지 않은 자원은 회수되는 것으로 알려져 있는데요.
덕분에 새로 컴퓨팅 자원을 할당받고, 런타임을 불러오고, 코드를 실행하는 것과 같은 Initialize 절차가 필요합니다.

[출처: AWS 공식 홈페이지]
처음에 서버리스 컴퓨팅을 사용하기로 했을 때, 가장 큰 문제점이 바로 Cold Start였습니다.
제 애플리케이션은 일요일 12시부터 3시까지 활발히 사용되지만, 때때로 Cold Start가 진행됐습니다.
아래처럼 다양한 방법으로 문제를 해결하고자 했습니다.
Cold Start 문제를 해결하기 위해 했던 가장 첫 번째 노력은 최선의 프로그래밍 언어를 찾고자 했습니다.
당시에는 javascript와 python 둘 중 선택하는 것이 보편적이었습니다.
저의 첫 번째 시도는 Node.js 런타임 이었습니다.
조사결과 javascript의 패키지는 python의 패키지보다 용량이 작고 작은 단위로 쪼개어져 있어서, 유리하다고 생각했습니다. 하지만 모든 백엔드 로직을 javascript로 최초에 구현함에도 Cold Start에서 자유롭지 못했습니다.
그 결과, 순간적으로 HTTP 요청이 오래 걸리다보니 개발자가 아닌 사용자들은 단순히 이걸 “멈췄다”고 인식하고 강제로 앱을 종료하거나 불편함을 호소하는 경우가 있었습니다.

[출처: Medium Blog]
고민이 깊어지는 와중에, 블로그 작성자가 각 프로그래밍 언어를 가지고 메모리별로, Cold Start 실험을 하고, 소요 시간을 평균으로 계산한 그래프를 발견했습니다.
무려 Node.js보다 Python이 100 ~ 200ms 빠른 Cold Start를 지원한다는 결과를 보고 저는 곧바로 Python으로 모든 비즈니스 로직을 다시 작성했습니다.
그 결과 Cold Start 시간이 100ms 정도 줄었지만, 여전히 편한 사용에는 무리가 있었습니다.
사실 Cold Start 문제를 해결하는 절대적인 방법이 있습니다. AWS Lambda 서비스는 프로비저닝을 지원하여 특정시간동안 Warm 상태를 보장하도록 컴퓨팅 자원을 할당하는 서비스가 있습니다. 다만 이 서비스는 웬만한 EC2 인스턴스보다 비싸므로 저에겐 합리적인 방법이 아니었습니다.
편법이지만, 이를 해결하기 위해 5분마다 모든 Lambda 함수를 더미 호출로 실행시켜 warm 상태를 유지하는 방법을 사용했습니다. 1주일에 3시간 컴퓨팅자원이 활성화 되어있는 것은 AWS Lambda 프리티어 한도에도 무리가 없으며 저에게는 가장 쉽고 합리적인 방법이었습니다.

[출처: AWS 공식 홈페이지]
Golang 1.x 런타임이 deprecated 되고, 2.x 버전의 런타임을 공식적으로 OS-only Runtime으로 AWS Lambda가 지원하게 되었습니다.
provided.al2023은 Amazon Linux 2023 버전의 인스턴스를 의미합니다. 다른 프로그래밍 언어처럼 런타임에 인터프리터 또는 JVM과 같은 머신이 로딩되는 것이 아니라, 순수하게 OS 위에서 작동하는 것을 의미합니다.
그럼 첫 사진에서 Runtime Initialization의 비중이 매우 작아진다는 것을 예측할 수 있습니다.
사실 OS-only Runtime은 Golang 뿐만 아니라, C++·Rust같은 언어도 지원하고 있습니다.
서버 개발에서 사용되는 다른 언어들과 다르게, Golang·Rust·C++은 컴파일된 바이너리 자체가 완전한 실행 단위라는 특징이 있습니다. 즉, 실행 시점에 별도의 VM(Java Virtual Machine)이나 인터프리터(Python, Node.js)가 필요하지 않습니다.
실제로 프로덕션 환경에서 순수 “성능”은 Go가 Node.js 또는 Java/Kotlin을 압도한다고 알려져 있는데요. 서버리스 환경에서도 비슷하게 적용된다고 이해하시면 좋을 것 같습니다.
또 바이너리로 컴파일 되는 언어들은 라이브러리/패키지까지 함께 바이너리에 포함되어 빌드되기 때문에, 정적 저장소에 별도로 라이브러리 계층을 두는 다른 언어들보다 Code 로딩 속도가 빠를 수 밖에 없습니다.
즉, OS-only Runtime의 등장은 Go·Rust·C++처럼 “바이너리로 배포되는 언어들”이 Lambda 환경에서 훨씬 빠르고 효율적으로 실행될 수 있는 길을 열어준 변화라고 볼 수 있습니다.
Go는 Rust나 C++처럼 바이너리로 배포되는 언어지만, 훨씬 단순한 문법과 빠른 컴파일 속도로 운영 효율성이 높습니다. Rust는 안전성과 성능이 뛰어나지만 학습과 빌드 부담이 크고, C++은 여전히 복잡한 문법과 관리 비용이 큽니다.
반면 Go는 짧은 빌드·배포 주기, 직관적인 문법, 내장된 고루틴으로 인한 경량 병렬 처리 덕분에 서버리스 환경에서 빠르게 개발하고 안정적으로 운영하기에 가장 실용적인 선택지라고 생각했습니다.
프로젝트에서 가장 많이 호출되는 API인 주문 등록하기(post_order) 메서드와 주문번호 가져오기(get_current_order_num) 메서드를 기존 Python 실행시간과 새로 작성한 Go 실행시간을 비교하여 측정했습니다.
# 로그 예시 REPORT RequestId: 0f4fd4d1-aaec-4694-8fdd-6abd1a7d7299 Duration: 259.61 ms Billed Duration: 335 ms Memory Size: 512 MB Max Memory Used: 30 MB Init Duration: 74.74 ms # 여기에 집중 !
Cold Start 된 인스턴스의 경우 CloudWatch 로그 그룹에 다음과 같이 로그가 남습니다.
핵심은 새로 인스턴스가 초기화되는 시간을 의미하는 Init Duration입니다.
백엔드 로직 특성상 연산이 많지 않고 대부분 실행 시간이 DynamoDB 등과 연결하는 IO 시간으로 작용합니다. 이 특성을 고려하여 Init Duration의 감소여부를 중요시 했습니다.

결과는 크게 차이가 났습니다.
주문 등록하기 메서드는 평균 433ms -> 74ms 로 약 82.8% 빨라졌고, 주문번호 가져오기 메서드는 평균 447ms -> 75ms 로 83.2% 빨라졌습니다.
매우 유의미한 결과라고 생각합니다. 실제로 Golang으로 배포하고 난 뒤 빠른 HTTP 호출이 체감되는 부분이 많았습니다.