AWS 배포, Docker, HTTPS, PortOne V2 결제, Webhook, 포인트 결제, 환불, 재고 정합성, 정기 구독 결제까지 CH4 프로젝트에서 나올 수 있는 면접 질문과 핵심 답변을 정리했습니다.
A.
EC2, RDS, S3는 각각 역할이 다르기 때문에 함께 사용했습니다.
EC2에 애플리케이션을 배포하고, 데이터는 RDS에 저장하며, 파일은 S3에 저장해서 서버와 저장소의 책임을 분리했습니다.
EC2와 RDS는 같은 VPC 안에 두고, RDS는 외부에서 직접 접근하지 못하도록 설정했습니다.
보안 그룹은 보통 다음과 같이 구성합니다.
즉, 사용자는 EC2에만 접근하고, DB는 EC2를 통해서만 접근할 수 있게 구성합니다.
RDS는 관리형 서비스라 편리하지만 비용이 더 들 수 있고, 세부 설정을 완전히 직접 제어하기는 어렵습니다.
반대로 EC2에 직접 MySQL을 설치하면 비용이나 설정 자유도는 장점이 될 수 있지만, 백업, 장애 복구, 보안 패치, 모니터링을 직접 관리해야 해서 운영 부담이 커집니다.
A.
Docker를 사용하면 애플리케이션 실행 환경을 이미지로 고정할 수 있습니다.
개발 환경과 배포 환경의 Java 버전, 라이브러리, 실행 방식 차이를 줄일 수 있고, 어디서 실행하더라도 동일한 방식으로 애플리케이션을 띄울 수 있습니다.
즉, 배포 환경의 차이로 발생하는 문제를 줄이기 위해 사용했습니다.
이미지 크기를 줄이기 위해 다음을 고려할 수 있습니다.
이렇게 하면 최종 이미지에는 애플리케이션 실행에 필요한 파일만 포함할 수 있습니다.
CI/CD 파이프라인에서 보통 test 단계와 deploy 단계를 분리합니다.
./gradlew test가 실패하면 해당 job이 실패 상태가 되고, 그 다음 배포 job이 실행되지 않도록 설정합니다.
즉, 테스트를 통과한 코드만 Docker 이미지 빌드와 배포 단계로 넘어가게 해서 안정성을 높입니다.
A.
HTTPS는 클라이언트와 서버 사이의 통신을 암호화하기 위해 적용합니다.
로그인 정보, 결제 정보, 개인정보가 네트워크에서 평문으로 노출되는 것을 막을 수 있습니다.
적용 방식은 보통 다음과 같습니다.
Let's Encrypt는 무료 인증서를 발급받아 EC2나 Nginx에 직접 적용하는 방식입니다.
ACM은 AWS Certificate Manager에서 인증서를 발급받고, ALB나 CloudFront 같은 AWS 서비스에 연결하는 방식입니다.
| 구분 | Let's Encrypt | ACM |
|---|---|---|
| 적용 위치 | EC2, Nginx 등 | ALB, CloudFront 등 |
| 관리 방식 | 직접 갱신 설정 필요 | AWS 서비스와 연동 시 관리 편리 |
| 사용 상황 | EC2 단독 구성 | ALB, CloudFront 사용 구성 |
EC2에 Nginx를 설치하고, Certbot으로 Let's Encrypt 인증서를 발급받아 Nginx에 적용할 수 있습니다.
흐름은 다음과 같습니다.
A.
Client Confirm 방식은 클라이언트에서 결제창을 띄우고, 서버에서 최종 검증과 확정 처리를 하는 방식입니다.
전체 흐름은 다음과 같습니다.
paymentId 등을 서버로 전달합니다.PAID로 변경합니다.핵심은 클라이언트 응답만 믿지 않고, 서버가 최종적으로 금액과 상태를 검증한 뒤 결제를 확정하는 것입니다.
클라이언트 값은 사용자가 조작할 수 있기 때문입니다.
상품 금액, 할인 금액, 포인트 사용 금액을 클라이언트에서만 계산하면 결제 금액을 임의로 낮춰 보낼 위험이 있습니다.
따라서 최종 결제 금액은 반드시 서버가 DB 기준으로 다시 계산하고 검증해야 합니다.
결제는 PG에서는 성공했지만, 내부 DB에는 반영되지 않은 상태가 될 수 있습니다.
이를 대비해 결제 확정 로직은 멱등하게 설계해야 합니다.
예를 들어 같은 paymentId로 다시 요청이 들어와도 이미 처리된 결제라면 중복 처리하지 않고 기존 결과를 반환합니다.
또한 Webhook이나 결제 상태 재조회 배치를 통해 PG 결제 상태와 내부 결제 상태를 다시 맞출 수 있습니다.
A.
Webhook은 같은 이벤트가 여러 번 올 수 있다고 가정하고 멱등하게 처리해야 합니다.
보통 다음 기준으로 중복 처리를 막습니다.
webhookEventIdpaymentIdorderId처리 흐름은 다음과 같습니다.
핵심은 같은 Webhook이 여러 번 와도 결제, 포인트, 재고가 중복 변경되지 않게 하는 것입니다.
Webhook 요청을 받은 직후, 비즈니스 로직을 실행하기 전에 처리합니다.
서명 검증에 실패한 요청은 신뢰할 수 없는 요청이므로 결제 상태 변경, 포인트 환불, 재고 복구 같은 내부 로직을 실행하면 안 됩니다.
즉, HMAC 검증은 Webhook 처리의 가장 앞단에서 수행해야 합니다.
외부 결제사의 Webhook 재전송 정책에만 의존하면 위험합니다.
서버 장애, 네트워크 장애, 응답 지연 등으로 Webhook이 누락되거나 늦게 도착할 수 있기 때문입니다.
따라서 Webhook은 보조 수단으로 보고, 서버에서는 결제 단건 조회 API나 상태 동기화 배치를 통해 결제 상태를 다시 확인할 수 있게 설계하는 것이 안전합니다.
A.
PG 실결제 금액은 최종 주문 금액에서 사용 포인트를 차감해서 계산했습니다.
PG 실결제 금액 = 최종 주문 금액 - 사용 포인트
예를 들어 주문 금액이 50,000원이고 포인트를 10,000원 사용하면 PG 결제 금액은 40,000원입니다.
다만 포인트 사용 가능 여부는 서버에서 검증해야 합니다.
결제 취소나 환불이 성공하면 사용했던 포인트를 다시 복구합니다.
이때 단순히 잔액만 증가시키는 것이 아니라, 포인트 사용 취소 내역을 포인트 거래 원장에 남기는 방식이 좋습니다.
예를 들어 다음과 같이 기록합니다.
USEREFUND이렇게 하면 포인트가 왜 증가하거나 감소했는지 추적할 수 있습니다.
서버에서 사용자의 현재 포인트 잔액을 조회하고, 요청 포인트가 잔액보다 크면 예외를 발생시킵니다.
이 경우 결제창을 띄우기 전에 요청을 차단해야 합니다.
응답은 보통 400 Bad Request로 처리하고, 메시지는 “보유 포인트보다 많은 포인트를 사용할 수 없습니다.”처럼 반환할 수 있습니다.
A.
포인트와 PG가 함께 사용된 결제에서는 환불 금액을 결제수단별로 나눠야 합니다.
예를 들어 총 결제 금액이 100,000원이고, 포인트 20,000원, PG 80,000원으로 결제했다면 비율은 다음과 같습니다.
부분 환불 금액이 30,000원이라면 비율대로 계산합니다.
다만 나눗셈 과정에서 소수점이 생길 수 있기 때문에 반올림 오차를 보정해야 합니다.
PG 환불 금액 = 전체 환불 금액 - 포인트 환불 금액
이렇게 계산하면 포인트와 PG 환불 금액의 합이 항상 전체 환불 금액과 일치합니다.
포인트는 내부 시스템에서 직접 관리할 수 있지만, PG 환불 금액은 외부 결제사에 정확한 금액으로 요청해야 합니다.
따라서 전체 환불 금액과 결제수단별 환불 합계가 어긋나면 안 됩니다.
한쪽을 기준으로 계산하고 나머지를 차액으로 보정하면 총액 불일치 문제를 줄일 수 있습니다.
실무에서는 회사 정책에 따라 오차를 포인트에 둘 수도 있고 PG에 둘 수도 있지만, 중요한 것은 합계가 정확히 맞도록 일관된 기준을 두는 것입니다.
네, 구분해서 처리하는 것이 좋습니다.
상품 단위 환불은 재고 복구, 주문 상품 상태 변경, 환불 가능 수량 검증이 함께 필요합니다.
반면 금액 단위 환불은 주로 결제 금액 정산과 환불 한도 검증이 중요합니다.
A.
재고 선차감 정책은 주문 생성 시점에 재고를 먼저 차감하는 방식입니다.
결제까지 기다렸다가 재고를 차감하면 동시에 많은 사용자가 결제할 때 초과 판매가 발생할 수 있습니다.
그래서 주문 생성 시점에 재고를 차감하고, 결제 실패나 주문 취소가 발생하면 재고를 복구하는 방식으로 설계합니다.
흐름은 다음과 같습니다.
이 과정은 하나의 트랜잭션 안에서 처리해서 주문 생성과 재고 차감이 함께 성공하거나 함께 실패하도록 해야 합니다.
재고처럼 동시에 많은 요청이 몰리고, 초과 판매가 절대 발생하면 안 되는 경우에는 비관적 락이 더 단순하고 안전할 수 있습니다.
낙관적 락은 충돌이 적을 때 유리하지만, 충돌이 많으면 재시도가 많이 발생할 수 있습니다.
반면 비관적 락은 먼저 데이터에 락을 걸고 처리하므로 같은 재고를 동시에 수정하는 상황을 강하게 제어할 수 있습니다.
DB의 비관적 락을 사용한다면 애플리케이션 인스턴스가 여러 대로 늘어나도 같은 DB row에 대한 락이 걸리기 때문에 기본적인 정합성은 유지됩니다.
하지만 애플리케이션 내부의 synchronized 같은 JVM 락만 사용했다면 인스턴스가 여러 대일 때는 유효하지 않습니다.
인스턴스가 N대로 늘어나는 환경에서는 DB Lock, Redis Lock 같은 외부 공유 자원을 기준으로 동시성을 제어해야 합니다.
트랜잭션이 오래 걸리거나 락 대기가 길어지면 DB 커넥션이 오래 점유됩니다.
그 결과 커넥션 풀이 고갈되고, 다른 요청들이 DB 커넥션을 얻지 못해 지연되거나 실패할 수 있습니다.
그래서 트랜잭션 범위를 짧게 유지하고, 외부 API 호출을 트랜잭션 안에서 오래 수행하지 않도록 주의해야 합니다.
A.
빌링키 기반 정기 결제는 사용자가 최초 1회 결제수단을 등록하면, 이후에는 서버가 빌링키를 이용해 정해진 주기마다 자동 결제를 요청하는 방식입니다.
구현 흐름은 다음과 같습니다.
스케줄러는 중복 실행될 수 있으므로 멱등하게 처리해야 합니다.
예를 들어 subscriptionId + billingDate 기준으로 결제 시도 이력을 저장하고, 이미 처리된 결제일이면 다시 결제하지 않도록 막습니다.
정기 결제 실패 시 바로 구독을 종료하기보다는 재시도 정책을 둘 수 있습니다.
예를 들어 다음과 같이 처리할 수 있습니다.
재시도 횟수와 주기는 서비스 정책에 따라 정하고, 중복 결제가 발생하지 않도록 결제 시도 이력을 반드시 저장해야 합니다.
회원 등급별 적립률은 코드에 하드코딩하기보다 정책 테이블이나 Enum으로 관리할 수 있습니다.
예를 들어 BASIC, VIP, VVIP 등급에 따라 적립률을 다르게 두고, 결제 완료 시점에 회원 등급을 조회해서 적립 포인트를 계산합니다.
중요한 점은 결제 당시 적용된 적립률을 이력으로 남기는 것입니다.
나중에 등급 정책이 바뀌더라도 과거 결제의 적립 내역은 그대로 추적할 수 있어야 합니다.
A.
결제 성공 응답과 Webhook은 도착 순서가 보장되지 않습니다.
클라이언트 성공 응답이 먼저 올 수도 있고, Webhook이 먼저 올 수도 있으며, 둘 다 동시에 들어올 수도 있습니다.
그래서 결제 확정 로직은 하나의 공통 메서드로 모으고, 멱등하게 처리해야 합니다.
예를 들어 다음과 같이 설계할 수 있습니다.
paymentId 기준으로 결제 정보 조회PAID 상태라면 추가 처리 없이 종료PAID로 변경핵심은 어느 경로로 들어오든 같은 확정 로직을 타게 만드는 것입니다.
그래야 중복 요청, 지연 요청, 동시 요청 상황에서도 결제가 한 번만 확정됩니다.
A.
결제 실패 원인을 추적하려면 외부 PG 응답과 내부 서버 로그를 연결할 수 있어야 합니다.
이를 위해 다음 값을 로그와 DB에 남기는 것이 좋습니다.
orderIdpaymentId이렇게 남겨두면 결제 실패가 외부 PG 문제인지, 서버 검증 실패인지, 내부 트랜잭션 문제인지 추적할 수 있습니다.
장애 위치에 따라 대응 방식이 달라집니다.
| 장애 위치 | 대응 방식 |
|---|---|
| 외부 PG 장애 | 실패 응답 저장, 사용자에게 결제 실패 안내, 재시도 유도 |
| Webhook 장애 | 결제 단건 조회 API나 배치로 상태 재동기화 |
| 내부 트랜잭션 장애 | 결제 상태와 내부 주문 상태 불일치 여부 확인 후 보정 |
| DB 장애 | 트랜잭션 롤백, 실패 로그 저장, 재처리 대상 등록 |
| 네트워크 장애 | timeout 설정, 재시도 정책, 중복 처리 방지 |
중요한 것은 결제 성공 여부를 클라이언트 응답만으로 판단하지 않고, PG 결제 상태와 내부 DB 상태를 비교해서 불일치를 복구할 수 있게 만드는 것입니다.
CH4 클라우드·결제 시스템 면접에서는 단순히 기능 구현 여부보다 장애 상황에서 정합성을 어떻게 보장했는지를 많이 물어볼 수 있습니다.
특히 다음 키워드는 꼭 연결해서 답변하는 것이 좋습니다.
면접에서는 “구현했습니다”에서 끝내기보다,
왜 그렇게 설계했는지, 장애가 나면 어떻게 복구할 수 있는지, 중복 요청이 와도 안전한지까지 함께 설명하는 것이 중요합니다.
출처: 내일배움 스파르타 클럽