본문 바로가기

CS/JVM

[JVM] CRaC 완전 정복 (1편) - 개념과 동작 원리

시리즈 구성

  • 1편: 개념과 동작 원리 ← 현재 글
  • 2편: Spring Boot 실전 적용
  • 3편: JDK 배포판 비교와 실무 선택 가이드

 


들어가며

Java 애플리케이션은 왜 뜨는 데 오래 걸릴까요?

 

Spring Boot 기반의 마이크로서비스를 컨테이너로 배포해 본 경험이 있다면, 인스턴스를 스케일 아웃할 때마다 기동에 수 초씩 걸리는 문제를 한 번쯤 마주했을 것입니다. 서버리스 환경(AWS Lambda 등)에서는 이 Cold Start 지연이 더욱 치명적으로 작용합니다.

 

이 문제를 해결하기 위해 OpenJDK 진영에서 등장한 기술이 바로 CRaC(Coordinated Restore at Checkpoint) 입니다. 이번 시리즈에서는 CRaC의 개념부터 실전 적용까지 단계적으로 살펴봅니다.

 


1. Java Cold Start 문제

JVM 기동 흐름

Java 애플리케이션이 실행되면 다음 단계를 거칩니다.

각 단계마다 비용이 발생합니다. 특히 JIT 웜업 구간이 문제입니다. JVM은 초기에 바이트코드를 인터프리터로 실행하다가, 자주 호출되는 메서드를 감지하면 네이티브 코드로 JIT 컴파일합니다. 이 최적화가 완료되기 전까지는 최고 성능에 도달하지 못합니다.

 

컨테이너 환경에서 문제가 심각한 이유

인스턴스가 100개 필요한 상황에서 인스턴스 하나를 띄우는 데 5초가 걸린다면, 그 5초 동안 트래픽은 기존 인스턴스에 집중됩니다. 서버리스 환경에서는 콜드 스타트마다 이 비용이 반복됩니다.

 


2. CRaC의 핵심 아이디어

체크포인트와 복원

CRaC는 발상 자체가 단순합니다.

"웜업이 완료된 JVM 상태를 통째로 저장해두고, 다음 실행 시 그 시점부터 재개하면 되지 않을까?"

체크포인트 이미지에는 JIT 컴파일된 코드까지 그대로 보존됩니다. 복원 직후부터 피크 성능으로 동작할 수 있습니다. 이는 클래스 아카이브만 캐싱하는 AppCDS와 본질적으로 다른 점입니다.

 

"Coordinated(조율된)"의 의미

단순한 프로세스 스냅샷과 다른 점이 바로 이 단어에 있습니다.

 

체크포인트 시점에는 소켓, 파일 핸들, DB 커넥션 같은 리소스가 열려 있습니다. 이런 리소스는 이미지에 저장할 수 없고, 복원 후 실행 환경이 달라져 있을 수도 있습니다(IP 변경, 시크릿 갱신 등). CRaC는 이를 애플리케이션과 협력해 처리합니다.

 


3. Resource API

beforeCheckpoint / afterRestore

CRaC는 체크포인트 직전과 복원 직후에 애플리케이션이 개입할 수 있도록 Resource 인터페이스를 제공합니다.

import org.crac.Context;
import org.crac.Core;
import org.crac.Resource;

public class DatabaseConnectionResource implements Resource {

    private Connection connection;

    public DatabaseConnectionResource() {
        // 생성 시 글로벌 컨텍스트에 등록
        Core.getGlobalContext().register(this);
    }

    @Override
    public void beforeCheckpoint(Context<? extends Resource> context) throws Exception {
        // 체크포인트 직전: 열린 리소스 정리
        connection.close();
        System.out.println("DB 커넥션 닫음");
    }

    @Override
    public void afterRestore(Context<? extends Resource> context) throws Exception {
        // 복원 직후: 리소스 재획득
        connection = dataSource.getConnection();
        System.out.println("DB 커넥션 재연결");
    }
}

 

여러 Resource가 등록된 경우 콜백 순서는 다음과 같습니다.

체크포인트 시: A.beforeCheckpoint() → B.beforeCheckpoint() (등록 순)
복원 시:       B.afterRestore()      → A.afterRestore()      (역순)

 

org.crac vs jdk.crac

CRaC API는 아직 Java SE 표준 스펙에 포함되지 않아 두 가지 패키지가 병존합니다.

패키지 위치 특징
jdk.crac CRaC JDK 내장 CRaC JDK에서만 동작
org.crac 외부 라이브러리 일반 JDK에서도 컴파일·실행 가능

 

org.crac은 호환성 래퍼 라이브러리입니다. CRaC JDK에서 실행하면 jdk.crac으로 위임하고, 일반 JDK에서 실행하면 더미 구현으로 동작합니다(체크포인트 호출 시 예외 발생, 앱은 계속 실행).

<!-- Maven -->
<dependency>
    <groupId>org.crac</groupId>
    <artifactId>crac</artifactId>
    <version>1.5.0</version>
</dependency>

실무에서는 org.crac을 사용하는 것이 일반적입니다. 코드는 JDK 독립적으로 작성하고, 실제 체크포인트 기능은 CRaC JDK + Linux 환경에서만 동작시키는 방식입니다.

 


4. C/R 엔진 종류

CRaC를 이해하려면 JDK 배포판C/R 엔진을 분리해서 봐야 합니다. 엔진은 실제로 JVM 상태를 어떻게 저장하고 복원하느냐를 결정합니다.

CRIU (Checkpoint/Restore In Userspace)

Linux 커널 기능을 활용한 전통적인 방식입니다. 외부 프로세스가 ptrace로 JVM 프로세스를 들여다보며 상태를 덤프합니다.

  • CAP_CHECKPOINT_RESTORE, SYS_PTRACE 권한 필요
  • Docker 빌드 단계에서는 해당 권한 부여 불가 → 멀티스테이지 빌드로 체크포인트 생성 불가
  • 컨테이너에서 PID 1로 실행 시 CRIU 프로세스 탈출이 불가능해 JVM이 더미 래퍼 프로세스를 자식으로 실행하는 우회 구조 필요

 

Warp (Azul 자체 개발)

2024년 10월 Azul Zulu에 추가된 CRIU 독립 엔진입니다. Linux 코어덤프 메커니즘에서 착안해, JVM이 자기 자신의 상태를 직접 읽어 ELF 형식의 이미지로 저장합니다.

  • 추가 권한 불필요 — Docker 빌드 단계에서도 체크포인트 생성 가능
  • 복원 시 PID/TID가 변경되므로 PID에 의존하는 코드는 Resource로 처리 필요
  • Azul Zulu(Linux x86_64, ARM 64-bit) 및 Azul Zing(Linux x86_64)에서 지원
  • 이미지 LZ4 압축, Concurrent Memory Loading, S3 원격 저장 등 고급 기능은 Azul Subscriber 빌드(유료)에서 Preview로 제공

VM 옵션 -XX:CRaCEngine=warp으로 활성화합니다.

 

개발·테스트용 엔진

실제 체크포인트 저장 없이 CRaC API를 검증할 수 있는 엔진 두 가지가 제공됩니다.

 

simengine — 체크포인트 직후 동일 프로세스 내에서 즉시 복원합니다. 디스크 저장이 없으며 -XX:CRaCRestoreFrom과 함께 사용할 수 없습니다. Linux, Windows, macOS 모두 지원하므로, 로컬 개발 환경에서 beforeCheckpoint / afterRestore 콜백 동작을 빠르게 확인할 때 활용합니다.

 

pauseengine — 체크포인트 시점에 JVM 프로세스를 일시 중단(pause)합니다. 복원은 별도 JVM 인스턴스로 -XX:CRaCRestoreFrom을 통해 수행합니다. Linux 전용이며, 복원 전 환경 변화(예: 환경변수, 시크릿 갱신)에 대한 동작을 검증할 때 유용합니다.

항목 CRIU Warp simengine pauseengine
방식 외부 프로세스 ptrace JVM 자체 덤프(ELF) 즉시 복원(저장 없음) JVM 일시 중단
추가 권한 필요 불필요 불필요 불필요
디스크 저장 O O X O
Docker 멀티스테이지 빌드 X O
지원 OS Linux Linux Linux / Windows / macOS Linux

 


5. 다른 JVM 기동 개선 기법과의 비교

CRaC만이 JVM 기동 시간을 개선하는 유일한 방법은 아닙니다. 각 기법의 포지션을 먼저 파악해두면 CRaC를 선택해야 하는 상황이 더 명확해집니다.

 

기법별 핵심 특징

기법 핵심 방식 복잡도 JIT 최고 성능 OS 제약
CDS JDK 핵심 클래스 아카이브 캐싱 ★ (JDK 12+ 기본 적용) 유지 없음
AppCDS 앱 클래스까지 아카이브 캐싱 유지 없음
CRaC 웜업된 JVM 프로세스 스냅샷 ★★ 웜업 상태 보존 Linux 전용
GraalVM Native 빌드 타임 AOT 컴파일 ★★★ 런타임 JIT 최적화 불가 플랫폼별 빌드
Project Leyden AOT 클래스 로딩/링킹/프로파일링 ★ (옵션 1개) 유지 (기대) 없음 (기대)

 

기법별 포지션 비교

 

CRaC가 유일한 이유

CRaC는 "빠른 기동"과 "JIT 최고 성능"을 동시에 달성하는 유일한 방법입니다.

  • GraalVM은 기동은 빠르지만 런타임 JIT 최적화(프로파일 기반 적응 최적화)를 포기합니다. 처리량이 높은 장시간 실행 서비스에서 성능 상한이 낮아지는 trade-off가 있습니다.
  • AppCDS는 클래스 로딩을 빠르게 하지만 JIT 웜업 비용은 그대로입니다.
  • CRaC는 웜업이 완료된 상태를 그대로 저장하므로, 복원 직후부터 피크 성능입니다.

단, Linux 전용이고 코드 수정(혹은 프레임워크 지원)이 필요하다는 제약이 있습니다.

 


정리

항목 내용
CRaC란 웜업 완료된 JVM 상태를 스냅샷으로 저장, 이후 즉시 복원하는 OpenJDK 기술
Coordinated의 의미 체크포인트 전후로 애플리케이션이 리소스를 직접 정리·재획득하는 협력 구조
Resource API beforeCheckpoint / afterRestore 콜백으로 리소스 라이프사이클 제어
org.crac 일반 JDK 호환 래퍼 라이브러리 (최신 버전: 1.5.0). 실제 동작은 CRaC JDK + Linux 필요
C/R 엔진 CRIU (권한 필요) / Warp (권한 불필요, Azul 전용) / simengine·pauseengine (개발·테스트용)
타 기법 대비 포지션 빠른 기동 + JIT 최고 성능을 동시에 달성하는 유일한 방법

 

다음 편에서는 Spring Boot에서 CRaC를 실제로 적용하는 방법을 다룹니다.

https://dmoritle.tistory.com/249

 

[JVM] CRaC 완전 정복 (2편) - Spring Boot 실전 적용

시리즈 구성1편: 개념과 동작 원리2편: Spring Boot 실전 적용 ← 현재 글3편: JDK 배포판 비교와 실무 선택 가이드 들어가며1편에서 CRaC의 개념과 동작 원리를 살펴봤습니다. 이번 편에서는 Spring Boot

dmoritle.tistory.com

 


참고 문서