코어뱅킹 우당탕탕 정산 자동화 개발

1. 소개

안녕하세요. 저는 코어뱅킹 팀에서 주니어 개발자로 일하고 있는 박찬얼입니다. 2022년 2월에 8퍼센트에 합류하여 세금 및 각종 수수료 정산 업무를 담당하고 있습니다. 이 글은 세금 정산 세미나를 소개했던, '코어뱅킹팀이 전하는 세금 정산의 모든 것' 글의 후속편으로, 8퍼센트에서 과거에 사용되던 정산 방법, 그로 인해 발생한 문제점들, 그리고 현재 코어뱅킹 팀이 개선한 방식과 그 이유를 공유하고자 합니다.

2. 정산

정산이란

정산은 수익이나 비용과 관련하여 재정적인 정리와 계산을 수행하는 과정입니다. 8퍼센트에서 처리하는 정산 종류는 6가지입니다.

정산 과정

정산 과정을 간단히 설명하면 아래의 그림과 같습니다.

우당탕탕 정산 자동화 개발-1

  1. 한달 동안 저장된 데이터를 기반으로 정산 대상 계좌들의 정산금을 계산합니다.
  2. 개별적으로 정산 대상 계좌에서 정산 계좌로 정산금을 지급합니다.
  3. 정산 계좌에 모인 정산금을 법인 계좌로 출금합니다.

기존 정산의 방법

한달에 한 번씩 정산 종류별로 수동으로 총 6번 정산합니다. (월별 정산)

기존 정산의 문제점

월별 정산 코드의 문제

과거 데이터 추적 및 수정이 어렵다.

지급 시간이 오래 걸린다.

3. 월별 정산에서 일별 정산으로

월별 정산에서 일별 정산으로 바꾼 이유

일별 정산을 위한 정산 모듈

우당탕탕 정산 자동화 개발-2

정산을 모듈화 한 이유

일별 정산 시스템 아키텍처

아키텍처의 목적은 ‘작업 시간을 단축하고, 주기적으로 정산 모듈을 호출하는 시스템을 구축한다.’ 였습니다.

시도한 방법들

❌ 멀티 프로세싱, 멀티 스레딩

멀티 프로세싱이나 멀티 스레딩을 사용하기 위해서는 독립적인 인스턴스가 필요합니다. 기존의 인스턴스를 사용하거나, 새로 인스턴스를 생성해야 합니다. 그리고 하나의 인스터스의 부하가 커질 수도 있고, 일을 안 하는 시간이 발생할 수 있습니다.

⭕️ 분산화, 비동기 작업, 주기적 호출을 지원해주는 프레임워크

우당탕탕 정산 자동화 개발-3

8퍼센트에서는 셀러리를 활용하여 비동기 작업과 분산을 처리하는 인프라가 구축되어 있었기 때문에 기존 인프라를 재활용하는 것이 효율적이라고 판단했습니다. 그래서 위와 같은 아키텍처를 선택했습니다.

어떻게 일정산을 자동화할 것인가?

자동화를 위해 주기적으로 작업을 실행시켜야 했습니다. 그래서 처음에 생각한 방법은 ‘하루에 한 번씩 정산 작업을 순서에 맞게 1시간 간격을 두고 호출하자’ 였습니다. 그러나 이 방법은 두 가지 문제가 있습니다.

첫 번째 문제

순서에 맞게 호출되지 않으면 오정산이 발생합니다.

두 번째 문제

작업이 실패한다면, 해당 작업부터 다시 호출해야 하고, 이를 수동으로 처리해야 합니다.

위 두 문제가 자동화에 치명적이라고 생각했고, 이를 해결하기 위해 ‘정산 모듈을 순서에 상관없이 하루에 여러 번 호출하는 방식’으로 결정했습니다. 하지만 이 경우에는 불필요한 태스크 호출이 발생하여 중복 작업이 수행될 수 있어서, 모듈은 멱등성을 가지고 있어야 한다고 판단했습니다.

*멱등성: 같은 작업을 여러 번 적용하더라도 한 번 적용했을 때와 상태의 차이가 없는 성질을 의미합니다. 예를 들어, 정산 작업에서 같은 계좌에 대해 중복으로 정산하지 않도록 멱등성을 적용할 수 있습니다.

멱등성을 갖춘 방법

중복 정산을 막기 위해 각 모듈은 멱등성을 필수적으로 가져야 합니다. 그래서 4가지 방법을 사용했습니다.

  1. 모든 모듈은 검증과정을 가지고 있고, 검증과정을 통과해야만 작업을 수행합니다.
  2. 이미 작업이 완료되면 검증 단계에서 실패해 작업을 반복 수행하지 않습니다.
  3. 같은 모듈이 동시에 실행되지 않도록 모듈별 락(lock)을 사용합니다.
  4. 모듈 내의 작업은 전체적으로 트랜잭션화 하여 부분적인 성공이 없도록 합니다.

4. 지급 시간을 최적화

도메인에 대한 배경지식

우당탕탕 정산 자동화 개발-4

도메인과 도메인을 해결하기 위한 비즈니스 로직

도메인 영역의 이해가 필요해서 동작 방식을 간략하게 설명하겠습니다. 정산금을 지급할 때는 농협에서 제공해주는 지급 API를 사용하고 있습니다. 이 API는 돈을 10만 원 단위로 잘라서 지급해야 한다는 조건이 있습니다. 그림과 같이 A 상품에 25만 원의 세금이 발생했다면, 10만 원, 10만 원, 5만 원 이렇게 나누어서 지급 API를 호출 해야 하기 때문에, 총 3번을 호출해야 합니다.

분산화 및 병렬 처리를 한 이유

정산 지급 작업에서는 외부 API 호출이 필요합니다. I/O 바운드 작업은 CPU보다는 주로 외부 자원에 대한 입출력 속도에 의해 제한되는 작업입니다. 그래서 서버 컴퓨터의 성능을 높이는 것보다 분산화 및 병렬 처리가 적합하다고 판단했습니다.

병렬 처리를 위해 고려한 점 1

병렬 처리를 위해서는 각 작업이 상호 간에 영향을 주지 않고 독립적으로 작동해야 합니다. 작업이 상호 간에 의존성이 없고 각각 독립적으로 처리될 수 있다면 병렬 처리가 가능합니다. 상품 태스크들은 서로 독립적으로 처리되며, 지급 API 태스크들도 서로 독립적으로 작동하기 때문에 병렬 처리가 가능하다고 판단했습니다.

병렬 처리를 위해 고려한 점 2

병렬로 처리하는 과정에서 작업 분할, 스케줄링, 동기화, 통신 등에 발생하는 오버헤드를 고려했습니다.

*오버헤드: 어떤 처리를 하기 위해 들어가는 간접적인 처리 시간

우당탕탕 정산 자동화 개발-5

오버헤드로 인해 작업량은 늘어났지만, 수행시간이 축소되었습니다. 상대적으로 오버헤드 비용이 낮고, 정산 작업을 처리할 수 있는 워커 서버들이 있으므로, 병렬처리가 적합하다고 판단했습니다.

비동기로 처리되는 지급 API 모두 다 성공했는지 어떻게 확인할까?

우당탕탕 정산 자동화 개발-6

지급 태스크를 호출하는 프로세스에서는 비동기로 처리된 지급이 성공했는지 알 수 없습니다. 그래서 지급 API 태스크가 성공한다면, DB에 처리 결과를 기록합니다. 이후 지급 확인 단계에서 DB에 기록된 비동기 작업의 처리 결과를 주기적으로 폴링하여 확인하고 있습니다.

지급 태스크를 여러 번 호출해서 동시에 실행된다면?

def process(self):
   """지급 처리"""
    with advisory_lock(lock_id=self.lock_id, wait=False) as acquired:
        if acquired:
            do_something()

지급을 두 번하는 이슈를 방지하기 위해 애플리케이션이 공유하는 DB에 advisory_lock을 사용해 락을 생성 했습니다. 그래서 하나의 태스크가 작업 중이라면, 같은 작업을 하는 태스크는 중복된 작업을 수행하지 않습니다.

최종 결과

우당탕탕 정산 자동화 개발-7

5. 성과

  1. 일별 정확성 향상

    월별 정산 방식보다 일별 정산은 정확하지 않은 데이터를 당일에 추적하고 수정하기 쉽습니다.

  2. 지급 속도 최적화

    시간이 오래 걸리는 I/O 작업을 병렬처리 하여 최대 4배 빠르게 했습니다.

  3. 중복 정산 방지

    모듈의 멱등성을 활용하여 중복 작업을 방지하였습니다.

  4. 자동화

    일정 간격으로 정산 모듈을 자동으로 호출하여 작업을 처리할 수 있습니다.

6. 마무리

정산 시스템을 개선하는 과정에서 여러 가지 고민과 시행착오를 겪었습니다. 하지만 팀장님의 리더십과 지도 덕분에 길을 잃을 때마다 다시 제대로 된 방향을 잡을 수 있었고, 이 덕분에 오차 없는 일정산 시스템을 만들 수 있었습니다. 하지만 아직 미흡하고, 고민해 볼 포인트가 많아서 지속해서 개선할 예정입니다. 읽어 주셔서 감사합니다.

글 작성을 도와주신 보성님과 재영님 감사합니다.