Spring Batch 실습 2 : 모니터링 설정

Prometheus, Grafana, Pushgateway 실습
정찬's avatar
Jun 11, 2025
Spring Batch 실습 2 : 모니터링 설정
 
 

개요

 
이번 시간에는 모니터링 설정을 해보겠다.
실습 환경은 다음과 같다.
  • 메트릭 수집: Prometheus, PushGateway
  • 시각화: Grafana
 
notion image
 

Dependency

 
모니터링을 위한 종속성을 추가해준다
 
implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-registry-prometheus:1.11.1' implementation 'io.prometheus:simpleclient_pushgateway'
 
💡주의할점
micrometer-registry-prometheus 1.13 버전부터 PrometheusMeterRegistry의 필드에 CollectorRegistry 가 존재하지 않는다. 따라서 CollectorRegistry 빈을 등록하거나, 1.12 이하 버전을 사용하도록 하자. 자세한 내용은 아래를 참고하면 된다.
 
실습 환경은 micrometer-registry-prometheus 1.11.1 버전을 사용하였다.
 

application.yml

 
yml파일에 다음과 같은 설정을 추가해준다.
prometheus: push: rate: 5000 # prometheus에 push 주기 (5초) job: name: batch_server_scrape # prometheus에서 인식할 pushgateway job 이름 grouping: key: appname pushgateway: url: localhost:9091 # pushgateway 주소

PrometheusConfiguration

 
프로메테우스와 푸쉬게이트웨이 설정 파일이다.
 
import io.micrometer.core.instrument.Metrics; import io.micrometer.prometheus.PrometheusConfig; import io.micrometer.prometheus.PrometheusMeterRegistry; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.exporter.PushGateway; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.Scheduled; import java.io.IOException; import java.util.HashMap; import java.util.Map; @Slf4j @Configuration public class PrometheusConfiguration { @Value("${prometheus.job.name}") private String prometheusJobName; @Value("${prometheus.grouping.key}") private String prometheusGroupingKey; @Value("${prometheus.pushgateway.url}") private String prometheusPushGatewayUrl; private final Map<String, String> groupingKey = new HashMap<>(); private PrometheusMeterRegistry prometheusMeterRegistry; private PushGateway pushGateway; @Bean public PrometheusMeterRegistry prometheusMeterRegistry() { this.prometheusMeterRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); Metrics.globalRegistry.add(prometheusMeterRegistry); return this.prometheusMeterRegistry; } @PostConstruct public void init() { pushGateway = new PushGateway(prometheusPushGatewayUrl); groupingKey.put("instance", prometheusGroupingKey); log.info("Prometheus PushGateway 설정 완료. URL: {}, Job: {}, Key: {}", prometheusPushGatewayUrl, prometheusJobName, prometheusGroupingKey); } @Scheduled(fixedRateString = "${prometheus.push.rate}") public void pushMetrics() { try { pushGateway.pushAdd( prometheusMeterRegistry.getPrometheusRegistry(), prometheusJobName, groupingKey ); log.debug("Prometheus 메트릭 Push 완료"); } catch (IOException e) { log.error("Metric Push 중 에러 발생: {}", e.getMessage(), e); } } }
 
Micrometer기반의 프로메테우스 메트릭을 PushGateway로 전송하는 설정이다.
JVM/시스템 메트릭들을 자동으로 수집하고, 푸쉬한다.
 
만약 배치와 관련된 추가적인 메트릭을 수집하고 싶다면 아래와 같이 리스너를 추가하면 된다.
 
import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Gauge; import io.prometheus.client.exporter.PushGateway; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.StepExecution; import org.springframework.stereotype.Component; @Component public class PushGatewayJobListener implements JobExecutionListener { private final PushGateway pushGateway = new PushGateway("localhost:9091"); @Override public void afterJob(JobExecution jobExecution) { if (jobExecution.getStatus() == BatchStatus.COMPLETED) { long processedCount = jobExecution.getStepExecutions().stream() .mapToLong(StepExecution::getWriteCount) .sum(); CollectorRegistry registry = new CollectorRegistry(); Gauge gauge = Gauge.build() .name("batch_job_processed_count") .help("Total processed records") .labelNames("job_name") .register(registry); gauge.labels(jobExecution.getJobInstance().getJobName()).set(processedCount); try { pushGateway.pushAdd(registry, "user_action_summary"); } catch (Exception e) { e.printStackTrace(); // 예외 처리 필요 } } } }
 
배치 작업이 성공적으로 끝났을 때 처리된 배치 작업의 수를 카운트하는 메트릭을 추가하는 리스너이다. 리스너를 Job, Step의 설정에 등록하면 된다.

Metric 확인

 
설정을 마치고 배치 작업을 실행시키면 수집된 메트릭이 pushgateway에 저장되고, 이를 프로메테우스가 가져간다.
 
notion image
Pushgateway 서버에서 메트릭을 확인해보면 수집된 메트릭들을 조회할 수 있다.
첫번째 “batch_server_scrape” 그룹은 PrometheusMeterRegistry 에서 자동으로 수집한 메트릭을 스케쥴러에서 일정 주기로 푸쉬했던 메트릭이다.
 
두번째 “user_action_summary” 그룹은 리스너를 등록해 배치 작업이 끝났을 때 수동으로 푸쉬했던 메트릭이다.
 
둘 다 제대로 조회되는 것을 볼 수 있다. 다만, 메트릭들을 문자 그대로 보는건 매우 불편하다. 따라서 시각화도구가 필요하다.
 

Metric Visualization

 
수집한 메트릭을 Grafana로 시각화해보겠다.
 

1. Grafana 서버에 접속

 
시각화 설정을 위해 그라파나 서버에 접속하자.
실습 환경에서는 http://localhost:3000 에 접속하면 된다.
 

2. Prometheus Datasource 등록

 
프로메테우스 데이터 등록을 해준다.
notion image
 
Datasource Name : Prometheus (대시보드와 Datasource는 이름으로 매핑된다. 이를 주의하자)
Datasource Connection : http://host.docker.internal:9000 (프로메테우스 서버 주소를 적는다)
 
notion image
 
아래로 내리고 Save & test 누르자

3. Dashboard 설정

 
notion image
 
왼쪽 설정 창에서 대시보드 클릭
 
notion image
 
Import a dashboard 클릭
 
notion image
 
Save 클릭
 
notion image
 
저장했던 대시보드에서 다시 Import a dashboard
 
{ "annotations": { "list": [ { "builtIn": 1, "datasource": { "type": "grafana", "uid": "2018" }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", "type": "dashboard" } ] }, "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, "id": 7, "links": [], "panels": [ { "datasource": "Prometheus", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green" }, { "color": "red", "value": 80 } ] } }, "overrides": [] }, "gridPos": { "h": 8, "w": 6, "x": 0, "y": 0 }, "id": 29, "options": { "colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "textMode": "auto", "wideLayout": true }, "pluginVersion": "12.0.1", "targets": [ { "editorMode": "code", "expr": "spring_batch_job_launch_count_total", "legendFormat": "{{spring_batch_job_name}} {{spring_batch_job_status}}", "range": true, "refId": "A" } ], "title": "Job Launch Total Count", "type": "stat" }, { "datasource": "Prometheus", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "stepBefore", "lineStyle": { "fill": "solid" }, "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "fieldMinMax": false, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green" }, { "color": "red", "value": 80 } ] }, "unit": "s" }, "overrides": [] }, "gridPos": { "h": 8, "w": 9, "x": 6, "y": 0 }, "id": 28, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "hideZeros": false, "mode": "single", "sort": "none" } }, "pluginVersion": "12.0.1", "targets": [ { "editorMode": "code", "expr": "spring_batch_job_active_seconds_max", "legendFormat": "{{spring_batch_job_active_name}}", "range": true, "refId": "A" } ], "title": "Job Duration", "type": "timeseries" }, { "datasource": "Prometheus", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green" }, { "color": "red", "value": 80 } ] }, "unit": "count" }, "overrides": [] }, "gridPos": { "h": 8, "w": 9, "x": 15, "y": 0 }, "id": 34, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "hideZeros": false, "mode": "single", "sort": "none" } }, "pluginVersion": "12.0.1", "targets": [ { "editorMode": "code", "expr": "spring_batch_job_launch_count_total", "legendFormat": "{{spring_batch_job_status}}", "range": true, "refId": "A" } ], "title": "Job Launch Count by Status", "type": "timeseries" }, { "datasource": "Prometheus", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green" }, { "color": "red", "value": 80 } ] }, "unit": "ops" }, "overrides": [] }, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 }, "id": 31, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "hideZeros": false, "mode": "single", "sort": "none" } }, "pluginVersion": "12.0.1", "targets": [ { "editorMode": "code", "expr": "rate(spring_batch_item_read_seconds_count[1m])", "legendFormat": "{{spring_batch_item_read_step_name}}", "range": true, "refId": "A" } ], "title": "Item Read Throughput", "type": "timeseries" }, { "datasource": "Prometheus", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green" }, { "color": "red", "value": 80 } ] }, "unit": "ops" }, "overrides": [] }, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 }, "id": 32, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "hideZeros": false, "mode": "single", "sort": "none" } }, "pluginVersion": "12.0.1", "targets": [ { "editorMode": "code", "expr": "rate(spring_batch_chunk_write_seconds_count[1m])", "legendFormat": "{{spring_batch_chunk_write_step_name}}", "range": true, "refId": "A" } ], "title": "Item Write Throughput", "type": "timeseries" } ], "preload": false, "refresh": "5s", "schemaVersion": 41, "tags": [], "templating": { "list": [] }, "time": { "from": "now-15m", "to": "now" }, "timepicker": {}, "timezone": "browser", "title": "Spring Batch Dashboard with pushgateway", "uid": "ce9je3wmy5ts0b", "version": 11 }
 
대시보드 설정 넣기
 

결과

 
notion image
 
위 Grafana Dashboard에서 다음과 같은 지표를 모니터링 할 수 있다.
  • 배치 잡 실행 횟수 및 상태
  • 잡 실행 시간
  • 읽기/쓰기 처리량(Throughput)
 
 
Share article

lushlife99