Google Cloud Next 2024 참관 후기 2편 - Google Cloud Serverless for Java developer

Google Cloud Next 2024 참관 후기 2편 - Google Cloud Serverless for Java developer

요약: 이번 2편에서는 데이브가 Google Cloud Next 2024 컨퍼런스 참석 후 인상 깊었던 내용을 전달합니다. Serverless는 초기 투자 비용이 적고 효율적인 리소스 사용을 강조하지만 Cold Start 문제를 안고 있습니다. Cold Start 문제를 완화하기 위한 몇 가지 방법을 GCP의 Serverless 플랫폼인 Cloud Run과 Java 최적화 관점으로 나누어 소개합니다.

시작하며

안녕하세요. 카카오페이 결제플랫폼파티 데이브입니다. 카카오페이 기술블로그로 인사드리는 것은 처음이네요. 저는 현재 카카오페이 결제의 공용플랫폼 개발 및 운영을 담당하고 있습니다.

이번에 좋은 기회가 되어 미국 라스베가스에서 열린 Google Cloud Next 2024 컨퍼런스에 참석하게 되었는데요. “Google Cloud Platform(이하 GCP)의 미래는 AI다.”라는 말이 과언이 아닐 정도로 AI에 대한 관심이 매우 뜨거웠습니다. 다만, 이 글에서는 AI 보다는 컨퍼런스의 세션들 중 가장 인상 깊었던 2가지 내용을 다루고자 하는데요.

바로 GCP의 Serveless 플랫폼과 Severless의 문제점인 Cold Start 해결 방법에 대해서입니다.

정말 많은 사람들이 모였고, 매우 활기찬 분위기였습니다.
정말 많은 사람들이 모였고, 매우 활기찬 분위기였습니다.

Serverless란 무엇일까요?

소프트웨어 아키텍처는 전통적인 모놀리틱 아키텍처(Monolithic architecture)에서, 유연성과 확장성이 높은 MSA (Microservices architecture)로 변화해 왔습니다. 이 과정에서 과다하게 늘어나는 서버 인스턴스들을 잘(?) 관리하기 위해, 컨테이너 기술과 Docker/Kubernetes 같은 오케스트레이션(Orchestration) 도구들이 활용되었는데요. 카카오페이도 MSA를 채택했고, 현재 많은 인스턴스들을 Kubernetes로 관리하고 있습니다. 이러한 도구의 활용은 복잡한 Microservice를 보다 쉽게 유지보수 할 수 있다는 점에서 도움이 되었는데요. 한편으로는 인프라 관리에 더 높은 전문성을 요구하였습니다.

또한 MSA는 서비스들이 파편화되어 있는데요. 시간이 지날수록 이 파편화된 서비스들이 분화하면서, 서비스들 간 관계는 더 복잡해지게 됩니다. 이 과정에서 유휴 인스턴스가 늘어나는 비효율이 발생하는데, 이러한 비효율은 인프라 비용을 기하급수적으로 늘리는 원인이 되었습니다.

따라서 Serverless는 MSA를 운영하면서, 맞닥뜨리는 현실적인 문제(복잡한 인프라 관리와 비효율적인 비용)를 해결하는 여러 대안들 중 하나라고 볼 수 있습니다. AWS, GCP, Azure와 같은 Cloud Service들은 인프라를 추상화하고, 자동화된 서비스(Backend As A Service / Function As A Service)로 제공하고 있는데요. 클라우드 서비스에 의한 자동화된 서버 인프라 관리, 이것이 Serverless입니다. “서버가 없다” 보다는 “서버를 신경 쓸 필요가 없다”인 거죠.

Serverless를 사용하면 어떤 게 좋아질까요?

  • 프로비저닝을 포함한 모든 서버 인프라 관리가 단순해집니다.
  • 사용한 만큼만 비용을 지불합니다.
  • 쉽고 빠른 Autoscaling이 지원됩니다.
  • 보안 업데이트와 같은 중요 패치가 자동 적용됩니다.

결론적으로 서비스를 투입하거나 제거하는 것이 쉬워지고, 적은 인원으로도 효율적으로 서비스를 운영할 수 있습니다.

담당하고 있는 서비스를 당장 런칭하고 싶다면, Serverless에 올리고 바로 실행하면 됩니다. 프로비저닝이 완료되기까지 기다릴 필요가 없죠. 만약 시장의 반응을 확인해 가면서 빠른 전환이 필요한 서비스를 개발 중이라면, Serverless에 올려뒀다 필요 없어지면 제거하면 되겠죠? 평소에는 사용량이 매우 적은 서비스라면, 요청이 들어오는 경우에만 비용이 청구되도록 Serverless에 배포하는 게 적합할 수 있습니다. 이렇게 Serverless를 사용하면, 개발 생산성을 높이고, 비용을 최적화하고 애플리케이션을 쉽게 확장할 수 있습니다.

제가 담당하고 있는 결제서비스를 예로 들어 볼게요. 결제서비스는 일단 하루를 기준으로 인입 트래픽의 폭이 매우 큰데요. 대형 이벤트가 있을 때는 상상 이상의 많은 트래픽이 일시에 몰리기도 합니다. 안정적인 서비스 운영을 위해, 결제 트랜잭션을 담당하는 서버는 최대 트래픽의 수배를 받아낼 수 있어야 하는데요. 문제는 이렇게 최대 트래픽 기준으로 서버 인프라를 구성하면, 평소에는 많은 서버들이 유휴 상태로 남게 됩니다. 만약 트래픽이 폭증하는 시점에만 추가 인스턴스 비용을 지출해도 된다면, 회사 입장에서는 매우 매력적이지 않을까요? 추가 인스턴스 비용이 얼마인지는 차치하고서라도 말이죠. Serverless 세상에서는 이것이 가능해집니다. 그렇다면 GCP에서는 어떤 Serverless 플랫폼을 제공하고 있을까요?

GCP에서 제공하는 Serverless 플랫폼

GCP는 Cloud Functions와 Cloud Run 2가지 Serverless 플랫폼을 제공합니다.

Cloud Functions

Cloud Functions는 단일 함수 단위로 동작하는 서버리스 실행 환경(serverless execution environment)으로, 클라우드 인프라 및 서비스에서 발생하는 이벤트에 연결된 함수를 동작하도록 할 수 있습니다. 예를 들면 파일 업로드 완료 시, 슬랙 API로 이벤트 훅을 전송할 수도 있고 HTTP로 호출 가능한 경량 API를 구성하는 것도 가능하죠.

Cloud Run

Cloud Run은 확장 가능한 인프라상에서 직접 컨테이너를 기동시킬 수 있는 완전 관리형 컴퓨팅 플랫폼(fully managed compute platform)입니다. Cloud Run을 사용하면 별도의 인프라 설정이나 관리 없이 컨테이너를 실행하여 서비스를 제공할 수 있습니다. Cloud Run은 컨테이너가 실행될 인스턴스에 대한 관리를 담당합니다.

Cloud Functions는 간단히 사용할 수 있는 대신 언어적/기능적 제약이 있습니다.
Cloud Functions는 간단히 사용할 수 있는 대신 언어적/기능적 제약이 있습니다.

What’s New in Cloud Run

Google Cloud Next 2024에서는 Cloud Run의 새로운 feature들을 소개했는데요. 대부분 private preview feature라 실제로 체험해 보지 못한 것은 많이 아쉬웠습니다. 다만 Cloud Run에서 Gemini를 적극 활용하려고 하는 것은 상당히 흥미로웠는데요. 실제 업데이트가 되면 많은 체험기들이 올라올 것이라고 기대하고 있습니다 :)

이번에 새롭게 소개된 기능은 다음과 같습니다.

  1. Cloud Run의 Service와 Job에서 Cloud Run에 NFS or CloudStorage Fuse filesystems Volume Mount가 가능해진다고 합니다.
  2. 리빌드와 다운타임 없이도 배포된 이미지의 보안패치가 가능해질 예정이라고 합니다.
  3. Cloud Run Service 접속 시 Deterministic(확정된) URL을 사용할 수 있을 예정이라고 합니다. Deterministic URL은 https://\{service_name\}.\{project_number\}.\{region\}.run.app의 형태이며, run.app은 생략할 수 있다고 하네요.
  4. Cloud Run 콘솔의 상단 배너를 통해 Gemini로부터 리소스 사용량과 최적화에 대한 조언을 제공한다고 합니다. 또 자연어 기반으로 추천 설정을 적용할 수 있는 채팅 기능이 지원될 예정이라고 하네요.
  5. Multi-region services가 가능해진다고 합니다. 한번의 배포로 여러 지역에 배포할 수 있고 요청 시 가장 가까운 region을 자동으로 호출한다고 합니다.

Cloud Run과 GKE는 어떻게 다를까요?

카카오페이는 현재 많은 서비스를 Kubernetes로 관리하고 있습니다. 목적에 따라 온프레미스로 구성해서 사용하기도 하고, AWS EKS를 이용하기도 하는데요. 올해부터는 GCP GKE도 도입 예정입니다. 그래서 Kubernetes 서비스를 구성할 수 있는 GKE와 Cloud Run이 어떤 차이가 있는지 살펴보았는데요. GKE(Google Kubernetes Engine)와는 비슷하면서도 큰 차이가 있습니다.

Cloud Run은 Kubernetes 클러스터를 직접 구성할 필요가 없습니다. 개발자는 최소/최대 인스턴스, 인스턴스별 최대 동시 호출 가능 횟수, CPU 할당 방식 등 일부 설정만 사용할 수 있습니다. 나머지는 Cloud Run이 알아서 합니다. 개발자 입장에서는 간단해서 좋은데요. 하지만 간단한 만큼 GKE 보다 자유도가 많이 떨어집니다.

같이 사용하여 상호보완하는 것은 어떨까요?
같이 사용하여 상호보완하는 것은 어떨까요?

Serverless를 사용하면 좋은 점만 있을까요?

당연하게도 Serverless도 문제가 있습니다. 앞서 Serverless 플랫폼에 대해 “클라우드 서비스에 의한 자동화된 서버 인프라 관리다.”, “사용한 만큼만 비용을 지불한다.”라고 했는데요. 바로 “사용한 만큼” 지불한다는 점이 장점이면서 동시에 큰 단점의 이유가 되기도 합니다. 지금부터는 이것에 대해 이야기를 해볼 건데요. 어떻게 하면 단점을 완화할 수 있을지도 같이 다뤄보겠습니다.

Serverless는 효율적인 리소스 사용에 초점이 맞춰져 있습니다. 따라서 평소에는 서버 리소스가 할당되어 있지 않고, 요청이 들어온 시점부터 할당되는데요. 이런 동작방식은 구조적으로 Cold Start가 자주 발생하게 만듭니다.

Cold Start(냉간 시동)가 뭐죠? 왜 문제가 되는 걸까요.

저는 차에 타면 엔진에 시동을 걸고 약 30초에서 1분 정도는 가만히 앉아 있는데요. 그런 다음에 천천히 2,000 rpm 밑으로 운행을 시작합니다. 이유는 가장 비싼 부품인 엔진에 무리를 주지 않기 위해서입니다. 충분히 예열되지 않은 엔진은 아직 최고 성능을 발휘할 수 없고, 이때 무리해서 운전하면 나중에 고장이 날 수도 있다고 하더라고요. 따라서 항상 예열을 하고, 서두르지 않으면서 타려고 합니다. 제 지갑은 작고 소듕하니까요. 아마도 많은 분들이 저처럼 운전할 거라 생각하는데요.

서버도 마찬가지입니다. 막 기동된 서버는 아직 예열(warm up)이 되지 않은 상태입니다. 그러다 첫 번째 요청이 들어오면 비로소 지연되었던 초기화 동작이 수행되는데요. 클래스를 메모리에 올리고, 스레드를 할당하고, 커넥션을 맺어서 풀에 등록하고, 초기화되지 않았던 여러 컴포넌트에서 연산을 동시에 수행합니다. 이 과정에서 GC가 발생하기도 하고, 동시에 많은 요청이 들어오면 Pending이 되었다가 처리되면서 응답시간이 지연되기도 합니다. 마치 갓 시동을 건 자동차처럼 무리하게 움직이면 덜덜거리고 버벅거리는 거죠. 이런 현상을 Cold Start(냉간 시동)라고 합니다.

Cold Start는 요청을 지연시킵니다. 지연은 곧 장애를 유발하고 장애는 전파될 수 있습니다. MSA에서 연쇄적으로 전파되는 장애가 로직의 버그보다 훨씬 더 치명적입니다. 따라서 Cold Start 문제를 완화하기 위해, 저희도 서버 기동 후 서비스 투입 전에 warm up을 진행하곤 합니다.

트래픽이 실제로 들어올 때 일시에 모든 스레드가 생성되면서 지연이 발생합니다. Cold Start입니다.
트래픽이 실제로 들어올 때 일시에 모든 스레드가 생성되면서 지연이 발생합니다. Cold Start입니다.

Cold Start는 예측 불가능한 지연을 유발할 수 있습니다.

Cold Start는 모든 서버에서 발생할 수 있는 문제지만 Serverless에서는 더 빈번하게 발생할 수 있고 그 영향 또한 치명적일 수 있습니다. 앞서 언급한 것처럼 Serverless는 효율적인 리소스 사용에 초점이 맞춰져 있습니다. 때문에 요청 트래픽이 없다면 인스턴스들은 유휴 상태로 전환되고 일정 시간 뒤에 제거됩니다. 그러다가 새로운 요청이 들어오면 서버를 기동하고 애플리케이션을 초기화해야 하는데, 그 사이에 요청들은 일정시간 Pending 되었다가 타임아웃 등으로 실패할 수 있게 됩니다. 클라이언트에서 타임아웃을 너무 길게 설정했다면 극단적으로는 스레드 고갈로 인한 서비스 장애가 발생할 수도 있죠. 인스턴스 시작 빈도가 높다면, Cold Start가 더 자주 발생할 수 있습니다. 또 상대적으로 Startup 시간이 긴 JVM 환경에서 더 큰 영향을 받을 수 있습니다.

Cold Start를 완화하기 위해 Startup 시간을 줄이는 것도 방법입니다.

Cold Start는 결국 서버가 트래픽을 받을 수 있는 상태가 되기까지 요청이 Pending 되면서 발생합니다. 그 말인즉슨 서버가 안정적으로 트래픽을 받을 수 있는 상태(Ready)가 되려면 Startup - Warmup을 거쳐야 하고, 이 Startup 시간을 줄일 수 있으면 Cold Start로 인한 문제도 완화될 수 있다는 말이죠. 그래서 지금부터는 Cold Start 이슈를 완화하고, 빠르게 서버를 기동 하는 방법들에 대해서 다뤄보겠습니다.

Cold Start 완화 방법

Startup 시간을 줄여서 Cold Start를 완화시키고 트래픽의 급격한 변화에 대응할 수 있는 복원력이 높으면서도 유연하게 확장 가능한 시스템을 만들기 위해, 지금부터 Cloud Run 최적화 관점Java 최적화 관점에서 Cold Start를 완화시킬 수 있는 몇 가지 방법들을 소개하겠습니다.

Cloud Run 최적화 관점

1. CPU 할당하기

Cloud Run의 리소스 할당 방식을 변경해서 Cold Start를 완화할 수 있습니다. Cloud Run의 기본 동작은 요청 트래픽이 없으면 CPU/메모리 할당이 제외되지만, 항상 할당하도록 변경할 수 있습니다. 항상 할당되어 있으면 불필요한 Cold Start도 없겠죠? 비용은 항상 청구되지만요. 일관된 트래픽 패턴을 보이는 경우에는 Serverless를 사용함에도 자원을 항상 할당하는 것이 유리할 수 있습니다. 또 항상 할당하도록 설정해도, Cloud Run이 비용 상으로 더 유리한 방식을 주기적으로 추천해 줍니다.
> 더 알아보기

Cloud Run CPU 할당 방식입니다.
Cloud Run CPU 할당 방식입니다.

2. 효율적인 최대 동시 요청 수 설정하기

인스턴스별 최대 동시 요청 수를 조정하면, 불필요한 인스턴스 생성과 그로 인한 Cold Start를 회피할 수 있습니다. Cloud Run에서는 1~1000(기본은 80) 사이로 최대 동시 요청 수를 지정할 수 있는데요. Cloud Run은 1분 동안 기존 인스턴스의 CPU 사용률(60% 기준)과 1분간 최대 동시 실행 수, 설정된 최소/최대 인스턴스 설정들을 매 5초마다 평가하고 이를 바탕으로 서버 투입/제외 여부를 결정합니다. 너무 작게 설정한 최대 동시 요청 수로 인해 Cloud Run은 불필요하게 많은 인스턴스를 자동으로 투입할 수 있기 때문에, 서비스에서 다룰 수 있는 동시 요청 수를 확인하고 최적의 동시 요청 수를 설정하여, autosacling으로 인한 불필요한 Startup을 최소화하는 겁니다.
> 더 알아보기

동시 요청 수를 너무 적게 설정하면, 대량의 트래픽(초록색 선) 인입 시 많은 인스턴스가 생성(보라색 선) 되고 Cold Start가 발생하겠죠.
동시 요청 수를 너무 적게 설정하면, 대량의 트래픽(초록색 선) 인입 시 많은 인스턴스가 생성(보라색 선) 되고 Cold Start가 발생하겠죠.

3. 최소 인스턴스 유지하기

Cloud Run에서는 트래픽이 없으면 인스턴스가 제거됩니다. 하지만 Cold Start의 영향을 최소화하기 위해 바로 제거하지 않고, 마지막 요청 이후 최대 15분 동안은 인스턴스를 유휴 상태로 유지합니다. 이러한 유휴 상태에서는 DB 커넥션 등이 유지되므로 15분 내로 새로운 요청 인입 시 Cold Start가 발생하지 않게 됩니다. 또는 항상 유지되는 최소 인스턴스 수를 설정하는 것도 방법입니다. 트래픽이 없어도 유휴 상태로 대기하여 불필요한 Startup을 억제할 수 있습니다. 항상 유지되는 최소 인스턴스를 설정하는 경우에는 15분 유지 룰이 동작하지 않습니다.
> 더 알아보기

4. Startup Probe 최적화하기

Cloud Run에서도 Startup Probe와 Liveness Probe를 제공합니다. Probe들은 서버 상태를 확인하고 컨테이너가 시작되었는지, 트래픽을 받을 준비가 되었는지, 컨테이너 상태가 정상인지를 확인합니다. 이러한 개념은 Kubernetes를 써보셨으면 아마 익숙하실 건데요. Cold Start를 완화하기 위해 Startup Probe의 동작 시점을 뒤로 이연하거나, Startup Probe가 Warm Up을 유발할 수 있는 Endpoint를 호출하도록 변경하는 것도 방법이 될 수 있습니다.
> 더 알아보기

Startup Probe는 서비스에 투입될 준비가 되었는지 확인하기 위해 특정 Endpoint를 호출합니다.
Startup Probe는 서비스에 투입될 준비가 되었는지 확인하기 위해 특정 Endpoint를 호출합니다.

5. Startup CPU Boost 사용하기

Cloud Run에서는 Startup 시간을 줄이기 위해 CPU Boost 기능을 제공합니다. 이 기능은 추가 CPU 리소스를 Startup 시간+10초 동안 할당하여 빠르게 애플리케이션을 기동 하도록 합니다. Startup 과정에서는 CPU 사용량이 평소보다 많기 때문에, 순간적으로 많은 리소스를 동적으로 할당할 수 있는 기술은 비용 측면에서도 꽤 합리적이라고 생각합니다.
> 더 알아보기

왠지 부아아아앙! 소리가 날 것 같은 느낌적 느낌...
왠지 부아아아앙! 소리가 날 것 같은 느낌적 느낌...

Java 최적화 관점

1. 최신 버전 사용 및 GC 튜닝하기

어떠한 변경 없이 단순히 Java 버전을 최신으로 사용하기만 해도 성능 향상이 있다고 합니다. 이미 많은 벤치마크 등을 통해 증명된 사실이죠. 또 적절한 GC를 선택하고 힙메모리를 튜닝하는 것은 Startup 시간을 줄이고, Startup 과정에서 발생할 수 있는 GC와 STW를 줄이는데 효과적입니다. JVM의 Startup 과정에서는 동적 로딩, 지연 초기화 등으로 인해 많은 연산이 수행되는데, 이 과정에서 GC가 종종 발생하기 때문에 이러한 방법은 Cold Start를 완화하는데 도움이 됩니다.
> 더 알아보기

저는 아직 자바 15도 안쓰고 있는데요. 갈길이 머네요..
저는 아직 자바 15도 안쓰고 있는데요. 갈길이 머네요..

2. C2 컴파일러 비활성하기

JVM은 JIT(Just In Time) 컴파일러를 이용하여 Tiered Compilation을 사용합니다. Tiered Compilation을 통해서 컴파일 수준(Level)을 나누고 각 수준에 맞춰 다른 컴파일러가 동작하는데요. 바로 C1(Client Compiler) 컴파일러, C2(Server Compiler) 컴파일러입니다.

컴파일 결과는 코드캐시에 저장됩니다.
컴파일 결과는 코드캐시에 저장됩니다.

C1 컴파일러는 빠르게 컴파일을 완료하는 것을, C2 컴파일러는 자주 사용하는 코드를 네이티브 코드로 변환하여 최종적으로 애플리케이션의 성능을 극대화하는 것을 목표로 합니다. C2 컴파일러는 정교한 최적화 기법을 사용하기 때문에 컴파일 시간과 리소스 사용이 많고 오버헤드가 발생할 수 있습니다. 또한 단순한 서비스나 C1 컴파일 만으로도 성능 목표를 만족하는 경우, 굳이 C2 컴파일러까지 사용하면서 Startup 시간을 지연시킬 필요가 없습니다.

이런 경우 런타임 시 arguments로 -XX:TieredStopAtLevel=1을 사용하여 C2 Compiler를 Disable 할 수 있는데요. 이 방법을 사용하면 C1 Compiler만 동작하게 되고 최적화 일부를 포기하는 대신 Startup 시간이 단축되는 것을 기대해 볼 수 있습니다.
> 더 알아보기

3. CDS 적용하기

CDS(Class Data Sharing)는 여러 JVM이 Startup 시간을 줄이기 위해, 클래스를 로딩하는 과정에서 로컬 공유 아카이브에 저장된 CDS 파일(.jsa)을 참조하여 기동 하는 방식입니다. 애플리케이션이 처음 실행될 때 자주 사용하는 클래스의 메타데이터(클래스 구조, 메소드, 필드 등)들이 CDS 파일에 저장되며, 이후로 실행되는 다른 JVM들은 클래스 로딩시간을 단축하여 Startup 시간을 효과적으로 줄일 수 있습니다. Java10 이후부터는 자바 표준 라이브러리+애플리케이션 내 정의된 클래스들의 메타데이터를 공유 아카이브에 저장할 수 있는 AppCDS(Application Class Data Sharing)이 도입되었습니다.
> 더 알아보기

동일한 물리 서버 위에서 동일한 JDK를 사용하고 있다면 CDS를 적용할 수 있습니다.
동일한 물리 서버 위에서 동일한 JDK를 사용하고 있다면 CDS를 적용할 수 있습니다.

4. CRaC 적용하기

저희 아내는 3살 된 아들 음식을 한 번에 몽땅 만들어 소분한 다음 냉동해 뒀다가 필요할 때마다 하나씩 해동해서 먹이는데요. 이 방식은 매번 요리를 할 필요가 없어 맞벌이 부부 입장에서 많은 시간을 아낄 수 있는 것이 큰 장점입니다.

CRaC(Coordinated Restore at Checkpoint)도 비슷한 방식으로 문제를 해결하고자 합니다. 미리 기동 해둔 애플리케이션을 중단한 채로 저장해 두고 필요할 때(트래픽이 유입되어 애플리케이션을 기동 해야 할 때)마다 복원하여 바로 서비스에 투입하는 방식입니다. 이를 위해서 CRaC는 기동 중인 인스턴스의 현재 상태(메모리, 스레드, 파일 디스크립터 등)를 파일로 저장합니다. 이것을 체크포인트라고 하는데요. 체크포인트에서 저장된 파일을 복원하면 애플리케이션은 중단되었던 상태에서 바로 이어서 실행됩니다. 가장 큰 장점은 하나의 체크포인트로 여러 개의 인스턴스를 복원할 수 있다는 점입니다. 트래픽 폭증으로 빠른 확장이 요구될 때 적절할 것 같습니다.
> 더 알아보기

Restore 시점에는 커넥션, 캐시 등을 초기화하여 서비스가 Warm Up 하도록 설정할 수 있습니다.
Restore 시점에는 커넥션, 캐시 등을 초기화하여 서비스가 Warm Up 하도록 설정할 수 있습니다.

CRaC를 사용할 때는 다음 사항을 고려해야 합니다.

  • Spring Boot 3.2부터 CRaC를 지원한다네요. 아직은 초기 단계라 실서비스에 적용할 때는 주의할 필요가 있습니다.
  • CRIU 기술 기반으로, Linux 실행 환경에서만 사용 가능합니다.
  • 체크포인트를 언제 하는지에 따라 서비스의 성능이 달라집니다. 배포는 어떻게 해야 할까요?
  • 비밀번호 같은 메모리에 저장된 데이터들도 직렬화되어 체크포인트 시점에 파일로 저장되는데, 외부에 유출될 수 있어 보안 취약점으로 관리해야 합니다. 볼륨 암호화를 해야 할까요?
  • 복원 시 여러 리소스들을 재구성하는데 이 과정에서 호환성 이슈로 지연이나 실패가 발생할 수 있다고 합니다.

하지만 성능 하나는 기깔납니다. 꼭 한번 써봐야겠습니다!
하지만 성능 하나는 기깔납니다. 꼭 한번 써봐야겠습니다!

5. GraalVM으로 AOT 컴파일 적용하기

JVM은 JIT(Just In Time) 컴파일을 이용하여 Tiered Compilation을 사용하고 있고, C2 컴파일러를 제외하는 방법으로 성능 향상을 꾀할 수 있다고 앞서 말씀드렸었는데요. 오라클은 C2 컴파일러를 개선하는 대신 C2 컴파일러를 대체할 수 있는 새로운 컴파일러를 만들었습니다.

이것이 바로 Graal 컴파일러인데요. C2 컴파일러를 개선하는 대신 새로운 컴파일러를 만든 이유는 여러 가지가 있겠지만, 기존 C2 컴파일러는 C++로 작성된 어느덧 30년 된 노령(?) 컴파일러라는 점도 이유로 작용했다고 합니다. 계속해서 개량을 해오고는 있지만 유지보수하는 비용보다는 새롭게 만드는 것이 싸다는 거죠.

Graal 컴파일러는 Java로 작성되어 유지보수가 용이하고, JavaScript, Python, Ruby, R 등 다양한 언어의 컴파일을 지원하여 JVM 없이 실행 가능한 네이티브 이미지를 생성할 수 있다고 합니다. 또한 기존의 JVM 대신 GraalVM 상에서 컴파일된 바이너리코드가 실행되기 때문에, JVM의 단점이었던 느린 초기화 문제가 해결되었습니다. 코드를 분석해서 필요한 부분만 한정적으로 컴파일함으로써 자원을 효율적으로 사용하고 그만큼 빠른 속도가 보장됩니다.

상당한 성능 개선이 확인됩니다.
상당한 성능 개선이 확인됩니다.

Graal 컴파일러는 코드를 GraalVM에서 실행가능한 네이티브 이미지로 변환하고, AOT(Ahead-of-Time) Compilation 지원합니다. AOT Compilation을 사용하면 런타임시간을 많이 줄일 수 있지만, 컴파일 시점에 런타임 시 동적으로 생성되는 클래스들까지 미리 컴파일 하기 위해서 Java가 가진 일부 동적 기능(Reflection, Dynamic Proxy 등)을 사용하는 데 제약을 받게 됩니다.

런타임에서 할 일은 줄었는데요.. 컴파일이 올래 걸리네요?!
런타임에서 할 일은 줄었는데요.. 컴파일이 올래 걸리네요?!

Graal 컴파일러로 AOT Compilation을 사용할 때의 단점은 컴파일 시 분석과 최적화에 많은 시간이 소요된다는 점입니다. 특정 3rd party library는 GraalVM에서 기동하지 못할 수도 있습니다. Spring Boot 3부터 GraalVM을 지원합니다.
> 더 알아보기

그리고… OpenJDK Project ‘Leyden’도 있습니다.

지금까지 Cold Start를 완화할 수 있는 최적화 방법들을 알아보았습니다. 상당히 길었는데요. 마지막으로 Leyden을 가볍게 소개하고 마무리하겠습니다. Leyden은 Serverless, Cloud 환경에서 애플리케이션 성능 최적화를 위해 시작된 OpenJDK Project입니다. Leyden은 어떻게 하면 애플리케이션을 빠르게 기동 할 수 있는지에 집중합니다. 이를 위해 활용 가능한 모든 Java 리소스를 검토하고 개선을 준비하고 있다고 합니다. CDS, Graal 컴파일러를 활용한 AOT Compilation, GraalVM의 SubstrateVM 기술과 같은 것들의 개선을 포함해서요.

Spring도 Leyden과 협력을 시작했습니다. Spring Framework에는 Startup 최적화를 위해 GraalVM의 SubstrateVM 기술을 활용하여, 애플리케이션 코드를 네이티브 바이너리로 변환하는 Spring AOT가 존재하는데요. Premain 최적화를 함께 적용하여 시작 시간을 기존보다 2~4배 단축할 수 있는 것을 확인했다고 합니다.
> 더 알아보기

빌드와 배포시간은 기존보다 더더더 늘어난다고 합니다.
빌드와 배포시간은 기존보다 더더더 늘어난다고 합니다.

마치며

긴 내용이 모두 끝났습니다. Serveless에서 시작해서 Project Leyden까지 다소 장황했는데요. 요약 가겠습니다.

  • Google Cloud Platform은 앞으로의 방향성을 AI와의 융합으로 결정한 것으로 보입니다.
  • Serverless는 초기 투자 비용이 적어, 일부 서비스에서는 합리적인 대안이 될 수 있습니다.
  • Serverless는 구조적으로 Cold Start가 자주 발생할 수 있는데, 이를 완화하는 최적화 방법들이 있습니다.
  • Java 최적화 방법들은 Serverless가 아니더라도, 서비스 성능 및 고가용성을 위해 고려할 필요가 있습니다.

특히 Java 개발자로서 그동안 미뤄뒀던 Java 버전과 SpringBoot 버전을 올려야 할 타이밍이라는 생각이 들었습니다. 대부분의 최적화 방법들이 버전 업그레이드를 요구하고 있는데요. 앞으로도 계속 새로운 개선안들이 나올 예정이라 더 이상 손 놓고 있으면 안 될 것 같습니다. CRaC 등 새로운 접근 방식은 상당히 흥미로웠고, Spring AOT, GraalVM의 벤치마크 결과가 너무 예뻐서(?) 실제 서비스에도 적용해 봐야겠다는 생각이 들었습니다.

이번 컨퍼런스 참석을 통해서, Google이 Cloud Service에서 정말 ‘이를 갈고 나왔다.’(라고 하기엔 죄다 private preview이긴 했는데요.)라는 생각이 들었습니다. 또한 정말 많은 사람들이 이번 Google Cloud Next 2024에 참석해서 깜짝 놀랐습니다. AWS 경쟁자인 GCP의 향후 행보가 매우 기대됩니다.

아이스 아메리카노 한잔이 먹고 싶었을 뿐이었는데요..
아이스 아메리카노 한잔이 먹고 싶었을 뿐이었는데요..

3편은 SRE팀 이든이 Generative AI with Enterprise Data 주제로 포스팅할 예정이니 많은 관심 부탁드립니다.

참고 자료

dave.pmh
dave.pmh

안녕하세요. 카카오페이 결제플랫폼파티 데이브입니다. Java/Kotlin 개발자이자, TL(Tech Leader)로써, 카카오페이에서 모두가 마음 놓고 결제할 수 있는 환경을 만드는 것이 목표입니다.