시스템 설계

1. 시스템 설계 문제를 받았을 때 어떤 순서로 접근하시겠어요?

문제 조건

  • 예시 서비스는 짧은 텍스트 콘텐츠의 생성과 조회만 지원하며, 수정·추천·검색은 범위에서 제외합니다.
  • DAU 100만 명이 하루 평균 20회 요청하고, 최대 트래픽은 일평균의 5배이며 읽기와 쓰기 비율은 9:1입니다.
  • 콘텐츠 하나의 평균 크기는 2KB이고 3년간 보존합니다.
  • 조회 p95는 200ms 이하, 월 가용성은 99.9%이며 성공 응답한 쓰기는 유실하지 않습니다.

기본 답변

먼저 주어진 조건을 기능 요구사항, 비기능 요구사항과 제약으로 분류하고 성공 기준을 고정합니다. 예시 조건은 하루 2천만 요청이므로 평균 약 231 QPS, 최대 약 1,158 QPS이며, 쓰기는 최대 약 116 QPS입니다. 하루 약 200만 개의 2KB 콘텐츠가 생기므로 복제와 인덱스를 제외한 3년 원본은 약 4.4TB입니다.

API는 POST /contents와 GET /contents/{id}로 시작하고, 원본 모델에는 ID, 작성자, 본문, 생성 시각과 상태를 둡니다. API 계약과 데이터 소유권을 먼저 정하면 중복 요청, 권한과 삭제 정책을 어느 계층에서 처리할지도 명확해집니다.

기본 구조는 stateless API 인스턴스와 영속 데이터베이스로 시작합니다. 쓰기는 원본 DB commit이 끝난 뒤 성공을 응답하고, 조회는 먼저 DB에서 제공하되 측정된 병목이 생기면 읽기 복제본이나 캐시를 추가합니다. 최대 약 1,200 QPS만으로 즉시 수평 샤딩할 필요는 없지만 3년간 약 4.4TB가 쌓이므로 시간 단위 파티셔닝·보관 계층과 장기 확장 경계는 미리 정합니다.

API는 여러 장애 영역에 분산하고 health check와 timeout을 적용합니다. DB는 복제와 시점 복구가 가능한 백업을 두며 replica lag와 장애 조치 시간을 계측합니다. 실제 트래픽이 예측을 넘으면 stateless 계층부터 수평 확장하고 저장 용량·연결 수·hot key가 단일 저장소 한계에 가까워질 때 파티션 재배치 비용까지 포함해 샤딩을 검토합니다.

네트워크 분할 때 성공한 쓰기의 유실을 막아야 하는 원본 경로는 일관성과 내구성을 우선하고, 오래된 값을 허용할 수 있는 공개 조회는 캐시로 응답 가능성을 높일 수 있습니다. 모든 구성 요소는 해결하려는 병목과 함께 쓰기 비용, stale 데이터, 운영 복잡도라는 대가를 설명해야 합니다.

핵심 키워드

  • Requirement Analysis
  • Capacity Estimation
  • API & Data Model
  • High-level Design
  • Data Flow
  • Trade-off

꼬리질문

질문 일 요청 수로 QPS를 어떻게 구하나요?

답변 포인트 일 요청 수를 86,400으로 나누면 평균 QPS가 됩니다. 최대 QPS는 실제 패턴이나 명시한 peak factor를 적용해야 합니다.

질문 네트워크 분할 상황에서 강한 일관성과 모든 요청에 대한 응답 가능성을 동시에 보장할 수 없다면 무엇을 선택하나요?

답변 포인트 성공한 쓰기의 유실을 허용하지 않는 원본 경로는 quorum을 얻지 못하면 쓰기를 거부하고 일관성을 우선합니다. 허용 시간 안의 stale 데이터로도 가치가 있는 조회 경로는 캐시나 지역 복제본으로 응답 가능성을 높일 수 있으며 경로별 정책을 분리합니다.

질문 최대 트래픽이 예상보다 10배 커졌을 때 무엇부터 확인하나요?

답변 포인트 CPU만 늘리기보다 지연 분포, DB 연결·IO, 캐시 적중률, 큐 lag와 hot key를 확인해 실제 포화 자원을 찾습니다. Stateless API는 수평 확장하기 쉽지만 원본 DB 병목은 쿼리·인덱스 개선과 읽기 분리 이후에 파티셔닝을 검토합니다.

질문 설계 때 사용한 규모 추정이 맞는지 운영에서 어떻게 검증하나요?

답변 포인트 요청 유형별 QPS, payload 크기, 읽기·쓰기 비율, 성장률과 peak factor를 계측하고 예상치와 정기적으로 비교합니다. 차이가 용량 계획과 SLO에 미치는 영향을 갱신하며 근거 없는 여유 용량을 무한히 추가하지 않습니다.

주의할 점

  • 주어진 요구사항을 임의로 바꾸지 말고 모호하거나 서로 충돌하는 조건은 확인해 해석을 명시합니다.
  • 요구사항과 규모를 분석하기 전에 특정 제품부터 선택하지 않습니다.
  • 캐시나 샤딩을 정답처럼 나열하지 말고 어떤 요구사항과 병목을 해결하는 선택인지 설명합니다.

2. 대규모 URL 단축 서비스를 설계해 주세요.

문제 조건

  • 긴 URL 등록, 만료일과 사용자 지정 별칭 설정, 단축 URL 리다이렉트와 클릭 통계 수집을 지원합니다.
  • 월 1억 개의 링크가 생성되고 최대 쓰기는 초당 500건, 최대 리다이렉트는 초당 20만 건입니다.
  • 리다이렉트 p95는 50ms 이하, 월 가용성은 99.99%입니다.
  • 생성 성공 후 같은 리전에서는 즉시 조회되어야 하며 다른 리전은 5초 이내, 클릭 통계는 1분 이내 반영합니다.
  • 명시적 만료가 없는 매핑은 5년간 보존합니다.

기본 답변

POST /links는 원본 URL, 선택적 별칭과 만료일을 받고 단축 키를 반환하며, GET /{shortKey}는 목적지로 리다이렉트합니다. 매핑 모델은 short key, target URL, owner, 생성·만료 시각과 상태를 가지며 클릭 이벤트는 원본 매핑과 분리합니다.

명시적 만료가 없는 매핑은 1억 개/월 × 60개월 = 60억 개까지 쌓입니다. Base62 순차 ID는 62^5 ≈ 9.16억으로 부족하고 62^6 ≈ 568억이므로 주소 공간만 보면 최소 6자가 필요합니다. 랜덤 키는 같은 길이에서 충돌 여지가 있으므로 예를 들어 8자 이상을 사용하고 유일성 제약과 재시도로 충돌을 처리하며, 키 길이는 허용 충돌 확률과 추측 난이도에 맞춰 정합니다. 평균 매핑을 500B로 가정하면 원본 약 3TB, 3중 복제 약 9TB에 인덱스·백업 여유가 추가됩니다. 순차 ID는 짧고 생성이 단순하지만 열거 가능하고, 랜덤 키는 조정이 적은 대신 더 긴 키와 충돌 처리가 필요합니다.

생성 요청은 유효성·악성 URL 정책을 확인하고 primary 저장소에 commit한 뒤 같은 리전 캐시를 채우고 응답합니다. 리다이렉트는 로컬 캐시, 분산 캐시, 저장소 순으로 조회하고 만료·차단 상태를 검사한 뒤 클릭 이벤트를 비동기 스트림에 기록하며, 집계 consumer가 1분 window 단위 통계를 갱신합니다.

읽기 복제본 지연 때문에 생성 직후 404가 되지 않도록 최근 생성 키의 cache write-through, primary fallback 또는 세션 단위 read-after-write 라우팅을 사용합니다. 변경 로그의 commit position과 리전별 replication watermark를 관측해 다른 리전 반영이 5초를 넘기기 전에 경보·우회하고, 지연 중인 리전은 primary 조회로 false 404를 막습니다. 이 우회는 지연과 리전 간 비용을 늘리므로 최근 생성 키에만 제한합니다. 유명 링크는 엣지와 로컬 캐시로 분산하고 캐시 장애 때는 저장소 보호용 제한과 circuit breaker를 적용합니다.

기본 리다이렉트는 목적지 변경과 통계 제어가 가능한 302 또는 메서드 보존이 필요한 경우 307을 사용하고, 영구 불변 링크만 301을 고려합니다. 강한 캐시는 지연과 비용을 낮추지만 목적지 변경·차단 반영을 늦출 수 있어 TTL을 정책에 맞춰야 합니다.

핵심 키워드

  • Base62
  • Collision
  • Redirect
  • Read-heavy
  • Cache

꼬리질문

질문 같은 긴 URL에는 항상 같은 키를 반환해야 하나요?

답변 포인트 필수는 아닙니다. 사용자·권한·만료·캠페인마다 별도 링크가 필요할 수 있어 중복 제거 정책을 요구사항으로 결정합니다.

질문 유명 링크 하나에 트래픽이 집중되면 어떻게 하나요?

답변 포인트 CDN·엣지 캐시와 애플리케이션 캐시를 사용하고 통계 이벤트는 비동기로 분산 처리합니다.

질문 생성 직후 단축 URL이 일부 서버에서 404가 되면 어떻게 진단하고 막나요?

답변 포인트 읽기 복제본 lag와 negative cache를 먼저 확인합니다. 생성 commit과 함께 캐시를 채우거나 최근 키는 primary로 fallback하고, 존재하지 않음의 negative TTL은 짧게 둬 복제 지연이 장기 404로 고착되지 않게 합니다.

질문 리다이렉트에서 301, 302와 307을 어떻게 선택하나요?

답변 포인트 301은 영구 이동으로 클라이언트·중간 캐시가 강하게 저장할 수 있어 불변 링크에 적합합니다. 302는 일반적인 임시 리다이렉트, 307은 원래 HTTP method와 body 보존이 필요할 때 사용하며 목적지 변경·차단·통계 통제 가능성과 캐시 효율을 비교합니다.

주의할 점

  • Base62는 암호화가 아니며 순차 ID는 열거될 수 있으므로 비공개 링크에는 별도 권한이나 추측하기 어려운 키가 필요합니다.
  • 존재하지 않는 키의 긴 negative cache는 복제 지연 중인 신규 링크를 계속 404로 만들 수 있습니다.

3. 팔로우 기반 뉴스피드 서비스를 설계해 주세요.

문제 조건

  • 게시물 작성·삭제, 팔로우·언팔로우와 최신순 홈 피드 조회를 지원하며 추천 랭킹은 제외합니다.
  • DAU 1,000만 명, 최대 피드 조회 초당 20만 건, 최대 게시물 작성 초당 1만 건입니다.
  • 첫 페이지 조회 p95는 200ms 이하, 월 가용성은 99.9%입니다.
  • 일반 게시물은 5초, 팔로워가 매우 많은 계정은 30초 안에 피드에 반영해도 됩니다.
  • 삭제·차단·비공개 전환은 1초 안에 이후 조회에서 노출되지 않아야 하며 게시물 원본은 2년간 보존합니다.

기본 답변

POST /posts, 팔로우·언팔로우 API와 GET /feed?cursor=...를 제공하고, Post와 Follow를 원본으로 둡니다. 사용자별 FeedItem은 post ID, 작성자 ID와 정렬 키만 가진 재구축 가능한 파생 데이터이며 원문을 복제하지 않습니다.

일반 사용자의 게시물은 이벤트 스트림을 거쳐 팔로워 타임라인에 미리 쓰는 fan-out-on-write로 처리합니다. fan-out 작업은 post ID와 follower ID를 멱등 키로 사용하고 checkpoint와 재시도 큐를 둬 worker 장애 후 이어서 처리합니다.

팔로워가 매우 많은 계정은 모든 타임라인에 복제하지 않고 별도 celebrity stream에 저장한 뒤 조회 시 사용자 타임라인과 병합합니다. 이 하이브리드 방식은 일반 피드의 낮은 읽기 지연과 유명 계정의 쓰기 증폭을 함께 제한하지만 읽기 병합 비용이 늘어납니다.

피드는 생성 시각과 post ID를 묶은 안정적인 cursor로 페이지를 넘겨 동시 삽입에도 중복·누락을 줄입니다. 삭제·차단·비공개 상태는 원본 권한 저장소와 빠른 deny cache에서 조회 시 다시 검사하고, 삭제 이벤트로 파생 피드를 비동기 정리합니다.

팬아웃 backlog와 lag를 계측해 일반 계정 5초, 유명 계정 30초 목표를 지키고, 임계치를 넘으면 배치 크기와 worker를 확장합니다. 파생 피드 일부를 잃어도 원본 Post와 Follow 이벤트에서 재구축할 수 있지만 권한 차단은 eventual 정리에만 의존해서는 안 됩니다.

핵심 키워드

  • Fan-out-on-write
  • Fan-out-on-read
  • Timeline
  • Cursor Pagination
  • Ranking

꼬리질문

질문 팔로워가 수천만 명인 사용자가 글을 쓰면 어떻게 하나요?

답변 포인트 모든 팔로워에게 즉시 복제하지 않고 유명 계정 게시물을 읽을 때 병합하거나 단계적으로 배포합니다.

질문 삭제된 게시물이 피드 캐시에 남으면 어떻게 하나요?

답변 포인트 원본 tombstone과 즉시 반영되는 권한 deny cache를 읽기 경로에서 검사해 노출부터 차단합니다. 삭제 이벤트로 타임라인과 캐시를 비동기 정리하되 이 정리가 끝날 때까지 접근 제어를 미루지 않습니다.

질문 Fan-out worker가 일부 팔로워만 처리한 뒤 죽으면 어떻게 복구하나요?

답변 포인트 게시물·팔로워 구간별 checkpoint를 저장하고 같은 작업을 재실행합니다. FeedItem에 post ID와 follower ID 유일 키를 두면 재시도가 중복 항목을 만들지 않으며 lag를 기준으로 누락 구간을 재수집할 수 있습니다.

질문 페이지를 넘기는 동안 새 게시물이 추가돼도 중복과 누락을 어떻게 줄이나요?

답변 포인트 offset 대신 마지막 항목의 정렬 시각과 고유 post ID를 묶은 cursor를 사용하고 그보다 이전인 항목만 조회합니다. 완전한 snapshot이 필요하지 않다면 새 게시물은 새로고침 때 위쪽에 표시합니다.

주의할 점

  • 사용자별 피드는 원본이 아니라 재구축 가능한 파생 데이터로 다루는 편이 안전합니다.
  • 삭제·차단·비공개 전환은 fan-out 정리 완료를 기다리지 말고 조회 권한 검사로 즉시 차단해야 합니다.

4. 일대일 및 그룹 채팅 서비스를 설계해 주세요.

문제 조건

  • 일대일 채팅, 최대 500명 그룹 채팅, 다중 기기 동기화, 전송·수신·읽음 상태를 지원합니다.
  • 동시 연결 100만 개, 최대 메시지 생성 초당 20만 건이며 대화방별 순서만 보장합니다.
  • 온라인 사용자 전달 p95는 500ms 이하, Gateway 월 가용성은 99.99%입니다.
  • 메시지는 1년간 보존하고 전달은 at-least-once를 허용하지만 사용자 화면의 중복은 제거해야 합니다.
  • 차단 또는 대화방 탈퇴는 1초 안에 새 메시지 접근 권한에 반영합니다.

기본 답변

클라이언트는 WebSocket으로 conversationId, clientMessageId와 본문을 보내며 REST API로 과거 메시지를 조회합니다. 원본 모델은 Conversation, ConversationMember, Message와 기기별 DeviceCursor로 나누고 서버가 대화방별 sequence와 message ID를 부여합니다.

Connection Gateway는 인증된 연결과 사용자·기기 위치를 관리합니다. Message 서비스는 현재 멤버십을 확인한 뒤 Message와 fan-out outbox를 대화방 파티션의 같은 트랜잭션으로 commit하고 그 후에만 송신자에게 ACK합니다. CDC·relay는 outbox를 이벤트 스트림에 at-least-once로 발행해 수신자가 연결된 Gateway로 전달하며, commit 후 relay가 죽어도 저장된 outbox offset부터 재개하므로 저장 성공과 발행 사이의 유실 구간을 없앱니다. 메시지 로그 자체를 fan-out 원본으로 쓰는 설계도 같은 보장을 줄 수 있습니다.

ACK가 유실되어 클라이언트가 재전송해도 senderId + clientMessageId 유일 키로 기존 message ID를 반환합니다. relay·Gateway 재시도로 같은 이벤트가 여러 번 와도 Gateway와 클라이언트는 message ID와 대화방 sequence로 중복 표시를 제거합니다. 각 기기는 마지막 동기화 cursor 이후 메시지를 가져오므로 오프라인 복구와 다중 기기 읽음 위치를 독립적으로 처리할 수 있습니다.

대화방 ID로 파티셔닝하면 방 내부 순서를 지키기 쉽지만 초대형 활성 방은 hot partition이 됩니다. 이 경우 순서 번호 부여는 한 논리적 sequencer로 유지하되 fan-out은 수신자 구간별 worker로 나누고, 느린 기기는 온라인 push 대신 저장소 동기화로 전환합니다.

Presence는 heartbeat와 TTL의 eventual 상태로 관리해 연결 경로와 분리합니다. 반면 차단·탈퇴 권한은 메시지 저장과 과거 조회 양쪽에서 확인하며, Pub/Sub 전달 성공을 저장 성공이나 사용자의 읽음으로 간주하지 않습니다.

핵심 키워드

  • WebSocket
  • Conversation Partition
  • Sequence
  • Offline Sync
  • Idempotency

꼬리질문

질문 여러 서버를 거쳐도 메시지 순서를 어떻게 지키나요?

답변 포인트 같은 대화방을 같은 파티션에 라우팅하고 서버가 부여한 sequence로 정렬합니다. 전역 순서는 보통 필요하지 않습니다.

질문 온라인 상태는 강한 일관성이 필요한가요?

답변 포인트 대부분 heartbeat와 TTL 기반의 근사 상태로 충분하며 연결 종료 감지가 늦을 수 있음을 허용합니다.

질문 서버는 메시지를 저장했지만 송신 ACK가 유실되면 어떻게 하나요?

답변 포인트 클라이언트가 같은 clientMessageId로 재전송하고 서버는 송신자 범위의 유일 키로 기존 message ID와 sequence를 반환합니다. 새 ID로 다시 저장하면 상대 화면에 중복이 생깁니다.

질문 매우 활발한 대형 그룹 하나가 단일 파티션을 포화시키면 순서를 유지하면서 어떻게 확장하나요?

답변 포인트 대화방 sequence 부여는 한 논리적 순서 영역으로 유지하고 저장 이후 fan-out을 수신자 구간별로 병렬화합니다. 처리 한계를 넘으면 대형 방 전용 파티션·서비스로 격리하되 임의 shard별 sequence로 방 전체 순서를 깨지 않습니다.

주의할 점

  • 전송 성공, 서버 저장, 상대 기기 표시와 사용자의 읽음은 서로 다른 상태입니다.
  • 다중 기기의 동기화 cursor와 읽음 위치는 사용자 전체 값 하나로 덮기보다 기기 상태와 사용자 집계 정책을 구분합니다.

5. 이메일, SMS, 푸시를 지원하는 대규모 알림 시스템을 설계해 주세요.

문제 조건

  • 트랜잭션 알림과 예약 마케팅 알림, 템플릿·언어·수신 설정 및 이메일·SMS·푸시 채널을 지원합니다.
  • 트랜잭션 알림은 최대 초당 1만 건, 캠페인 시작 시 전체 유입은 최대 초당 50만 건입니다.
  • 요청 접수 p95는 200ms 이하이며 트랜잭션 알림은 30초, 마케팅 알림은 10분 안에 발송을 시도합니다.
  • 내부 처리는 at-least-once이며 중복 발송을 최소화하고 채널별 외부 제공자 장애를 격리합니다.
  • 요청과 전달 상태는 90일간 보존하고 사용자 수신 거부는 다음 발송부터 적용합니다.

기본 답변

POST /notifications는 request ID, 수신자, template ID와 version, 변수, 우선순위와 예약 시각을 받습니다. Notification, ChannelAttempt와 DeliveryStatus를 분리해 하나의 요청이 여러 채널 시도로 확장되는 과정과 외부 결과를 추적합니다.

Ingress는 요청을 내구성 큐에 기록한 뒤 빠르게 접수 응답을 반환합니다. Orchestrator는 발송 직전에 최신 수신 설정, quiet hours, 언어와 채널 정책을 평가하고 고정된 template version으로 렌더링해 채널별 우선순위 큐로 보냅니다.

채널 worker는 제공자별 rate limit와 timeout을 적용하고 requestId + channel 유일 키로 내부 중복을 막습니다. 제공자가 idempotency key를 지원하면 같은 키를 전달하며, timeout 결과가 불명확하면 즉시 새 요청을 만들지 않고 조회 API와 delivery receipt로 확인합니다.

트랜잭션과 마케팅 큐·worker pool을 분리해 캠페인 폭주가 OTP 같은 긴급 알림을 막지 않게 합니다. 재시도는 지수 backoff와 jitter를 사용하고 영구 실패는 원인·template version과 함께 DLQ에 보관해 수정 후 안전하게 재처리합니다.

외부 제공자가 멱등 키와 상태 조회를 모두 지원하지 않으면 종단 간 중복 발송을 완전히 보장할 수 없습니다. 이 한계를 측정 가능한 중복률로 관리하고, 채널 대체 발송은 중복 위험과 사용자 선호를 비교해 명시적 정책으로만 수행합니다.

핵심 키워드

  • Queue
  • Template
  • Retry
  • DLQ
  • Delivery Receipt

꼬리질문

질문 같은 알림의 중복 발송을 어떻게 막나요?

답변 포인트 요청 ID와 채널별 발송 키로 내부 중복을 막고, 제공자가 멱등 키를 지원하면 같은 키를 전달합니다. 지원하지 않으면 조회 API, delivery receipt와 주기적인 대조 작업으로 불명확한 발송 결과를 확인합니다.

질문 대형 이벤트로 알림이 폭증하면 어떻게 하나요?

답변 포인트 우선순위 큐, 채널별 rate limit, 예약·배치 처리와 backpressure로 긴급 알림을 보호합니다.

질문 알림이 큐에 들어간 뒤 사용자가 수신을 거부하면 어떻게 하나요?

답변 포인트 오래된 preference snapshot만 메시지에 고정하지 않고 실제 외부 발송 직전에 최신 수신 설정을 다시 확인합니다. 법적·필수 트랜잭션 알림은 일반 마케팅 수신 거부와 별도 정책으로 분류합니다.

질문 Template 오류를 고친 뒤 DLQ를 재처리하면 어떤 버전을 사용하나요?

답변 포인트 감사와 동일한 결과 재현이 필요하면 요청에 고정된 template version을 사용합니다. 잘못된 template 자체가 실패 원인이면 승인된 새 version으로 명시적으로 migration한 재처리 작업을 만들고 무심코 현재 최신본을 적용하지 않습니다.

주의할 점

  • 큐에서 소비됐다는 사실만으로 사용자가 알림을 받았다고 간주하면 안 됩니다.
  • 외부 제공자가 멱등성과 상태 조회를 지원하지 않으면 정확히 한 번의 실제 발송을 보장한다고 표현하면 안 됩니다.

6. 대용량 파일 업로드와 다운로드 서비스를 설계해 주세요.

문제 조건

  • 최대 20GB 파일의 재개 가능한 업로드, 비공개 보관, 권한 기반 다운로드와 버전 관리를 지원합니다.
  • 동시 업로드 5만 건, 최대 다운로드 초당 50만 건이며 파일 바이트가 애플리케이션 서버를 통과하지 않게 합니다.
  • 제어 API p95는 200ms 이하, 메타데이터와 다운로드 경로의 월 가용성은 99.99%입니다.
  • 완료 성공 응답한 파일은 유실하지 않으며 원본은 사용자가 삭제할 때까지 보존합니다.
  • 악성코드 검사가 끝나기 전에는 공유할 수 없고 다운로드 서명 URL은 15분 후 만료됩니다.

기본 답변

POST /files/uploads는 파일명, 크기, content type과 checksum을 받고 upload ID와 짧게 만료되는 part URL을 반환합니다. FileMetadata에는 owner, object key, version, 크기, checksum과 UPLOADING·QUARANTINED·AVAILABLE 상태를 저장합니다.

클라이언트는 객체 스토리지로 part를 직접 보내고 완료된 part 번호와 checksum을 기록해 재개합니다. 완료 API는 요청 소유권, 총 크기, part 목록과 전체 checksum을 검증한 뒤 multipart upload를 확정하므로 애플리케이션 서버는 20GB 바이트를 중계하지 않습니다.

확정된 객체는 격리 prefix에 두고 이벤트로 악성코드 검사와 파생 파일 생성을 시작합니다. 모든 검사가 끝난 뒤 조건부 상태 변경으로 AVAILABLE이 되며 실패한 객체는 공개 키로 이동하지 않고 삭제 또는 재검사 대상으로 남깁니다.

다운로드 API는 현재 권한과 파일 상태를 검사하고 15분짜리 서명 URL을 발급합니다. 불변 object key에 version을 포함하고 CDN은 해당 버전을 캐시하므로 새 버전 배포와 캐시 무효화가 단순해집니다.

업로드 세션과 미완료 part에는 만료 수명 주기를 적용하고 완료 이벤트는 멱등하게 처리합니다. 서명 URL은 발급 후 즉시 회수하기 어려우므로 짧은 TTL과 CDN 차단 목록 사이에서 폐기 지연, 제어 비용과 다운로드 성능을 조절합니다.

핵심 키워드

  • Object Storage
  • Presigned URL
  • Multipart Upload
  • Checksum
  • CDN

꼬리질문

질문 업로드가 중간에 끊기면 어떻게 복구하나요?

답변 포인트 upload ID와 완료된 part를 저장하고 누락된 part만 다시 보내며 오래된 미완료 업로드는 수명 주기로 제거합니다.

질문 악성 파일이 즉시 배포되지 않게 하려면 어떻게 하나요?

답변 포인트 검사 전에는 격리 상태로 저장하고 검사 완료 이벤트 이후에만 공개 다운로드를 활성화합니다.

질문 Presigned URL로 선언한 크기보다 큰 파일이나 다른 content type을 올리는 것을 어떻게 막나요?

답변 포인트 발급 전에 사용자 quota를 확인하고 서명 조건에 object key, 만료, part와 허용 크기·content type을 제한합니다. 완료 API에서도 실제 객체 크기와 checksum을 다시 검증하고 조건이 다르면 확정하지 않습니다.

질문 권한을 회수했는데 이미 발급된 다운로드 URL과 CDN 캐시가 남아 있으면 어떻게 하나요?

답변 포인트 서명 URL을 짧게 만료시키고 민감 파일은 매 요청 권한 검사가 가능한 CDN signed cookie·edge authorization을 사용합니다. 긴 캐시가 필요하면 object version을 폐기 목록에 올리고 purge하되 전파 지연을 보안 정책에 포함합니다.

주의할 점

  • 클라이언트가 보낸 파일명, MIME 타입과 크기만 신뢰하지 말고 서버 측 검증과 권한 확인을 수행합니다.
  • 업로드 완료 응답은 part 업로드 성공만이 아니라 전체 크기·checksum과 메타데이터 commit이 끝난 뒤 반환해야 합니다.

7. 검색어 자동완성 서비스를 설계해 주세요.

문제 조건

  • 입력 prefix에 대해 언어·지역별 상위 10개 검색어를 반환하며 개인화와 본문 검색은 제외합니다.
  • 후보 검색어는 1억 개, 최대 요청은 초당 5만 건입니다.
  • 응답 p95는 50ms 이하, 월 가용성은 99.9%입니다.
  • 기본 인기도는 매일 갱신하고 급상승 검색어는 1분 안에 반영합니다.
  • 개인정보와 차단 검색어는 갱신 지연과 관계없이 결과에 노출되면 안 됩니다.

기본 답변

GET /suggestions?prefix=...&locale=...는 정규화한 prefix와 언어·지역을 키로 상위 10개 후보를 반환합니다. 저장 모델은 locale + prefix → [(termId, score, version)] 형태로 두고 검색어 원문과 안전 상태는 별도 원본에서 관리합니다.

단일 노드 Trie는 공통 prefix를 압축하고 동적 탐색에 유리하지만 1억 후보 전체를 메모리에 복제하기 어렵습니다. 이 조건에서는 배치가 자주 조회되는 prefix의 Top-K를 미리 계산해 분산 Key-Value 인덱스로 배포하고, 작은 언어 사전이나 fallback 탐색에만 압축 Trie를 사용하는 편이 적합합니다.

요청은 로컬 캐시와 prefix 인덱스에서 조회해 50ms 목표를 맞춥니다. 짧고 인기 있는 prefix는 hot key가 되므로 Top-K 값을 여러 읽기 노드와 로컬 캐시에 복제하며, 긴 prefix는 hash partition으로 고르게 분산합니다.

일일 배치는 안정된 인기도를 새 version의 인덱스로 만들고 원자적으로 alias를 전환합니다. 스트리밍 집계는 최근 1분 delta를 만들고 조회 시 base와 병합하며, stream 장애 시 마지막 base만 제공해 정확도보다 가용성을 우선합니다.

차단·개인정보 목록은 권위 원본에서 단조 증가하는 version을 붙여 일반 인기도 인덱스와 분리된 deny filter로 만들고, 조회 노드와 캐시에 push한 뒤 적용 ACK를 추적합니다. 조회는 base와 delta 후보를 병합한 마지막 단계에서 반드시 해당 filter를 적용하며, filter가 없거나 무결성 검증에 실패하거나 요구 version보다 뒤처지면 오래된 Top-K를 그대로 반환하지 않습니다. 이때 빈 결과·안전한 고정 후보로 fail-closed하거나 불확실한 후보를 권위 원본에 동기 확인해 절대 비노출 조건을 지키며, 안전성을 위해 일부 가용성과 지연을 희생합니다. Top-K 사전 계산은 빠르지만 임의 prefix와 최신성에 약하고, 동적 Trie·오타 탐색은 유연하지만 메모리와 CPU 비용이 커지는 trade-off가 있습니다.

핵심 키워드

  • Prefix Index
  • Trie
  • Top-K
  • Ranking
  • Offline Aggregation

꼬리질문

질문 새로운 인기 검색어를 얼마나 빨리 반영하나요?

답변 포인트 스트리밍 집계로 최근 delta를 만들고 안정된 결과는 배치 인덱스에 병합해 정확성과 비용을 조절합니다.

질문 오타까지 지원하려면 어떻게 하나요?

답변 포인트 편집 거리, n-gram 같은 후보 생성 방식을 사용하되 지연과 후보 폭증을 제한합니다.

질문 Trie와 prefix별 Top-K Key-Value 인덱스는 어떤 조건에서 선택하나요?

답변 포인트 Trie는 임의 prefix와 동적 탐색에 유리하지만 큰 문자 집합과 1억 후보를 메모리에 유지하는 비용이 큽니다. 요청 prefix가 반복되고 Top-K가 작다면 사전 계산한 KV 인덱스가 지연과 분산 배포에 유리하지만 갱신과 저장 중복 비용이 늘어납니다.

질문 한두 글자 prefix가 hot key가 되면 어떻게 처리하나요?

답변 포인트 값이 작고 자주 바뀌지 않으므로 여러 읽기 노드와 프로세스 로컬 캐시에 복제합니다. 단순히 prefix를 hash shard로 더 쪼개면 한 요청이 모든 shard를 병합해야 하므로 hot prefix 전용 복제와 갱신 version 관리가 더 적합할 수 있습니다.

주의할 점

  • 언어별 정규화와 개인정보·유해 검색어 필터링을 함께 설계해야 합니다.
  • 차단 목록은 느린 배치 인덱스 갱신만 기다리지 말고 조회 직전의 즉시 반영 필터로 적용합니다.

8. 여러 서버에서 동작하는 분산 Rate Limiter를 설계해 주세요.

문제 조건

  • API key·테넌트·엔드포인트별 분당 한도와 최대 100건의 순간 burst를 지원합니다.
  • 활성 키 100만 개, 여러 리전에 걸친 최대 요청은 초당 200만 건입니다.
  • 제한 판정 p99는 5ms 이하, limiter 월 가용성은 99.99%입니다.
  • 결제·쓰기 API는 장애 시 fail-closed, 공개 조회 API는 제한된 시간 동안 fail-open합니다.
  • 로컬 quota를 사용할 때 전역 한도 초과는 1% 이내까지 허용합니다.

기본 답변

정책 모델은 subject, endpoint, 분당 refill rate, burst capacity, plan version과 실패 정책을 가집니다. Gateway는 판정 요청에 현재 시각과 비용을 전달하고 결과로 허용 여부, 남은 quota와 재시도 가능 시각을 받아 일관된 응답 헤더를 제공합니다.

최대 100건의 순간 burst를 허용하므로 token bucket이 기본 선택입니다. 중앙 상태 저장소에서 마지막 refill 시각과 토큰 수를 하나의 원자 연산으로 갱신하며 TTL로 비활성 키를 제거합니다. Sliding log보다 메모리가 작고 fixed window 경계의 두 배 허용 문제를 피할 수 있습니다.

Gateway는 가까운 리전 limiter에 요청하고 limiter는 subject와 endpoint hash로 상태를 분산합니다. 한 고객의 hot key는 단일 원자 카운터 병목이 되므로 중앙 allocator가 subject·endpoint·window의 남은 quota에서 수량을 원자적으로 예약하고 epoch, leaseId, grant, duration을 담은 quota lease를 리전·노드에 발급하며, 로컬 노드는 grant까지만 소진합니다. 정상 상태에서는 모든 유효 lease의 grant 합이 전역 quota를 넘지 않습니다.

Lease 활성화는 발급·수신 ACK로 확정하고 로컬은 받은 duration을 monotonic deadline으로 바꾸되 전파 지연과 안전 여유만큼 일찍 사용을 중단합니다. Allocator는 ACK 시점부터 lease 기간과 보장된 최대 시계 오차·전파 지연이 모두 지난 뒤에만 미소진분을 재발급하며, ACK가 없거나 오차 상한을 보장할 수 없으면 해당 grant를 계속 예약해 fail-closed합니다. Failover한 allocator도 복제된 lease를 전역 잔여량에서 차감하고, 새 epoch는 연결된 노드의 이후 사용을 fence하지만 partition된 노드의 기존 결정을 즉시 취소하지 못하므로 기존 lease가 안전하게 만료될 때까지 겹치는 grant를 내주지 않습니다. 분할 중 비상 quota와 이중 소진 위험량의 합은 window 한도의 floor(1%) 이내로 예약합니다. 예를 들어 한도가 1,000이면 최대 10건이고 100 미만이면 로컬 초과를 허용하지 않습니다.

저장소 장애 때 결제·쓰기 API는 남은 유효 lease만 사용하고 소진 후 fail-closed로 보호합니다. 공개 조회는 미리 예약한 1% 비상 quota 범위와 짧은 기간 안에서만 fail-open하며, 복구 시 사용량을 대조하고 이전 lease의 안전 만료를 확인한 뒤 새 epoch 발급을 재개합니다. 작은 lease와 큰 안전 여유는 오차를 줄이지만 중앙 갱신 빈도·지연과 quota 미사용량을 늘리므로, limiter 지연·거부율·미보고 lease·초과 추정치를 별도 SLI로 관측합니다.

핵심 키워드

  • Token Bucket
  • Sliding Window
  • Atomic Operation
  • Burst
  • Retry-After

꼬리질문

질문 서버별 로컬 카운터만 사용하면 어떤 문제가 있나요?

답변 포인트 서버 수에 비례해 제한을 초과하고 재배치 때 상태가 사라지므로 중앙 카운터나 quota 분배가 필요합니다.

질문 특정 고객 카운터가 hot key가 되면 어떻게 하나요?

답변 포인트 정확한 전역 한도의 원자적 갱신은 병목이 될 수 있습니다. 로컬 quota 임대나 계층형 limiter로 분산하되 허용할 일시 초과량을 명시하고, 하위 키 분산은 정확도와 집계 비용의 trade-off를 함께 설명합니다.

질문 서버 시계가 뒤로 이동하거나 리전별 시간이 다르면 token refill은 어떻게 하나요?

답변 포인트 Token refill은 상태 저장소 기준 시각으로 원자 계산하고 큰 시간 도약도 capacity로 clamp합니다. Lease는 발급·수신 ACK 뒤 duration을 로컬 monotonic deadline으로 바꾸고 안전 여유만큼 일찍 중단하며, allocator는 최대 시계 오차와 전파 지연까지 지난 뒤에만 quota를 재발급합니다. 이 상한을 보장할 수 없으면 겹치는 재발급을 하지 않습니다.

질문 중앙 상태 저장소가 장애 나면 모든 API를 같은 방식으로 처리하나요?

답변 포인트 아닙니다. 결제·쓰기처럼 남용 비용이 큰 경로는 fail-closed하고, 공개 조회는 짧은 시간 보수적 로컬 한도로 fail-open할 수 있습니다. 정책별 지속 시간과 복구 후 quota 재동기화 방법을 미리 정합니다.

주의할 점

  • Limiter 장애가 전체 요청 장애로 번지지 않도록 API 중요도별 실패 정책을 정합니다.
  • 정확한 전역 한도와 낮은 지연·높은 가용성을 모두 비용 없이 얻을 수 없으며 허용 초과량을 수치로 관리해야 합니다.

9. 대규모 읽기 서비스의 캐시 계층을 설계해 주세요.

문제 조건

  • 상품 상세 조회와 관리자 상품 수정만 지원하며 관계형 데이터베이스가 원본입니다.
  • 최대 조회 초당 30만 건, 최대 수정 초당 3천 건이고 상위 1% 상품이 전체 조회의 40%를 차지합니다.
  • 조회 p95는 100ms 이하, 월 가용성은 99.95%입니다.
  • 가격·재고는 2초, 설명은 5분까지 오래된 값을 허용합니다.
  • 캐시 데이터는 유실 가능하지만 원본 커밋이 성공한 변경을 캐시 때문에 되돌리면 안 됩니다.

기본 답변

GET /products/{id}는 Product와 가격·재고 version을 읽고, 관리자 수정 API는 조건부 version으로 원본 DB를 갱신합니다. 캐시 키에는 product ID와 schema version을 넣고 값에는 source version과 생성 시각을 포함해 허용된 stale 시간을 판정합니다.

읽기에는 cache-aside를 사용합니다. hit이면 가격·재고 2초, 설명 5분 정책 안에서 반환하고, miss이면 같은 키의 요청을 single-flight로 합쳐 DB를 한 번 읽은 뒤 TTL jitter를 적용해 저장합니다. 상위 hot 상품은 프로세스 로컬의 매우 짧은 캐시로 한 단계 더 흡수합니다.

쓰기는 DB commit 후 새 version을 담은 invalidation event를 outbox로 발행합니다. 소비자는 기존 캐시를 삭제하거나 더 높은 version의 값으로 교체하며, event 지연 중에도 읽기 값의 version과 나이를 확인해 가격·재고의 2초 한도를 넘으면 원본으로 우회합니다.

단순한 commit 후 삭제에는 경쟁이 있습니다. 조회 A가 이전 DB 값을 읽은 뒤 수정 B가 commit하고 캐시를 삭제했는데 A가 이전 값을 다시 캐시에 넣을 수 있습니다. 삭제와 별도로 최신 source version의 tombstone·high-water mark를 유지하고 cache fill은 이 fence 이상의 version만 CAS로 허용해야 합니다. 또는 current-version pointer와 versioned value를 분리해 이전 값이 최신 pointer를 되돌리지 못하게 하며, 값 삭제와 함께 비교 기준까지 없애서는 안 됩니다.

캐시 장애 시 DB로 트래픽이 한꺼번에 몰리지 않도록 admission control, stale-if-error와 제한된 fallback을 적용합니다. 다만 stale-if-error는 필드별 freshness 계약을 늘리는 예외가 아닙니다. 설명은 5분 한도 안에서 반환할 수 있지만 가격·재고는 생성 후 2초 이내 값에만 적용하고, 이를 넘으면 원본 용량만큼만 조회한 뒤 나머지는 오래된 가격·재고 대신 명시적 일시 오류나 재고 확인 불가 응답으로 축소합니다. Negative cache는 존재하지 않는 ID 공격에 유용하지만 짧은 TTL을 사용합니다. 올바르게 구성된 Bloom Filter는 false positive만 허용하지만 갱신이 늦으면 신규 상품을 없다고 판단할 수 있으므로 새 filter version을 완성해 원자 전환하거나 최신 생성 구간은 우회합니다.

핵심 키워드

  • Cache-aside
  • TTL Jitter
  • Invalidation
  • Stampede
  • Hot Key

꼬리질문

질문 DB 갱신과 캐시 삭제 중 무엇을 먼저 하나요?

답변 포인트 DB commit 후 삭제가 기본이지만, 이전 값을 읽던 요청이 삭제 뒤 stale 값을 다시 채울 수 있습니다. 값과 별도로 단조 증가하는 version tombstone을 남겨 낮은 source version의 cache fill을 CAS로 거부하거나, current-version pointer와 versioned value를 분리해야 합니다. Outbox는 무효화를 재전달하지만 비교할 fence 자체를 대신하지는 않습니다.

질문 모든 데이터에 긴 TTL을 적용하면 안 되나요?

답변 포인트 적중률은 높아져도 오래된 값과 무효화 부담이 커지므로 변경 빈도와 stale 허용 시간을 기준으로 정합니다.

질문 인기 상품의 TTL이 만료될 때 DB 요청이 동시에 폭증하면 어떻게 하나요?

답변 포인트 TTL jitter로 만료 시점을 분산하고 같은 키의 miss를 single-flight로 합칩니다. 허용된 stale 값을 먼저 반환하며 한 worker만 갱신하는 stale-while-revalidate도 사용할 수 있습니다.

질문 캐시 클러스터 전체가 장애 나면 DB fallback만 하면 되나요?

답변 포인트 전체 30만 QPS를 DB로 보내면 연쇄 장애가 날 수 있습니다. 요청 우선순위와 admission control로 원본 용량만큼만 통과시키고 circuit breaker와 점진적 cache warm-up으로 복구합니다. 로컬 stale-if-error도 필드별 한도 안에서만 사용하므로 가격·재고는 2초를 넘겨 반환하지 않고, 설명만 5분 한도까지 허용합니다.

주의할 점

  • 캐시는 원본 저장소의 완전한 대체가 아니며 허용할 일관성 모델을 명시해야 합니다.
  • 무효화 이벤트가 전달됐다는 사실만 믿지 말고 source version과 cache age로 오래된 값의 상한을 검사합니다.

10. 메시지 큐를 사용하는 이벤트 기반 처리 시스템을 설계해 주세요.

문제 조건

  • 주문 서비스가 이벤트를 발행하고 재고·알림·분석 소비자가 독립적으로 처리합니다.
  • 평균 초당 5만 건, 최대 초당 20만 건이며 소비자 장애 시 30분 분량의 유입을 버퍼링합니다.
  • 정상 시 이벤트 처리 p95는 5초 이하, 장애 복구 후 backlog는 1시간 안에 해소합니다.
  • 주문별 순서는 보장하지만 서로 다른 주문의 전역 순서는 요구하지 않고 전달은 at-least-once입니다.
  • 이벤트는 7일간 보존하여 재처리할 수 있고 스키마 변경은 이전 소비자와 호환되어야 합니다.

기본 답변

이벤트 envelope에는 event ID, event type, schema version, aggregate ID, aggregate sequence, 발생 시각과 payload를 둡니다. 주문 DB의 상태 변경과 outbox insert를 한 트랜잭션으로 commit하고 relay가 outbox를 브로커에 발행해 DB 성공·이벤트 유실 사이의 공백을 없앱니다.

브로커는 order ID를 partition key로 사용해 같은 주문의 이벤트 순서를 유지하고 여러 파티션으로 초당 20만 건을 분산합니다. 논리 이벤트 수로 보면 최대 유입 20만 건/초의 30분 backlog는 200,000 × 1,800 = 3.6억 건이고, 평균 5만 건/초의 7일 보존량은 50,000 × 604,800 = 302.4억 건입니다. 실제 디스크·네트워크는 평균 직렬화 크기, 복제 계수, 인덱스와 안전 여유를 곱해 산정합니다. 최대 유입이 복구 중에도 계속된다고 가정하면 3.6억 건을 1시간에 비우는 데 추가 10만 건/초가 필요하므로 장애가 난 각 consumer group은 최소 30만 건/초를 처리해야 하며, 파티션 수와 브로커 읽기 용량도 이 처리율에 맞춥니다.

소비자는 event ID의 inbox 기록과 로컬 업무 변경을 같은 DB 트랜잭션으로 commit한 뒤 offset을 ack하므로 재전달된 이벤트의 DB 변경은 제거할 수 있습니다. 그러나 알림·결제 API 같은 외부 side effect는 이 트랜잭션 경계 밖이어서 inbox만으로 중복을 막지 못합니다. 업무 트랜잭션에 effect outbox를 함께 기록하고 dispatcher가 안정된 idempotency key로 외부 API를 호출한 뒤 결과를 대사해야 합니다. 외부 제공자가 멱등 키나 결과 조회를 지원하지 않으면 호출 성공 직후 장애가 난 경우의 정확히 한 번 실행은 보장할 수 없으므로 중복 허용·보상 정책을 명시합니다.

재시도 가능한 오류는 제한된 backoff를 적용하고 poison event는 격리합니다. 다만 같은 주문의 엄격한 순서가 필요하면 후속 이벤트를 먼저 진행시키지 않고 해당 aggregate만 주차한 뒤 수정 후 원래 순서로 재처리합니다.

Lag, 처리율과 oldest event age로 backpressure를 관측하고 소비자를 확장하되 파티션 수가 병렬성 상한이 됩니다. 7일 replay는 복구에 유용하지만 외부 알림·결제 같은 side effect가 다시 실행되지 않도록 replay mode와 schema 호환 규칙을 별도로 둡니다.

핵심 키워드

  • Partition
  • Consumer Group
  • At-least-once
  • Idempotent Consumer
  • DLQ

꼬리질문

질문 DB 저장 후 이벤트 발행 전에 서버가 죽으면 어떻게 하나요?

답변 포인트 같은 DB 트랜잭션에 outbox를 저장하고 별도 relay가 큐로 전달하는 Transactional Outbox를 사용할 수 있습니다.

질문 계속 실패하는 메시지가 파티션을 막으면 어떻게 하나요?

답변 포인트 순서 완화가 가능하면 제한된 재시도 후 DLQ로 격리합니다. 같은 키의 엄격한 순서가 필요하면 해당 키나 파티션을 중지·격리하고, 원인을 수정한 뒤 원래 순서로 재처리합니다.

질문 소비자가 DB commit 후 offset ACK 전에 죽으면 어떻게 되나요?

답변 포인트 이벤트가 다시 전달됩니다. Event ID를 업무 변경과 같은 트랜잭션의 inbox에 기록하면 로컬 DB 변경은 중복되지 않지만, 트랜잭션 밖의 외부 API 호출까지 보호하지는 못합니다. 외부 효과는 effect outbox, 제공자에게 전달하는 안정된 idempotency key와 결과 조회·대사로 별도 보호해야 하며 제공자가 이를 지원하지 않으면 정확히 한 번 실행을 보장할 수 없습니다.

질문 7일 전 이벤트를 재생할 때 새 소비자 코드와 스키마가 달라졌다면 어떻게 하나요?

답변 포인트 Envelope의 schema version으로 호환 decoder를 선택하고 additive 변경을 우선합니다. Replay 전용 consumer와 출력 namespace를 사용해 검증한 뒤 전환하며 알림·결제 같은 side effect는 replay mode에서 억제하거나 멱등 키로 차단합니다.

주의할 점

  • 브로커의 exactly-once 옵션만으로 외부 DB와 API를 포함한 종단 간 중복이 자동 제거되지는 않습니다.
  • DLQ로 이동해 파티션을 진행시키는 선택은 같은 aggregate의 순서 보장을 완화하는 결정일 수 있습니다.

11. 중복 요청과 외부 결제사 장애를 고려한 주문·결제 시스템을 설계해 주세요.

문제 조건

  • 주문 생성, 재고 예약, 외부 결제 승인, 취소·환불과 상태 조회를 지원합니다.
  • 최대 주문 생성은 초당 2만 건이고 주문 접수 p95는 500ms 이하, 결제는 30초 안에 최종화하는 것을 목표로 합니다.
  • 월 가용성은 99.99%이며 확인된 주문은 재고를 초과할 수 없고 동일 요청의 이중 결제를 허용하지 않습니다.
  • 결제사 호출과 webhook은 중복되거나 순서가 바뀔 수 있고 timeout 결과는 성공 여부가 불명확할 수 있습니다.
  • 주문 이력과 금융 원장은 7년간 보존합니다.

기본 답변

POST /orders는 고객 범위의 idempotency key와 payload hash를 받고, 같은 키·같은 payload에는 최초 order ID와 응답을 반환하며 다른 payload 재사용은 충돌로 거부합니다. Order, InventoryReservation, PaymentAttempt, LedgerEntry와 Outbox를 분리해 각 상태 전이를 기록합니다.

주문은 CREATED → RESERVED → PAYMENT_PENDING → CONFIRMED 상태 머신으로 진행합니다. 재고는 상품 version의 조건부 갱신이나 원자 차감으로 예약하고 만료 시각을 두며, 각 전이와 이벤트는 같은 로컬 DB 트랜잭션으로 commit합니다.

결제 요청에는 payment attempt의 멱등 키를 전달하고 성공 응답이나 검증된 webhook을 받아야 승인 상태로 전환합니다. 호출 timeout은 실패가 아니라 UNKNOWN/PENDING으로 남기고 제공자 조회와 정산 작업으로 확인해 새로운 결제를 성급히 만들지 않습니다.

결제가 최종 실패하면 Saga 보상으로 예약 재고를 해제하고, 확인 후 취소는 별도 환불 상태 머신으로 처리합니다. webhook은 서명을 확인하고 제공자 event ID와 상태 version으로 중복·역순 이벤트가 이미 확정된 상태를 되돌리지 못하게 합니다.

금액 이동은 수정 가능한 주문 행만으로 판단하지 않고 append-only 복식 원장과 결제사 정산 결과를 대조합니다. 재고와 결제의 강한 정확성을 위해 일부 장애에서 주문 완료가 늦어지는 선택을 받아들이고, 고객에게 PENDING 상태와 안전한 재조회 API를 제공합니다.

핵심 키워드

  • State Machine
  • Idempotency Key
  • Ledger
  • Saga
  • Reconciliation

꼬리질문

질문 결제사 호출은 성공했지만 응답이 시간 초과되면 어떻게 하나요?

답변 포인트 같은 idempotency key로 상태를 조회하고 주문을 Pending에 둔 뒤 webhook과 정산으로 확정합니다.

질문 재고 예약 후 결제가 실패하면 어떻게 하나요?

답변 포인트 예약 만료와 보상 이벤트로 재고를 해제하며 해제 작업도 중복 실행에 안전해야 합니다.

질문 같은 idempotency key로 금액이 다른 요청이 오면 어떻게 하나요?

답변 포인트 키와 최초 payload hash를 함께 저장하고 payload가 다르면 기존 주문을 반환하지 않고 충돌 오류로 거부합니다. 키는 고객·업무 범위와 만료 정책을 가져야 다른 사용자의 요청과 충돌하지 않습니다.

질문 결제 성공 webhook 뒤에 더 오래된 실패 webhook이 도착하면 어떻게 하나요?

답변 포인트 제공자 event ID와 payment attempt version을 저장하고 상태 머신에서 허용된 전이만 조건부 적용합니다. 이미 확정된 성공을 오래된 실패가 되돌리지 못하게 하고 서로 모순된 상태는 정산 대상으로 보냅니다.

주의할 점

  • 네트워크 오류를 결제 실패로 단정해 새 결제를 만들면 이중 결제가 발생할 수 있습니다.
  • Idempotency key만 저장하지 말고 요청 fingerprint와 최초 응답을 함께 보관해야 의미가 다른 재사용을 탐지할 수 있습니다.

12. 예약 작업과 주기 작업을 실행하는 분산 스케줄러를 설계해 주세요.

문제 조건

  • 일회성 실행, 시간대가 지정된 cron, 취소, 상태 조회와 수동 재실행을 지원합니다.
  • 예약 작업 1억 개를 보관하고 최대 분당 100만 개가 동시에 실행 시각에 도달합니다.
  • 예약 시각으로부터 실행 시작 p99는 10초 이하, 월 가용성은 99.99%입니다.
  • 작업 유실은 허용하지 않고 at-least-once 실행을 전제로 handler가 멱등해야 합니다.
  • 실행 이력은 90일간 보존하고 시간대·DST와 지연 실행 misfire 정책을 적용합니다.

기본 답변

POST /jobs는 일회성 시각 또는 cron·time zone, payload, misfire와 재시도 정책을 받고 job ID를 반환합니다. Job, 다음 실행 시각을 가진 Schedule, 개별 시도의 JobRun과 발행용 Outbox를 분리해 취소와 실행 이력을 추적합니다.

예약 데이터는 다음 실행 시각 bucket과 hash shard로 나눕니다. Scheduler는 DB 기준 시각으로 가까운 bucket을 조회하고 조건부 갱신으로 due job의 짧은 lease와 증가하는 fencing token을 얻습니다.

Lease 획득과 실행 큐 발행 사이의 유실을 막기 위해 같은 트랜잭션에 JobRun과 outbox를 기록하고 relay가 큐에 전달합니다. relay가 중복 발행해도 run ID가 같으며, 발행 전 장애 난 lease는 reaper가 만료 후 다시 수집합니다.

Worker는 visibility timeout 안에 작업을 처리하고 장기 작업은 heartbeat로 lease를 연장합니다. 완료 commit 후 ACK가 유실되면 재실행될 수 있으므로 handler는 run ID로 멱등해야 하고, 보호 자원은 이전 fencing token의 늦은 쓰기를 거부해야 합니다.

Cron은 저장된 time zone과 DST 정책에 따라 다음 시각을 계산하고 누락된 실행을 즉시 한 번 수행할지 건너뛸지 misfire 정책으로 결정합니다. 매우 많은 작업이 같은 정각에 몰리면 shard와 worker를 확장하고 허용된 작업에는 deterministic jitter를 적용하지만 예약 의미를 임의로 바꾸지는 않습니다.

핵심 키워드

  • Scheduler
  • Lease
  • Visibility Timeout
  • Fencing Token
  • Misfire

꼬리질문

질문 같은 작업을 여러 scheduler가 동시에 발견하면 어떻게 하나요?

답변 포인트 조건부 갱신, row claim과 lease로 한 scheduler가 enqueue 권한을 얻고 작업 ID로 중복을 제어합니다. Fencing token은 보호 자원에서 오래된 소유자의 쓰기를 거부하는 장치입니다.

질문 서버 시간이 서로 다르면 어떻게 하나요?

답변 포인트 시간원을 동기화하고 DB 시간 같은 기준을 사용하며 허용 오차와 misfire 정책을 둡니다.

질문 Worker가 작업을 완료했지만 ACK 전에 죽으면 어떻게 하나요?

답변 포인트 Visibility timeout 후 같은 run이 다시 전달될 수 있습니다. Handler가 run ID 또는 업무 idempotency key로 결과를 기록하고 이미 완료된 run이면 side effect를 반복하지 않아야 합니다.

질문 DST 전환 때 존재하지 않거나 두 번 나타나는 지역 시각의 cron은 어떻게 처리하나요?

답변 포인트 Job의 IANA time zone과 정책 version을 저장하고 건너뛴 시각은 skip 또는 다음 유효 시각 실행, 중복 시각은 한 번 또는 두 번 실행 중 명시된 정책을 따릅니다. 계산 결과와 실행한 local time·UTC를 이력에 남깁니다.

주의할 점

  • lease 획득만으로 작업의 정확히 한 번 실행이 보장되지는 않습니다.
  • Job claim과 queue 발행을 별도 비원자 연산으로 두면 scheduler 장애 때 작업이 유실될 수 있으므로 outbox나 lease 재수집 경로가 필요합니다.

13. 마이크로서비스 환경의 API Gateway와 서비스 디스커버리를 설계해 주세요.

문제 조건

  • 외부 API를 200개 내부 서비스로 라우팅하고 TLS 종료, 인증, 공통 rate limit과 요청 추적을 제공합니다.
  • 최대 초당 100만 요청, 서비스 인스턴스 최대 1만 개이며 자동 확장으로 인스턴스가 수시로 변경됩니다.
  • Gateway 추가 지연 p95는 10ms 이하, 월 가용성은 99.99%입니다.
  • 새 인스턴스 등록과 비정상 인스턴스 제외는 5초 안에 반영합니다.
  • Gateway에는 서비스별 비즈니스 조합 로직을 두지 않고 리전 내부 엔드포인트만 선택합니다.

기본 답변

Gateway의 route model은 host·path·method, 대상 service, 정책 version, timeout, retry 허용 조건과 인증·제한 규칙을 가집니다. Registry는 service, instance ID, zone, address, lease 만료와 health 상태를 저장하고 변경 사항을 versioned snapshot과 watch stream으로 배포합니다.

인스턴스는 시작 후 readiness가 통과해야 등록하고 주기적 heartbeat로 lease를 갱신하며 종료 전 draining 상태를 알립니다. heartbeat가 끊긴 인스턴스는 TTL 뒤 제외하되 active health check와 실제 요청 실패를 함께 사용해 일시적 control plane 단절을 인스턴스 장애로 오판하지 않습니다.

Gateway data plane은 마지막으로 검증된 endpoint snapshot을 메모리에 유지하고 요청마다 registry를 조회하지 않습니다. 리전·zone, 연결 수와 최근 실패를 기준으로 대상을 선택하고 timeout과 outlier ejection으로 stale endpoint 실패를 제한합니다. Retry는 route가 보장한 멱등 요청이거나, 비멱등 요청이라면 안정된 idempotency key를 upstream이 중복 제거할 때만 허용합니다. 이와 별개로 body가 있는 요청은 멱등 여부와 무관하게 Gateway가 안전하게 buffer·replay할 수 있어야 하며, 일부 전송된 streaming body를 재생할 수 없다면 PUT도 재시도하지 않습니다. 연결 전에 확실히 실패했거나 정책에 포함된 502·503·504 응답이면 다른 정상 인스턴스로 제한된 횟수만 재시도하되, 처리 여부가 불명확한 timeout은 멱등성이 없으면 재시도하지 않습니다. 각 시도 timeout을 전체 요청 deadline 안에 두고 retry budget으로 재시도 폭증을 막습니다.

이 조건처럼 공통 Gateway가 이미 있는 외부 트래픽은 server-side discovery가 클라이언트를 단순하게 만듭니다. 내부 고성능 호출에서 client-side discovery를 쓰면 프록시 hop을 줄일 수 있지만 모든 클라이언트가 resolver·load balancing과 갱신 로직을 올바르게 배포해야 합니다.

Control plane 장애 중에는 마지막 snapshot으로 계속 처리하되 TTL을 무한정 신뢰하지 않고 data plane 관측으로 불량 endpoint를 제거합니다. Gateway는 여러 장애 영역에 stateless로 분산하고 route 변경은 canary와 즉시 rollback 가능한 version으로 배포해 중앙 계층의 장애 범위를 제한합니다.

핵심 키워드

  • API Gateway
  • Service Registry
  • Discovery
  • Control Plane
  • Circuit Breaker

꼬리질문

질문 Gateway가 단일 장애점이 되지 않나요?

답변 포인트 여러 stateless 인스턴스를 장애 영역에 분산하고 고가용성 로드밸런서로 앞단을 구성합니다.

질문 registry 연결이 끊기면 기존 인스턴스를 바로 제거해야 하나요?

답변 포인트 즉시 제거하면 정상 트래픽도 멈출 수 있어 마지막 version snapshot을 유지합니다. Registry lease와 active health check, 실제 요청 실패를 구분하고 data plane이 stale endpoint를 임시 제외하되 control plane 복구 후 최신 snapshot과 조정합니다.

질문 Client-side discovery와 Gateway·프록시 기반 server-side discovery는 어떻게 선택하나요?

답변 포인트 Client-side는 hop과 프록시 병목을 줄이지만 모든 언어 클라이언트에 resolver·LB·갱신 로직을 배포해야 합니다. Server-side는 정책을 중앙화하고 클라이언트를 단순화하지만 data plane 용량과 가용성을 별도로 확보해야 합니다.

질문 새 인스턴스가 등록됐지만 아직 준비되지 않았거나 종료 중이면 어떻게 막나요?

답변 포인트 Startup과 readiness가 통과한 뒤 등록하고 종료 시 draining 상태로 새 요청을 막은 뒤 기존 연결을 끝냅니다. Heartbeat만 살아 있다는 이유로 트래픽을 보내지 않고 애플리케이션 readiness와 실제 요청 결과를 함께 사용합니다.

주의할 점

  • Gateway에 서비스별 비즈니스 로직을 계속 넣으면 배포 병목과 강한 결합이 생깁니다.
  • Registry의 control plane 가용성과 실제 요청을 전달하는 data plane 가용성을 같은 것으로 간주하지 않습니다.

14. 멀티 리전 서비스와 재해 복구 전략을 설계해 주세요.

문제 조건

  • 세 리전에서 사용자 프로필과 콘텐츠의 읽기·쓰기를 제공하고 읽기 비율은 90%입니다.
  • DAU 500만 명, 최대 초당 10만 요청이며 로컬 리전 조회 p95는 150ms 이하입니다.
  • 월 가용성은 99.99%이고 리전 전체 장애를 견뎌야 합니다.
  • 일반 콘텐츠의 RPO는 5분, RTO는 15분이며 계정 보안 변경은 사용자의 쓰기 리전에서 강한 일관성을 요구합니다.
  • 삭제 복구를 위한 독립 백업은 30일간 보존합니다.

기본 답변

Profile과 Content에는 owner의 home region, version과 변경 시각을 기록하고 API는 요청자의 가까운 정상 리전으로 라우팅합니다. 읽기는 지역 복제본에서 처리하되 계정 보안 변경과 직후 확인은 home region의 원본을 사용합니다.

Stateless API와 캐시는 세 리전에서 active-active로 운영하고 일반 쓰기는 사용자의 home region 한 곳에서 직렬화합니다. 일반 콘텐츠 변경 로그는 다른 리전으로 비동기 복제해 90% 읽기를 로컬에서 처리하고, 계정 보안 변경은 지정된 대기 리전까지 동기 복제한 뒤 성공을 응답합니다. 데이터 종류별 version과 lag를 응답 경로가 판단할 수 있게 합니다.

Home region 장애가 확인되면 분산된 fencing epoch를 증가시키고 지정된 대기 리전만 새 writer로 승격합니다. 이전 writer는 더 낮은 epoch의 쓰기를 거부해야 split-brain을 막을 수 있으며, 전역 라우터는 데이터 승격이 끝난 뒤 쓰기 트래픽을 전환합니다.

복구 후에는 양쪽 변경 로그를 대조하고 새 writer의 상태를 원본으로 삼아 이전 리전을 따라잡게 한 뒤 점진적으로 읽기와 쓰기를 failback합니다. 동시에 수정 가능한 일반 콘텐츠가 있다면 version 조건부 쓰기와 업무별 병합 정책을 사용하고 보안 데이터에는 자동 병합을 적용하지 않습니다.

비동기 복제는 지연과 리전 독립성을 얻는 대신 최대 5분의 데이터 손실 가능성을 가집니다. 별도 계정과 저장소의 30일 백업, 정기 복원 시험과 리전 전환 훈련으로 복제된 삭제·손상까지 복구할 수 있는지 검증합니다.

핵심 키워드

  • Multi-region
  • Active-active
  • Active-passive
  • RPO
  • RTO

꼬리질문

질문 두 리전에서 같은 데이터를 동시에 수정하면 어떻게 하나요?

답변 포인트 단일 쓰기 리전, 버전 기반 조건부 쓰기, 업무 규칙 병합이나 CRDT처럼 데이터에 맞는 충돌 정책이 필요합니다.

질문 DNS만 바꾸면 즉시 장애 조치가 되나요?

답변 포인트 DNS cache와 TTL 때문에 늦을 수 있어 글로벌 로드밸런서, 연결 draining과 데이터 준비 상태도 고려합니다.

질문 장애 난 기존 writer가 네트워크 복구 후 다시 쓰기 시작하는 split-brain을 어떻게 막나요?

답변 포인트 승격 때 단조 증가 fencing epoch를 발급하고 저장 계층이 현재 epoch보다 오래된 writer의 쓰기를 거부해야 합니다. 라우팅 전환만으로는 기존 연결과 지연 요청의 쓰기를 막을 수 없습니다.

질문 장애 리전이 복구되면 바로 원래 구조로 failback해도 되나요?

답변 포인트 먼저 새 writer의 로그와 snapshot으로 복구 리전을 따라잡고 checksum·lag를 검증합니다. 읽기 canary부터 점진 전환한 뒤 계획된 새 epoch로 쓰기를 옮기며 급한 자동 failback보다 안정된 현 writer 유지가 안전할 수 있습니다.

주의할 점

  • 복제는 백업이 아니며 잘못된 삭제도 복제되므로 독립 백업과 복구 시험이 필요합니다.
  • RPO와 RTO는 문서상의 목표가 아니라 정기 복원·리전 전환 훈련에서 실제 측정해야 합니다.

15. 대규모 서비스의 관측 가능성과 과부하 대응 체계를 설계해 주세요.

문제 조건

  • 100개 서비스가 최대 초당 100만 요청을 처리하며 핵심 API의 p99 목표는 300ms입니다.
  • 핵심 사용자 흐름의 월 가용성 SLO는 99.95%이고 장애는 5분 안에 탐지하는 것을 목표로 합니다.
  • Telemetry 수집 오버헤드는 서비스 CPU의 2%, 네트워크 트래픽의 1% 이내로 제한합니다.
  • 로그는 30일, 표본 trace는 7일간 보존하며 민감정보는 수집하지 않습니다.
  • 과부하 시 핵심 쓰기를 보호하고 추천·통계 같은 best-effort 요청부터 제한합니다.

기본 답변

핵심 사용자 흐름별 성공률과 지연 분포를 SLI로 정의하고 99.95% 월 SLO와 error budget을 계산합니다. 요청량·오류·지연과 saturation을 서비스·route·상태 코드처럼 제한된 카디널리티 label의 metric으로 수집하며 요청 ID를 일반 metric label로 넣지 않습니다.

Structured log와 distributed trace는 trace ID 또는 correlation ID로 연결하고 표본 trace의 span에 의존성·오류 정보를 남깁니다. Metric exemplar가 지원되면 집계된 지연 구간에서 대표 trace로 이동하되, 민감정보와 무제한 사용자·URL 값을 label이나 span 속성으로 수집하지 않습니다.

Telemetry agent와 collector는 로컬 buffer와 batch 전송으로 애플리케이션 경로에서 분리하고 수집 백엔드 장애 때 표본율과 비핵심 로그를 줄입니다. 관측 파이프라인 장애가 서비스 요청을 막지 않게 하면서 drop 비율 자체는 별도 health metric으로 감시합니다.

Alert는 단일 순간 임계치보다 사용자 증상과 짧고 긴 구간의 error-budget burn rate를 사용합니다. 대시보드는 p50·p95·p99를 route, region과 의존성별로 분해하고 경보마다 owner, runbook과 최근 변경 정보를 연결합니다.

과부하에는 bounded queue, admission control과 우선순위 기반 load shedding으로 best-effort 요청부터 줄입니다. 모든 원격 호출에 deadline을 전파하고 retry budget 안에서 지수 backoff와 jitter를 적용하며 circuit breaker와 bulkhead로 느린 의존성의 장애 전파를 제한합니다.

핵심 키워드

  • SLI
  • SLO
  • Error Budget
  • Distributed Trace
  • Load Shedding

꼬리질문

질문 재시도가 왜 장애를 더 크게 만들 수 있나요?

답변 포인트 실패 요청이 동시에 재시도되면 retry storm이 발생하므로 retry budget, backoff, jitter와 멱등성이 필요합니다.

질문 평균 응답시간은 정상인데 사용자가 느리다고 하면 무엇을 보나요?

답변 포인트 평균이 숨기는 p95·p99 tail latency를 경로, 리전, 고객군과 의존성별로 분해합니다.

질문 요청별 correlation ID를 metric label로 넣으면 왜 문제가 되나요?

답변 포인트 요청마다 새로운 label 조합이 생겨 time series 수와 메모리·저장 비용이 폭증합니다. Metric은 service·route·status처럼 제한된 값을 사용하고 요청 연결은 log·trace ID와 exemplar로 수행합니다.

질문 Telemetry 백엔드가 장애 났을 때 서비스 요청도 실패시켜야 하나요?

답변 포인트 일반적으로 요청 경로와 격리하고 로컬 bounded buffer, batch와 sampling으로 서비스를 보호합니다. Buffer가 차면 우선순위 낮은 telemetry를 버리되 drop 비율과 collector 상태는 독립 경로로 경보해야 합니다.

주의할 점

  • 로그를 많이 모으는 것만으로는 관측 가능성이 되지 않으며 SLO와 대응 절차로 연결해야 합니다.
  • 사용자 ID, 전체 URL과 correlation ID처럼 고카디널리티 값을 일반 metric label로 사용하지 않습니다.
  • 무제한 재시도와 무제한 큐는 장애를 지연시키는 것이 아니라 부하와 복구 시간을 키울 수 있습니다.