
큐시즘 33기에서 뭉치장 백엔드 개발을 진행했다.
뭉치장은 베이커리 웨이팅이 싫은 빵 덕후들이 원하는 매장의 빵을 목표 수량 달성 조건으로 함께 모아 공동구매하고, 지정 픽업일에 웨이팅 없이 직접 수령할 수 있는 베이커리 단체구매 중개 서비스다.
백엔드에서는 단순 CRUD보다, 공동구매라는 도메인 특성상 결제와 상태 변화가 실제 비즈니스 정합성에 직접 연결되는 문제가 많았다.
특히 결제가 됐는지보다 결제가 되어도 시스템 상태가 틀어지지 않는지, 운영 환경에서 같은 작업이 중복 실행되지 않는지를 더 중요하게 생각했다.
1. 결제 승인에서 중복 처리와 초과 참여를 막았다
공동구매 결제는 일반적인 단건 주문 결제보다 상태가 복잡했다.
결제가 성공하더라도 그 시점에 공구가 아직 참여 가능한 상태인지, 남은 수량이 충분한지, 이미 같은 주문이 처리된 적은 없는지까지 함께 확인해야 했다.
특히 위험했던 건 두 가지였다.
- 여러 사용자가 거의 동시에 같은 공구에 참여하는 상황
- 사용자 결제 완료 요청과 PG 웹훅이 비슷한 시점에 함께 들어오는 경우
이 상황을 제대로 막지 않으면 동일 주문이 두 번 승인되거나, 실제보다 더 많은 수량이 반영될 수 있었다.
그래서 승인 로직은 공구 단위 Redis 분산락으로 먼저 감싸고, 그 안에서 다시 주문 데이터를 비관적 락으로 조회하는 방식으로 구성했다.
인스턴스 간 경쟁은 Redis에서 먼저 줄이고, 실제 데이터 수정 경쟁은 DB 락으로 마무리하는 구조였다.
또 수량 증가 역시 단순 조회 후 저장이 아니라, 남은 수량이 충분할 때만 반영되는 조건부 update 방식으로 처리했다.
애플리케이션 단에서 여러 번 검증하더라도 마지막 순간에는 DB가 한 번 더 막아주는 구조를 의도했다.
이 과정에서 가장 중요하게 본 건 승인 성공률 자체보다 정합성을 깨지 않는 결제 흐름이었다.
결제 시스템은 성공했다는 결과보다, 그 성공이 시스템 상태를 망가뜨리지 않도록 만드는 설계가 먼저라는 걸 알게되었다.
2. 취소와 환불도 상태가 꼬이지 않도록 설계했다
처음에는 결제 승인 로직이 가장 어렵다고 생각했는데, 실제로는 취소와 환불 쪽이 더 까다로웠다.
공동구매 서비스에서는 목표 달성 전 취소 가능, 달성 후 환불 제한, 목표 미달 시 자동 환불 처리처럼 상태 규칙이 얽혀 있었기 때문이다.
문제는 취소 요청이 한 경로로만 들어오지 않는다는 점이었다.
사용자가 직접 취소할 수도 있고, PG 웹훅이 재전송될 수도 있고, 시스템이 목표 미달 공구를 실패 처리하면서 자동 환불 대상으로 바꿀 수도 있었다.
이때 같은 참여건이 여러 번 환불 처리되면 수량 차감이 중복되거나 상태가 쉽게 꼬일 수 있었다.
그래서 취소/환불에서는 참여 상태, 주문 상태, 결제 상태, 공구 상태를 따로 보지 않고 하나의 흐름으로 맞추는 방향으로 설계했다.
이미 환불된 건은 멱등하게 처리하고, 실제 환불 반영이 필요한 경우에만 최신 공구 상태를 다시 확인한 뒤 수량 차감과 상태 전환을 진행하도록 구성했다.
이 작업을 하면서 느낀 건, 결제 도메인은 승인 API 하나만 잘 만든다고 끝나지 않는다는 점이었다.
오히려 실제 서비스에서는 취소와 환불이 길게 이어지는 라이프사이클 전체를 얼마나 안정적으로 관리하느냐가 더 중요했다.
3. Redis로 실행 제어와 실시간 상태 관리를 함께 풀었다
개발 단계에서는 스케줄러가 단순해 보여도, 운영 환경에서는 중복 실행이 쉽게 장애로 이어질 수 있었다.
서버가 여러 대인 환경에서 같은 스케줄이 동시에 실행되면, 동일 공구를 두 번 실패 처리하거나 같은 환불 대상을 중복 반영할 수 있기 때문이다.
그래서 공구 마감 상태 전환이나 환불 대기 처리처럼 정합성에 직접 영향을 주는 작업은 단순 @Scheduled에만 의존하지 않고, Redis 분산락으로 인스턴스 간 단일 실행을 보장하는 구조로 설계했다.
또 같은 JVM 안에서도 이전 작업이 끝나지 않았으면 다음 실행이 겹치지 않도록 제어해, 한 서버 내부와 여러 서버 환경 모두에서 중복 실행 가능성을 줄였다.
Redis는 스케줄러 락뿐 아니라, 실시간 활성 조회자 수를 관리하는 용도에도 활용했다.
공구 상세 화면에서 지금 몇 명이 보고 있는지는 사용자 반응을 보여주는 지표였고, 이 값은 짧은 주기로 계속 바뀌는 데이터였다.
이런 성격의 데이터를 heartbeat마다 RDB에 반영하는 방식은 비용이 크고, 만료 처리도 번거롭다고 판단했다.
그래서 이 기능은 처음부터 Redis로 구현하는 방향으로 잡았다.
사용자별 마지막 활동 시간을 기준으로 만료 대상은 제거하고, 현재 사용자 상태는 갱신하고, 활성 인원 수는 바로 계산할 수 있도록 구성해 짧은 생명주기의 실시간 상태를 Redis에서 처리했다.
이 과정에서 Redis는 단순 캐시가 아니라, 빠르게 변하고 곧 사라지는 데이터를 다루기 위한 저장소에 가까웠다.
기능을 구현하면서도 데이터 성격에 따라 저장소를 다르게 가져가야 한다는 점을 더 분명하게 확인할 수 있었다.
4. 핵심 트랜잭션과 후속 작업의 경계를 분리했다
회원가입, 로그인, 공구 상태 전환, 결제 성공 같은 기능은 본 작업 자체도 중요했지만, 그 뒤에 알림 전송, 메트릭 기록, 외부 연동 같은 후속 작업이 함께 따라붙었다.
이 부가 작업들을 핵심 트랜잭션 안에 모두 묶어두면, 외부 요인 때문에 본 로직이 느려지거나 실패 전파 범위가 커질 수 있었다.
반대로 트랜잭션이 확정되기 전에 알림이나 메트릭이 먼저 처리되면, 롤백된 데이터를 기준으로 후속 작업이 실행되는 문제도 생길 수 있었다.
그래서 인증 메트릭은 커밋 이후에만 기록하고, 일부 알림은 트랜잭션이 정상적으로 끝난 뒤 별도 흐름에서 동작하도록 분리했다.
또 알림 발송은 비동기 executor를 두고 처리해 핵심 비즈니스 로직이 불필요하게 오래 점유되지 않도록 구성했다.
이 작업을 하면서 트랜잭션 경계를 어디까지 가져갈지 결정하는 일이 단순 구현보다 더 중요하다는 걸 느꼈다.
핵심 로직을 짧고 안정적으로 유지하고, 후속 작업은 성공 확정 이후 분리하는 방식이 전체 흐름을 더 안전하게 만들었다.
5. 알림도 운영 가능한 흐름으로 만들었다
알림은 부가 기능처럼 보이지만, 중복 발송이나 유실이 생기면 사용자 경험이 크게 나빠질 수 있다.
특히 결제 성공, 공구 달성, 요청 반려 같은 이벤트는 한 번만 정확하게 전달되는 게 중요했다.
문제는 이벤트가 재발행되거나, 외부 발송 과정이 일시적으로 실패할 수 있다는 점이었다.
그래서 발송 이력을 별도 테이블로 관리하고, 동일 이벤트가 중복 발송되지 않도록 유니크 기준을 두어 제어했다.
이미 발송 이력이 있는 경우에는 스킵하고, 실패한 건은 재시도 횟수와 다음 시점을 계산해 다시 처리할 수 있도록 구성했다.
이 구조는 단순히 알림을 보낸 수준이 아니라, 발송 이력과 실패 흐름까지 관리할 수 있는 형태에 가까웠다.
성공과 실패, 재시도 기록이 남기 때문에 운영 중 추적과 대응도 훨씬 수월해졌다.
6. 결제 기능을 운영 지표로 관찰할 수 있게 했다
구현을 하면서 자연스럽게 관심이 갔던 부분은 기능 추가보다도, 이 로직이 실제 운영 상황에서도 안전하게 동작하는지였다.
특히 결제는 장애가 나면 곧바로 사용자 신뢰와 연결되기 때문에, 일반 API와 같은 기준으로 볼 수 없다고 생각했다.
그래서 결제 완료 API, 웹훅 처리, 인증/인가 오류, 응답 지연처럼 도메인 단위로 확인해야 하는 운영 지표를 따로 보려 했다.
단순 서버 up/down이 아니라, 실제 결제 흐름이 얼마나 안정적으로 처리되고 있는지를 기준으로 관측해야 의미가 있다고 봤다.
이 부분은 아직 끝난 작업이라기보다, 더 다듬어가고 있는 영역이기도 하다.
특히 앞으로는 부하테스트를 통해 동시 결제 상황, 웹훅 재전송, 트래픽 증가 시 병목 지점을 실제로 검증해보려고 한다.
구현 단계에서 정합성을 고려한 설계를 했다면, 다음 단계에서는 그 설계가 실제 부하에서도 버티는지 확인하는 작업까지 연결해보는 게 목표다.
마무리
이번 프로젝트에서는 기능을 추가하는 것 자체보다, 상태가 복잡한 도메인을 어떻게 안정적으로 운영 가능한 구조로 만들 것인지에 대한 고민이 더 컸다.
결제 승인과 환불, 스케줄러 중복 실행 방지, Redis 활용, 트랜잭션 경계 분리, 알림 재시도, 운영 지표 설계까지 모두 결국 같은 방향의 문제였다.
단순히 API가 동작하는 수준을 넘어서, 동시에 요청이 몰리거나 외부 이벤트가 중복으로 들어오더라도 시스템 상태가 쉽게 망가지지 않도록 설계하는 경험을 할 수 있었다.
이번 프로젝트를 통해 백엔드는 기능 구현만이 아니라, 정합성과 운영 안정성을 끝까지 책임지는 역할이라는 점을 더 분명하게 배웠다.
'후기' 카테고리의 다른 글
| [한국대학생IT경영학회] 밋업 프로젝트 중간 회고 (1) | 2026.05.10 |
|---|---|
| [한국대학생IT경영학회] 큐시즘 33기 개발 백엔드 파트 합격 후기 (1) | 2026.03.23 |