์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ ์Šคํ”„๋ง ๋ถ€ํŠธ GraalVM Native Image๋กœ ๋นŒ๋“œํ•˜๊ธฐ: ์ดˆ๊ณ ์† ์‹คํ–‰์˜ ์„ธ๊ณ„๋กœ!

๐Ÿš€ ์Šคํ”„๋ง ๋ถ€ํŠธ GraalVM Native Image๋กœ ๋นŒ๋“œํ•˜๊ธฐ: ์ดˆ๊ณ ์† ์‹คํ–‰์˜ ์„ธ๊ณ„๋กœ!

์ž๋ฐ” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒˆ๋กœ์šด ํŒจ๋Ÿฌ๋‹ค์ž„, Native Image ์™„๋ฒฝ ๊ฐ€์ด๋“œ

์•ˆ๋…•, ์นœ๊ตฌ! ๐Ÿ˜Š ์˜ค๋Š˜์€ ์ •๋ง ํฅ๋ฏธ์ง„์ง„ํ•œ ์ฃผ์ œ๋ฅผ ๊ฐ€์ง€๊ณ  ์™”์–ด. ์Šคํ”„๋ง ๋ถ€ํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ GraalVM Native Image๋กœ ๋นŒ๋“œํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณผ ๊ฑฐ์•ผ.

ํ˜น์‹œ ์ž๋ฐ” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘ํ•  ๋•Œ ๋„ˆ๋ฌด ๋А๋ ค์„œ ๋‹ต๋‹ตํ–ˆ๋˜ ์  ์žˆ์ง€? ๐ŸŒ ๋˜๋Š” ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋„ˆ๋ฌด ๋งŽ์ด ๋จน์–ด์„œ ๊ณ ๋ฏผํ–ˆ๋˜ ๊ฒฝํ—˜? ๊ทธ๋Ÿฐ ๋ฌธ์ œ๋“ค์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋งˆ๋ฒ• ๊ฐ™์€ ๊ธฐ์ˆ ์ด ๋ฐ”๋กœ GraalVM Native Image์•ผ!

์ด ๊ธ€์„ ๋‹ค ์ฝ๊ณ  ๋‚˜๋ฉด, ๋„ˆ๋„ ๋„ค์ดํ‹ฐ๋ธŒ ์ด๋ฏธ์ง€ ๋นŒ๋“œ ์ „๋ฌธ๊ฐ€๊ฐ€ ๋  ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ. ์ž, ๊ทธ๋Ÿผ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ? ๐ŸŽฏ

๐Ÿค” GraalVM Native Image๊ฐ€ ๋ญ๊ธธ๋ž˜?

๋จผ์ € ๊ธฐ๋ณธ๋ถ€ํ„ฐ ์•Œ์•„๋ณด์ž. GraalVM Native Image๋Š” ์ž๋ฐ” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋„ค์ดํ‹ฐ๋ธŒ ์‹คํ–‰ ํŒŒ์ผ๋กœ ์ปดํŒŒ์ผํ•˜๋Š” ๊ธฐ์ˆ ์ด์•ผ.

์ผ๋ฐ˜์ ์ธ ์ž๋ฐ” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ JVM(Java Virtual Machine) ์œ„์—์„œ ์‹คํ–‰๋˜์ง€? ๊ทธ๋Ÿฐ๋ฐ Native Image๋Š” JVM ์—†์ด๋„ ์ง์ ‘ ์šด์˜์ฒด์ œ์—์„œ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋Š” ๋…๋ฆฝ์ ์ธ ์‹คํ–‰ ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์ค˜. ๐Ÿ˜ฎ

โœจ Native Image์˜ ๋†€๋ผ์šด ์žฅ์ ๋“ค

1. ์ดˆ๊ณ ์† ์‹œ์ž‘ ์‹œ๊ฐ„ โšก
์ผ๋ฐ˜ ์Šคํ”„๋ง ๋ถ€ํŠธ ์•ฑ์ด 10-20์ดˆ ๊ฑธ๋ฆฐ๋‹ค๋ฉด, Native Image๋Š” 0.1์ดˆ ์ดํ•˜๋กœ ์‹œ์ž‘๋ผ. ์ •๋ง ๋ˆˆ ๊นœ์งํ•  ์‚ฌ์ด์•ผ!

2. ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ ๐Ÿ’พ
JVM ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋ณด๋‹ค ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ›จ์”ฌ ์ ๊ฒŒ ์‚ฌ์šฉํ•ด. ๋ณดํ†ต 1/5 ์ˆ˜์ค€์œผ๋กœ ์ค„์–ด๋“ค์–ด.

3. ์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ์— ์ตœ์ ํ™” ๐Ÿณ
Docker๋‚˜ Kubernetes ๊ฐ™์€ ์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ์—์„œ ๋น ๋ฅธ ์Šค์ผ€์ผ๋ง์ด ๊ฐ€๋Šฅํ•ด์ ธ.

4. ์„œ๋ฒ„๋ฆฌ์Šค ํ™˜๊ฒฝ์˜ ๊ตฌ์„ธ์ฃผ โ˜๏ธ
AWS Lambda ๊ฐ™์€ ์„œ๋ฒ„๋ฆฌ์Šค ํ™˜๊ฒฝ์—์„œ ์ฝœ๋“œ ์Šคํƒ€ํŠธ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์–ด.

โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ ๋ฌผ๋ก  ๋‹จ์ ๋„ ์žˆ์–ด. ๋นŒ๋“œ ์‹œ๊ฐ„์ด ๊ธธ๊ณ , ๋ฆฌํ”Œ๋ ‰์…˜์ด๋‚˜ ๋™์  ํ”„๋ก์‹œ ๊ฐ™์€ ๊ธฐ๋Šฅ์— ์ œ์•ฝ์ด ์žˆ์ง€. ํ•˜์ง€๋งŒ ์Šคํ”„๋ง ๋ถ€ํŠธ 3.0๋ถ€ํ„ฐ๋Š” ์ด๋Ÿฐ ๋ฌธ์ œ๋“ค์ด ๋งŽ์ด ๊ฐœ์„ ๋์–ด!
JVM vs Native Image ๋น„๊ต ์ „ํ†ต์ ์ธ JVM ๐ŸŒ ์‹œ์ž‘ ์‹œ๊ฐ„: 10-20์ดˆ ๋ฉ”๋ชจ๋ฆฌ: 500MB+ JVM ํ•„์š”: O ๋™์  ๊ธฐ๋Šฅ: ์™„๋ฒฝ ์ง€์› Native Image ๐Ÿš€ ์‹œ์ž‘ ์‹œ๊ฐ„: 0.1์ดˆ ์ดํ•˜ ๋ฉ”๋ชจ๋ฆฌ: 100MB ์ดํ•˜ JVM ํ•„์š”: X ๋™์  ๊ธฐ๋Šฅ: ์ œํ•œ์  ์ง„ํ™”!

๐Ÿ› ๏ธ ์‹œ์ž‘ํ•˜๊ธฐ ์ „ ์ค€๋น„๋ฌผ

Native Image ๋นŒ๋“œ๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ํ•„์š”ํ•œ ๊ฒƒ๋“ค์„ ์ฑ™๊ฒจ๋ณด์ž. ๋งˆ์น˜ ์š”๋ฆฌํ•˜๊ธฐ ์ „์— ์žฌ๋ฃŒ๋ฅผ ์ค€๋น„ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ง์ด์•ผ! ๐Ÿ‘จโ€๐Ÿณ

1๏ธโƒฃ GraalVM ์„ค์น˜ํ•˜๊ธฐ

๊ฐ€์žฅ ๋จผ์ € GraalVM์„ ์„ค์น˜ํ•ด์•ผ ํ•ด. GraalVM์€ Oracle์—์„œ ๊ฐœ๋ฐœํ•œ ๊ณ ์„ฑ๋Šฅ JDK์•ผ.

GraalVM Community Edition์„ ๋‹ค์šด๋กœ๋“œํ•˜๋ฉด ๋ฌด๋ฃŒ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ๊ณต์‹ ์›น์‚ฌ์ดํŠธ(graalvm.org)์—์„œ ์šด์˜์ฒด์ œ์— ๋งž๋Š” ๋ฒ„์ „์„ ๋‹ค์šด๋ฐ›์œผ๋ฉด ๋ผ.

2๏ธโƒฃ Native Image ์ปดํฌ๋„ŒํŠธ ์„ค์น˜

GraalVM์„ ์„ค์น˜ํ–ˆ๋‹ค๋ฉด, Native Image ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”๊ฐ€๋กœ ์„ค์น˜ํ•ด์•ผ ํ•ด. ํ„ฐ๋ฏธ๋„์—์„œ ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋ผ:

gu install native-image
๐Ÿ’ก ํŒ: gu๋Š” GraalVM Updater์˜ ์•ฝ์ž์•ผ. GraalVM์˜ ํŒจํ‚ค์ง€ ๊ด€๋ฆฌ์ž๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ผ!

3๏ธโƒฃ ๋นŒ๋“œ ๋„๊ตฌ ์ค€๋น„

Native Image ๋นŒ๋“œ๋ฅผ ์œ„ํ•ด์„œ๋Š” C ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ํ•„์š”ํ•ด. ์šด์˜์ฒด์ œ๋ณ„๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ค€๋น„ํ•˜๋ฉด ๋ผ:

Windows: Visual Studio Build Tools ์„ค์น˜
macOS: Xcode Command Line Tools ์„ค์น˜
Linux: gcc์™€ glibc-devel ํŒจํ‚ค์ง€ ์„ค์น˜

4๏ธโƒฃ ์Šคํ”„๋ง ๋ถ€ํŠธ ๋ฒ„์ „ ํ™•์ธ

์Šคํ”„๋ง ๋ถ€ํŠธ 3.0 ์ด์ƒ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ฐ•๋ ฅํžˆ ์ถ”์ฒœํ•ด! ๐ŸŽฏ

์Šคํ”„๋ง ๋ถ€ํŠธ 3.0๋ถ€ํ„ฐ Native Image ์ง€์›์ด ๋Œ€ํญ ๊ฐœ์„ ๋๊ฑฐ๋“ . ํŠนํžˆ AOT(Ahead-Of-Time) ์ปดํŒŒ์ผ ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋˜์–ด์„œ ๋นŒ๋“œ๊ฐ€ ํ›จ์”ฌ ์ˆ˜์›”ํ•ด์กŒ์–ด.

๐Ÿ“ฆ ํ”„๋กœ์ ํŠธ ์„ค์ •ํ•˜๊ธฐ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์Šคํ”„๋ง ๋ถ€ํŠธ ํ”„๋กœ์ ํŠธ๋ฅผ Native Image๋กœ ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•ด๋ณด์ž! ๐ŸŽจ

Maven ํ”„๋กœ์ ํŠธ ์„ค์ •

Maven์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด pom.xml ํŒŒ์ผ์— ๋‹ค์Œ ์„ค์ •์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ด:

๐Ÿ“„ pom.xml
<project>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
    </parent>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
โœ… ํฌ์ธํŠธ: spring-boot-starter-parent 3.0 ์ด์ƒ์„ ์‚ฌ์šฉํ•˜๋ฉด native-maven-plugin์ด ์ž๋™์œผ๋กœ ์„ค์ •๋ผ!

Gradle ํ”„๋กœ์ ํŠธ ์„ค์ •

Gradle์„ ์„ ํ˜ธํ•œ๋‹ค๋ฉด? ๊ฑฑ์ • ๋งˆ! build.gradle ํŒŒ์ผ์„ ์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๋ฉด ๋ผ:

๐Ÿ“„ build.gradle
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'org.graalvm.buildtools.native' version '0.9.28'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

graalvmNative {
    binaries {
        main {
            imageName = 'my-native-app'
            mainClass = 'com.example.Application'
            buildArgs.add('--verbose')
        }
    }
}
๐Ÿ’ก ์•Œ์•„๋‘๋ฉด ์ข‹์•„์š”: graalvmNative ๋ธ”๋ก์—์„œ ๋‹ค์–‘ํ•œ ๋นŒ๋“œ ์˜ต์…˜์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์–ด. imageName์œผ๋กœ ์ตœ์ข… ์‹คํ–‰ ํŒŒ์ผ ์ด๋ฆ„์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์ง€!

๐ŸŽฏ Native Image ๋นŒ๋“œ ์‹คํ–‰ํ•˜๊ธฐ

๋“œ๋””์–ด ๋นŒ๋“œ๋ฅผ ์‹คํ–‰ํ•  ์‹œ๊ฐ„์ด์•ผ! ๐Ÿ˜Š ์„ค๋ ˆ์ง€ ์•Š์•„? ์ฒ˜์Œ ๋นŒ๋“œํ•  ๋•Œ๋Š” ์‹œ๊ฐ„์ด ์ข€ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ปคํ”ผ ํ•œ ์ž” ์ค€๋น„ํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์•„. โ˜•

Maven์œผ๋กœ ๋นŒ๋“œํ•˜๊ธฐ

Maven ํ”„๋กœ์ ํŠธ๋ผ๋ฉด ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ด:

./mvnw -Pnative native:compile

๋˜๋Š” ์Šคํ”„๋ง ๋ถ€ํŠธ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์‚ฌ์šฉํ•ด์„œ:

./mvnw -Pnative spring-boot:build-image

๐Ÿ” ๋ช…๋ น์–ด ์˜ต์…˜ ์„ค๋ช…

-Pnative: native ํ”„๋กœํŒŒ์ผ์„ ํ™œ์„ฑํ™”ํ•ด
native:compile: ๋„ค์ดํ‹ฐ๋ธŒ ์ด๋ฏธ์ง€๋กœ ์ปดํŒŒ์ผํ•ด
spring-boot:build-image: Docker ์ด๋ฏธ์ง€๋กœ๋„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด

Gradle๋กœ ๋นŒ๋“œํ•˜๊ธฐ

Gradle ์‚ฌ์šฉ์ž๋ผ๋ฉด ์ด๋ ‡๊ฒŒ:

./gradlew nativeCompile

Docker ์ด๋ฏธ์ง€๋กœ ๋นŒ๋“œํ•˜๋ ค๋ฉด:

./gradlew bootBuildImage
โฐ ์‹œ๊ฐ„ ์†Œ์š” ์•ˆ๋‚ด ์ฒซ ๋นŒ๋“œ๋Š” 5-10๋ถ„ ์ •๋„ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์–ด. ํ”„๋กœ์ ํŠธ ํฌ๊ธฐ์— ๋”ฐ๋ผ ๋” ์˜ค๋ž˜ ๊ฑธ๋ฆด ์ˆ˜๋„ ์žˆ์ง€. ํ•˜์ง€๋งŒ ๊ทธ๋งŒํ•œ ๊ฐ€์น˜๊ฐ€ ์žˆ์–ด!

๋นŒ๋“œ ๊ณผ์ • ์ดํ•ดํ•˜๊ธฐ

Native Image ๋นŒ๋“œ ๊ณผ์ •์€ ์ผ๋ฐ˜ ์ž๋ฐ” ์ปดํŒŒ์ผ๊ณผ๋Š” ๋งŽ์ด ๋‹ฌ๋ผ. ์–ด๋–ค ์ผ์ด ์ผ์–ด๋‚˜๋Š”์ง€ ์•Œ์•„๋ณด์ž:

1๋‹จ๊ณ„: ์ •์  ๋ถ„์„ (Static Analysis)
์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•ด์„œ ์‹ค์ œ๋กœ ์‚ฌ์šฉ๋˜๋Š” ํด๋ž˜์Šค์™€ ๋ฉ”์„œ๋“œ๋ฅผ ์ฐพ์•„๋‚ด.
2๋‹จ๊ณ„: AOT ์ปดํŒŒ์ผ
์Šคํ”„๋ง ๋ถ€ํŠธ 3.0์˜ AOT ์—”์ง„์ด ์‹คํ–‰ ์‹œ์ ์— ํ•„์š”ํ•œ ์„ค์ •๋“ค์„ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•ด.
3๋‹จ๊ณ„: ๋„ค์ดํ‹ฐ๋ธŒ ์ปดํŒŒ์ผ
GraalVM์ด ๋ฐ”์ดํŠธ์ฝ”๋“œ๋ฅผ ๋„ค์ดํ‹ฐ๋ธŒ ๋จธ์‹  ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜ํ•ด.
4๋‹จ๊ณ„: ๋งํ‚น
๋ชจ๋“  ์ฝ”๋“œ์™€ ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ•˜๋‚˜์˜ ์‹คํ–‰ ํŒŒ์ผ๋กœ ๋ฌถ์–ด.
Native Image ๋นŒ๋“œ ํ”„๋กœ์„ธ์Šค ์†Œ์Šค ์ฝ”๋“œ ๐Ÿ“ ์ •์  ๋ถ„์„ ํด๋ž˜์Šค ์Šค์บ” ๐Ÿ” AOT ์ปดํŒŒ์ผ ์ตœ์ ํ™” โš™๏ธ ๋„ค์ดํ‹ฐ๋ธŒ ๋ฐ”์ด๋„ˆ๋ฆฌ ์‹คํ–‰ ํŒŒ์ผ ๐Ÿš€ ๋นŒ๋“œ ์‹œ๊ฐ„ ๋น„๊ต ์ผ๋ฐ˜ ๋นŒ๋“œ: 30์ดˆ Native Image ๋นŒ๋“œ: 5-10๋ถ„ (ํ•˜์ง€๋งŒ ์‹คํ–‰์€ ์ดˆ๊ณ ์†!)

๐Ÿ”ง ๋ฆฌํ”Œ๋ ‰์…˜๊ณผ ๋™์  ๊ธฐ๋Šฅ ์ฒ˜๋ฆฌํ•˜๊ธฐ

Native Image์˜ ๊ฐ€์žฅ ํฐ ๋„์ „ ๊ณผ์ œ๋Š” ๋ฐ”๋กœ ๋ฆฌํ”Œ๋ ‰์…˜์ด์•ผ. ์ž๋ฐ”์˜ ๋ฆฌํ”Œ๋ ‰์…˜์€ ๋Ÿฐํƒ€์ž„์— ํด๋ž˜์Šค ์ •๋ณด๋ฅผ ๋™์ ์œผ๋กœ ๊ฐ€์ ธ์˜ค๋Š” ๊ธฐ๋Šฅ์ธ๋ฐ, Native Image๋Š” ์ปดํŒŒ์ผ ํƒ€์ž„์— ๋ชจ๋“  ๊ฑธ ๊ฒฐ์ •ํ•ด์•ผ ํ•˜๊ฑฐ๋“ . ๐Ÿค”

๋ฆฌํ”Œ๋ ‰์…˜ ์„ค์ • ํŒŒ์ผ ๋งŒ๋“ค๊ธฐ

๋ฆฌํ”Œ๋ ‰์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ํด๋ž˜์Šค๋“ค์€ ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ • ํŒŒ์ผ์— ๋“ฑ๋กํ•ด์•ผ ํ•ด.
src/main/resources/META-INF/native-image ๋””๋ ‰ํ† ๋ฆฌ์— ์„ค์ • ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด๋ณด์ž:

๐Ÿ“„ reflect-config.json
[
  {
    "name": "com.example.MyClass",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allDeclaredFields": true
  },
  {
    "name": "com.example.AnotherClass",
    "methods": [
      {"name": "specificMethod", "parameterTypes": ["java.lang.String"]}
    ]
  }
]
๐Ÿ’ก ์ž๋™ ์ƒ์„ฑ ํŒ: GraalVM์˜ Tracing Agent๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฆฌํ”Œ๋ ‰์…˜ ์„ค์ •์„ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด!

Tracing Agent ์‚ฌ์šฉํ•˜๊ธฐ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๋ฉด์„œ ๋ฆฌํ”Œ๋ ‰์…˜ ์‚ฌ์šฉ์„ ์ถ”์ ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด์•ผ:

java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image \
     -jar target/myapp.jar

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ์‚ฌ์šฉ๋œ ๋ชจ๋“  ๋ฆฌํ”Œ๋ ‰์…˜, ํ”„๋ก์‹œ, ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ์„ ๊ธฐ๋กํ•ด์„œ ์„ค์ • ํŒŒ์ผ์„ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ค˜! ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€? ๐Ÿ˜Ž

์Šคํ”„๋ง ๋ถ€ํŠธ์˜ ์ž๋™ ํžŒํŠธ

๋‹คํ–‰ํžˆ ์Šคํ”„๋ง ๋ถ€ํŠธ 3.0๋ถ€ํ„ฐ๋Š” ๋Œ€๋ถ€๋ถ„์˜ ์Šคํ”„๋ง ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ํžŒํŠธ๊ฐ€ ์ด๋ฏธ ์ œ๊ณต๋ผ.
ํ•˜์ง€๋งŒ ์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์ถ”๊ฐ€ ์„ค์ •์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์–ด.

๐ŸŽจ RuntimeHints ์ธํ„ฐํŽ˜์ด์Šค ํ™œ์šฉ

์ฝ”๋“œ๋กœ ์ง์ ‘ ํžŒํŠธ๋ฅผ ์ œ๊ณตํ•  ์ˆ˜๋„ ์žˆ์–ด:

@Configuration
public class MyRuntimeHints implements RuntimeHintsRegistrar {
    
    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        // ๋ฆฌํ”Œ๋ ‰์…˜ ํžŒํŠธ ๋“ฑ๋ก
        hints.reflection()
            .registerType(MyClass.class, 
                MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
                MemberCategory.INVOKE_DECLARED_METHODS);
        
        // ๋ฆฌ์†Œ์Šค ํžŒํŠธ ๋“ฑ๋ก
        hints.resources()
            .registerPattern("config/*.properties");
        
        // ํ”„๋ก์‹œ ํžŒํŠธ ๋“ฑ๋ก
        hints.proxies()
            .registerJdkProxy(MyInterface.class);
    }
}
โœ… ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค: ๊ฐ€๋Šฅํ•˜๋ฉด RuntimeHints๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ข‹์•„. JSON ํŒŒ์ผ๋ณด๋‹ค ํƒ€์ž… ์•ˆ์ „ํ•˜๊ณ  IDE ์ง€์›๋„ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฑฐ๋“ !

๐Ÿณ Docker์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ

Native Image๋Š” Docker์™€ ํ™˜์ƒ์˜ ๊ถํ•ฉ์ด์•ผ! ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์—„์ฒญ๋‚˜๊ฒŒ ์ค„์ผ ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๐ŸŽ‰

๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ Dockerfile

ํšจ์œจ์ ์ธ Docker ์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ Dockerfile ์˜ˆ์ œ์•ผ:

๐Ÿ“„ Dockerfile
FROM ghcr.io/graalvm/native-image:ol8-java17-22 AS builder

WORKDIR /app
COPY . .

RUN ./mvnw -Pnative native:compile

FROM oraclelinux:8-slim

WORKDIR /app
COPY --from=builder /app/target/my-native-app .

EXPOSE 8080
ENTRYPOINT ["./my-native-app"]

๐Ÿ“Š ์ด๋ฏธ์ง€ ํฌ๊ธฐ ๋น„๊ต

์ผ๋ฐ˜ JVM ๊ธฐ๋ฐ˜ ์ด๋ฏธ์ง€: 200-300MB
Native Image ๊ธฐ๋ฐ˜: 50-80MB

๋ฌด๋ ค 70% ์ด์ƒ ๊ฐ์†Œ! ๐ŸŽฏ

Spring Boot Buildpacks ํ™œ์šฉ

Dockerfile ์—†์ด๋„ Native Image Docker ์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด:

./mvnw spring-boot:build-image

๋˜๋Š” Gradle:

./gradlew bootBuildImage
๐Ÿ’ก ๊ฟ€ํŒ: Buildpacks๋Š” ์ž๋™์œผ๋กœ ์ตœ์ ํ™”๋œ ๋ ˆ์ด์–ด๋ฅผ ๋งŒ๋“ค์–ด์ค˜์„œ ๋นŒ๋“œ ์บ์‹ฑ์ด ํšจ์œจ์ ์ด์•ผ!

Kubernetes ๋ฐฐํฌ

Native Image๋Š” Kubernetes ํ™˜๊ฒฝ์—์„œ ์ง„๊ฐ€๋ฅผ ๋ฐœํœ˜ํ•ด. ๋น ๋ฅธ ์‹œ์ž‘ ์‹œ๊ฐ„ ๋•๋ถ„์— ์˜คํ† ์Šค์ผ€์ผ๋ง์ด ํ›จ์”ฌ ํšจ๊ณผ์ ์ด๊ฑฐ๋“ !

๐Ÿ“„ deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-native-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-native-app
  template:
    metadata:
      labels:
        app: my-native-app
    spec:
      containers:
      - name: app
        image: my-native-app:latest
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 1
          periodSeconds: 3
โœ… ์ฃผ๋ชฉํ•  ์ : initialDelaySeconds๊ฐ€ 1์ดˆ์•ผ! JVM ๊ธฐ๋ฐ˜์ด์—ˆ๋‹ค๋ฉด 30์ดˆ๋Š” ํ•„์š”ํ–ˆ์„ ๊ฑฐ์•ผ.

โšก ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ

Native Image์˜ ์„ฑ๋Šฅ์„ ๊ทน๋Œ€ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•๋“ค์„ ์•Œ์•„๋ณด์ž! ์ด๋ฏธ ์ถฉ๋ถ„ํžˆ ๋น ๋ฅด์ง€๋งŒ, ๋” ๋น ๋ฅด๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. ๐Ÿš€

1. PGO (Profile-Guided Optimization)

ํ”„๋กœํŒŒ์ผ ๊ธฐ๋ฐ˜ ์ตœ์ ํ™”๋Š” ์‹ค์ œ ์‹คํ–‰ ํŒจํ„ด์„ ๋ถ„์„ํ•ด์„œ ๋” ํšจ์œจ์ ์ธ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ธฐ๋ฒ•์ด์•ผ.

Step 1: ํ”„๋กœํŒŒ์ผ๋ง ๋ชจ๋“œ๋กœ ๋นŒ๋“œ
./mvnw -Pnative -Dagent=true native:compile
Step 2: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ํ•˜๋ฉฐ ํ”„๋กœํŒŒ์ผ ์ˆ˜์ง‘
./target/my-app
Step 3: ํ”„๋กœํŒŒ์ผ์„ ์‚ฌ์šฉํ•ด ์žฌ๋นŒ๋“œ
./mvnw -Pnative -Dpgo=true native:compile

๐Ÿ“ˆ PGO ํšจ๊ณผ

์ฒ˜๋ฆฌ๋Ÿ‰: 10-30% ํ–ฅ์ƒ
๋ ˆ์ดํ„ด์‹œ: 5-15% ๊ฐ์†Œ
๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ: ์•ฝ๊ฐ„ ์ฆ๊ฐ€ (5% ๋‚ด์™ธ)

2. ๋นŒ๋“œ ์˜ต์…˜ ์ตœ์ ํ™”

๋‹ค์–‘ํ•œ ๋นŒ๋“œ ์˜ต์…˜์œผ๋กœ ์„ฑ๋Šฅ์„ ํŠœ๋‹ํ•  ์ˆ˜ ์žˆ์–ด:

<buildArgs>
    <buildArg>-O3</buildArg>
    <buildArg>-march=native</buildArg>
    <buildArg>--gc=G1</buildArg>
    <buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
    <buildArg>-H:+ReportExceptionStackTraces</buildArg>
</buildArgs>
โš ๏ธ ์ฃผ์˜: -march=native๋Š” ํŠน์ • CPU์— ์ตœ์ ํ™”๋˜๋ฏ€๋กœ ๋‹ค๋ฅธ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ ์•ˆ ๋  ์ˆ˜ ์žˆ์–ด!

3. ๋ฉ”๋ชจ๋ฆฌ ์„ค์ • ์กฐ์ •

Native Image๋„ ๋ฉ”๋ชจ๋ฆฌ ์„ค์ •์ด ์ค‘์š”ํ•ด:

-Xmx64m -Xms64m

JVM๋ณด๋‹ค ํ›จ์”ฌ ์ ์€ ๋ฉ”๋ชจ๋ฆฌ๋กœ๋„ ์ž˜ ๋Œ์•„๊ฐ€! ๐Ÿ˜Š

4. ๋ถˆํ•„์š”ํ•œ ๊ธฐ๋Šฅ ์ œ๊ฑฐ

์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์Šคํ”„๋ง ๋ถ€ํŠธ ์ž๋™ ์„ค์ •์„ ์ œ์™ธํ•˜๋ฉด ๋นŒ๋“œ ์‹œ๊ฐ„๊ณผ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์–ด:

@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class,
    HibernateJpaAutoConfiguration.class
})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

๐Ÿ› ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ฐ€์ด๋“œ

Native Image ๋นŒ๋“œํ•˜๋‹ค ๋ณด๋ฉด ์—ฌ๋Ÿฌ ๋ฌธ์ œ๋ฅผ ๋งŒ๋‚  ์ˆ˜ ์žˆ์–ด. ํ”ํ•œ ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž! ๐Ÿ”ง

๋ฌธ์ œ 1: ClassNotFoundException

์ฆ์ƒ: ๋นŒ๋“œ๋Š” ์„ฑ๊ณตํ–ˆ๋Š”๋ฐ ์‹คํ–‰ ์‹œ ํด๋ž˜์Šค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋‹ค๋Š” ์—๋Ÿฌ๊ฐ€ ๋‚˜.

์›์ธ:
๋ฆฌํ”Œ๋ ‰์…˜์œผ๋กœ ๋กœ๋“œ๋˜๋Š” ํด๋ž˜์Šค๊ฐ€ ์„ค์ •์— ํฌํ•จ๋˜์ง€ ์•Š์•˜์–ด.

ํ•ด๊ฒฐ:
reflect-config.json์— ํ•ด๋‹น ํด๋ž˜์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ Tracing Agent๋ฅผ ์‚ฌ์šฉํ•ด.

๋ฌธ์ œ 2: ๋นŒ๋“œ ์‹œ๊ฐ„์ด ๋„ˆ๋ฌด ์˜ค๋ž˜ ๊ฑธ๋ฆผ

์ฆ์ƒ: ๋นŒ๋“œ๊ฐ€ 30๋ถ„ ์ด์ƒ ๊ฑธ๋ ค.

์›์ธ:
ํ”„๋กœ์ ํŠธ๊ฐ€ ํฌ๊ฑฐ๋‚˜ ์˜์กด์„ฑ์ด ๋งŽ์•„.

ํ•ด๊ฒฐ:
- ๋นŒ๋“œ ๋จธ์‹ ์˜ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋Š˜๋ ค (์ตœ์†Œ 8GB ๊ถŒ์žฅ)
- ๋ณ‘๋ ฌ ๋นŒ๋“œ ์˜ต์…˜ ์‚ฌ์šฉ: -H:NumberOfThreads=8
- ๋ถˆํ•„์š”ํ•œ ์˜์กด์„ฑ ์ œ๊ฑฐ

๋ฌธ์ œ 3: ๋ฆฌ์†Œ์Šค ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ

์ฆ์ƒ: application.properties๋‚˜ ์ •์  ํŒŒ์ผ์„ ๋ชป ์ฐพ์•„.

์›์ธ:
๋ฆฌ์†Œ์Šค๊ฐ€ ๋„ค์ดํ‹ฐ๋ธŒ ์ด๋ฏธ์ง€์— ํฌํ•จ๋˜์ง€ ์•Š์•˜์–ด.

ํ•ด๊ฒฐ:
resource-config.json ์„ค์ •:
{
  "resources": {
    "includes": [
      {"pattern": "application.properties"},
      {"pattern": "static/.*"},
      {"pattern": "templates/.*"}
    ]
  }
}

๋ฌธ์ œ 4: ๋™์  ํ”„๋ก์‹œ ์—๋Ÿฌ

์ฆ์ƒ: ํ”„๋ก์‹œ ์ƒ์„ฑ ์‹คํŒจ ์—๋Ÿฌ.

์›์ธ:
JDK ํ”„๋ก์‹œ๋‚˜ CGLIB ํ”„๋ก์‹œ๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์–ด.

ํ•ด๊ฒฐ:
proxy-config.json ์ž‘์„ฑ:
[
  {
    "interfaces": ["com.example.MyInterface"]
  }
]
๐Ÿ’ก ๋””๋ฒ„๊น… ํŒ: --verbose ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ๋นŒ๋“œ ๊ณผ์ •์„ ์ž์„ธํžˆ ๋ณผ ์ˆ˜ ์žˆ์–ด!

๐Ÿ“Š ์‹ค์ „ ์˜ˆ์ œ: REST API ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๋Š” ์˜ˆ์ œ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž! ๊ฐ„๋‹จํ•œ REST API๋ฅผ Native Image๋กœ ๋นŒ๋“œํ•ด๋ณผ ๊ฑฐ์•ผ. ๐ŸŽจ

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

my-native-app/
โ”œโ”€โ”€ src/
โ”‚   โ””โ”€โ”€ main/
โ”‚       โ”œโ”€โ”€ java/
โ”‚       โ”‚   โ””โ”€โ”€ com/example/
โ”‚       โ”‚       โ”œโ”€โ”€ Application.java
โ”‚       โ”‚       โ”œโ”€โ”€ controller/
โ”‚       โ”‚       โ”‚   โ””โ”€โ”€ UserController.java
โ”‚       โ”‚       โ”œโ”€โ”€ model/
โ”‚       โ”‚       โ”‚   โ””โ”€โ”€ User.java
โ”‚       โ”‚       โ””โ”€โ”€ service/
โ”‚       โ”‚           โ””โ”€โ”€ UserService.java
โ”‚       โ””โ”€โ”€ resources/
โ”‚           โ”œโ”€โ”€ application.properties
โ”‚           โ””โ”€โ”€ META-INF/
โ”‚               โ””โ”€โ”€ native-image/
โ”‚                   โ””โ”€โ”€ reflect-config.json
โ””โ”€โ”€ pom.xml

๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

๐Ÿ“„ Application.java
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.nativex.hint.TypeHint;

@SpringBootApplication
public class Application {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

๋„๋ฉ”์ธ ๋ชจ๋ธ

๐Ÿ“„ User.java
package com.example.model;

public class User {
    private Long id;
    private String name;
    private String email;
    
    // ์ƒ์„ฑ์ž
    public User() {}
    
    public User(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
    
    // Getter & Setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

์„œ๋น„์Šค ๋ ˆ์ด์–ด

๐Ÿ“„ UserService.java
package com.example.service;

import com.example.model.User;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

@Service
public class UserService {
    
    private final Map<Long, User> users = new ConcurrentHashMap<>();
    private final AtomicLong idCounter = new AtomicLong();
    
    public UserService() {
        // ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ
        createUser(new User(null, "ํ™๊ธธ๋™", "hong@example.com"));
        createUser(new User(null, "๊น€์ฒ ์ˆ˜", "kim@example.com"));
    }
    
    public List<User> getAllUsers() {
        return new ArrayList<>(users.values());
    }
    
    public Optional<User> getUserById(Long id) {
        return Optional.ofNullable(users.get(id));
    }
    
    public User createUser(User user) {
        Long id = idCounter.incrementAndGet();
        user.setId(id);
        users.put(id, user);
        return user;
    }
    
    public Optional<User> updateUser(Long id, User user) {
        if (users.containsKey(id)) {
            user.setId(id);
            users.put(id, user);
            return Optional.of(user);
        }
        return Optional.empty();
    }
    
    public boolean deleteUser(Long id) {
        return users.remove(id) != null;
    }
}

์ปจํŠธ๋กค๋Ÿฌ

๐Ÿ“„ UserController.java
package com.example.controller;

import com.example.model.User;
import com.example.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final UserService userService;
    
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        return userService.getUserById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }
    
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.createUser(user);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(
            @PathVariable Long id, 
            @RequestBody User user) {
        return userService.updateUser(id, user)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        return userService.deleteUser(id)
            ? ResponseEntity.ok().build()
            : ResponseEntity.notFound().build();
    }
}

์„ค์ • ํŒŒ์ผ

๐Ÿ“„ application.properties
spring.application.name=my-native-app
server.port=8080
logging.level.root=INFO

๋นŒ๋“œ ๋ฐ ์‹คํ–‰

์ด์ œ ๋นŒ๋“œํ•ด๋ณด์ž:

# ๋นŒ๋“œ
./mvnw -Pnative native:compile

# ์‹คํ–‰
./target/my-native-app

API ํ…Œ์ŠคํŠธ:

# ๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ
curl http://localhost:8080/api/users

# ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ
curl http://localhost:8080/api/users/1

# ์‚ฌ์šฉ์ž ์ƒ์„ฑ
curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"์ด์˜ํฌ","email":"lee@example.com"}'
โœ… ์„ฑ๊ณต! ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด 0.1์ดˆ ๋งŒ์— ์‹œ์ž‘๋˜๊ณ  ๋ฉ”๋ชจ๋ฆฌ๋Š” 50MB๋งŒ ์‚ฌ์šฉํ•ด!

๐ŸŽ“ ๊ณ ๊ธ‰ ์ฃผ์ œ: AOT ์ฒ˜๋ฆฌ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•

์Šคํ”„๋ง ๋ถ€ํŠธ 3.0์˜ AOT(Ahead-Of-Time) ์ฒ˜๋ฆฌ๋ฅผ ์ง์ ‘ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ์–ด. ๋” ์„ธ๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์œ ์šฉํ•˜์ง€! ๐ŸŽฏ

BeanFactoryInitializationAotProcessor

๋นˆ ์ดˆ๊ธฐํ™” ๊ณผ์ •์„ AOT ์‹œ์ ์— ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์–ด:

@Component
public class MyAotProcessor implements BeanFactoryInitializationAotProcessor {
    
    @Override
    public BeanFactoryInitializationAotContribution 
            processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
        
        // ํŠน์ • ๋นˆ์— ๋Œ€ํ•œ AOT ์ฒ˜๋ฆฌ
        String[] beanNames = beanFactory.getBeanNamesForType(MyService.class);
        
        if (beanNames.length == 0) {
            return null;
        }
        
        return (generationContext, beanFactoryInitializationCode) -> {
            // ์ฝ”๋“œ ์ƒ์„ฑ ๋กœ์ง
            GeneratedMethod method = beanFactoryInitializationCode
                .getMethods()
                .add("initMyService", method -> {
                    method.addCode("// ์ดˆ๊ธฐํ™” ์ฝ”๋“œ");
                });
        };
    }
}

RuntimeHintsRegistrar ๊ณ ๊ธ‰ ํ™œ์šฉ

๋ณต์žกํ•œ ํžŒํŠธ ๋“ฑ๋ก๋„ ๊ฐ€๋Šฅํ•ด:

@Configuration
public class AdvancedRuntimeHints implements RuntimeHintsRegistrar {
    
    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        // ํŒจํ‚ค์ง€ ์ „์ฒด ์Šค์บ”
        hints.reflection()
            .registerType(
                TypeReference.of("com.example.model.*"),
                hint -> hint
                    .withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)
                    .withMembers(MemberCategory.DECLARED_FIELDS)
            );
        
        // ์กฐ๊ฑด๋ถ€ ํžŒํŠธ ๋“ฑ๋ก
        if (isProductionProfile()) {
            hints.resources()
                .registerPattern("prod-config/*.yml");
        }
        
        // ์ง๋ ฌํ™” ํžŒํŠธ
        hints.serialization()
            .registerType(MySerializableClass.class);
        
        // JNI ํžŒํŠธ
        hints.jni()
            .registerType(MyNativeClass.class,
                MemberCategory.INVOKE_PUBLIC_METHODS);
    }
    
    private boolean isProductionProfile() {
        // ํ”„๋กœํŒŒ์ผ ์ฒดํฌ ๋กœ์ง
        return true;
    }
}
๐Ÿ’ก ํ”„๋กœ ํŒ: AOT ์ฒ˜๋ฆฌ๋ฅผ ์ž˜ ํ™œ์šฉํ•˜๋ฉด ๋Ÿฐํƒ€์ž„ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ํฌ๊ฒŒ ์ค„์ผ ์ˆ˜ ์žˆ์–ด!

๐ŸŒ ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ๋ณ„ ๋ฐฐํฌ ์ „๋žต

Native Image๋ฅผ ๋‹ค์–‘ํ•œ ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ์— ๋ฐฐํฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž! โ˜๏ธ

AWS Lambda

Native Image๋Š” AWS Lambda์˜ ์ฝœ๋“œ ์Šคํƒ€ํŠธ ๋ฌธ์ œ๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ํ•ด๊ฒฐํ•ด:

๐Ÿ“„ template.yaml (SAM)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  MyNativeFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: org.springframework.cloud.function.adapter.aws.FunctionInvoker
      Runtime: provided.al2
      CodeUri: target/function.zip
      MemorySize: 512
      Timeout: 30
      Environment:
        Variables:
          SPRING_CLOUD_FUNCTION_DEFINITION: myFunction

๐Ÿ“Š Lambda ์„ฑ๋Šฅ ๋น„๊ต

JVM ๊ธฐ๋ฐ˜:
- ์ฝœ๋“œ ์Šคํƒ€ํŠธ: 5-10์ดˆ
- ๋ฉ”๋ชจ๋ฆฌ: 512MB ํ•„์š”
- ๋น„์šฉ: ๋†’์Œ

Native Image:
- ์ฝœ๋“œ ์Šคํƒ€ํŠธ: 100-200ms
- ๋ฉ”๋ชจ๋ฆฌ: 256MB ์ถฉ๋ถ„
- ๋น„์šฉ: 50% ์ ˆ๊ฐ ๊ฐ€๋Šฅ

Google Cloud Run

Cloud Run์—์„œ๋„ Native Image๊ฐ€ ๋น›์„ ๋ฐœํ•ด:

# ๋นŒ๋“œ
./mvnw spring-boot:build-image

# ํƒœ๊ทธ
docker tag my-native-app gcr.io/PROJECT_ID/my-native-app

# ํ‘ธ์‹œ
docker push gcr.io/PROJECT_ID/my-native-app

# ๋ฐฐํฌ
gcloud run deploy my-native-app \
  --image gcr.io/PROJECT_ID/my-native-app \
  --platform managed \
  --region asia-northeast1 \
  --memory 256Mi

Azure Container Instances

Azure์—์„œ๋„ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์–ด:

az container create \
  --resource-group myResourceGroup \
  --name my-native-app \
  --image myregistry.azurecr.io/my-native-app:latest \
  --cpu 1 \
  --memory 0.5 \
  --ports 8080
โœ… ํด๋ผ์šฐ๋“œ ๋ฐฐํฌ ํŒ: Native Image๋Š” ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ ๊ฒŒ ์“ฐ๋ฏ€๋กœ ๋” ์ž‘์€ ์ธ์Šคํ„ด์Šค ํƒ€์ž…์„ ์„ ํƒํ•ด์„œ ๋น„์šฉ์„ ์ ˆ๊ฐํ•  ์ˆ˜ ์žˆ์–ด!

๐Ÿ“ˆ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๊ด€์ฐฐ์„ฑ

Native Image ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋„ ๋ชจ๋‹ˆํ„ฐ๋ง์ด ์ค‘์š”ํ•ด! ์–ด๋–ป๊ฒŒ ํ•˜๋Š”์ง€ ์•Œ์•„๋ณด์ž. ๐Ÿ‘€

Spring Boot Actuator

Actuator๋Š” Native Image์—์„œ๋„ ์ž˜ ๋™์ž‘ํ•ด:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

์„ค์ •:

management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.metrics.export.prometheus.enabled=true

Prometheus & Grafana

Prometheus๋กœ ๋ฉ”ํŠธ๋ฆญ์„ ์ˆ˜์ง‘ํ•˜๊ณ  Grafana๋กœ ์‹œ๊ฐํ™”ํ•  ์ˆ˜ ์žˆ์–ด:

๐Ÿ“„ prometheus.yml
scrape_configs:
  - job_name: 'my-native-app'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['localhost:8080']

๋ถ„์‚ฐ ์ถ”์ 

Micrometer Tracing์œผ๋กœ ๋ถ„์‚ฐ ์ถ”์ ๋„ ๊ฐ€๋Šฅํ•ด:

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
</dependency>
๐Ÿ’ก ๋ชจ๋‹ˆํ„ฐ๋ง ํŒ: Native Image๋Š” ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ํŒจํ„ด์ด JVM๊ณผ ๋‹ค๋ฅด๋‹ˆ, ์ฒ˜์Œ์—๋Š” ๋ฉด๋ฐ€ํžˆ ๊ด€์ฐฐํ•˜๋Š” ๊ฒŒ ์ข‹์•„!

๐Ÿ”„ CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ•

Native Image ๋นŒ๋“œ๋ฅผ CI/CD ํŒŒ์ดํ”„๋ผ์ธ์— ํ†ตํ•ฉํ•ด๋ณด์ž! ๐Ÿš€

GitHub Actions

๐Ÿ“„ .github/workflows/native-build.yml
name: Native Image Build

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup GraalVM
      uses: graalvm/setup-graalvm@v1
      with:
        version: 'latest'
        java-version: '17'
        components: 'native-image'
        github-token: ${{ secrets.GITHUB_TOKEN }}
    
    - name: Cache Maven packages
      uses: actions/cache@v3
      with:
        path: ~/.m2
        key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
    
    - name: Build Native Image
      run: ./mvnw -Pnative native:compile
    
    - name: Test Native Image
      run: ./target/my-native-app &
    
    - name: Upload artifact
      uses: actions/upload-artifact@v3
      with:
        name: native-image
        path: target/my-native-app

GitLab CI

๐Ÿ“„ .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

native-build:
  stage: build
  image: ghcr.io/graalvm/native-image:ol8-java17-22
  script:
    - ./mvnw -Pnative native:compile
  artifacts:
    paths:
      - target/my-native-app
    expire_in: 1 week
  cache:
    paths:
      - .m2/repository

docker-build:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker build -t my-native-app:$CI_COMMIT_SHA .
    - docker push my-native-app:$CI_COMMIT_SHA

Jenkins Pipeline

๐Ÿ“„ Jenkinsfile
pipeline {
    agent any
    
    tools {
        maven 'Maven 3.9'
    }
    
    environment {
        GRAALVM_HOME = '/usr/lib/graalvm'
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        
        stage('Build Native Image') {
            steps {
                sh './mvnw -Pnative native:compile'
            }
        }
        
        stage('Test') {
            steps {
                sh './target/my-native-app &'
                sh 'sleep 5'
                sh 'curl http://localhost:8080/actuator/health'
            }
        }
        
        stage('Deploy') {
            steps {
                sh 'docker build -t my-native-app:latest .'
                sh 'docker push my-registry/my-native-app:latest'
            }
        }
    }
}
โš ๏ธ CI/CD ์ฃผ์˜์‚ฌํ•ญ: Native Image ๋นŒ๋“œ๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•ด. CI ์„œ๋ฒ„์— ์ถฉ๋ถ„ํ•œ ๋ฉ”๋ชจ๋ฆฌ(์ตœ์†Œ 8GB)๋ฅผ ํ• ๋‹นํ•ด์•ผ ํ•ด!

๐ŸŽจ ์žฌ๋Šฅ๋„ท์—์„œ ๋ฐฐ์šฐ๋Š” Native Image

Native Image ๋นŒ๋“œ๊ฐ€ ์ฒ˜์Œ์—๋Š” ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์–ด. ํ•˜์ง€๋งŒ ๊ฑฑ์ •ํ•˜์ง€ ๋งˆ! ์žฌ๋Šฅ๋„ท์—๋Š” ์Šคํ”„๋ง ๋ถ€ํŠธ์™€ GraalVM ์ „๋ฌธ๊ฐ€๋“ค์ด ๋งŽ์ด ์žˆ์–ด. ๐Ÿ˜Š

์‹ค์ œ ํ”„๋กœ์ ํŠธ์— Native Image๋ฅผ ์ ์šฉํ•˜๊ณ  ์‹ถ์€๋ฐ ๋ง‰๋ง‰ํ•˜๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท์—์„œ ์ „๋ฌธ๊ฐ€์˜ ๋„์›€์„ ๋ฐ›์•„๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. 1:1 ์ปจ์„คํŒ…๋ถ€ํ„ฐ ํ”„๋กœ์ ํŠธ ์ „์ฒด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๊นŒ์ง€ ๋‹ค์–‘ํ•œ ์„œ๋น„์Šค๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๊ฑฐ๋“ !

ํŠนํžˆ ๋ณต์žกํ•œ ๋ ˆ๊ฑฐ์‹œ ์‹œ์Šคํ…œ์„ Native Image๋กœ ์ „ํ™˜ํ•˜๋Š” ์ž‘์—…์€ ๊ฒฝํ—˜์ด ํ’๋ถ€ํ•œ ์ „๋ฌธ๊ฐ€์˜ ๊ฐ€์ด๋“œ๊ฐ€ ํฐ ๋„์›€์ด ๋ผ. ์žฌ๋Šฅ๋„ท์—์„œ ์ž์‹ ์—๊ฒŒ ๋งž๋Š” ์ „๋ฌธ๊ฐ€๋ฅผ ์ฐพ์•„๋ณด์ž! ๐ŸŽฏ

๐Ÿ’ก ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค ์ด์ •๋ฆฌ

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ Native Image ๋นŒ๋“œ์˜ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค๋ฅผ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ! ๐Ÿ“

1. ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ ๋‹จ๊ณ„

โœ… ์Šคํ”„๋ง ๋ถ€ํŠธ 3.0 ์ด์ƒ ์‚ฌ์šฉ
โœ… Spring Initializr์—์„œ GraalVM Native Support ์„ ํƒ
โœ… ํ•„์š”ํ•œ ์˜์กด์„ฑ๋งŒ ์ตœ์†Œํ•œ์œผ๋กœ ์ถ”๊ฐ€
โœ… ๋ฆฌํ”Œ๋ ‰์…˜ ์‚ฌ์šฉ์„ ์ตœ์†Œํ™”ํ•˜๋Š” ์„ค๊ณ„

2. ๊ฐœ๋ฐœ ๋‹จ๊ณ„

โœ… ์ •๊ธฐ์ ์œผ๋กœ Native Image ๋นŒ๋“œ ํ…Œ์ŠคํŠธ
โœ… Tracing Agent๋กœ ์„ค์ • ์ž๋™ ์ƒ์„ฑ
โœ… RuntimeHints๋กœ ๋ช…์‹œ์  ํžŒํŠธ ์ œ๊ณต
โœ… ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์™€ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

3. ๋นŒ๋“œ ์ตœ์ ํ™”

โœ… PGO ํ™œ์šฉ์œผ๋กœ ์„ฑ๋Šฅ ํ–ฅ์ƒ
โœ… ๋ถˆํ•„์š”ํ•œ ์ž๋™ ์„ค์ • ์ œ์™ธ
โœ… ๋นŒ๋“œ ์บ์‹œ ํ™œ์šฉ
โœ… ๋ฉ€ํ‹ฐ ์Šคํ…Œ์ด์ง€ Docker ๋นŒ๋“œ

4. ๋ฐฐํฌ ๋ฐ ์šด์˜

โœ… ์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ ํ™œ์šฉ
โœ… ์ ์ ˆํ•œ ๋ฉ”๋ชจ๋ฆฌ ์„ค์ •
โœ… ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๋กœ๊น… ๊ตฌ์„ฑ
โœ… ๋กค๋ฐฑ ๊ณ„ํš ์ˆ˜๋ฆฝ

5. ๋ฌธ์ œ ํ•ด๊ฒฐ

โœ… --verbose ์˜ต์…˜์œผ๋กœ ๋””๋ฒ„๊น…
โœ… ์ปค๋ฎค๋‹ˆํ‹ฐ ํ™œ์šฉ (Stack Overflow, GitHub Issues)
โœ… ๊ณต์‹ ๋ฌธ์„œ ์ฐธ์กฐ
โœ… ์ „๋ฌธ๊ฐ€ ๋„์›€ ๋ฐ›๊ธฐ (์žฌ๋Šฅ๋„ท ํ™œ์šฉ!)

Native Image ์„ฑ๊ณต ๋กœ๋“œ๋งต Step 1 ํ™˜๊ฒฝ ์„ค์ • GraalVM ์„ค์น˜ Native Image ์ค€๋น„ Step 2 ํ”„๋กœ์ ํŠธ ์„ค์ • ์˜์กด์„ฑ ์ถ”๊ฐ€ ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์ • Step 3 ์ฝ”๋“œ ์ž‘์„ฑ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ํžŒํŠธ ์„ค์ • Step 4 ๋นŒ๋“œ ์‹คํ–‰ Native ์ปดํŒŒ์ผ ํ…Œ์ŠคํŠธ ๊ฒ€์ฆ Step 5 ์ตœ์ ํ™” ์„ฑ๋Šฅ ํŠœ๋‹ PGO ์ ์šฉ Step 6 ๋ฐฐํฌ ํด๋ผ์šฐ๋“œ ๋ฐฐํฌ ๋ชจ๋‹ˆํ„ฐ๋ง ์„ค์ • ๐ŸŽ‰ ์„ฑ๊ณต! ์ดˆ๊ณ ์† ์•ฑ ์™„์„ฑ

๐Ÿš€ ๋ฏธ๋ž˜ ์ „๋ง๊ณผ ๋ฐœ์ „ ๋ฐฉํ–ฅ

GraalVM Native Image๋Š” ๊ณ„์† ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์–ด. ์•ž์œผ๋กœ ์–ด๋–ค ๋ณ€ํ™”๊ฐ€ ์žˆ์„์ง€ ์‚ดํŽด๋ณด์ž! ๐Ÿ”ฎ

1. ๋นŒ๋“œ ์‹œ๊ฐ„ ๋‹จ์ถ•

Oracle๊ณผ ์ปค๋ฎค๋‹ˆํ‹ฐ๋Š” ๋นŒ๋“œ ์‹œ๊ฐ„์„ ์ค„์ด๊ธฐ ์œ„ํ•ด ๋…ธ๋ ฅํ•˜๊ณ  ์žˆ์–ด. ์ ์ง„์  ์ปดํŒŒ์ผ, ๋” ๋‚˜์€ ์บ์‹ฑ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ๋“ฑ์ด ๊ฐœ๋ฐœ ์ค‘์ด์•ผ.

2. ๋” ๋‚˜์€ ๋””๋ฒ„๊น… ์ง€์›

ํ˜„์žฌ๋Š” Native Image ๋””๋ฒ„๊น…์ด ์ œํ•œ์ ์ด์ง€๋งŒ, ์•ž์œผ๋กœ๋Š” JVM ์ˆ˜์ค€์˜ ๋””๋ฒ„๊น… ๊ฒฝํ—˜์„ ์ œ๊ณตํ•  ๊ณ„ํš์ด๋ž˜.

3. ๋™์  ๊ธฐ๋Šฅ ์ง€์› ํ™•๋Œ€

๋ฆฌํ”Œ๋ ‰์…˜, ๋™์  ํ”„๋ก์‹œ ๋“ฑ์˜ ์ œ์•ฝ์ด ์ ์  ์ค„์–ด๋“ค๊ณ  ์žˆ์–ด. ์Šคํ”„๋ง ํ”„๋ ˆ์ž„์›Œํฌ๋„ ์ด์— ๋งž์ถฐ ๊ณ„์† ๊ฐœ์„ ๋˜๊ณ  ์žˆ์ง€.

4. ํด๋ผ์šฐ๋“œ ๋„ค์ดํ‹ฐ๋ธŒ ์ตœ์ ํ™”

Kubernetes, ์„œ๋ฒ„๋ฆฌ์Šค ํ™˜๊ฒฝ์— ๋”์šฑ ์ตœ์ ํ™”๋œ ๊ธฐ๋Šฅ๋“ค์ด ์ถ”๊ฐ€๋  ์˜ˆ์ •์ด์•ผ.

๐ŸŒŸ ์ฃผ๋ชฉํ•  ๋งŒํ•œ ๋ฐœ์ „

Project Leyden: OpenJDK์˜ ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ๋กœ, JVM๊ณผ Native Image์˜ ์žฅ์ ์„ ๊ฒฐํ•ฉํ•˜๋ ค๋Š” ์‹œ๋„์•ผ.

CRaC (Coordinated Restore at Checkpoint): JVM ์ƒํƒœ๋ฅผ ์Šค๋ƒ…์ƒท์œผ๋กœ ์ €์žฅํ•˜๊ณ  ๋ณต์›ํ•˜๋Š” ๊ธฐ์ˆ ๋กœ, Native Image์˜ ๋Œ€์•ˆ์ด ๋  ์ˆ˜ ์žˆ์–ด.

Spring Native ํ†ตํ•ฉ: ์Šคํ”„๋ง ํŒ€์ด Native Image ์ง€์›์„ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์œผ๋กœ ๋งŒ๋“ค๊ณ  ์žˆ์–ด.

๐Ÿ“š ์ถ”๊ฐ€ ํ•™์Šต ๋ฆฌ์†Œ์Šค

๋” ๊นŠ์ด ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ด๋Ÿฐ ๋ฆฌ์†Œ์Šค๋“ค์„ ํ™œ์šฉํ•ด๋ด! ๐Ÿ“–

๊ณต์‹ ๋ฌธ์„œ

โ€ข GraalVM ๊ณต์‹ ๋ฌธ์„œ: graalvm.org/docs
โ€ข ์Šคํ”„๋ง ๋ถ€ํŠธ Native Image ๊ฐ€์ด๋“œ: spring.io/guides
โ€ข Native Build Tools ๋ฌธ์„œ: graalvm.github.io/native-build-tools

์ปค๋ฎค๋‹ˆํ‹ฐ

โ€ข Spring Community Forum
โ€ข GraalVM Slack ์ฑ„๋„
โ€ข Stack Overflow #graalvm ํƒœ๊ทธ
โ€ข GitHub Discussions

๋ธ”๋กœ๊ทธ & ํŠœํ† ๋ฆฌ์–ผ

โ€ข Baeldung์˜ Spring Native ์‹œ๋ฆฌ์ฆˆ
โ€ข DZone์˜ GraalVM ์•„ํ‹ฐํด
โ€ข Medium์˜ ์‹ค์ „ ๊ฒฝํ—˜๋‹ด
โ€ข ์žฌ๋Šฅ๋„ท์˜ ์ง€์‹์ธ์˜ ์ˆฒ (๋‹ค์–‘ํ•œ ๊ฐœ๋ฐœ ํŒ!)

์˜์ƒ ์ž๋ฃŒ

โ€ข Spring I/O ์ปจํผ๋Ÿฐ์Šค ๋ฐœํ‘œ
โ€ข Devoxx ์„ธ์…˜
โ€ข YouTube ํŠœํ† ๋ฆฌ์–ผ
โ€ข ์˜จ๋ผ์ธ ๊ฐ•์˜ ํ”Œ๋žซํผ

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์ฝ์—ˆ๋‹ค๋ฉด ์ •๋ง ๋Œ€๋‹จํ•ด! ๐Ÿ‘ ์šฐ๋ฆฌ๋Š” GraalVM Native Image์˜ ๊ธฐ์ดˆ๋ถ€ํ„ฐ ๊ณ ๊ธ‰ ์ฃผ์ œ๊นŒ์ง€ ๋ชจ๋‘ ์‚ดํŽด๋ดค์–ด.

Native Image๋Š” ์ž๋ฐ” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํŒจ๋Ÿฌ๋‹ค์ž„์„ ๋ฐ”๊พธ๋Š” ํ˜์‹ ์ ์ธ ๊ธฐ์ˆ ์ด์•ผ. ๋น ๋ฅธ ์‹œ์ž‘ ์‹œ๊ฐ„, ์ ์€ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ, ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ ์ตœ์ ํ™” ๋“ฑ ๋งŽ์€ ์žฅ์ ์ด ์žˆ์ง€. ๋ฌผ๋ก  ๋นŒ๋“œ ์‹œ๊ฐ„์ด ๊ธธ๊ณ  ์ผ๋ถ€ ์ œ์•ฝ์‚ฌํ•ญ์ด ์žˆ์ง€๋งŒ, ์Šคํ”„๋ง ๋ถ€ํŠธ 3.0๊ณผ ํ•จ๊ป˜๋ผ๋ฉด ์ด๋Ÿฐ ๋ฌธ์ œ๋“ค๋„ ๋งŽ์ด ํ•ด๊ฒฐ๋์–ด. ๐Ÿ˜Š

์ฒ˜์Œ์—๋Š” ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์–ด. ํ•˜์ง€๋งŒ ์ฐจ๊ทผ์ฐจ๊ทผ ๋”ฐ๋ผํ•˜๋‹ค ๋ณด๋ฉด ์–ด๋А์ƒˆ Native Image ์ „๋ฌธ๊ฐ€๊ฐ€ ๋˜์–ด ์žˆ์„ ๊ฑฐ์•ผ! ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•˜๋ฉด์„œ ๊ฒฝํ—˜์„ ์Œ“๋Š” ๊ฒŒ ๊ฐ€์žฅ ์ค‘์š”ํ•ด.

๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์งˆ๋ฌธํ•˜๊ฑฐ๋‚˜, ์žฌ๋Šฅ๋„ท์—์„œ ์ „๋ฌธ๊ฐ€์˜ ๋„์›€์„ ๋ฐ›๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ํ˜ผ์ž ๊ณ ๋ฏผํ•˜์ง€ ๋ง๊ณ  ํ•จ๊ป˜ ์„ฑ์žฅํ•˜์ž! ๐Ÿš€

์ž, ์ด์ œ ๋‹น์‹ ์˜ ์Šคํ”„๋ง ๋ถ€ํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ Native Image๋กœ ๋นŒ๋“œํ•ด๋ณผ ์‹œ๊ฐ„์ด์•ผ. ์ดˆ๊ณ ์†์œผ๋กœ ์‹คํ–‰๋˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณด์ž! ํ™”์ดํŒ…! ๐Ÿ’ชโœจ

๐ŸŽŠ ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค! ๐ŸŽŠ

๋‹น์‹ ์€ ์ด์ œ GraalVM Native Image ๋นŒ๋“œ์˜ ๋ชจ๋“  ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!
์ด ์ง€์‹์„ ๋ฐ”ํƒ•์œผ๋กœ ๋ฉ‹์ง„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”!

๐Ÿš€ ๐Ÿ’ป โšก ๐ŸŽฏ โœจ

Happy Coding! ์ฆ๊ฑฐ์šด ๊ฐœ๋ฐœ ๋˜์„ธ์š”! ๐Ÿ˜Š

๋” ๋งŽ์€ ๊ฐœ๋ฐœ ์ง€์‹์€ ์žฌ๋Šฅ๋„ท์˜ ์ง€์‹์ธ์˜ ์ˆฒ์—์„œ ๋งŒ๋‚˜์š”!

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.