Toss Payments Settlement Legacy System Overhaul: A Review

토스페이먼츠 정산 레거시 시스템 개편기 리뷰

토스페이먼츠 정산 레거시 시스템 개편기 리뷰: “쿼리의 시대”에서 “도메인의 시대”로

아래 글은 발표 스크립트를 기반으로, 레거시 정산(Settlement) 시스템을 신규 시스템으로 개편하면서 겪은 장애/리스크, 기술적 원인, 아키텍처 의사결정, 검증·투입 전략, 배치 운영 프레임워크 선택과 운영 고도화를 실무 관점에서 재구성한 리뷰입니다.

(주석: 정산은 “고객 결제금이 PG를 거쳐 가맹점(상점)에 정확한 조건으로 지급되는 과정”을 의미합니다. PG는 Payment Gateway입니다.)


1) 🧭 개편의 출발점: “정산은 돈이므로, 틀리면 바로 사고입니다”

정산 도메인은 본질적으로 미션 크리티컬합니다. (주석: 미션 크리티컬은 장애가 곧 금전 손실/법적 이슈/신뢰 하락으로 연결되는 특성을 말합니다.)

정산 시스템이 처리해야 하는 것은 단순 합산이 아니라, 가맹점별 계약 조건(수수료), 지급 주기, 결제수단/할부/해외결제(환전) 등 다양한 규칙의 조합입니다. 이 조합 수가 커질수록 “정확성”과 “운영 가능성”이 동시에 요구됩니다.


2) 🧱 20년 레거시의 가치와 한계: “돌아가지만, 바꾸기 어려운 시스템”

20년 이상 운영된 레거시는 보통 두 가지를 동시에 갖습니다.

  • 강점: 비즈니스 요구를 오랜 기간 흡수해 온 “규칙의 집합체”

  • 약점: 규칙이 “어떤 형태로” 축적되었는지가 유지보수 비용을 결정

이 발표의 핵심은, 레거시가 세 가지 구조적 한계에 도달했고 이를 극복하기 위해 개편이 진행되었다는 점입니다.


3) 🚨 한계 #1: 비즈니스 로직이 코드가 아니라 “거대한 SQL 한 방 쿼리”에 종속

첫 번째 문제는 정산 규칙이 애플리케이션 코드가 아니라 복잡한 SQL 쿼리에 잠겨 있었다는 것입니다.

  • 여러 도메인을 엮는 수많은 JOIN, UNION ALL, 서브쿼리

  • 분기 처리를 위한 중첩 DECODE, CASE WHEN

    (주석: DECODE는 Oracle 계열에서 조건 분기를 구현하는 함수로, 현대 SQL에서는 CASE로 더 많이 표현합니다.)

결과적으로 “정책 변경”이 들어오면,

  • 어디를 바꿔야 하는지 찾기 어렵고

  • 수정 범위가 커지며

  • 테스트와 롤백 비용이 증가합니다

변경 리스크가 선형이 아니라 기하급수적으로 커지는 구조였습니다.


4) 🔥 장애/리스크 시나리오: “한 방 쿼리의 작은 수정이 전체 정산을 흔듭니다”

실무에서 자주 발생하는 리스크는 다음과 같습니다.

  • 특정 결제수단 수수료 규칙 변경 → 쿼리 일부 변경

    → 다른 조인 조건/필터에 영향 → 다른 가맹점 결과까지 흔들림

  • 쿼리 최적화(힌트/인덱스 유도) 시도

    → 특정 플랜에서만 빨라지고 다른 플랜에서 급격히 느려짐 → 배치 SLA 붕괴

    (주석: SLA는 서비스가 지켜야 하는 목표 처리시간/가용성 약속입니다.)

“코드”는 모듈 테스트로 위험을 쪼갤 수 있지만, “복잡한 단일 쿼리”는 위험도 같이 단일화됩니다.


5) 🧩 첫 번째 설계 결정: 쿼리를 ‘개선’이 아니라 ‘분해’한다 (분할정복)

발표에서 가장 중요한 전환점은 거대한 쿼리를 먼저 분석하고 분해하는 접근입니다.

  • 조인·유니언으로 엮인 하위 쿼리를 카테고리 분류

  • 분류된 카테고리를 다시 기능 단위로 세분화

  • “작고 명확한 단위”가 될 때까지 반복

이 방식은 단지 리팩터링 기법이 아니라, 개편 프로젝트의 리스크 관리 전략입니다.


6) 🧠 분할정복의 실무적 효과: “전면 교체”가 아니라 “점진 전환”이 가능해집니다

분해가 끝나면 얻는 가장 큰 이점은 다음입니다.

  • 시스템 전체를 한 번에 바꾸지 않아도 됩니다

  • 특정 기능(예: 환전, 체크카드 결제 유형)부터 우선 개편할 수 있습니다

  • 신규 요구사항이 들어오면, 해당 기능의 개편 우선순위를 조정해 장기 프로젝트에서도 단계별 가치 전달이 가능해집니다

즉 “빅뱅 전환”이 아니라 스트랭글러(점진 교체) 전략으로 움직일 수 있습니다.

(주석: Strangler Pattern은 레거시를 한 번에 버리지 않고, 신규 기능을 덧대며 점진적으로 대체하는 접근입니다.)


7) 🧱 신규 구조의 형태: “쿼리 하나가 하던 일”을 역할 객체로 분리

분해된 기능은 신규 시스템에서 다음처럼 바뀝니다.

  • 수수료 계산기(Fee Calculator)

  • 환전기(Exchange)

  • 계약/정책 해석기(Contract Resolver)

  • 기타 도메인별 계산기

핵심은 비즈니스 규칙이 코드 구조에 드러나도록(Explicit) 만들었다는 점입니다.


8) 🧾 (주석) “도메인 모델”이 중요한 이유

(주석: 도메인 모델은 비즈니스 개념(계약, 수수료, 정산 결과, 지급 등)을 코드의 타입/객체로 표현해, 규칙과 변경지점을 명시적으로 만드는 방식입니다.)

한 방 쿼리 체계에서는 “정산 규칙”이 SQL 내부에 암호처럼 숨어있지만, 도메인 모델에서는

  • 규칙이 클래스/메서드/정책 객체로 표면화되고

  • 테스트가 가능해지며

  • 변경 영향 범위가 좁아집니다


9) 🧨 한계 #2: 정산 데이터 모델링이 ‘집계 저장’ 중심이라 원인 추적이 어려움

레거시의 두 번째 문제는 데이터가 최소 단위가 아니라 특정 기준으로 집계되어 저장된 구조였습니다.

예를 들어 거래 1, 2, 3의 계산 결과가

  • 거래별로 저장되는 것이 아니라

  • 어떤 기준으로 뭉쳐진 “집계 결과”로 저장되면

문제가 발생했을 때

  • 어느 거래가 원인인지 역추적이 매우 어렵고

  • 다른 제품 요구를 위해 데이터를 다시 쓰기도 어렵습니다


10) 🔍 장애 분석 관점: “집계 결과 오류”는 디버깅 비용이 폭발합니다

집계 테이블에서 숫자가 틀리면, 실무 대응은 보통 다음이 됩니다.

  • 원천 거래 재조회

  • 여러 조건으로 재집계

  • “당시 계약 조건”을 찾기 위해 설정 이력 확인

  • 동일 문제 재발 방지를 위해 임시 검증 배치 추가

즉, 오류 1건이 “사람의 시간”을 크게 태웁니다.

정산에서 이 비용은 곧 운영 리스크입니다.


11) 🧱 두 번째 설계 결정: 정산 결과를 “정규화(최소 단위)”로 저장

신규 시스템은 저장 방식을 정상화(정규화) 합니다.

  • 거래 1 → 결과 1

  • 거래 2 → 결과 2

  • 거래 3 → 결과 3

(주석: 정규화는 중복·집계를 최소화하고, 원자적(Atomic) 단위로 데이터를 저장해 무결성과 유연성을 확보하는 모델링 원칙입니다.)

이렇게 하면

  • 원인 추적이 거래 단위로 명확해지고

  • 다양한 제품 요구(조회/리포트/정산분석)에 대해 최소 단위 데이터를 조합해 대응할 수 있습니다


12) 🧷 운영 개선 #1: “계산 당시 설정값” 스냅샷 저장

정산 환경에서는 가맹점 설정(계약, 수수료율, 정책)이 실시간으로 변할 수 있습니다.

따라서 “왜 이 결과가 나왔는지”를 설명하려면, 결과와 함께 당시 설정값 스냅샷이 필요합니다.

  • 결과 레코드 + 계산에 사용된 설정 스냅샷

  • 이후 설정이 바뀌어도, 과거 결과의 재현성과 설명 가능성이 유지

(주석: 스냅샷은 특정 시점의 상태를 복제 저장해, 미래 변경과 독립적으로 과거를 재현하도록 하는 방식입니다.)


13) 🧯 운영 개선 #2: 재처리(리트라이) 가능한 단위로 “실패를 기록”

레거시에서는 “수천만 건을 한 DB 트랜잭션으로 처리하고 마지막에 한 번 커밋”하는 구조가 있었다고 합니다.

이 구조는 마지막 1건이 실패해도 전체 롤백이 발생합니다.

(주석: 트랜잭션은 DB 작업을 하나의 원자적 단위로 묶는 것입니다. 롤백은 중간 실패 시 이전 상태로 되돌리는 동작입니다.)

신규 시스템은 거래마다 처리 상태를 기록하여

  • 실패 건만 빠르게 재처리

  • 복구 시간 단축

  • 장애 영향 범위 축소

즉 “실패를 정상 상태로 모델링”한 것입니다.


14) 📈 부작용: 최소 단위 저장의 대가 = 데이터 폭증

정규화로 얻는 이점은 크지만, 저장량이 급격히 늘어납니다.

따라서 신규 시스템은 성능/비용/운영성 관점에서 추가 설계가 필요합니다.


15) 🗂️ 데이터 최적화 #1: 정산일자 기반 레인지 파티셔닝 + 인덱스 전략

정산 조회는 대부분 정산 일자를 포함합니다.

따라서 테이블을 날짜 기준으로 레인지 파티셔닝하면 물리적으로 탐색 범위를 줄일 수 있습니다.

  • 파티션 프루닝(Partition Pruning): 필요한 날짜 파티션만 스캔

    (주석: 파티션 프루닝은 조건절로 인해 특정 파티션만 접근하는 최적화입니다.)

  • 정산일자를 선두로 한 복합 인덱스(Composite Index) 구성

    (주석: 복합 인덱스는 여러 컬럼을 묶어 인덱스를 구성하는 방식이며, 선두 컬럼이 조건에 포함되면 효율이 크게 증가합니다.)


16) 🧾 데이터 최적화 #2: 조회 전용 테이블(집계) 분리로 쓰기·읽기 충돌 제거

정규화 시스템은 조회 시 실시간 집계가 필요해져 조회가 느려질 수 있습니다.

또한 제품 요구로 인해 조회 조건 인덱스를 계속 추가하면, 쓰기 배치 성능이 떨어집니다.

이를 해결하기 위해

  • 자주 쓰는 조회 조건 기반 집계 테이블(조회 전용) 을 별도로 둡니다

  • 제품에 필요한 인덱스는 조회 전용 테이블에 집중합니다

이는 전형적인 CQRS 성격의 분리로 해석할 수 있습니다.

(주석: CQRS는 Command(쓰기)와 Query(읽기) 모델을 분리해 각각 최적화하는 아키텍처 패턴입니다.)


17) 🚨 한계 #3: 배치 성능 이슈 — 거래가 늘면 처리시간이 폭발

레거시 배치는

  • 거래 → 정산 데이터 생성까지 시간이 오래 걸리고

  • 거래량이 조금만 늘어도 처리 시간이 급격히 증가하며

  • 목표 거래 규모에서는 “하루 종일 돌려도 안 끝나는 상태”가 예상되었습니다

정산은 보통 새벽 시간대에 몰리며(지급/마감/정산 기준), 이 구간의 성능 문제는 곧 비즈니스 운영 리스크입니다.


18) 🧰 스프링 배치로 개편했지만, 자동으로 해결되지 않는 2가지

신규 시스템은 Spring Batch 기반으로 개편합니다. 그러나 발표에서 강조하듯, 도구를 바꾼다고 문제가 사라지지 않습니다.

  • 문제 A: 대량 I/O

  • 문제 B: 싱글 스레드 처리량 한계

(주석: I/O는 DB/네트워크/디스크 입출력입니다. 대량 I/O는 지연과 비용을 동시에 악화시킵니다.)


19) 🧊 I/O 개선 #1: “계약/설정 정보” 배치 전처리 캐시

레거시는 거래 처리 중 매번 가맹점 설정을 DB에서 조회했습니다.

하지만 정산 배치 시점에는 “적용할 계약 정보가 이미 결정된 상태”입니다.

따라서

  • 배치 전처리에서 필요한 설정을 메모리 캐시

  • 이후 처리에서는 캐시 조회로 대체

(주석: 캐시는 자주 접근하는 데이터를 빠른 저장소(메모리 등)에 보관해 반복 조회 비용을 줄이는 기법입니다.)

효과는 직관적입니다: 조회 횟수 자체를 구조적으로 제거합니다.


20) 📦 I/O 개선 #2: ItemProcessor로 “한 건씩” 전달되는 구조의 함정과 래퍼(Wrapper)

Spring Batch의 기본 구조는 대체로 다음입니다.

  • ItemReader → ItemProcessor → ItemWriter

    (주석: Reader는 읽고, Processor는 변환/계산하고, Writer는 저장합니다.)

문제는 Processor가 한 건씩 받는 상황에서, 내부에서 추가 I/O를 일으키면

  • 100만 건이면 I/O도 100만 번 추가됩니다

해결은 여러 대상을 하나로 묶은 래퍼 객체로 전달하는 방식입니다.

  • Processor는 “객체 1개”를 받지만

  • 그 객체는 “처리 대상 N건”을 포함

  • 따라서 필요한 데이터를 벌크 조회(Bulk Fetch) 로 가져올 수 있습니다

    (주석: 벌크 조회는 여러 건을 한 번의 쿼리로 가져오는 방식입니다.)


21) 🧾 I/O 개선 #3: JDBC 배치 인서트로 “N번 INSERT”를 “1번 INSERT(묶음)”로

레거시가 결과 1건당 INSERT 1번이었다면, 거래량 증가에 따라 DB I/O가 선형 증가합니다.

신규 시스템은

  • JDBC Batch Insert로 여러 건을 묶어 삽입합니다

    (주석: JDBC 배치 인서트는 PreparedStatement에 여러 row를 쌓고 executeBatch로 한 번에 전송하는 방식입니다.)

이는 DB round-trip(네트워크 왕복) 비용을 줄여, 대량 쓰기에서 큰 효과를 얻습니다.


22) ⚡ 병렬화 #1: 외부 API 호출은 “동시에” 하고 “나중에 조합”

도메인 분리로 인해, 과거 한 방 쿼리에서 한 번에 가져오던 정보가

  • 서로 다른 API 서버에 나뉘어 존재하는 상황이 생깁니다

거래 1건 기준 10ms 대기라도,

  • 수백만 건이면 누적 대기가 수시간이 됩니다

따라서

  • 여러 외부 호출을 병렬로 실행

  • 응답을 모아 조합하는 방식으로 총 대기시간을 “최대값 수준”으로 낮춥니다

(주석: 병렬 호출은 CompletableFuture, Reactor, 코루틴 등으로 구현할 수 있으며 핵심은 I/O 바운드 작업을 동시화하는 것입니다.)


23) 🧵 병렬화 #2: 멀티스레드 스텝의 현실 — “Thread-Safe하지 않은 Reader”가 가장 위험

Spring Batch는 멀티스레드 스텝을 제공하지만, 쓰레드만 늘린다고 해결되지 않습니다.

핵심 병목인 Reader가 쓰레드 세이프하지 않으면:

  • 이미 처리된 거래를 다시 읽어 중복 정산

  • 특정 거래를 모든 스레드가 건너뛰어 정산 누락

(주석: Thread-Safe는 여러 스레드가 동시에 접근해도 상태가 깨지지 않는 성질입니다.)

정산에서 중복/누락은 곧 사고입니다.


24) 🧯 1차 대응: 동기화(Synchronization)는 정합성을 지키지만 성능을 죽입니다

동기화를 적용하면 정합성은 확보되지만,

  • Reader 구간이 순차화되어

  • 병렬 처리 이점이 사실상 사라집니다

즉 “정확하지만 느린 시스템”이 됩니다. 정산 배치에서는 SLA 관점에서 곤란합니다.


25) 🧠 최종 해법: 모듈러(Modulo) 기반 파티셔닝으로 “처리 범위를 스레드별로 고정”

발표에서 제시된 핵심 해법은 다음입니다.

  • id % threadCount == k 인 데이터만 k번 스레드가 처리

  • 스레드가 서로 같은 거래를 읽지 않도록 처리 범위를 분할

  • 동기화 없이도 중복/누락을 구조적으로 방지

(주석: 모듈러 연산은 나머지 연산입니다. 데이터 분할 키로 흔히 사용됩니다.)

이 방식의 장점은

  • 구현이 단순하고

  • 데이터 분배가 비교적 균등하며

  • 정합성과 성능을 동시에 만족시키기 쉽다는 점입니다


26) ✅ “이제 만들었으니 배포”가 불가능한 이유: 레거시와 동일 동작 증명이 필요

정산은 결과가 다르면 바로 고객/가맹점 영향입니다.

따라서 신규 시스템은 레거시와 동일한 동작을 한다는 증거가 필요하고, 전환 영향도를 최소화해야 합니다.

여기서부터가 진짜 실무 난이도가 높습니다: “개발”이 아니라 “검증·투입”입니다.


27) 🧪 검증 난이도: 경우의 수가 수만~수백만으로 터집니다

지원하는 계산 유형이 수천 개,

각 유형의 세부 설정이 수십 개,

결제수단/할부/해외 여부 등 거래 속성이 추가되면 조합이 폭발합니다.

사람이 손으로 검증할 수 없습니다. 따라서 자동화가 필수입니다.


28) 🏗️ 테스트 자동화 플랫폼 + “계산 전용 API” 설계

발표에서는 “테스트 자동화 플랫폼”을 적극 활용했다고 합니다.

핵심 전략은:

  • 가능한 케이스를 취합해 테스트 데이터 구성

  • 그 데이터를 입력값으로 계산 결과를 확인할 수 있는 계산 API 구현

  • 이 계산 API는 배치 모듈의 코어 계산 모듈을 그대로 사용

즉 테스트가 “모형”이 아니라 실제 로직을 관통합니다.

(주석: 이런 방식은 테스트 더블을 과도하게 쓰지 않고, 핵심 도메인 로직을 재사용하여 테스트 신뢰도를 높입니다.)


29) 🐤 투입(Deploy) 전략의 조건: “빠른 복구” + “감당 가능한 영향 범위”

신규 투입에서 고민한 두 가지는 매우 현실적입니다.

  1. 이슈가 발생해도 빠르게 복구 가능해야 함

  2. 영향받는 가맹점 수가 너무 크면 대응 공수가 폭증하므로, 감당 가능한 규모여야 함

따라서 “원자적 전환 단위”를 정의해야 합니다.

  • 사업자번호, 가맹점ID, 결제수단 등 다양한 단위 후보


30) 🐦 배치 레이어 까나리(Canary): 레거시/신규를 동시에 돌리고, 결과가 같을 때만 신규를 채택

발표의 투입 설계는 매우 강력합니다.

  • 레거시로도 정산 결과 생성

  • 신규로도 정산 결과 생성

  • 두 결과를 비교 검증

    • 일치하면 신규 결과를 투입

    • 불일치하면 레거시 결과를 투입하고 원인 트래킹

(주석: Canary Deployment는 일부 트래픽/대상에만 새 버전을 적용해 위험을 제한하는 배포 전략입니다.)

정산에서 까나리는 특히 유효합니다. 왜냐하면 “정확성”을 실데이터로 검증하면서도, 사고는 막을 수 있기 때문입니다.


31) 🔬 투입 단위의 미세화: 가맹점 → 결제수단 → 결제권(트랜잭션)까지

까나리의 강점은 “부분 전환”에 있습니다. 발표에서는 투입 단위를 더 세분화했다고 합니다.

  • 특정 가맹점만 신규 적용

  • 같은 가맹점 내에서도 특정 결제수단만 신규 적용

  • 더 나아가 결제권 단위로도 전환 가능

이렇게 하면 문제가 발생했을 때

  • 영향 범위가 좁고

  • 롤백이 빠르며

  • 원인 분석의 대상 집합이 작아집니다


32) 🧰 배치 운영의 또 다른 전쟁: “젠킨스 잡 1,781개”가 의미하는 것

여기부터는 개발이 아니라 운영 체계의 이야기입니다.

발표에서 언급된 숫자(젠킨스 잡 수)는 배치 운영의 복잡도를 상징합니다.


33) 🏚️ 과거 운영: IDC 여러 서버에 흩어진 단일 인스턴스 배치 + 크론 관리

레거시 배치는

  • 여러 IDC 서버에 흩어져 있고

  • 단일 서버 장애 시 정상화가 어려우며

  • 크론 정의가 서버마다 달라 “정의의 단일 소스(Single Source of Truth)”가 없었습니다

(주석: *크론(cron)*은 리눅스에서 주기 실행을 정의하는 스케줄러입니다.)

이 구조는 장애 대응에서 매우 취약합니다.


34) ☁️ 1차 개선: 쿠버네티스로 옮겼지만, 정산 배치에는 함정이 있었습니다

배치를 K8s로 옮기면 인프라 취약성은 줄지만, 발표에서는 두 가지 문제가 발생했습니다.

  1. AWS ↔ 데이터센터 간 물리적 거리로 RTT 증가 → 쿼리 중심 배치 성능 저하

    (주석: RTT는 네트워크 왕복 시간입니다.)

  2. K8s CronJob이 시스템 제약으로 “정확히 한 번 실행(Exactly-Once)” 보장을 어렵게 함 → 중복 실행 발생

    (주석: Exactly-Once는 동일 작업이 중복 실행되지 않음을 보장하는 성질입니다.)

정산 배치에서 중복 실행은 매우 위험합니다.


35) 🧭 배치 프레임워크 재선택: “정확히 한 번”과 “워크플로우 명세”가 최우선

결국 더 안정적인 프레임워크를 고민하게 됩니다.

발표에서는 젠킨스를 선택한 이유를 다음처럼 정리합니다.

  • 정산은 스테이트풀(Stateful) 도메인

    (주석: 상태가 이어지며, 한 번의 실행 결과가 다음 실행(지급 등)의 입력이 되는 형태)

  • 배치 간 선후행(Workflow) 명세가 중요

  • 안정성과 플러그인 생태계, 구현 유연성


36) 🏗️ 젠킨스의 강점: 파이프라인만으로 “의존관계가 있는 배치 흐름”을 단순하게 표현

정산 배치는 단일 잡이 아니라, 여러 잡이 연결된 파이프라인입니다.

  • 계산 완료 → 지급 데이터 생성 → 후속 정산/리포트

  • 실패 시 재처리 흐름

  • 의존관계가 명확해야 운영이 가능한 구조

젠킨스 파이프라인은 이 부분에서 단순하면서도 강력합니다.


37) 💸 운영 고도화 #1: 다이나믹 프로비저닝으로 배치 실행 노드를 탄력적으로 운영

정산 배치는 특정 시간(새벽 등)에 몰립니다.

  • 고정 노드면: 몰릴 때 지연 발생(처리량 부족)

  • 노드를 과하게 잡으면: 특정 시간 외에는 자원 낭비(비용 증가)

따라서 “필요할 때 늘리고, 필요 없으면 회수”하는 다이나믹 프로비저닝을 구현합니다.

(주석: 프로비저닝은 서버/노드 자원을 할당하는 행위입니다. 다이나믹은 이를 자동·탄력적으로 한다는 의미입니다.)


38) 🧾 운영 고도화 #2: 잡 선언(Code)화 — UI 클릭에서 Git 기반 관리로

젠킨스 운영의 고전적인 고통은 UI 기반 관리입니다.

  • 파이프라인 설정 실수로 잡이 실행 안 됨

  • 환경변수 변경해야 하는데 잡이 너무 많음

  • 파라미터 하나 추가하려고 UI에서 수백 개 수정

발표에서는 이를 “잡 선언 코드화”로 해결합니다.

  • Git 레포지토리에 잡 정의를 코드로 관리

  • 커밋만 하면 잡 생성/변경 가능

  • 코드 리뷰로 변경 안정성 상승

  • JVM 버전, 노드 설정, 모니터링 설정 같은 공통 설정도 일괄 관리

(주석: 흔히 Job DSL, Jenkins Configuration as Code(JCasC), Pipeline-as-Code 같은 접근으로 구현합니다.)


39) 🔭 운영 고도화 #3: 모니터링/분석 도구 체계화 (Thread Dump, Profiler, Prometheus, Pinpoint)

배치 운영에서 자주 만나는 이슈는 다음입니다.

  • OOM(Out Of Memory)

    (주석: JVM 힙이 부족해 프로세스가 비정상 상태/종료되는 문제)

  • 멀티스레드 환경에서의 블로킹/데드락

    (주석: 데드락은 서로가 서로의 락을 기다리며 영원히 멈추는 상태)

  • Reader/Processor/Writer 중 특정 구간 지연으로 전체 SLA 악화

이를 해결하려면 “감”이 아니라 “증거”가 필요합니다.

  • 배치 실행마다 스레드 덤프 생성

  • 프로파일러로 CPU/메모리 병목 메서드 파악

  • Prometheus로 JVM 메모리/CPU 지표 수집

  • Pinpoint(APM)로 외부 호출 지연/쿼리 지연 탐지

관측 가능성(Observability) 을 운영의 기본값으로 올립니다.

(주석: Observability는 로그/메트릭/트레이싱으로 시스템 내부 상태를 외부에서 재구성할 수 있는 능력입니다.)


40) 🧠 정리: 이 개편이 “기술 부채 상환”이 아니라 “운영 가능한 정산 플랫폼” 재구축인 이유

발표를 관통하는 메시지는 다음으로 요약됩니다.

  • 쿼리에 갇힌 규칙을 도메인 객체로 끌어올려 변경 가능성을 확보

  • 집계 저장에서 원자 저장으로 바꿔 디버깅 가능성/재처리 가능성 확보

  • 데이터 폭증은 파티셔닝/인덱스/조회모델 분리로 성능·비용 균형 확보

  • 배치 성능은 캐시/벌크/배치인서트/병렬화로 처리량 확장

  • 검증·투입은 테스트 자동화 + 배치 까나리로 정확성 증명 + 리스크 제한

  • 운영은 젠킨스 선택 + 코드화 + 프로비저닝 + 관측가능성으로 지속 운영 체계 확보


41) 🧭 실무 교훈 1: “정산 시스템은 ‘정확성’이 기능이 아니라 운영 모델입니다”

정확성은 단순히 “코드가 맞다”가 아니라,

  • 재현 가능한가(스냅샷),

  • 실패를 좁게 격리할 수 있는가(거래 단위 기록),

  • 비교 검증이 가능한가(까나리),

  • 중복/누락을 구조적으로 차단했는가(파티셔닝/모듈러 분배)

    까지 포함합니다.


42) 🧭 실무 교훈 2: “전환은 개발이 아니라, 검증·투입·관측의 총합입니다”

레거시를 대체할 때 가장 어려운 부분은 기능 구현보다

  • 기존과 동일함을 증명하고

  • 영향도를 제한하며

  • 장애를 빨리 감지하고

  • 빠르게 복구하는 체계를 갖추는 것입니다

발표의 설계는 이 현실을 정면으로 다룹니다.


43) 🧭 실무 교훈 3: “대량 데이터 시스템은 읽기/쓰기/운영을 분리해서 설계해야 합니다”

정규화는 정답에 가깝지만, 그대로 두면 비용과 성능이 터집니다.

따라서

  • 파티셔닝/인덱싱으로 물리 탐색을 줄이고

  • 조회 전용 모델로 제품 요구를 흡수하고

  • 배치 처리량은 I/O 구조를 바꿔 확보해야 합니다


44) 🧭 실무 교훈 4: 멀티스레딩은 “락을 어떻게 잡을지”가 아니라 “범위를 어떻게 나눌지”가 핵심입니다

동기화는 쉽게 정합성을 확보하지만, 병렬 처리의 장점을 제거합니다.

모듈러 기반 분배처럼 “처리 범위 자체를 분할”하면, 락 비용 없이 정합성을 확보할 수 있습니다.


45) 🧭 마무리: 레거시 개편의 정석은 “분해 → 증명 → 제한적 전환 → 운영 자동화”입니다

이 발표의 흐름을 실무 프레임으로 정리하면 다음의 순서입니다.

  1. 거대한 위험(한 방 쿼리/집계 저장/단일 트랜잭션)을 분해

  2. 신규 시스템이 기존과 같음을 증명(테스트 자동화/계산 API)

  3. 리스크를 제한한 까나리 전환

  4. 배치 실행/정의/관측을 운영 체계로 자동화

정산 같은 돈의 도메인에서는, 이 정도의 설계 강도가 “과한 것”이 아니라 “필요조건”에 가깝습니다.

느낀점

레거시 시스템을 개선할 때 어떤 것부터 해야 할지 막막했을 것 같은 상황에서도, 전체를 한 번에 바꾸려 하기보다 문제를 분해하고 우선순위를 정해 하나씩 해결해 나간 점이 특히 인상 깊었습니다. 기술적인 선택 하나하나가 단순히 “잘 동작하게 만드는 것”이 아니라, 장애를 어떻게 줄일지, 운영에서 어떻게 검증하고 복구할지까지 함께 고민한 결과라는 점에서 더 설득력이 느껴졌습니다.

Last updated