Alert
์ด ๊ธ์ Claude Code์ ๋์์ ๋ฐ์ ์์ฑ๋์์ต๋๋ค
TL;DR
- JAR์ Java์ ํจํค์ง ํฌ๋งท์ด๊ณ , DE ๋๊ตฌ(Flink, Spark, Kafka ๋ฑ)๊ฐ JVM ๊ธฐ๋ฐ์ด๋ผ ์์ฃผ ๋ง์ฃผ์น๋ค
- Fat JAR์ ์์กด์ฑ์ ๋ชฝ๋ ๋ด์ ๋จ์ผ ์คํ ํ์ผ, Thin JAR์ ์๊ธฐ ์ฝ๋๋ง ๋ด์ ๊ฐ๋ฒผ์ด ํ์ผ
- Shading์ Fat JAR์ ๋ง๋ค ๋ ํจํค์ง ์ถฉ๋์ ํผํ๊ธฐ ์ํด ๊ฒฝ๋ก๋ฅผ ์ฌ๋ฐฐ์นํ๋ ๊ธฐ์
1. JAR์ด ๋ญ๋ฐ, ์ DE์์ ์๊พธ ๋์?
JAR = Java Archive
JAR์ Java ์ฝ๋๋ฅผ ์ปดํ์ผํ .class ํ์ผ๊ณผ ์ค์ ํ์ผ๋ค์ ํ๋๋ก ๋ฌถ์ ์์ถ ํ์ผ์ด๋ค. ๋ณธ์ง์ ์ผ๋ก zip ํ์ผ๊ณผ ๊ฐ๋ค.
# JAR ๋ด๋ถ ๊ตฌ์กฐ ํ์ธ (์ค์ ๋ก zip์ด๋ค)
unzip -l some-library.jar
jar tf some-library.jarPython์์ .whl์ด ํจํค์ง ๋ฐฐํฌ ๋จ์์ธ ๊ฒ์ฒ๋ผ, Java ์ํ๊ณ์์๋ .jar์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ ํ๋ฆฌ์ผ์ด์
์ ๋ฐฐํฌ ๋จ์๋ค.
์ DE ๋๊ตฌ๋ค์ด ์ ๋ถ Java(JVM) ๊ธฐ๋ฐ์ธ๊ฐ?
๋๋ถ๋ถ์ ๋ถ์ฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋๊ตฌ๋ 2000๋ ๋ ํ๋ฐ~2010๋ ๋ ์ด๋ฐ์ ํ์ํ๋ค. ์ด ์๊ธฐ์ ๋๊ท๋ชจ ๋ถ์ฐ ์์คํ ์ ๋ง๋ค๊ธฐ ์ํ ํ์ค์ ์ธ ์ ํ์ง๊ฐ Java/JVM์ด์๋ค.
- Hadoop(2006) ์ด ์์์ ์ด๋ค. Google์ MapReduce ๋ ผ๋ฌธ์ Java๋ก ๊ตฌํํ ๊ฒ์ด Hadoop์ด๊ณ , ์ดํ ๋๋ถ๋ถ์ ๋น ๋ฐ์ดํฐ ๋๊ตฌ๊ฐ Hadoop ์ํ๊ณ ์์ ๋ง๋ค์ด์ก๋ค
- JVM์ ๊ฐ์ โ ํฌ๋ก์ค ํ๋ซํผ(โWrite Once, Run Anywhereโ), ๊ฒ์ฆ๋ GC์ ๋ฉํฐ์ค๋ ๋ฉ, ํ๋ถํ ๋คํธ์ํฌ/์ง๋ ฌํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ถ์ฐ ์์คํ ๊ฐ๋ฐ์ ์ ํฉํ๋ค
- ์ํ๊ณ ์ฐ์ ํจ๊ณผ โ Hadoop์ด Java๋๊น ๊ทธ ์์ ๋ง๋๋ Hive, HBase๋ Java, ๊ทธ ๋ค์ ์ธ๋์ธ Spark(Scala/JVM), Kafka(Java), Flink(Java)๋ ์์ฐ์ค๋ฝ๊ฒ JVM์ ์ ํํ๋ค
| ๋๊ตฌ | ์ธ์ด | ์์ ์ฐ๋ | ๋น๊ณ |
|---|---|---|---|
| Hadoop | Java | 2006 | Google MapReduce์ ์คํ์์ค ๊ตฌํ |
| Hive | Java | 2010 | Hadoop ์์ SQL ์์ง |
| Kafka | Java/Scala | 2011 | LinkedIn์์ ๊ฐ๋ฐ |
| Spark | Scala(JVM) | 2014 | Hadoop MapReduce์ ๋์ |
| Flink | Java | 2014 | ์คํธ๋ฆผ ์ฒ๋ฆฌ ์ค์ฌ |
| Airflow | Python | 2014 | ์ํฌํ๋ก ์ค์ผ์คํธ๋ ์ดํฐ (JVM ์๋) |
Python์ผ๋ก ์ฐ๋๋ฐ ์ JAR์ ์์์ผ ํ ๊น?
PySpark, PyFlink ๊ฐ์ Python API๋ ๋ด๋ถ์ ์ผ๋ก JVM ํ๋ก์ธ์ค๋ฅผ ๋์์ ๋์ํ๋ค. Python์ ๋๋ผ์ด๋ฒ ์ฝ๋๋ฅผ ์์ฑํ๋ ์ธํฐํ์ด์ค์ผ ๋ฟ, ์ค์ ๋ถ์ฐ ์ฒ๋ฆฌ๋ JVM์ด ํ๋ค. ๊ทธ๋์ ์ปค๋ฅํฐ ์ค์น, ์์กด์ฑ ์ถฉ๋, ํด๋์คํจ์ค ๋ฌธ์ ๋ฑ JVM ์ชฝ ์ด์๋ฅผ ํผํ ์ ์๋ค.
DE์์ JAR์ ์ง์ ๋ค๋ฃจ๋ ์ํฉ๋ค
- ์ปค๋ฅํฐ ์ค์น โ Flink SQL์์ Kafka ์์ค๋ฅผ ์ฐ๋ ค๋ฉด
flink-sql-connector-kafka-*.jar์lib/์ ๋ฃ์ด์ผ ํ๋ค - Spark job ์ ์ถ โ
spark-submit app.jar๋ก ์ ํ๋ฆฌ์ผ์ด์ JAR์ ํด๋ฌ์คํฐ์ ์ ๋ฌํ๋ค - JDBC ๋๋ผ์ด๋ฒ โ Airflow๋ Spark์์ DB์ ์ฐ๊ฒฐํ ๋ ๋๋ผ์ด๋ฒ JAR์ ํด๋์คํจ์ค์ ์ถ๊ฐํ๋ค
- ์์กด์ฑ ์ถฉ๋ ํด๊ฒฐ โ ์๋ก ๋ค๋ฅธ ๋ฒ์ ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ถฉ๋ํ ๋ Fat/Thin JAR๊ณผ Shading ๊ฐ๋ ์ด ํ์ํ๋ค
2. ์ฉ์ด ์ ์
Fat JAR (Uber JAR)
- ๋ชจ๋ ์์ฒด ์ฝ๋ + ๋ชจ๋ ์์กด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ๋์ JAR๋ก ํฉ์น ํ์ผ
- โFatโ์ด๋ผ๋ ์ด๋ฆ์ ์์กด ์ฝ๋๊น์ง ์ ๋ถ ํฌํจํด์ ํ์ผ ๋ฉ์น๊ฐ ํฌ๋ค๋ ๋น์ ์์ ์๋ค
- Uber๋ ๋ ์ผ์ด๋ก โ~์์, ์ด์ํโ์ด๋ผ๋ ๋ป์ผ๋ก, ์ผ๋ฐ JAR์ ๋์ด์ ๋ค๋ ์๋ฏธ
Thin JAR (Slim JAR)
- ๋ชจ๋ ์์ฒด ์ฝ๋๋ง ๋ด๊ณ , ์์กด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋ฐํ์์ ๋ณ๋ ์ ๊ณต๋๋ค๊ณ ๊ฐ์ ํ๋ JAR
MANIFEST.MF์Class-Pathํญ๋ชฉ์ด๋ ์ธ๋ถ lib ๋๋ ํ ๋ฆฌ๋ฅผ ํตํด ์์กด์ฑ์ ์ฐธ์กฐํ๋ค
Shading (Relocating)
- Fat JAR์ ๋ง๋ค๋ฉด์ ๋ด๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํจํค์ง ๊ฒฝ๋ก๋ฅผ ์ฌ๋ฐฐ์น(relocate) ํ๋ ๊ธฐ์
- ์:
com.google.guavaโmy.shaded.com.google.guava - ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์๋ก ๋ค๋ฅธ ๋ฒ์ ์ด ํด๋์คํจ์ค์ ๊ณต์กดํ ๋ ์๊ธฐ๋ ์ถฉ๋์ ๋ฐฉ์งํ๋ค
3. Fat JAR vs Thin JAR ๋น๊ต
| ๊ตฌ๋ถ | Fat JAR | Thin JAR |
|---|---|---|
| ํฌํจ ๋ด์ฉ | ๋ชจ๋ ์ฝ๋ + ๋ชจ๋ ์์กด JAR | ๋ชจ๋ ์ฝ๋๋ง |
| ํ์ผ ํฌ๊ธฐ | ํผ (์์ญ~์๋ฐฑ MB) | ์์ (์ KB~์ MB) |
| ๋ฐฐํฌ ํธ์์ฑ | ํ์ผ ํ๋๋ง ์ ๋ฌํ๋ฉด ๋ | ์์กด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํจ๊ป ๋ฐฐํฌํด์ผ ํจ |
| ์์กด์ฑ ๊ด๋ฆฌ | JAR ๋ด๋ถ์ ๊ณ ์ ๋จ | ์ธ๋ถ์์ ๋ฒ์ ์ ์ง์ ์ ์ด ๊ฐ๋ฅ |
| ์ถฉ๋ ์ํ | ์ฌ๋ฌ Fat JAR์ด ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค๋ณต ํฌํจํ ์ ์์ | ํ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ณต์ ํ๋ฏ๋ก ์ค๋ณต ์์ |
| ์คํจ ๋ชจ๋ | ๋ฒ์ ์ถฉ๋ ์ NoSuchMethodError | ์์กด ๋๋ฝ ์ ClassNotFoundException |
์ธ์ ๋ญ ์ธ๊น?
- Fat JAR โ CLI ๋๊ตฌ, Lambda ๋ฐฐํฌ, Flink/Spark job ์ ์ถ์ฒ๋ผ ๋จ์ผ ํ์ผ ์คํ์ด ํ์ํ ๋
- Thin JAR โ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐฐํฌ, ์์กด ๋ฒ์ ์ ์๋น์๊ฐ ์ง์ ๊ฒฐ์ ํด์ผ ํ ๋
4. Shading์ด ํ์ํ ์ด์
JVM์ ๊ฐ์ FQCN(Fully Qualified Class Name)์ ๊ฐ์ง ํด๋์ค๊ฐ ํด๋์คํจ์ค์ ๋ ๊ฐ ์ด์ ์์ผ๋ฉด ๋จผ์ ๋ฐ๊ฒฌ๋ ๊ฒ๋ง ๋ก๋ํ๋ค. ์ด๊ฒ์ด JAR Hell์ ํต์ฌ์ด๋ค.
์ ํ์ ์ธ ์ถฉ๋ ์๋๋ฆฌ์ค
๋ด ํ๋ก์ ํธ โ guava 31
โโโ ๋ผ์ด๋ธ๋ฌ๋ฆฌ A โ guava 27 (Fat JAR์ ํฌํจ)
๋ผ์ด๋ธ๋ฌ๋ฆฌ A์ Fat JAR์ guava 27์ด ๋ค์ด ์๋๋ฐ, ๋ด ํ๋ก์ ํธ๋ guava 31์ ์ด๋ค. ํด๋์คํจ์ค์ guava๊ฐ ๋ ๋ฒ ์ฌ๋ผ๊ฐ๋ฉด์ NoSuchMethodError๊ฐ ํฐ์ง๋ค.
Shading์ผ๋ก ํด๊ฒฐ
๋ผ์ด๋ธ๋ฌ๋ฆฌ A๋ฅผ ๋น๋ํ ๋ guava๋ฅผ shading ์ฒ๋ฆฌํ๋ฉด ๋ด๋ถ์ ์ผ๋ก com.google.common โ a.shaded.com.google.common์ผ๋ก ๋ฐ๋๋ค. ๋ guava๊ฐ ์๋ก ๋ค๋ฅธ ํจํค์ง ๊ฒฝ๋ก๋ฅผ ๊ฐ๊ฒ ๋๋ฏ๋ก ์ถฉ๋์ด ์ฌ๋ผ์ง๋ค.
5. ๋น๋ ๋๊ตฌ๋ณ Fat JAR ๋ง๋ค๊ธฐ
Maven โ maven-shade-plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<relocations>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>my.shaded.com.google.common</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>mvn package
# target/my-app-1.0-SNAPSHOT.jar โ shading ์ ์ฉ๋ Fat JARMaven โ maven-assembly-plugin
shading ์์ด ๋จ์ํ ์์กด์ฑ์ ํฉ์น๊ธฐ๋ง ํ ๋ ์ฌ์ฉํ๋ค.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.7.1</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.example.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>Gradle โ Shadow Plugin
plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1'
}
shadowJar {
relocate 'com.google.common', 'my.shaded.com.google.common'
}./gradlew shadowJar
# build/libs/my-app-1.0-all.jarshade vs shadow
Maven์์๋ shade, Gradle์์๋ shadow๋ผ๋ ์ด๋ฆ์ ์ฐ์ง๋ง ํ๋ ์ผ์ ๋์ผํ๋ค. ์์กด์ฑ์ Fat JAR๋ก ํฉ์น๋ฉด์ ์ ํ์ ์ผ๋ก ํจํค์ง ๊ฒฝ๋ก๋ฅผ relocate ํ๋ค.
6. Spring Boot์ Fat JAR
Spring Boot๋ Java์ ์น ์ ํ๋ฆฌ์ผ์ด์ ํ๋ ์์ํฌ๋ค. Python์ Django/FastAPI, Node.js์ Express ๊ฐ์ ํฌ์ง์ ์ผ๋ก, Java ๋ฐฑ์๋ ๊ฐ๋ฐ์์ ์ฌ์ค์ ํ์ค์ด๋ค.
Spring Boot๋ ์์ฒด์ ์ธ Fat JAR ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ๋ค. ์ผ๋ฐ์ ์ธ Uber JAR๊ณผ๋ ๋ค๋ฅธ ๋ฐฉ์์ด๋ค.
๊ตฌ์กฐ ์ฐจ์ด
# ์ผ๋ฐ Uber JAR โ ๋ชจ๋ .class๋ฅผ ํ์ด์ ํฉ์นจ
my-app.jar
โโโ com/example/Main.class
โโโ com/google/common/...
โโโ org/apache/...
# Spring Boot Fat JAR โ ์์กด JAR์ ๊ทธ๋๋ก ๋ด์ฅ
my-app.jar
โโโ BOOT-INF/
โ โโโ classes/ โ ๋ด ์ฝ๋
โ โโโ lib/ โ ์์กด JAR ํ์ผ๋ค (์๋ณธ ๊ทธ๋๋ก)
โโโ META-INF/
โโโ org/springframework/boot/loader/ โ ์ปค์คํ
ํด๋์ค๋ก๋
- Spring Boot๋ ์์กด JAR์ ํ์ง ์๊ณ ์๋ณธ JAR ๊ทธ๋๋ก ๋ด์ฅํ๋ค
spring-boot-loader๋ผ๋ ์ปค์คํ ํด๋์ค๋ก๋๊ฐBOOT-INF/lib/์์ JAR์ ์ฝ๋๋ค- ์ด ๋ฐฉ์ ๋๋ถ์ shading ์์ด๋ ํด๋์ค ์ถฉ๋ ๋ฌธ์ ๊ฐ ์ค์ด๋ ๋ค
# Spring Boot Fat JAR ์คํ
java -jar my-app.jar
# ๋ด๋ถ ๊ตฌ์กฐ ํ์ธ
jar tf my-app.jar | head -207. ์ค๋ฌด์์ ์ฃผ์ํ ์
์๋น์ค ํ์ผ ๋ณํฉ (SPI)
Java์ ServiceLoader๋ META-INF/services/ ์๋ ํ์ผ์ ์ฝ์ด ๊ตฌํ์ฒด๋ฅผ ์ฐพ๋๋ค. Fat JAR๋ก ํฉ์น ๋ ์ฌ๋ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๊ฐ์ ์๋น์ค ํ์ผ์ ๊ฐ์ง๊ณ ์์ผ๋ฉด ํ๋๋ง ๋จ๊ณ ๋๋จธ์ง๊ฐ ์ฌ๋ผ์ง๋ค.
<!-- maven-shade-plugin: ์๋น์ค ํ์ผ ๋ณํฉ ์ค์ -->
<transformers>
<transformer implementation=
"org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>// Gradle Shadow: ์๋น์ค ํ์ผ ๋ณํฉ
shadowJar {
mergeServiceFiles()
}์๋ช ๋ JAR ์ฒ๋ฆฌ
์ผ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(BouncyCastle ๋ฑ)๋ JAR์ ์๋ช
์ด ๋์ด ์๋ค. Fat JAR๋ก ํฉ์น๋ฉด ์๋ช
์ด ๊นจ์ง๋ฉด์ SecurityException์ด ๋ฐ์ํ๋ค.
<!-- ์๋ช
ํ์ผ ์ ์ธ -->
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>Fat JAR ์ค๋ณต ๋ฐฐํฌ ๊ธ์ง
๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํฌํจํ๋ Fat JAR๊ณผ Thin JAR์ ๋์์ ํด๋์คํจ์ค์ ์ฌ๋ฆฌ๋ฉด ํด๋์ค๊ฐ ๋ ๋ฒ ๋ก๋๋๋ฉด์ ์์ธก ๋ถ๊ฐ๋ฅํ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
8. Flink ํ๊ฒฝ ์ค๋ฌด ํ
Flink SQL Connector๋ ์ค์น ํธ์๋ฅผ ์ํด Fat JAR ํํ๋ก ๋ฐฐํฌ๋๋ค.
$FLINK_HOME/lib์๋ Fat JAR๋ง ๋๊ณ , Thin JAR์ ์ ๊ฑฐํ๋ค- DataStream API์์ Kafka client ๋ฒ์ ์ ์ธ๋ฐํ๊ฒ ์ ์ดํ๋ ค๋ฉด Thin JAR + ๋ช ์์ ์์กด ์ ์ธ์ ์ฌ์ฉํ๋ค
- Fat JAR ๋ด๋ถ์ ํน์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ํฌํจ๋๋์ง ํ์ธํ๋ ๋ฐฉ๋ฒ:
jar tf flink-sql-connector-kafka-3.2.0-1.18.jar | grep org/apache/kafka | head- Fat JAR๊ณผ Thin JAR์ ๋์์
lib/์ ๋ฃ์ผ๋ฉด ์ค๋ณต ํด๋์ค ๋ก๋ฉ์ผ๋กNoSuchMethodError,ClassNotFoundException์ํ์ด ์๋ค
9. ํ๋์ ์ ๋ฆฌ
| ๊ฐ๋ | ํต์ฌ |
|---|---|
| Fat JAR | ์์กด์ฑ ํฌํจ, ๋จ์ผ ํ์ผ ๋ฐฐํฌ, ํฌ๊ธฐ ํผ |
| Thin JAR | ์๊ธฐ ์ฝ๋๋ง, ๊ฐ๋ฒผ์, ์ธ๋ถ ์์กด์ฑ ํ์ |
| Shading | Fat JAR ๋ด๋ถ ํจํค์ง ๊ฒฝ๋ก ์ฌ๋ฐฐ์น๋ก ์ถฉ๋ ๋ฐฉ์ง |
| Spring Boot JAR | ์์กด JAR์ ํ์ง ์๊ณ ์๋ณธ ๊ทธ๋๋ก ๋ด์ฅ, ์ปค์คํ ๋ก๋ ์ฌ์ฉ |
| SPI ๋ณํฉ | Fat JAR ๋น๋ ์ META-INF/services/ ํ์ผ ๋ณํฉ ํ์ |