Spring Batch와 Jenkins를 활용한 스테이킹 이자 지급 파이프라인 구축기
개요
코인 거래소에서 운영되는 스테이킹 서비스는 사용자의 예치 자산에 대해 매일 정해진 시각에 이자를 지급해야 합니다. 제가 맡았던 작업은 매일 00시에 최대 5만 명의 사용자에게 이자를 정산하고 지급하는 배치 파이프라인을 만드는 것이었습니다.
처음부터 이 작업을 단순 스케줄러로 처리하기보다,
정산이라는 성격에 맞는 안정적인 배치 처리 구조를 먼저 선택하는 것이 중요하다고 판단했습니다.
문제 상황: 매일 00시에 반드시 끝나야 하는 이자 정산
스테이킹 이자 지급은 일반적인 조회성 기능과 달리 다음 조건을 동시에 만족해야 했습니다.
- 매일 00시에 정해진 기준으로 실행되어야 한다.
- 사용자 수가 최대 5만 명까지 증가할 수 있다고 가정해야 한다.
- 처리 도중 실패한 건은 누락 없이 다시 처리할 수 있어야 한다.
- 정산 결과는 DB 기준으로 추적 가능해야 한다.
당시 구조는 DB 기반 정산 방식이었기 때문에
결국 핵심은 “정해진 시각에 대량의 데이터를 안정적으로 읽고, 계산하고, 저장할 수 있는가”였습니다.
특히 정산 작업은 한 번 실패하면 단순히 “다시 요청하면 되는 기능”이 아니었습니다. 누가 얼마를 받아야 하는지, 어디까지 반영되었는지, 중간에 실패한 사용자는 누구인지가 명확해야 했습니다.
즉, 이 작업은 단순한 스케줄링 문제가 아니라
대량 데이터 처리 + 실패 복구 + 실행 이력 관리가 필요한 전형적인 배치 작업에 가까웠습니다.
왜 Spring Batch를 선택했는가
정산 기능을 설계하면서 가장 먼저 든 생각은
“이건 스케줄러가 아니라 배치로 풀어야 한다” 는 것이었습니다.
처음에는 @Scheduled 기반의 단순 배치도 가능하다고 봤지만, 정산 작업의 특성을 생각하면 운영 안정성 측면에서 한계가 분명했습니다.
Spring Batch를 선택한 이유는 크게 세 가지였습니다.
1. 실패한 작업을 다시 실행할 수 있어야 했다
정산은 중간에 일부 사용자 처리에서 실패할 수 있습니다.
예를 들어
- 특정 사용자 데이터 정합성이 맞지 않거나
- 일시적인 DB 문제로 저장에 실패하거나
- 외부 시스템 연동 과정에서 예외가 발생할 수 있습니다.
이때 가장 중요한 것은 전체 작업이 어디까지 수행되었는지 추적하고, 실패 지점부터 재처리할 수 있는가입니다.
Spring Batch는 Job, Step, Execution 단위로 실행 이력을 관리하기 때문에 운영 중 실패가 발생했을 때 단순히 “처음부터 다시”가 아니라
실패한 작업을 기준으로 재실행 전략을 가져갈 수 있다는 점이 매우 큰 장점이었습니다.
2. 대량 데이터 처리를 전제로 한 구조가 필요했다
최대 5만 명의 사용자에게 매일 00시에 이자를 지급해야 했기 때문에, 한 건씩 순차적으로 처리하는 단순 로직은 비효율적이었습니다.
Spring Batch는 chunk 기반 처리 모델을 제공하기 때문에 데이터를 일정 단위로 나누어 읽고, 처리하고, 저장하는 구조를 자연스럽게 만들 수 있습니다.
이 방식은 다음 측면에서 유리했습니다.
- 한 번에 너무 많은 데이터를 메모리에 올리지 않아도 된다.
- 일정 단위로 커밋할 수 있어 트랜잭션 범위를 제어하기 쉽다.
- 대량 데이터 처리 시 운영 안정성을 확보하기 좋다.
즉, 예상 사용자 수가 증가하더라도
단순 반복문보다 훨씬 운영 가능한 구조로 확장하기 쉬운 선택지였습니다.
3. 정산 작업은 실행 이력과 상태 관리가 중요했다
정산 작업에서는 “오늘 배치가 실행됐는가?”보다 더 중요한 질문이 있습니다.
- 몇 시에 시작했는가
- 몇 건을 읽었는가
- 몇 건이 성공했고 몇 건이 실패했는가
- 어떤 Step에서 에러가 났는가
Spring Batch는 메타 테이블을 통해 이러한 실행 정보를 관리할 수 있기 때문에 운영 관점에서 배치의 상태를 추적하고 문제를 분석하기 쉬운 구조를 제공했습니다.
정산처럼 돈이 관련된 작업에서는 이 부분이 특히 중요했습니다. 단순히 로직이 동작하는 것보다,
문제가 생겼을 때 원인을 설명할 수 있는 시스템이어야 했기 때문입니다.
전체 아키텍처
전체 흐름은 Jenkins가 배치 실행 순서를 제어하고, 실제 대량 정산 처리는 Spring Batch가 담당하는 구조로 설계했습니다.
왜 단순 스케줄러나 Quartz가 아니라 Jenkins를 선택했는가
Spring Batch로 정산 로직을 구성한 뒤에는
이 배치를 어떤 방식으로 운영하고 실행할 것인가를 결정해야 했습니다.
후보는 크게 세 가지였습니다.
- 애플리케이션 내부의 단순 스케줄러(
@Scheduled) - Quartz
- Jenkins
처음에는 “어차피 00시에 한 번 실행하면 되니 스케줄러면 충분하지 않을까?”라고 생각할 수도 있습니다. 하지만 실제 운영을 생각해 보면, 정산 배치는 단순히 시간을 맞춰 실행하는 것만으로 끝나지 않았습니다.
필요했던 것은 다음과 같았습니다.
- 작업 실행 결과를 운영자가 쉽게 확인할 수 있어야 한다.
- 실패 시 즉시 알림을 받을 수 있어야 한다.
- 필요하면 특정 단계만 재실행할 수 있어야 한다.
이 요구사항을 기준으로 보면 Jenkins가 가장 잘 맞았습니다.
단순 스케줄러의 한계
@Scheduled는 가장 구현이 간단합니다. 애플리케이션 안에서 바로 00시 실행을 걸 수 있기 때문에 초기 개발 속도는 빠릅니다.
하지만 운영 관점에서는 한계가 명확했습니다.
- 실행 흐름이 애플리케이션 내부에 묶인다.
- 단계별 실행 상태를 보기 어렵다.
- 실패 알림, 수동 재실행, 운영 이력 관리 등을 별도로 붙여야 한다.
즉, 단순 스케줄러는 실행 자체는 쉽지만 운영 도구로는 부족했습니다.
Quartz를 선택하지 않은 이유
Quartz는 단순 스케줄러보다 훨씬 강력합니다. 트리거 관리, 스케줄 관리, 클러스터링 같은 기능도 제공하기 때문에 스케줄링만 놓고 보면 충분히 좋은 선택지였습니다.
다만 제가 만들고 싶었던 것은 “정해진 시간에 하나의 작업을 실행하는 시스템”이 아니라
정산 과정을 여러 단계로 나누고, 운영자가 눈으로 확인하고, 실패 시 빠르게 대응할 수 있는 파이프라인이었습니다.
Quartz는 스케줄링 엔진으로는 강력하지만, 다음 부분은 결국 추가 구현이 더 필요하다고 판단했습니다.
- 운영 화면
- 단계별 파이프라인 구성
- 알림 연동
- Job 단위 수동 실행 관리
즉, Quartz는 좋은 스케줄러였지만
제가 원한 것은 스케줄러를 넘어서 운영 파이프라인 플랫폼에 가까웠습니다.
Jenkins를 선택한 이유
Jenkins를 선택한 가장 큰 이유는
Job 단위로 작업을 나누고, Jenkins Pipeline으로 전체 로직을 조립할 수 있다는 점이었습니다.
정산 배치를 하나의 거대한 실행 단위로 두기보다, 예를 들어 다음처럼 나누어 운영하는 구조를 생각했습니다.
- 정산 대상 생성
- 이자 계산
- 지급 처리
- 결과 검증
- 후속 알림
이렇게 나누면 각 단계를 독립적인 Job으로 볼 수 있고, Jenkins Pipeline으로 이 순서를 유연하게 연결할 수 있습니다.
이 방식의 장점은 명확했습니다.
- 특정 단계 실패 시 어느 구간에서 문제가 났는지 바로 확인할 수 있다.
- 필요하면 특정 Job만 재실행할 수 있다.
- 배치 로직을 운영 흐름 단위로 시각적으로 관리할 수 있다.
- 전체 작업을 하나의 파이프라인으로 조립할 수 있다.
특히 운영 입장에서는
“배치가 돌았는가?” 보다 “어느 단계까지 성공했고 어디서 실패했는가?” 가 더 중요했는데, Jenkins는 이 부분을 매우 직관적으로 보여줄 수 있었습니다.
Slack 플러그인 연동이 실무적으로 컸다
Jenkins를 선택한 또 하나의 큰 이유는 Slack 연동 플러그인이었습니다.
정산 배치는 매일 00시에 실행되기 때문에, 장애가 나도 누군가가 직접 Jenkins에 들어가서 확인하기 전까지 모르면 운영 대응이 늦어질 수 있습니다.
그래서 저는 다음이 중요하다고 봤습니다.
- 배치 시작 알림
- 성공 알림
- 실패 알림
- 실패 시 어떤 Job에서 문제가 났는지 공유
Jenkins는 이런 운영 알림을 플러그인 기반으로 비교적 쉽게 붙일 수 있었고, 실제로 정산 실패를 빠르게 감지하고 대응하는 체계를 만드는 데 유리했습니다. —
정리
정산 배치의 핵심은 단순히 00시에 실행하는 것이 아니었습니다.
- 대량 정산 작업을 여러 단계로 나눌 수 있어야 했고
- 각 단계의 성공/실패를 추적할 수 있어야 했고
- 실패를 운영 채널로 빠르게 전달할 수 있어야 했습니다.
이 기준에서 보면
- 단순 스케줄러는 구현은 쉽지만 운영성이 부족했고
- Quartz는 스케줄링에는 강하지만 파이프라인 운영 도구로는 아쉬웠고
- Jenkins는 Job 분할, Pipeline 조립, Slack 알림이라는 실무적인 장점이 있었습니다.
그래서 최종적으로는
정산 로직은 Spring Batch로 만들고, 실행과 운영 파이프라인은 Jenkins로 관리하는 구조를 선택했습니다.