서버리스 코드로 작업할 때 매우 일반적인 접근 방식은 매우 빠른 콜드 스타트로 명성이 높은 Python, Node 또는 Go 애플리케이션으로 작성하는 것입니다.
그러나 AWS Lambda와 같은 서버리스 환경을 대상으로 하는 기존 Java 앱이 있다면 어떻게 될까요? 아마도 우리 코드 베이스의 대부분은 Java를 호스팅하고 있으며 우리는 재사용하고 싶은 도구와 라이브러리로 구성된 풍부한 생태계를 개발했습니다. 이러한 애플리케이션 전체를 다른 언어로 다시 작성하는 것은 비용이 많이 들고 정적 유형 안전성 및 컴파일 시간 최적화와 같은 기능을 포기하는 것입니다.
얼마 전 저는 다음과 같은 정확한 시나리오에 직면했습니다. Java로 작성된 9개의 AWS Lambda 앱은 콜드 스타트 시 속도가 매우 느려서 일부 앱이 가끔 시간 초과되는 경우가 있었습니다.
문제의 Lambda는 API Gateway 뒤에 배치되었으며 해당 REST API를 호출하여 관리 작업에 사용되었습니다. 이 기능은 그다지 많이 사용되지 않았기 때문에 콜드 스타트가 불가피했습니다. 그러나 이는 중요한 서비스가 아니었기 때문에 실험을 위한 완벽한 기회였습니다. 즉, 이러한 람다를 복구할 수 있는지 알아보는 것입니다.
이 문제를 해결하기 위해 GraalVM 및 Quarkus와 같은 프레임워크를 성공적으로 사용하는 개발자에 대한 여러 다른 블로그 게시물을 만난 지 얼마 되지 않았습니다. 그래서 직접 사용해 보기로 했습니다.
그런데 이 도구는 대체 무엇인가요?
간단히 말하면 GraalVM은 Java를 네이티브 이미지로 컴파일하고 Graal JVM을 사용하여 실행할 수 있는 도구 세트와 함께 제공되는 Java 가상 머신입니다.
일반적으로 Java는 이름에서 알 수 있듯이 코드 실행 중에 최적화 및 컴파일을 수행하는 "JIT(Just In Time)" 컴파일러를 사용합니다. JVM 최적화 프로그램이 지속적으로 프로그램 실행을 모니터링하고 시간이 지남에 따라 더 나은 성능으로 변환되는 미세 조정을 수행한다는 점을 고려하면 장기 실행 애플리케이션은 이점을 누릴 수 있습니다.
애플리케이션이 한 번 인스턴스화되고 몇 시간 이상 실행될 것으로 예상되는 경우에는 유용하지만 Java 앱을 빠르게 부팅하고 수행하기를 원하는 Kubernetes, AWS Lambda 및 배치 작업을 처리하는 경우에는 그리 좋지 않습니다. 시간에 민감한 작업과 수요에 따른 규모 - 자동차 매니아를 위한 터보 지연에 대해 이야기합니다.
이때 GraalVM의 네이티브 이미지 기능이 도움이 됩니다. JIT 컴파일러를 사용하는 대신 AOT(Ahead of Time) 코드를 컴파일하는 매우 다른 접근 방식을 선택합니다. 정적 코드 분석을 사용하여 파이를 미리 굽고 심지어 애플리케이션 코드가 실행될 때마다 실행할 준비가 되도록 빌드 시간 동안 특정 클래스를 사전 초기화하기도 합니다.
결과는? 매우 빠른 콜드 스타트 덕분에 앱의 수명이 짧고 빠르게 부팅해야 하는 서버리스 도메인에서 네이티브 이미지의 성능이 매우 뛰어납니다.
한 가지 주목해야 할 점은 GraalVM이 AOT를 지원하더라도 기존 JVM을 즉시 대체하여 Java로 작성된 GraalVM의 새로운 JIT 컴파일러를 통해 더 나은 성능을 제공할 수도 있다는 것입니다.
하지만 잠깐만요, 더 있습니다! 네이티브 이미지에는 알려진 실행 경로에 있는 코드만 포함되어 있으므로 지방을 잘라내고 유지하도록 명시적으로 선언되지 않은 모든 Java 클래스는 사용할 수 없게 됩니다. 실행될 것으로 예상되는 비트만 유지하므로 애플리케이션의 보안이 강화됩니다.
호스트를 손상시키는 수단으로 원격 코드 실행을 사용하는 악명 높은 Log4J 취약점을 예로 들어 보겠습니다. 네이티브 이미지를 사용하면 공격을 전달하는 데 필요한 라이브러리 코드 조각에 접근할 수 없기 때문에 가젯 체이닝이 성공할 가능성이 거의 없습니다.
반면에 Quarkus는 AWS Lambda를 네이티브 실행 파일로 구체적으로 구성하고 구축하기 위한 확장 기능을 제공하여 네이티브 이미지를 더 쉽게 구축할 수 있는 도구 상자와 함께 제공되는 서버리스 애플리케이션에 최적화된 Java 프레임워크입니다.
Lambda 최적화 여정 중에 대체 최적화 기술도 접했습니다. 이러한 최적화 중 하나는 더 빠른 콜드 스타트를 제공하기 위해 Lambda 실행 중에 C1 컴파일러를 독점적으로 사용하도록 제안한 것입니다. 일반적으로 JVM 내에서 실행되는 Java 애플리케이션은 더 빠르지만 덜 최적인 C1과 그 뒤를 이어 느리지만 오랫동안 실행되는 Java 앱에 더 최적의 성능을 제공하는 C2로 구성된 계층형 컴파일을 사용합니다. Lambda의 수명이 짧다는 점을 고려하면 C2 컴파일의 이점은 무시할 수 있습니다.
AWS Lambda용 C1 컴파일 구성 과정을 안내하는 가이드는 여기에서 확인할 수 있습니다.
물론 저는 GraalVM 마스터 플랜과 비교하여 이 기술이 얼마나 많은 개선을 제공할 수 있는지 알고 싶었기 때문에 아래 결과에도 포함했습니다.
JVM의 계층형 컴파일과 GraalVM의 새로운 JIT 컴파일러에 대한 자세한 내용은 Baeldung 기사에서 확인할 수 있습니다.
아이러니하게도 변경 사항을 프로덕션에 적용한 지 몇 달 후 AWS는 실행 중인 Lambda의 스냅샷을 찍고 다시 초기화하는 대신 스냅샷 이미지를 다음과 같이 사용하는 최신 SnapStart 기능을 선보였습니다. 더 빠른 콜드 스타트를 약속하는 복원 지점입니다. 나는 GraalVM을 사용하는 것이 낭비적인 노력인지 알아보고 이를 내 결과에도 포함시켰는지 알아보기 위해 노력해야 했습니다.
SnapStart를 최대한 활용하려면 beforeCheckpoint 및 afterRestore 후크를 활용하기 위한 코드 리팩터링이 필요했다는 점에 주목할 가치가 있습니다(자세한 내용은 여기 참조). 가능한 한 큰 코드 변경을 피하고 싶었기 때문에 이러한 메소드를 구현하거나 코드를 재배열하지 않고 이 기능을 "있는 그대로" 사용했습니다.
이제 GraalVM으로 돌아왔습니다! 놀랍게도 이 솔루션을 통합한 후에는 빌드 구성 파일과 일부 필수 메타데이터를 추가하고 조정하는 것 외에는 Java 코드 변경이 전혀 필요하지 않았습니다.
믿을 수 없을 만큼 좋은 것 같나요?
조금 그럴 수도 있어요. Java 세계에서 AOT 컴파일을 사용하고 있다는 점을 고려하면 많은 라이브러리가 의존하는 리플렉션, 프록시, 인터페이스 및 서비스 레지스트리와 같은 언어 기능을 사용할 때 특정 문제가 발생합니다. 이것이 GraalVM 컴파일러가 최종 아티팩트에 포함될 수 있도록 특정 클래스 및 서비스를 명시적으로 등록하는 추가 구성 메타데이터를 선언하도록 요구하는 이유입니다. GraalVM은 실행 파일과 함께 실행하여 이 프로세스를 더 쉽게 만들 수 있는 필수 구성을 자동으로 식별하는 데 사용할 수 있는 소위 에이전트를 제공합니다.
Quarkus는 잘 알려진 라이브러리를 "네이티브 이미지 친화적"으로 만들기 위해 여러 확장 기능을 제공하지만 기존 코드 기반으로 작업 중이었고 목표는 주요 리팩터링(또는 해당 문제에 대한 코드 변경)을 피하는 것이었습니다. ), 네이티브 이미지를 성공적으로 생성하기 위해 기존 라이브러리에 필요한 필수 구성 파일을 생성하기로 결정했습니다.
네이티브 이미지 컴파일은 리소스 집약적이며 표준 JVM 런타임을 대상으로 하는 바이트코드 컴파일에 비해 시간이 상당히 오래 걸린다는 점에 유의하세요. 메모리 부족 문제를 방지하기 위해 빌드 노드에 더 많은 RAM을 할당해야 할 수도 있습니다. 이는 거래를 중단할 수는 없지만 반드시 명심해야 할 사항입니다.
이제 네이티브 이미지 Lambda를 컴파일하고 패키징했으므로 이를 테스트 환경에 배포할 차례입니다. 일반적으로 Java Lambda는 AWS의 Java 런타임을 활용하여 실행합니다. 그러나 Graal JVM 내부에 래핑된 앱 코드가 포함된 바이너리 아티팩트인 네이티브 이미지를 사용하려는 경우 AWS가 제공하는 "사용자 지정" Amazon Linux 환경 중 하나를 선택해야 합니다.
Postman API 컬렉션을 사용하여 9개 Lambda 모두에 요청을 보내고 위에 언급된 각 기술에 대한 콜드 스타트 응답 시간을 측정했습니다. 항상 콜드 스타트가 발생하는지 확인하기 위해 다음 호출에서 이미 웜 상태일 수 있는 인스턴스를 사용하지 않도록 보장하는 대상 Lambda의 구성을 다시 로드했습니다. 모든 Lambda는 1GB RAM으로 구성되었습니다. 또한 프로세스에 시간이 많이 소요된다는 점을 고려하여 모든 구성에 대해 단일 호출을 측정했습니다. 그러나 관찰된 응답 시간은 매우 명확한 그림을 그렸습니다.
그래서 효과가 있었나요? 전적으로! 결과는 다음과 같습니다.
확실한 승자는 GraalVM 네이티브 이미지입니다. 평균적으로 변경되지 않은 Java Lambda에 비해 속도가 3배 향상되었습니다. 더 이상 시간 초과가 없고 훨씬 더 나은 응답 시간이 바로 제가 원했던 것입니다. 달성하세요.
SnapStart는 코드 변경 없이는 생각보다 성능이 좋지 않았습니다. SnapStart 기능과 함께 C1 컴파일러를 사용하면 콜드 스타트 시간이 더 낮아졌지만 여전히 GraalVM의 네이티브 이미지를 능가하지는 못했습니다. 빠르고 쉽게 개선을 구현할 수 있는 실행 가능한 옵션이 아니라는 말은 아닙니다. 그러나 Lambda를 최대한 최적화하고 구성과 빌드 프로세스를 조정할 시간과 리소스가 있다면 성능과 보안 측면에서 GraalVM이 확실히 뛰어납니다.
GraalVM의 주장에 따르면 네이티브 이미지는 일반 JVM 이미지에 비해 효과적으로 실행하는 데 더 적은 리소스가 필요합니다. 저는 이러한 Lambda가 작동해야 하는 RAM의 양을 줄이면 콜드 스타트와 웜 스타트 성능이 어떻게 유지되는지 확인하고 싶었습니다. 이번에는 이 테스트를 수행하기 위해 단일 Lambda 앱만 선택했습니다. 결과는 다음과 같습니다.
그리고 약속대로 이행했습니다! 일반 JVM Lambda는 256MB 이하의 구성을 시도할 때 메모리가 부족한 반면, 네이티브 이미지는 단계적이지 않고 계속 실행되는 것처럼 보였습니다. 128MB가 사용 가능한 가장 낮은 메모리 옵션이 아니었다면 얼마나 더 낮아질 수 있었는지 궁금합니다. 네이티브 이미지는 콜드 스타트 시 속도가 더 빠를 뿐만 아니라 제한된 리소스로 작업할 때 일관된 성능을 제공하므로 운영 비용이 절감됩니다.
Java의 생태계는 서버리스 애플리케이션과 관련하여 Java를 게임에 유지하는 많은 새로운 기술과 개선 사항이 매일 등장하면서 풍부하고 광대합니다. 그러한 새로운 기술 중 하나가 GraalVM입니다. 연구 프로젝트로 시작된 것이 이제는 천천히 채택되고 있으며 HotSpot과 같은 표준 JVM에 대한 실행 가능한 대안으로 제시되고 있습니다. 이 블로그 게시물에서는 GraalVM이 제공하는 기능의 표면만을 간신히 다루었으며 독자들이 이를 더 자세히 탐색해 보시기를 바랍니다. GraalVM을 활용하여 시간과 비용을 절약할 수 있었던 Adyen(기사 링크) 또는 Facebook(기사 링크)과 같은 회사의 여러 성공 사례가 있습니다.
다음번에 Java를 옵션으로 할인하려는 경우 GraalVM을 사용해 보세요. 이제 Spring Boot 3는 GraalVM 네이티브 이미지를 기본적으로 지원하므로 서버리스 워크로드에 이를 사용하여 GraalVM이 제공하는 성능, 낮은 리소스 소비 및 추가 보안을 활용하는 것이 그 어느 때보다 쉬워졌습니다.
위 내용은 Java도 서버리스일 수 있습니다: 빠른 콜드 스타트를 위해 GraalVM 사용의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!