Spring 미드레벨 시스템 설계 면접
새 면접Q. 결제 시스템에서 네트워크 장애로 인해 클라이언트가 같은 결제 요청을 여러 번 보낼 수 있습니다. 중복 결제를 방지하면서 멱등성을 보장하는 API를 설계할 때 고려해야 할 요소들과 구현 방법을 설명해주세요. 특히 Idempotency Key의 저장 및 관리 전략을 포함해서 설명해주세요.
멱등성 보장을 위해 클라이언트가 생성한 고유한 Idempotency Key를 요청 헤더로 받아 Redis나 DB에 저장합니다. 요청 처리 전 해당 키의 존재 여부를 확인하고, 이미 존재하면 저장된 응답을 반환하며, 없으면 처리 후 키와 결과를 저장합니다. Idempotency Key는 TTL을 설정하여 24시간 정도 보관하며, 처리 중 상태(PROCESSING)와 완료 상태(COMPLETED)를 구분하여 동시 요청 시 중복 처리를 방지합니다. 분산 락(Redis distributed lock)을 활용하여 동일 키에 대한 동시 처리를 원천 차단하고, 트랜잭션과 함께 사용하여 데이터 정합성을 보장합니다.
- • 클라이언트 생성 Idempotency Key를 헤더로 받아 중복 요청 식별
- • Redis 또는 DB에 키와 처리 결과를 저장하고 TTL 설정으로 관리
- • 처리 중(PROCESSING) 상태 관리로 동시 요청 처리 방지
- • 분산 락을 활용한 동시성 제어 및 트랜잭션 정합성 보장
Idempotency Key 저장소로 Redis와 DB 중 어느 것을 선택할 것인지, 그리고 Redis 장애 시 fallback 전략은 어떻게 구성하시겠습니까?
Q. 대규모 게시판 시스템에서 댓글 조회 API를 설계할 때, Offset 기반 페이징과 Cursor 기반 페이징의 차이점을 설명하고, 각각의 성능 특성과 적합한 사용 사례를 비교해주세요. 특히 실시간으로 댓글이 계속 추가되는 상황에서 어떤 방식이 더 적합한지 설명해주세요.
Offset 기반 페이징은 LIMIT/OFFSET을 사용하여 구현이 간단하지만, 페이지 번호가 커질수록 DB가 앞의 모든 행을 스캔해야 하므로 성능이 저하됩니다. Cursor 기반 페이징은 마지막 조회 항목의 ID나 타임스탬프를 기준으로 다음 데이터를 조회하여 인덱스를 효율적으로 사용하고 성능이 일정합니다. 실시간으로 댓글이 추가되는 상황에서 Offset 방식은 페이지 중간에 데이터가 삽입되면 중복 조회나 누락이 발생할 수 있지만, Cursor 방식은 특정 지점 이후의 데이터만 조회하므로 일관성이 보장됩니다. 무한 스크롤이나 실시간 피드에는 Cursor 방식이, 페이지 번호 네비게이션이 필요한 경우에는 Offset 방식이 적합합니다.
- • Offset 페이징은 구현이 간단하지만 페이지가 깊어질수록 성능 저하
- • Cursor 페이징은 인덱스를 효율적으로 사용하여 일정한 성능 유지
- • 실시간 데이터 추가 시 Cursor 방식이 중복/누락 없이 일관성 보장
- • 무한 스크롤은 Cursor, 페이지 번호 네비게이션은 Offset이 적합
Cursor 기반 페이징에서 정렬 기준이 생성일시(created_at)인데, 동일한 시간에 생성된 댓글이 여러 개 있을 경우 어떻게 처리하시겠습니까?
Q. Spring 애플리케이션에서 Cache-Aside 패턴과 Write-Through 패턴의 차이점을 설명하고, 각 패턴의 장단점과 적합한 사용 사례를 비교해주세요. 또한 캐시와 DB 간 데이터 불일치가 발생할 수 있는 시나리오와 이를 해결하기 위한 방법을 설명해주세요.
Cache-Aside 패턴은 애플리케이션이 캐시를 먼저 확인하고 없으면 DB에서 조회 후 캐시에 저장하는 방식으로, 읽기 중심 워크로드에 적합하며 캐시 장애 시에도 시스템이 동작합니다. Write-Through 패턴은 쓰기 시 캐시와 DB에 동시에 저장하여 데이터 일관성이 높지만, 쓰기 지연이 발생하고 사용하지 않는 데이터도 캐시에 저장될 수 있습니다. Cache-Aside에서는 업데이트 시 캐시 삭제 후 DB 업데이트 순서가 중요하며, 동시성 문제로 인해 stale 데이터가 캐시에 남을 수 있습니다. 이를 해결하기 위해 TTL 설정, 이벤트 기반 캐시 무효화, 또는 Write-Behind 패턴과 혼합하여 사용하거나, 분산 락을 활용한 원자적 업데이트를 구현합니다.
- • Cache-Aside는 읽기 중심, 지연 로딩 방식으로 캐시 장애에 강함
- • Write-Through는 쓰기 시 동시 저장으로 일관성 높지만 쓰기 지연 발생
- • Cache-Aside에서 업데이트 시 캐시 삭제와 DB 업데이트 순서로 인한 불일치 가능
- • TTL, 이벤트 기반 무효화, 분산 락으로 데이터 일관성 보장
캐시 삭제(Delete)와 캐시 업데이트(Update) 중 어느 전략을 선호하시는지, 그리고 그 이유는 무엇인가요?
Q. 공개 API 서비스에서 사용자별로 분당 요청 횟수를 제한하는 Rate Limiting 시스템을 설계해야 합니다. Token Bucket 알고리즘과 Sliding Window 알고리즘의 동작 원리를 설명하고, Spring에서 Redis를 활용한 분산 환경에서의 구현 방안을 설명해주세요.
Token Bucket 알고리즘은 일정 속도로 토큰이 채워지는 버킷에서 요청마다 토큰을 소비하는 방식으로, 버스트 트래픽을 허용하면서도 평균 처리율을 제한합니다. Sliding Window 알고리즘은 현재 시점부터 과거 일정 시간 동안의 요청 수를 계산하여 더 정확한 제한이 가능하지만 메모리 사용량이 많습니다. Redis의 Sorted Set을 사용하여 Sliding Window를 구현할 수 있으며, 타임스탬프를 score로 사용하고 ZREMRANGEBYSCORE로 만료된 요청을 제거합니다. Token Bucket은 Redis의 INCR과 EXPIRE를 조합하거나 Lua 스크립트로 원자적 연산을 구현하며, Spring Interceptor나 Filter에서 요청 전처리 단계에서 검증합니다.
- • Token Bucket은 버스트 트래픽 허용, Sliding Window는 정확한 시간 기반 제한
- • Redis Sorted Set으로 Sliding Window 구현, 타임스탬프 기반 만료 처리
- • Token Bucket은 Redis INCR/EXPIRE 또는 Lua 스크립트로 원자적 연산
- • Spring Interceptor나 Filter에서 요청 전처리 단계에 적용
Rate Limiting에서 거부된 요청에 대해 429 응답과 함께 Retry-After 헤더를 어떻게 계산하여 제공하시겠습니까?
Q. 주문 생성 후 재고 차감, 포인트 적립, 알림 발송 등 여러 후속 작업이 필요한 시스템을 설계할 때, Spring Events와 메시지 큐(Kafka, RabbitMQ)를 활용한 이벤트 기반 아키텍처의 차이점을 설명하고, 트랜잭션 커밋 후 이벤트 발행을 보장하기 위한 Transactional Outbox 패턴을 설명해주세요.
Spring Events(@TransactionalEventListener)는 동일 애플리케이션 내에서 비동기 처리에 적합하며 구현이 간단하지만, 애플리케이션 재시작 시 이벤트가 손실되고 분산 시스템 간 통신이 불가능합니다. 메시지 큐는 내구성과 재처리가 보장되며 마이크로서비스 간 통신에 적합하지만 인프라 복잡도가 증가합니다. Transactional Outbox 패턴은 비즈니스 데이터와 함께 이벤트를 Outbox 테이블에 저장하여 트랜잭션 원자성을 보장하고, 별도의 스케줄러나 CDC(Change Data Capture)가 Outbox 테이블을 폴링하여 메시지 큐로 발행합니다. 이를 통해 트랜잭션 커밋과 이벤트 발행의 일관성을 보장하며, 최소 한 번(at-least-once) 전달을 구현할 수 있습니다.
- • Spring Events는 단일 앱 내 비동기 처리에 적합, 메시지 큐는 분산 시스템과 내구성 보장
- • Transactional Outbox 패턴은 이벤트를 DB 테이블에 저장하여 트랜잭션 원자성 보장
- • 별도 스케줄러나 CDC가 Outbox 테이블 폴링 후 메시지 큐로 발행
- • at-least-once 전달 보장과 트랜잭션 커밋-이벤트 발행 일관성 확보
Transactional Outbox 패턴에서 이벤트가 중복 발행될 수 있는데, 이벤트 소비자 측에서 멱등성을 어떻게 보장하시겠습니까?
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!