Spring Batch 입문 46편. Spring Batch 6 신규 — Java Flight Recorder (JFR) 통합. JFR JVM 옵션 (-XX:StartFlightRecording), 자동 생성되는 batch 이벤트 (job · step · item read/write · transaction boundary), JMC (Java Mission Control) 분석, async-profiler 와의 결합, production 안전 프로파일링까지 정리한 학습 노트. Micrometer (45편) 와 보완 관계.
이 글은 Spring Batch 입문에서 운영까지 시리즈 48편 중 46편이에요. 45편 의 Micrometer 가 비즈니스 메트릭 이라면, 46편의 Java Flight Recorder (JFR) 는 JVM 레벨 프로파일링. Part 11 (운영·완주) 시작.
JFR — JVM 의 블랙박스
JFR is a low-overhead, event-based profiling tool built into the JVM that allows developers to collect detailed information about the performance and behavior of their applications. — 공식 reference
JFR 은 JVM 에 직접 박혀 있어서 별도 agent 가 필요 없어요. overhead 가 약 1~2% 라 production 에서 켜둬도 안전하고, GC · thread · I/O 부터 custom event 까지 이벤트 단위로 기록합니다. JDK 11+ 부터는 무료, 그 이전엔 commercial license 가 필요했어요.
→ Spring Batch 6 = batch 활동을 JFR 이벤트로 자동 기록.
활성화 — JVM 옵션
java -XX:StartFlightRecording:filename=my-batch-job.jfr,dumponexit=true \
-jar my-batch-job.jar
-XX:StartFlightRecording = JVM 시작 시 JFR 자동 활성.
자주 쓰는 옵션:
| 옵션 | 의미 |
|---|---|
filename=path.jfr |
출력 파일 |
dumponexit=true |
JVM 종료 시 자동 dump |
duration=10m |
10분 후 자동 종료 |
maxsize=200M |
파일 크기 한도 |
maxage=30m |
최근 30분만 유지 |
settings=profile |
상세 profile (default = profile.jfc) |
JFC (JFR Configuration) = JFR 이 어떤 이벤트를 어느 정도로 수집할지 정의한 설정 파일.
Production 권장 옵션
-XX:StartFlightRecording:filename=batch.jfr,dumponexit=true,maxsize=500M,maxage=1h,settings=profile
최근 1시간 + 최대 500MB — production 부담 최소화.
자동 기록되는 Spring Batch 이벤트
Spring Batch will automatically create JFR events for key batch processing activities, such as job and step executions, item reads and writes, as well as transaction boundaries. — 공식 reference
| 이벤트 | 의미 |
|---|---|
jdk.batch.JobExecution |
Job 실행 시작·종료 |
jdk.batch.StepExecution |
Step 실행 시작·종료 |
jdk.batch.ItemRead |
각 item read |
jdk.batch.ItemWrite |
chunk write |
jdk.batch.Transaction |
chunk transaction boundary |
→ 어느 chunk·어느 item 이 느린지 JFR 만으로 추적 가능.
JMC (Java Mission Control) — 분석 GUI
JFR 파일 분석에 쓰는 표준 도구는 세 가지예요. Oracle 무료 GUI 인 Java Mission Control (JMC), 기본 viewer 인 VisualVM, 그리고 IntelliJ 의 JFR support 같은 IDE plugin.
JMC 사용 흐름
JFR 파일 (batch.jfr) 을 JMC 에서 열면, Threads tab 으로 각 thread 의 CPU/IO 시간을 보고, Method Profiling 으로 가장 시간 많이 쓴 메서드를 찾을 수 있어요. GC tab 은 Garbage Collection (메모리 회수 작업) 분포, Allocation 은 메모리 할당 hot spots, Event Browser 에서 jdk.batch.* 필터를 걸면 batch 이벤트만 추릴 수 있습니다.
Batch 이벤트 분석 예제
jdk.batch.StepExecution
step1 COMPLETED 3.2s
step2 COMPLETED 87s ← 병목!
step3 COMPLETED 4.5s
jdk.batch.ItemRead (step2 안)
count=10000, avg=0.5ms, max=120ms ← 일부 read 가 비정상
jdk.batch.Transaction (step2 안)
count=100, avg=850ms ← chunk commit 이 느림
→ step2 의 chunk transaction commit 이 진짜 병목.
Micrometer vs JFR 비교
| 항목 | Micrometer (45편) | JFR (46편) |
|---|---|---|
| 데이터 | aggregated (count·avg·p99) | raw event |
| 저장소 | Prometheus·Datadog 등 | 로컬 .jfr 파일 |
| 활성화 | ObservationRegistry bean | JVM 옵션 |
| GUI | Grafana | JMC |
| trace context | 분산 트레이싱 (Brave·OTel) | thread 단위 |
| overhead | metric 등록 비용 | 1~2% (JVM 내장) |
| 사용 시점 | always-on production | 문제 발생 시 ad-hoc 또는 always-on |
| JVM 레벨 정보 | X | ✓ (GC · thread · allocation) |
결론:
- 상시 모니터링 = Micrometer
- 상세 troubleshooting = JFR + JMC
- 둘 다 켜기 = 권장 (overhead 합쳐도 5% 이하)
사용 시나리오 — Batch 성능 troubleshooting
시나리오: Step 이 갑자기 느려짐
먼저 Micrometer 알람에서 spring_batch_step p99 가 평소 1분이던 게 10분으로 튀어요. 다음 실행에 JFR 옵션을 추가하고 재현해서 jfr 파일을 수집합니다. JMC 분석에서 jdk.batch.StepExecution event 의 시작·종료 시각 과 그 사이 GC pause · thread blocked 를 검사하면, 예컨대 GC 가 잦아서 chunk commit 이 pause 되는 식의 원인이 잡혀요.
→ Micrometer 가 alert 신호, JFR 이 원인 분석.
Custom JFR 이벤트 추가
Spring Batch 외 비즈니스 이벤트 도 JFR 에 박을 수 있음:
import jdk.jfr.Event;
import jdk.jfr.Label;
import jdk.jfr.Category;
@Category("Batch")
@Label("Custom Item Process")
public class ItemProcessEvent extends Event {
@Label("Item ID")
private long itemId;
@Label("Processing Type")
private String processType;
public ItemProcessEvent(long itemId, String processType) {
this.itemId = itemId;
this.processType = processType;
}
}
// 사용
public class MyProcessor implements ItemProcessor<Customer, EnrichedCustomer> {
@Override
public EnrichedCustomer process(Customer customer) {
ItemProcessEvent event = new ItemProcessEvent(customer.getId(), "enrich");
event.begin();
try {
return enrich(customer);
} finally {
event.commit();
}
}
}
→ JMC 에서 custom event 별 분석. 도메인 특화 추적.
async-profiler 결합
JFR + async-profiler 결합:
# async-profiler 로 JFR 형식 출력
./profiler.sh -d 60 -o jfr -f profile.jfr <pid>
async-profiler 는 stack sampling 이 JFR 보다 정확하고, off-CPU profiling (waiting/blocked thread), lock contention, allocation profiling 까지 잡아줘요.
→ JFR 의 Spring Batch 이벤트 + async-profiler 의 상세 stack = 강력 조합.
JMC 가 두 source 결합한 jfr 파일 열림.
Production 안전 가이드
1. 항상 dumponexit=true
-XX:StartFlightRecording:filename=app.jfr,dumponexit=true
JVM crash · OOM (Out-Of-Memory, 메모리 부족 사망) 시에도 직전까지 데이터 가 보존돼요.
2. maxsize · maxage 한도
-XX:StartFlightRecording:filename=app.jfr,maxsize=200M,maxage=30m
긴 운영에서 최근 N 분만 유지 — 디스크 부담 ↓.
3. settings 조정
default.jfc 는 기본값이고 overhead 가 1% 이하라 가볍고, profile.jfc 는 상세 profiling 용이라 1~2% 정도 부담이 있어요. 운영은 default, 분석 시에는 profile 로 갑니다.
4. JFR 의 보안
JFR 파일에 실행 메서드 이름·class 이름 포함 — 코드 정보 누출.
→ 외부 공유 시 sanitize. 권한 관리.
5. JVM 호환성
JDK 11+ 는 무료고, JDK 8 은 commercial license 가 필요해요 (구버전). OpenJDK · Oracle JDK · Eclipse Temurin 모두 지원합니다.
자주 쓰는 분석 패턴
패턴 1: GC overhead 검증
JMC Threads tab → GC Pause Events
G1Old·G1Mixed·G1Young 분포 검사. GC pause 가 chunk transaction 길이 와 일치하면 GC tuning 필요.
패턴 2: I/O 병목 검증
JMC Event Browser → jdk.FileRead · jdk.SocketRead
batch 가 I/O wait 비중 검사. 외부 API · DB · 파일 중 어느 것이 느린지.
패턴 3: Thread 분석
JMC Threads → thread별 CPU · 대기 시간
worker thread 가 대기 시간이 길다 = 외부 시스템 응답 느림. CPU 시간이 길다 = process 로직 무거움.
패턴 4: Allocation hot spots
JMC Memory → Allocation
가장 객체 할당이 많은 메서드. unnecessary allocation → 메모리·GC 부담 → 최적화.
패턴 5: Lock contention
async-profiler -e lock
multi-threaded Step 의 lock 경쟁 검출.
자주 만나는 사고
사고 1: JFR 파일 생성 안 됨
JVM 옵션 syntax (콜론 위치) 가 잘못됐거나, JDK 8 commercial license 에 동의하지 않았거나, 파일 쓰기 권한이 없을 때 발생해요. syntax · JDK 버전 · 권한을 차례로 검증합니다.
사고 2: Batch 이벤트 안 보임
Spring Batch 6 미만이면 JFR 통합 자체가 없어요. Spring Batch 6.0+ 로 올립니다.
사고 3: JFR 파일이 너무 큼
maxsize 를 안 잡고 길게 운영하면 그렇게 돼요. maxsize=200M,maxage=1h 같은 한도를 걸어주면 됩니다.
사고 4: JMC 가 jfr 파일 못 열음
JMC 버전이 JFR 버전보다 낮을 때예요 (예: JDK 21 JFR 을 JMC 8 로 여는 경우). JMC 를 최신 버전으로 올립니다.
사고 5: Production 부담 우려
JFR 활성 시 부하가 의심된다는 이유. default.jfc 는 overhead 1% 이하라는 측정 데이터가 많으니 실측해보고 결정하면 됩니다.
사고 6: Custom event 가 너무 잦음
각 item 마다 event 를 commit 하면 event 가 폭증해요. sampling 을 쓰거나 aggregated event 로 묶습니다.
사고 7: Async/Remote 환경 event 분산
JFR 은 JVM 단위 라 분산 batch 의 worker JVM 들 jfr 파일이 흩어져요. 각 worker jfr 을 수집해서 JMC 에서 비교 분석 하거나 통합 dashboard 로 묶습니다.
운영 권장 패턴
Pattern 1: 항상 JFR 활성 (production)
java -XX:StartFlightRecording:filename=/var/log/batch/app.jfr,dumponexit=true,maxsize=500M,maxage=2h,settings=default \
-jar my-batch.jar
상시 활성 — overhead 1% 이하. 문제 발생 시 즉시 분석.
Pattern 2: Ad-hoc 분석 (개발/troubleshooting)
java -XX:StartFlightRecording:filename=detail.jfr,dumponexit=true,settings=profile,duration=10m \
-jar my-batch.jar
상세 profile 10분 — 재현 가능한 문제 분석.
Pattern 3: 실시간 JFR 시작
jcmd <pid> JFR.start filename=runtime.jfr settings=profile duration=5m
jcmd = 실행 중 JVM 에 runtime command. JFR 동적 시작/중지.
jcmd <pid> JFR.dump filename=now.jfr # 현재까지 dump
jcmd <pid> JFR.stop # 종료
Pattern 4: Custom domain event
@Label("Order Validation")
@Category("Batch")
public class OrderValidationEvent extends Event {
@Label("Order ID") long orderId;
@Label("Result") String result;
public static OrderValidationEvent start(long orderId) {
OrderValidationEvent event = new OrderValidationEvent();
event.orderId = orderId;
event.begin();
return event;
}
}
// Processor
@Override
public ValidatedOrder process(Order order) {
OrderValidationEvent event = OrderValidationEvent.start(order.getId());
try {
ValidatedOrder result = validator.validate(order);
event.result = "VALID";
return result;
} catch (ValidationException e) {
event.result = "INVALID";
throw e;
} finally {
event.commit();
}
}
비즈니스 이벤트 추적. JMC 에서 validation result 별 분포 분석.
Pattern 5: CI/CD 의 성능 회귀 감지
CI/CD (Continuous Integration / Continuous Delivery) = 빌드·테스트·배포 자동화 파이프라인.
# 빌드 파이프라인 안
java -XX:StartFlightRecording:filename=ci.jfr,dumponexit=true -jar app.jar
jfr summary ci.jfr > current.txt
diff baseline.txt current.txt
jfr summary 명령 = jfr 파일 요약. baseline 과 비교 = 성능 회귀 감지.
Micrometer + JFR 통합 운영
[Production]
├─ Micrometer + Prometheus → Grafana 대시보드 (상시)
│ 알람: spring.batch.job p99 > 1h → Slack
│
└─ JFR (default settings) → /var/log/batch/*.jfr (상시)
알람 발생 시: 가장 최근 jfr 파일 분석 → JMC
원인 발견 → 수정 → 회귀 테스트
두 도구 상호 보완. Micrometer = 무엇이 문제인가, JFR = 왜 문제인가.
시험 직전 한 번 더 — JFR 함정 압축 노트
- Spring Batch 6 신규 — JFR 통합
- JFR = JVM 내장 low-overhead profiling
- overhead 1~2% (production 안전)
- JDK 11+ 무료, JDK 8 = commercial license
- 활성화 =
-XX:StartFlightRecording:filename=app.jfr,dumponexit=true - 옵션 — filename · dumponexit · duration · maxsize · maxage · settings
- 자동 batch 이벤트 =
jdk.batch.JobExecution·StepExecution·ItemRead·ItemWrite·Transaction - 분석 도구 — JMC (Java Mission Control) · VisualVM · IDE plugin
- JMC tabs — Threads · Method Profiling · GC · Allocation · Event Browser
- Micrometer vs JFR:
- Micrometer = aggregated 메트릭 (상시 모니터링)
- JFR = raw event + JVM 레벨 (상세 troubleshooting)
- 둘 다 권장 (overhead 합쳐도 5% 이하)
- Custom JFR Event =
extends Event+@Category+@Label+begin/commit - async-profiler 결합 = JFR 형식 출력 + stack sampling + off-CPU 추가
- Production 가이드:
- 항상
dumponexit=true maxsize·maxage한도default.jfc(운영) vsprofile.jfc(분석)- JFR 파일 보안 (코드 정보 누출)
- 분석 패턴:
- GC overhead (GC pause 분포)
- I/O 병목 (
jdk.FileRead·SocketRead) - Thread 분석 (CPU vs 대기)
- Allocation hot spots
- Lock contention (async-profiler)
jcmd <pid> JFR.start/dump/stop= 실행 중 동적 제어- 함정 — JVM 옵션 syntax (콜론 위치)
- 함정 — Spring Batch 6 미만 (이벤트 없음)
- 함정 — JFR 파일 크기 (maxsize 미설정)
- 함정 — JMC 버전 mismatch
- 함정 — production 부담 우려 (실측 후 결정, default.jfc 1% 이하)
- 함정 — Custom event 폭증 (sampling 또는 aggregated)
- 함정 — Async/Remote 분산 (각 worker jfr 통합 분석)
- 패턴 — 항상 JFR 활성 (default.jfc · maxage=2h)
- 패턴 — Ad-hoc 상세 분석 (profile.jfc · duration=10m)
- 패턴 —
jcmd실시간 시작 - 패턴 — Custom domain event (Order validation)
- 패턴 — CI/CD 회귀 감지 (
jfr summarydiff) - 운영 통합 — Micrometer (무엇이) + JFR (왜)
공식 문서: Java Flight Recorder support 에서 원문을 확인할 수 있어요.
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 41편 — Common Patterns · 흔한 운영 패턴 카탈로그
- 42편 — Spring Batch Integration · 두 프레임워크 경계
- 43편 — Launching via Messages · JobLaunchingGateway · Informational
- 44편 — Async Processors · Remote Chunking · Partitioning
- 45편 — Observability · Micrometer · Tracing
다음 글: