왜 금융 시스템에서는 Redis를 사용하는가

11 minute read

Published:

목차

  1. 작성 이유
  2. 금융 시스템에서 가장 중요한 가치
  3. 멱등성이란
  4. Redis란
  5. 금융 시스템에서 Redis는 어디에 있을까?
  6. 실제 금융 시스템에서의 Redis 활용 사례
    • 세션 저장소(Session Storage)
    • 인증 토큰(Authentication Token) 저장소
    • 사용자 프로필 저장소(User Profile Storage)
    • 캐시 프리페칭(Cache Prefetching)
  7. 멱등성 처리 시스템 아키텍처
    • Architecutre Overview
    • 시스템 내 Redis의 구체적 역할
    • 멱등성 보장을 위한 3단계 로직
      • SUCCESS (이미 처리된 요청)
      • PROCESSING (처리 중인 요청)
      • NEW (최초 요청)
  8. Spring Boot + Redisson 분산 락 구현 예시
    • 금융 시스템에서 각 기술의 역할
    • 코드
    • 코드의 금융 아키텍처적 포인트
    • 흐름도

작성 이유

입금, 출금, 송금과 같은 금융 거래는 단순한 데이터 처리가 아니라 데이터 정합성과 원자성이 반드시 보장되어야 한다. 예를 들어 출금은 성공했지만 입금이 실패한다면 치명적인 문제가 발생할 수 있다.

Kafka를 학습하며 왜 금융 시스템이 이벤트 기반 아키텍처를 사용하는지를 고민하게 되었고, 그 과정에서 이벤트 전달만큼 중요한 것이 멱등성(Idempotency) 이라는 점을 알게 되었다. 금융 시스템에서는 동일한 거래가 중복 처리되는 것을 막아야 하기 때문이다.

이번 글에서는 금융 시스템에서 Redis가 멱등성을 보장하는 데 어떤 역할을 하는지 살펴본다.


금융 시스템에서 가장 중요한 가치

금융 시스템에서 가장 중요한 가치는 돈을 정확하고 안전하게 처리하는 것이다. 사용자는 화려한 기능보다 내 돈이 사라지지 않고, 중복 결제되지 않으며, 정확하게 관리된다는 신뢰를 기대한다.

이를 위해 금융 시스템은 정확성(Correctness), 일관성(Consistency), 신뢰성(Reliability), 보안(Security) 을 핵심 가치로 삼으며, 이를 대규모 환경에서도 유지하기 위해 멱등성(Idempotency), 낮은 지연 시간(Low Latency), 확장성(Scalability) 을 고려한 기술과 아키텍처를 사용한다.


멱등성이란

멱등성(Idempotency)은 동일한 요청이 여러 번 수행되더라도 시스템의 최종 상태가 한 번 수행한 것과 동일하게 유지되는 성질을 의미한다.

예를 들어 송금 요청이 네트워크 문제로 여러 번 전달되더라도 실제 출금은 한 번만 이루어지고, 거래 내역도 중복 생성되지 않아야 한다.

즉, 멱등성은 중복 요청으로부터 데이터 정합성을 보장하는 금융 시스템의 핵심 설계 원칙이다.


Redis란

멱등성은 다양한 방법으로 구현할 수 있지만, Redis는 가장 널리 사용되는 기술 중 하나이다.

Redis는 메모리 기반 저장소로 요청을 빠르게 조회할 수 있으며, SETNX와 같은 원자적 연산을 통해 중복 요청을 효과적으로 차단한다. 또한 TTL(Time To Live) 을 이용해 만료된 멱등성 키를 자동으로 삭제할 수 있어 관리도 효율적이다.

이러한 특징 덕분에 Redis는 금융 시스템을 비롯한 대규모 서비스에서 멱등성 구현에 널리 활용된다.


금융 시스템에서 Redis는 어디에 있을까?

오늘날 은행의 가장 중요한 지점은 더 이상 건물이 아니라 고객의 스마트폰이다. 모바일 뱅킹이 금융 서비스의 중심이 되면서 고객은 계좌 조회, 송금 등 대부분의 업무를 언제 어디서나 처리한다.

하지만 모바일 뱅킹의 확산은 금융 시스템에 새로운 과제를 안겨주었다. 오랜 기간 운영된 레거시 시스템은 증가하는 트래픽과 실시간 서비스 요구를 모두 만족시키기 어렵기 때문이다.

대표적인 과제는 다음과 같다.

낮은 지연 시간(Low Latency)

고객은 계좌 조회나 송금과 같은 기능에서 즉각적인 응답을 기대한다.

확장성(Scalability)

급여일이나 이벤트처럼 트래픽이 급증하는 상황에서도 안정적으로 서비스를 제공해야 한다.

신뢰성(Reliability)

서비스 장애나 성능 저하는 고객 신뢰와 직결되므로 항상 안정적인 서비스가 요구된다.

데이터 사일로(Data Silos)

여러 시스템에 흩어진 데이터를 통합해 웹, 모바일 등 모든 채널에서 일관된 금융 경험을 제공해야 한다.

이러한 과제를 해결하기 위해 금융권은 실시간 데이터 처리, 캐싱, 멱등성 관리, 이상 거래 탐지(Fraud Detection) 등 다양한 영역에서 Redis를 활용한다. Redis는 단순한 캐시를 넘어 금융 시스템의 성능과 안정성을 높이는 핵심 인프라로 사용되고 있다.


실제 금융 시스템에서의 Redis 활용 사례

세션 저장소(Session Storage)

세션 저장소는 사용자가 로그인한 후 로그아웃하거나 세션이 만료될 때까지 사용자 상태를 저장하는 공간이다. 이를 통해 애플리케이션은 사용자의 로그인 상태와 활동을 지속적으로 관리할 수 있다.

Redis를 사용하는 이유

  • 메모리 기반으로 빠른 조회 속도 제공
  • 다양한 자료구조 지원
  • TTL을 통한 세션 자동 만료
  • 수평 확장 지원

인증 토큰(Authentication Token) 저장소

인증 토큰은 사용자가 매번 로그인하지 않고도 안전하게 서비스를 이용할 수 있도록 하는 인증 정보이다. 금융 서비스와 오픈뱅킹에서는 토큰을 이용해 일정 시간 동안만 안전하게 API 접근 권한을 부여한다.

Redis를 사용하는 이유

  • 빠른 인증 처리
  • TTL을 통한 토큰 자동 만료
  • 복제와 클러스터링을 통한 높은 가용성

사용자 프로필 저장소(User Profile Storage)

사용자 프로필에는 계좌 설정, 사용자 환경설정, 기기 정보 등이 저장된다. 금융사는 이를 활용해 개인화 서비스를 제공하고 이상 거래(Fraud Detection)를 실시간으로 탐지한다.

Redis를 사용하는 이유

  • 빠른 데이터 조회
  • 다양한 자료구조 지원
  • 실시간 개인화 및 이상 거래 탐지에 적합

캐시 프리페칭(Cache Prefetching)

캐시 프리페칭은 사용자가 요청하기 전에 필요한 데이터를 Redis에 미리 저장해 두는 방식이다. 금융사는 이를 활용해 여러 시스템에 분산된 계좌, 카드, 대출, 투자 정보를 하나의 대시보드에서 빠르게 제공한다.

또한 Redis Data Integration(RDI) 을 이용하면 원본 데이터 변경 사항을 Redis에 실시간으로 반영하여 최신 상태를 유지할 수 있다.

앞의 세 가지가 사용자 상태(Session, Authentication, Profile) 를 관리하는 용도였다면, Cache Prefetching은 여러 금융 시스템에 분산된 데이터를 Redis에 미리 적재하여 통합 조회 성능을 높이는 아키텍처 패턴에 가깝다. 금융권에서 Redis를 캐시 계층(Cache Layer) 으로 활용하는 대표적인 사례 중 하나이다.

Redis를 사용하는 이유

  • 빠른 응답 속도
  • 여러 시스템의 데이터 통합
  • 높은 확장성과 가용성

멱등성 처리 시스템 아키텍처

Architecutre Overview

  • 클라이언트가 Spring Boot 기반 결제 API를 호출한다.
  • 백엔드는 요청을 검증한 후 Stripe의 PaymentIntent 또는 Checkout Session을 생성한다.
  • Redis에 멱등성 키(Idempotency Key)를 저장하여 동일한 결제 요청이 중복 처리되지 않도록 한다.
  • 결제 정보와 거래 내역을 데이터베이스에 저장한다.
  • Stripe가 카드 결제를 처리한다.
  • Stripe는 Webhook을 통해 최종 결제 결과를 백엔드에 전달한다.
  • 백엔드는 Webhook 이벤트를 기반으로 결제 상태를 최종적으로 업데이트한다.

시스템 내 Redis의 구체적 역할

Idempotency Key 검증

결제 요청이 들어오면 Spring Boot 애플리케이션은 메인 데이터베이스를 조회하기 전에 Redis에서 Idempotency Key의 존재 여부를 먼저 확인한다.

이미 동일한 키가 존재한다면 중복 요청으로 판단하여 결제를 다시 수행하지 않는다.

원자적 상태 관리 (Store Idempotency)

처음 들어온 요청이라면 SETNX(Set if Not Exists) 와 같은 원자적 연산을 이용해 해당 키를 PROCESSING 상태로 저장한다. 이를 통해 동일한 요청이 동시에 들어와도 하나의 요청만 실제 결제를 수행할 수 있다.

결제가 완료되면 SUCCESS 상태와 응답 데이터를 함께 저장한다. 이후 동일한 Idempotency Key로 요청이 들어오면 결제 로직을 다시 실행하지 않고 Redis에 저장된 결과를 그대로 반환한다.

즉, Redis는 중복 결제를 방지하고 동일한 요청에 항상 동일한 응답을 제공하는 방파제 역할을 수행한다.

멱등성 보장을 위한 3단계 로직

단순히 Redis에 키가 존재하는지만 확인하면 동시성(Race Condition) 문제가 발생할 수 있다. 따라서 금융 시스템에서는 SETNX와 같은 원자적 연산이나 분산 락(Distributed Lock) 을 이용해 상태를 관리한다.

[클라이언트 요청] ➔ Idempotency-Key 추출
                     │
         ┌───────────┴───────────┐
      (존재함)                (존재하지 않음)
         │                       │
 [Redis 상태 확인]        [Redis에 키 선점 (PROCESSING)]
   ├── PROCESSING: "잠시 후 재시도" 예외 반환       │
   └── SUCCESS: 기존에 저장된 성공 응답 반환  [비즈니스 로직 및 DB 반영]
                                 │
                          [Redis 상태 업데이트]
                          (PROCESSING ➔ SUCCESS)
                                 │
                           [최종 응답 반환]

SUCCESS (이미 처리된 요청)

동일한 Idempotency Key 가 이미 성공적으로 처리된 상태이다.

비즈니스 로직이나 DB 트랜잭션을 다시 수행하지 않고, Redis에 저장된 응답을 그대로 반환한다. 이를 통해 동일한 요청에 항상 동일한 결과를 제공한다.

PROCESSING (처리 중인 요청)

동일한 요청이 현재 처리 중인 상태이다.

예를 들어 첫 번째 요청이 PG 연동이나 DB 트랜잭션을 수행하는 동안 동일한 요청이 다시 들어오면, 새로운 결제를 수행하지 않고 409 Conflict와 같은 응답을 반환하거나 처리가 끝날 때까지 대기시킨다.

NEW (최초 요청)

Redis에 키가 없다면 최초 요청으로 판단한다.

먼저 PROCESSING 상태를 원자적으로 저장한 뒤 비즈니스 로직을 수행한다. 처리가 완료되면 상태를 SUCCESS로 변경하고 응답을 저장한다. 이후 동일한 요청이 들어오면 저장된 응답을 반환하여 멱등성을 보장한다.


Spring Boot + Redisson 분산 락 구현 예시

금융 시스템에서는 락 획득에 실패할 때 Redis에 계속 요청을 보내는 스핀 락(Spin Lock) 방식보다, Redis Pub/Sub을 이용해 락 해제 알림을 받은 뒤 다시 락을 획득하는 Redisson 분산 락이 많이 사용된다.

이 방식은 불필요한 Redis 접근을 줄여 높은 동시성 환경에서도 Redis의 부하를 효과적으로 완화할 수 있다.

금융 시스템에서 각 기술의 역할

분산 환경에서는 하나의 기술만으로 중복 결제를 완벽하게 방지할 수 없다. Kafka, Redis, 그리고 Idempotency 상태 관리가 각자의 역할을 수행하며 함께 동작한다.

Kafka

Kafka는 결제 이벤트를 여러 서비스에 안정적으로 전달하는 역할을 한다. 다만, 동일한 이벤트의 중복 처리까지 보장하지는 않는다.

Redis 분산 락(Redisson)

Redis 분산 락은 동일한 Idempotency Key에 대해 하나의 서버만 비즈니스 로직을 수행하도록 보장하여, 여러 서버가 동시에 동일한 결제를 처리하는 Race Condition을 방지한다.

Idempotency 상태(PROCESSING / SUCCESS)

Idempotency 상태는 요청이 처리 중인지(PROCESSING), 이미 완료되었는지(SUCCESS) 를 판단하는 기준이다. 이미 처리된 요청이라면 기존 응답을 반환하여 중복 실행을 방지한다.

Redis는 단순한 캐시가 아니라, 세션 관리, 인증 토큰 저장, 캐시 계층, 그리고 멱등성 보장까지 금융 시스템의 신뢰성을 뒷받침하는 핵심 인프라로 활용된다.

        Kafka
          │
          ▼
   Redis Distributed Lock
          │
          ▼
  Payment Business Logic
          │
          ▼
 Redis (PROCESSING → SUCCESS)
          │
          ▼
       Response

코드

@Component
@RequiredArgsConstructor
@Slf4j
public class IdempotencyFacade {

    private final RedissonClient redissonClient;
    private final PaymentService paymentService;

    // Redis 분산 락 Key Prefix
    private static final String LOCK_PREFIX = "lock:idempotency:";

    // 멱등성 상태 저장 Key Prefix
    private static final String KEY_PREFIX = "idempotency:";

    public PaymentResponse processPayment(String idempotencyKey, PaymentRequest request) {

        // 동일한 idempotencyKey에 대해 하나의 요청만 처리하도록 분산 락 생성
        RLock lock = redissonClient.getLock(LOCK_PREFIX + idempotencyKey);

        // 요청 처리 상태(PROCESSING / SUCCESS)를 저장할 Redis Bucket
        RBucket<String> statusBucket = redissonClient.getBucket(KEY_PREFIX + idempotencyKey);

        try {

            // 최대 5초 동안 락 획득을 대기
            // leaseTime을 지정하지 않았기 때문에 Redisson Watchdog가 자동으로 TTL을 연장한다.
            // (작업이 오래 걸려도 락이 중간에 만료되지 않음)
            boolean locked = lock.tryLock(5, TimeUnit.SECONDS);

            // 이미 동일 요청이 수행 중이라 락을 획득하지 못한 경우
            if (!locked) {
                throw new ConflictException("동일한 요청이 처리 중입니다.");
            }

            // Redis에 저장된 현재 멱등성 상태 조회
            String status = statusBucket.get();

            // 이미 성공한 요청이라면
            // 실제 결제 로직을 다시 수행하지 않고 기존 응답을 반환
            if ("SUCCESS".equals(status)) {
                log.info("멱등성 요청 감지. 기존 응답 반환. key={}", idempotencyKey);
                return getCachedResponse(idempotencyKey);
            }

            // 다른 서버 또는 스레드에서 이미 처리 중이라면
            // 중복 실행을 막기 위해 요청 거부
            if ("PROCESSING".equals(status)) {
                throw new ConflictException("현재 처리 중인 요청입니다.");
            }

            // 최초 요청이므로 처리 중 상태 저장
            // TTL을 두어 장애가 발생해도 영구적으로 PROCESSING 상태가 남지 않도록 한다.
            statusBucket.set("PROCESSING", 30, TimeUnit.MINUTES);

            // 실제 결제 비즈니스 로직 수행
            PaymentResponse response = paymentService.execute(request);

            // 성공 응답을 Redis에 저장하여
            // 동일 요청이 다시 들어오면 그대로 반환할 수 있도록 한다.
            cacheResponse(idempotencyKey, response);

            // 결제가 정상적으로 완료되었음을 기록
            statusBucket.set("SUCCESS", 30, TimeUnit.MINUTES);

            return response;

        } catch (InterruptedException e) {

            // 락 대기 중 인터럽트 발생
            Thread.currentThread().interrupt();
            throw new SystemException("락 대기 중 인터럽트가 발생했습니다.");

        } catch (Exception e) {

            // 결제 실패 시 PROCESSING 상태 제거
            // 이후 동일 요청이 다시 들어오면 재시도할 수 있도록 한다.
            statusBucket.delete();

            throw e;

        } finally {

            // 현재 스레드가 락을 보유한 경우에만 안전하게 락 해제
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }

        }
    }
}

코드의 금융 아키텍처적 포인트

tryLock()을 통한 동시성 제어

Kafka 환경에서는 재시도(Retry)나 장애 복구 과정에서 동일한 결제 이벤트가 여러 번 전달될 수 있다. 이때 여러 Consumer가 동시에 같은 결제를 처리하려고 시도하면 중복 결제가 발생할 수 있다.

RedissontryLock()을 사용하면 동일한 Idempotency Key에 대해 하나의 스레드만 비즈니스 로직을 수행할 수 있다. Kafka는 이벤트를 안정적으로 전달하지만 동일 자원에 대한 상호 배제(Mutual Exclusion) 는 보장하지 않기 때문에 Redis 분산 락이 필요하다.

Redis 기반 분산 락(Distributed Lock)

여러 애플리케이션 서버가 동일한 Redis를 공유하면, 하나의 락은 하나의 서버만 획득할 수 있다.

따라서 동일한 Idempotency Key로 여러 요청이 동시에 들어오더라도 가장 먼저 락을 획득한 서버만 결제를 수행하고, 나머지 요청은 대기하거나 실패한다. 이를 통해 분산 환경에서도 안전한 동시성 제어가 가능하다.

PROCESSING 상태를 이용한 멱등성 보장

분산 락만으로는 모든 중복 요청을 방지할 수 없다.

예를 들어 락을 획득한 요청이 PG사와 통신하는 동안 동일한 요청이 다시 들어올 수 있다. 이때 Redis에 PROCESSING 상태를 저장해 두면 후속 요청은 현재 처리 중인 거래임을 확인하고 중복 실행을 막을 수 있다.

결제가 완료되면 상태를 SUCCESS로 변경하고 응답을 저장한다. 이후 동일한 요청이 들어오면 저장된 응답을 반환하여 멱등성을 보장한다.

즉, Redis 분산 락은 동시성을 제어하고, PROCESSINGSUCCESS 상태는 요청의 생명주기를 관리한다. 두 가지를 함께 사용해야 분산 환경에서도 중복 결제를 안정적으로 방지할 수 있다.

흐름도

아래 흐름도를 보면 Kafka는 이벤트 전달, Redis는 동시성 제어, Idempotency는 중복 실행 방지를 담당하며, 세 기술이 함께 안전한 결제 시스템을 구성하는 과정을 확인할 수 있다.

결제 요청
     │
     ▼
 Kafka 이벤트 전달
     │
     ▼
 Redis 분산 락 (동시성 제어)
     │
     ▼
 PROCESSING 상태 저장
     │
     ▼
 결제 비즈니스 로직 수행
     │
     ▼
 SUCCESS 상태 저장 및 응답 캐싱
     │
     ▼
 동일 요청 재발생
     │
     ▼
 기존 응답 반환 (멱등성 보장)

참고

Gemini
ChatGPT
Claude.ai
코어뱅킹과 Kafka는 어떻게 연결되는가
왜 Redis를 학습하려 하는가
How leading financial institutions use Redis to drive growth
From tap to transaction: power real-time mobile banking with Redis
Building a Payment System with Spring Boot, Stripe, Redis Idempotency & Webhooks (Complete Guide)