시스템 설계 예상문제 (고급)
Backend Engineering · System Design Practice
시스템 설계 예상문제
대규모 시스템 설계 면접에서 자주 다루는 주제를 실제 운영 상황으로 재구성한 연습 자료입니다. 요구사항을 좁히고, 병목을 찾고, 선택의 근거와 실패 대응을 설명하는 데 초점을 둡니다.
글로벌 URL 단축·리다이렉트 플랫폼 설계
상황
조건
규모와 시스템 구성
- 월 30억 리다이렉트, 평균 12만 RPS·이벤트 피크 150만 RPS
- 일 2천만 링크 생성, 7년 보존, 읽기:쓰기 약 500:1
- 서울·도쿄·버지니아 3개 리전, CDN/Redis/MySQL 기반
- 사용자 지정 alias, 만료, 클릭 통계, 신고·차단 API 제공
현재 문제와 의사결정 제약
- Hot Key상위 0.01% 링크가 전체 조회의 35% 차지
- Correctness키 충돌·재시도에도 매핑은 불변이어야 함
- Availability기존 링크 월 99.99%, 리다이렉트 p99 80ms 목표
- Security차단은 30초, 오탐 해제는 5분 이내 전파
꼬리 질문
요구사항을 어떤 순서로 구체화하고, 단축 키 생성과 저장소를 어떻게 선택하겠습니까? 단순히 Base62를 사용한다는 답을 넘어 충돌, 재시도, 다중 리전 생성, 사용자 지정 alias가 동시에 존재할 때 지켜야 할 불변식과 선택하지 않은 대안이 더 적합해지는 조건을 설명해 주세요.
답변 예시 보기
요구사항과 불변식부터 좁히기
먼저 링크를 수정 가능한가, 같은 원본 URL을 여러 번 줄이면 같은 키를 주는가, 삭제한 alias를 재사용하는가를 확인하겠습니다. 이 사례에서는 발급된 key는 정확히 하나의 longUrl에만 매핑되고 재사용하지 않는다를 핵심 불변식으로 둡니다. 리다이렉트 가용성과 생성 일관성은 분리합니다. 기존 링크 조회는 리전 단절에도 허용하지만 새 alias의 유일성은 일시적으로 생성을 거부하더라도 지키는 식입니다.
선택과 트레이드오프
- 운영 인력과 기간을 고려해 각 리전에 겹치지 않는 ID 구간을 임대하고, 단조 증가 ID를 Base62로 표현하는 방식을 우선 검토합니다. 구간 임대는 중앙 sequence 의존도를 줄이지만 ID가 어느 정도 예측 가능하므로 난독화 permutation이나 별도 랜덤 suffix가 필요합니다.
- 완전 랜덤 7~8자 키는 중앙 조정이 없지만 생일 역설에 따른 충돌 검사와 재시도가 필요합니다. DB의 unique constraint가 최종 방어선이며, API의 idempotency key와 요청 fingerprint를 저장해 응답 유실 후 재시도가 새 링크를 만들지 않게 합니다.
- 사용자 alias는 자동 키와 namespace를 분리하거나 동일 unique index에서 강하게 예약합니다. alias 생성은 home region의 writer로 라우팅하고 합의가 안 되면 409/503을 반환해 split-brain 중복을 피합니다.
저장소는 key 단건 조회가 대부분이므로 분산 KV도 맞지만, 3개월·4명이라는 제약에서는 샤딩된 RDB와 캐시로 시작할 수 있습니다. 용량·쓰기 처리량·리전 독립성이 임계점을 넘으면 Dynamo 계열 KV로 옮기되, TTL·2차 조회·백업 복구 기능까지 비교합니다.
유명 캠페인 링크가 150만 RPS를 만들고 동시에 한 리전의 Redis가 장애 났습니다. CDN·지역 캐시·원본 저장소의 역할과 TTL/무효화 정책을 어떻게 나누겠습니까? 캐시 stampede, 존재하지 않는 키 공격, 잘못 캐시된 매핑, 리전 장애 시의 degraded mode까지 포함해 설명해 주세요.
답변 예시 보기
읽기 경로와 실패 모드
정상 경로는 Anycast/DNS → edge redirect cache → 리전 서비스 → 로컬 Redis → 영속 저장소로 둡니다. 매핑이 불변이라는 특성을 활용해 정상 링크는 긴 TTL과 stale-while-revalidate를 사용할 수 있습니다. 다만 만료·차단 여부는 같은 값에 긴 TTL로 묶지 않고 더 짧고 빠르게 갱신되는 정책 계층으로 분리해야 합니다.
- hot key는 CDN에서 흡수하고 리전 내부에서는 요청 병합(single flight), soft TTL, TTL jitter로 동시 원본 조회를 줄입니다. Redis 장애 때 모든 miss를 DB로 보내지 않고 DB 보호용 concurrency limit와 admission control을 둡니다.
- 존재하지 않는 키는 짧은 negative cache와 Bloom filter로 막되, 방금 생성된 링크가 404로 고정되지 않도록 생성 이벤트로 negative cache를 무효화합니다. 공격자가 무작위 키를 순회하면 IP·ASN·tenant 단위 rate limit을 별도로 적용합니다.
- 리전 원본이 끊기면 이미 검증된 정상 링크는 last-known-good 값을 제한 시간 동안 제공하고, 신규·미확인 링크는 다른 리전으로 조회합니다. 다른 리전도 불확실하면 보안 성격과 사업 영향을 기준으로 fail-open 범위를 명시합니다.
검증과 관측
계층별 hit ratio, origin RPS, single-flight 대기 수, stale 응답 비율, 잘못된 목적지 검증 오류를 대시보드로 분리합니다. hot key 부하와 Redis primary failover를 함께 주입해 DB QPS 상한, p99, 정상 링크 성공률이 유지되는지 검증하고, 오래된 캐시로 인한 위험은 별도 보안 SLO로 측정합니다.
보안팀이 피싱 URL을 30초 안에 전 세계에서 차단하려 하지만, CDN purge API 일부가 실패하거나 잘못된 탐지 규칙이 정상 캠페인 10만 개를 차단할 수 있습니다. 빠른 전파와 오탐 피해 사이에서 정책 데이터, 승인, 캐시 일관성, 복구 절차를 어떻게 설계하겠습니까?
답변 예시 보기
매핑과 정책의 수명주기 분리
longUrl 매핑은 불변 데이터로 유지하고, ALLOW·QUARANTINED·BLOCKED와 정책 버전을 별도 저장하겠습니다. 리다이렉트 edge는 매핑 캐시가 있어도 더 최신인 signed deny-list 또는 정책 버전을 확인합니다. 차단 이벤트는 고우선 topic으로 모든 리전에 전파하고, 각 소비자가 적용한 version과 시각을 중앙에서 수집해 30초 SLO 미달 지역을 식별합니다.
- 확신도가 높은 악성 도메인이나 법적 요청은 자동 BLOCKED, 휴리스틱 탐지는 짧은 QUARANTINED와 검토 큐로 분리합니다. 대량 변경은 예상 영향 링크·트래픽을 preview하고 비율 상한과 2인 승인을 적용합니다.
- CDN purge만 성공 조건으로 믿지 않고 edge에서 작은 고우선 deny-list를 먼저 조회하게 합니다. purge 재시도는 멱등하게 하고, 적용 확인이 안 된 POP는 우회하거나 보안 요구에 따라 요청을 보수적으로 차단합니다.
- 모든 변경은 actor·근거·정책 버전·영향 범위를 immutable audit log에 남기며, 해제는 이전 버전으로 되돌리는 새 이벤트로 처리합니다. 오탐 해제 시 negative/deny cache까지 역방향 전파되는지 점검합니다.
운영 훈련에는 일부 POP 이벤트 누락, 오래된 consumer, 잘못된 대량 규칙을 포함합니다. 차단 전파 p95/p99뿐 아니라 오탐 링크 수, 롤백 시간, 사용자 이의제기 처리 시간을 함께 측정해야 속도만 최적화하는 오류를 피할 수 있습니다.
클릭 통계팀은 국가·기기·캠페인별 실시간 수치를 1분 이내에 보고 싶고 광고 정산팀은 중복 없는 확정 수치를 요구합니다. 리다이렉트 지연을 늘리지 않으면서 이벤트 수집·중복 제거·늦은 이벤트·개인정보·온라인 수치와 배치 정산의 불일치를 어떻게 다루겠습니까?
답변 예시 보기
사용자 경로와 분석 경로 분리
리다이렉트 응답은 통계 저장 성공을 기다리지 않습니다. edge/서비스가 eventId, linkId, eventTime, 거친 지역·기기 정보를 비차단 로그나 지역 collector에 기록하고 즉시 3xx를 반환합니다. 버퍼가 가득 차면 정산 대상 이벤트와 일반 분석 이벤트의 우선순위를 나누고, 어느 정도의 분석 손실을 허용하는지 명시합니다.
잠정치와 확정치의 계약
- 스트림 처리는 event time과 watermark로 1분 집계를 만들고, 늦은 이벤트는 일정 기간 update/retraction합니다. eventId 중복 제거의 보존 기간은 재시도 최대 시간과 비용을 근거로 정합니다.
- 실시간 대시보드는 ‘잠정’ 표식을 두고, 원본 immutable log를 재처리한 일별 배치 결과를 정산 기준으로 삼습니다. 두 경로가 같은 캠페인 귀속·봇 필터 규칙을 공유하도록 버전된 규칙 라이브러리나 데이터 계약을 둡니다.
- IP와 user agent는 목적에 필요한 최소 수준으로 축약·해시하고 보존 기간과 접근 권한을 제한합니다. 삭제 요청이 원본·파생 집계에 어떻게 적용되는지도 법무와 합의합니다.
실시간/확정 수치 차이율, 미수집률, 중복률, watermark 지연을 운영 지표로 두고 임계치를 넘으면 정산 확정을 보류합니다. 리다이렉트 p99와 수집 손실률을 함께 부하 테스트해 관측 기능이 핵심 경로를 방해하지 않는지 확인합니다.
대규모 실시간 채팅·대형 채널 설계
상황
조건
규모와 기능
- DAU 4천만, 동시 WebSocket 700만, 평균 메시지 40만 건/초
- 1:1·그룹·20만 명 공개 채널, 멀티 디바이스 로그인
- 읽음 위치, 푸시 알림, 30일 검색, 이미지 첨부
- 서울·싱가포르 2개 리전 active-active
현재 문제와 보장 범위
- Reconnectgateway 장애 후 누락·중복·재접속 폭주
- Ordering클라이언트 시각 차이로 대화 순서 역전
- Fan-out대형 채널이 broker·inbox 저장소 고갈
- Moderation삭제 전파와 증거 보존 요구가 충돌
꼬리 질문
700만 WebSocket 연결을 여러 gateway에 분산하고 특정 사용자의 현재 연결을 찾아 메시지를 전달하는 전체 흐름을 설계해 주세요. connection registry의 stale 정보, gateway 장애, 100만 단말의 동시 재접속, 멀티 디바이스, 전달 ACK의 의미를 어떻게 정의할지도 설명해 주세요.
답변 예시 보기
컴포넌트와 상태 분리
gateway는 TLS/WebSocket과 heartbeat를 담당하되 메시지의 유일한 영속 저장소가 되지 않게 합니다. 인증 후 userId-deviceId → gatewayId-connectionId를 TTL lease로 registry에 등록하고 주기적으로 갱신합니다. 메시지는 chat service가 대화방 로그에 먼저 append하고, delivery router가 registry를 조회해 온라인 연결에 전송합니다. registry가 틀려도 메시지가 사라지지 않고 재동기화로 복구되어야 합니다.
- gateway 장애는 lease 만료로 정리하며, router는 stale route 전송 실패를 재등록 신호로 취급합니다. connectionId에 gateway epoch를 포함해 이전 프로세스의 늦은 응답이 새 연결 상태를 덮지 않게 합니다.
- 서버 전달 ACK, 단말 수신 ACK, 사용자가 읽은 read cursor를 별도 상태로 정의합니다. ACK 유실은 중복 재전송을 만들 수 있으므로 messageId 기준으로 클라이언트와 inbox 소비를 멱등하게 합니다.
- 재접속에는 지수 백오프·지터·gateway별 admission limit을 적용하고, 클라이언트가 마지막 확정 conversation sequence를 보내 누락 구간만 pull하게 합니다. 여러 기기는 각 device cursor와 사용자 공통 read cursor의 의미를 제품과 합의합니다.
gateway kill, registry 지연, 리전 네트워크 단절을 주입해 연결 복구 시간, 미확인 메시지 수, 중복률, broker lag를 측정합니다. registry가 unavailable일 때 전체 발송을 막을지 offline inbox로 전환할지도 명시합니다.
서울과 싱가포르에서 같은 대화방에 동시에 메시지가 작성될 때 어떤 순서 보장을 사용자에게 제공하겠습니까? 전역 total order를 포기한다면 대화방별 sequence, 리전 장애 조치, 낙관적 UI, 중복 발행과 늦게 도착한 메시지를 어떻게 조정할지 설명해 주세요.
답변 예시 보기
보장 범위를 먼저 선언
모든 대화방의 전역 순서는 필요하지 않습니다. 이 사례에는 대화방 내부에서 서버가 확정한 순서와 같은 사용자가 보낸 메시지의 program order를 목표로 하겠습니다. 클라이언트 시각은 표시 참고값일 뿐 정렬 기준으로 믿지 않습니다. conversationId를 partition key로 사용해 한 시점에는 하나의 ordering authority가 sequence를 부여하도록 합니다.
다중 리전 선택
- 각 대화방에 home region을 두면 순서는 단순하지만 원거리 쓰기 지연과 리전 장애 시 failover 시간이 생깁니다. 대화방 참여자 지역에 따라 home을 정하고, 변경 시 epoch/fencing token을 올려 구 leader의 쓰기를 거부합니다.
- active-active에서 양쪽이 독립 sequence를 만들면 가용성은 높지만 완전한 선형 순서를 즉시 제공하기 어렵습니다. 공개 채널은 regionId+local sequence로 받아 후결정 정렬할 수 있지만 1:1 금융 상담 등 강한 순서가 필요하면 home writer를 유지하는 식으로 등급을 나눕니다.
- 클라이언트는 clientMessageId로 임시 버블을 표시하고 서버 ACK의 sequence와 대조합니다. gap을 발견하면 range fetch하고, 같은 clientMessageId의 중복 응답은 합칩니다. 늦은 메시지를 화면 중간에 삽입할지 ‘지연 도착’으로 표시할지는 UX 계약입니다.
failover 중 허용할 RPO와 메시지 전송 중단 시간을 정하고, 두 리전 leader가 동시에 살아나는 상황에서 중복 sequence가 생성되지 않는지 검증합니다. 순서 역전율과 gap 복구 시간도 운영 지표로 둡니다.
500명 그룹과 20만 명 공개 채널에 같은 fan-out 전략을 적용할 수 있을까요? 활성/비활성 회원, 온라인 push와 과거 조회, 읽음 상태, 느린 consumer, 유명 채널 하나가 다른 방을 방해하는 문제까지 고려해 혼합 전략과 전환 기준을 설명해 주세요.
답변 예시 보기
비용 모델에 따른 혼합
소규모 방은 메시지 작성 시 회원 inbox에 pointer를 fan-out하면 읽기 지연과 unread 계산이 단순합니다. 하지만 20만 명 채널에서 메시지마다 영구 inbox 행을 만들면 대부분 읽지 않는 사용자까지 쓰기 증폭을 감당하게 됩니다. 대형 채널은 방 로그를 한 번 저장하고 온라인 활성 사용자에게만 ephemeral push하며, 재접속 사용자는 channel cursor 이후를 pull하는 fan-out on read가 적합합니다.
- 전환 기준은 회원 수 하나가 아니라 메시지 빈도×활성 회원 수, push 성공률, 저장 비용, 목표 읽기 지연으로 계산합니다. 기준을 넘나드는 방을 즉시 마이그레이션하지 않도록 hysteresis를 두고 전략 버전을 메타데이터에 기록합니다.
- partition은 conversationId로 하되 초대형 채널 한 개가 단일 partition을 고갈시키면 ingest와 delivery를 분리합니다. ordering log는 한 stream에 유지하고 fan-out 작업은 messageId와 recipient shard로 병렬화하며, tenant/channel quota와 전용 worker pool로 격리합니다.
- 읽음은 개별 메시지 boolean 대신 방별 lastReadSequence를 저장합니다. 대형 채널의 정확한 전체 read receipt는 비용과 개인정보 면에서 필요한지 확인하고, 필요하면 최근 활성 회원만 집계합니다.
backpressure 시 오래된 공개 채널 push를 버리고 pull로 복구할 수 있지만 1:1 메시지는 durable queue를 유지합니다. 유형별 queue age, drop 수, inbox write amplification, 재접속 fetch 지연을 측정해 정책을 조정합니다.
신고된 욕설 메시지를 2초 안에 모든 사용자 화면에서 숨겨야 하지만 감사팀은 원문과 처리 이력을 1년 보존해야 합니다. 이미 push된 메시지, 검색 인덱스·캐시·첨부 파일, 잘못된 자동 차단의 복구, 운영자 권한을 포함한 삭제·moderation 흐름을 설계해 주세요.
답변 예시 보기
가시성과 보존을 분리
원본 행을 물리 삭제하는 대신 messageId의 visibility를 VISIBLE→QUARANTINED→REMOVED로 전환하고 정책 version을 기록합니다. moderation 이벤트는 고우선 topic으로 gateway, 조회 서비스, 검색 색인에 전파합니다. 클라이언트에도 redact 이벤트를 보내 이미 렌더링된 버블을 대체하지만 오프라인 단말은 다음 동기화 시 최신 visibility를 반드시 확인합니다.
- 조회 경로는 캐시된 본문만 믿지 않고 작은 tombstone/visibility 집합을 최종 필터로 적용합니다. 검색 색인 삭제와 CDN 첨부 purge는 비동기여도 UI 노출을 먼저 막고, 완료 상태를 추적합니다.
- 감사 원문은 일반 운영 DB와 분리해 암호화하고 최소 권한·break-glass 승인·조회 감사 로그·1년 TTL을 둡니다. 사용자 삭제 요청과 법적 보존 hold가 충돌할 때의 책임 주체를 명확히 합니다.
- 자동 모델은 확신도에 따라 임시 숨김과 즉시 차단을 나누고, 대량 차단에는 rate limit과 영향도 상한을 둡니다. 오탐 복원도 새 version 이벤트로 처리해 모든 파생 저장소가 역전파를 받게 합니다.
2초 SLO는 ‘서버 조회에서 노출되지 않음’과 ‘온라인 화면에서 사라짐’을 구분해 측정합니다. consumer 중단·CDN purge 실패·오탐 대량 변경 훈련으로 누락 저장소와 복구 시간을 검증합니다.
규정 준수형 멀티채널 알림 플랫폼 설계
상황
조건
규모와 시스템 구성
- 평균 5만 건/초, 캠페인 피크 30만 건/초, 일 15억 건
- 4개 채널·채널별 2개 공급자, 예약·취소·fallback
- 계열사별 템플릿, 12개 언어, 사용자별 동의·timezone
- 요청 상태 조회, webhook, 비용·전달률 리포트 제공
현재 문제와 보장 범위
- Priority캠페인이 OTP worker·quota 고갈
- Unknown공급자 timeout 후 실제 전송 여부 불명
- Compliance오래된 동의 cache로 야간 발송 발생
- Migration60개 호출자의 API·상태 의미가 서로 다름
꼬리 질문
요청 접수부터 정책 확인, 템플릿 렌더링, 외부 공급자 전송, 결과 webhook까지 컴포넌트와 상태 전이를 설계해 주세요. API가 언제 성공을 반환해야 하는지, DB 커밋과 메시지 발행 사이의 유실, 예약 취소와 중복 요청을 어떻게 다룰지도 설명해 주세요.
답변 예시 보기
접수와 최종 전달을 구분
API 성공은 공급자 전달 성공이 아니라 플랫폼이 책임지고 처리할 요청을 내구성 있게 접수했다는 의미로 정의합니다. tenantId+idempotencyKey로 동일 요청의 기존 notificationId와 상태를 반환하고, 요청 행과 outbox 이벤트를 같은 로컬 트랜잭션으로 커밋한 뒤 202를 응답합니다. relay 중복 발행은 downstream이 notificationId/channelAttempt 기준으로 멱등 처리합니다.
- 상태는 ACCEPTED→SCHEDULED→POLICY_CHECKED→DISPATCHING→DELIVERED/FAILED/SUPPRESSED처럼 구분하고 공급자 ACK와 실제 단말 전달을 같은 ‘성공’으로 부르지 않습니다. 상태 전이는 expected version 조건부 갱신으로 역행을 막습니다.
- 정책 서비스는 동의·timezone·frequency cap을 발송 직전에 다시 확인하고, 템플릿은 승인된 version을 요청에 고정합니다. 오래 대기한 예약이 최신 규정을 우회하지 않게 합니다.
- 취소는 상태 머신의 command입니다. 아직 dispatch 전이면 차단하고, 이미 공급자에 제출했다면 취소 불가능 상태를 명확히 반환합니다. 예약 스캐너는 lease와 fencing token으로 중복 claim을 막되 worker 실행은 멱등하게 만듭니다.
outbox age, 상태 체류 시간, 정책 차단률, webhook 재시도와 공급자별 결과 미확정 건수를 관측합니다. DB 커밋 직후 프로세스 종료, relay 중복, 예약 worker failover를 주입해 최종 상태가 수렴하는지 확인합니다.
SMS 공급자가 3초 후 timeout을 반환했는데 실제로는 문자를 전송했을 수 있습니다. OTP·결제 알림·마케팅에서 같은 재시도 정책을 써야 할까요? 멱등키 지원 여부, 상태 조회 API, 대체 채널 fallback, 재시도 상한과 결과 불명 상태의 운영 처리를 어떻게 결정하겠습니까?
답변 예시 보기
업무 영향에 따른 전달 의미 선택
timeout은 실패가 아니라 결과 불명입니다. 공급자가 idempotency key를 지원하면 같은 providerRequestId로 재시도하고 상태 조회 API/webhook과 대조할 수 있습니다. 지원하지 않으면 내부 dedup만으로 공급자 내부의 중복 발송을 막을 수 없으므로 알림 유형별로 유실과 중복 중 어느 피해가 큰지 결정해야 합니다.
- OTP는 짧은 유효시간과 최신 코드만 유효하게 해 제한된 재발송을 허용할 수 있습니다. 단, SMS timeout 직후 push fallback을 보내 두 채널의 코드가 달라지지 않도록 인증 challengeId를 공유합니다.
- 결제 완료처럼 중복이 불쾌하지만 유실도 큰 알림은 상태 조회를 먼저 하고 일정 시간 UNKNOWN으로 보류한 뒤, 동일 내용임을 알 수 있는 eventId를 포함해 제한 재시도합니다. 마케팅은 중복 피해와 규정 위험이 더 크므로 at-most-once에 가깝게 중단할 수 있습니다.
- 재시도는 오류 분류, exponential backoff+jitter, retry budget, 공급자별 circuit breaker를 둡니다. 영구 오류와 결과 불명을 DLQ에서 분리하고 운영자가 근거 없이 일괄 재발송하지 못하게 영향도 preview와 승인을 둡니다.
공급자별 unknown rate, 중복 신고, 최종 전달률, fallback 비용을 함께 측정합니다. ‘exactly once 발송’이라고 약속하기보다 어느 구간에서 중복 가능성이 남고 사용자 경험으로 어떻게 완화하는지 명시합니다.
오전 10시에 3천만 건 캠페인이 시작된 직후 SMS 공급자 처리량이 절반으로 떨어졌습니다. OTP 5초 SLO를 지키면서 캠페인을 하루 안에 끝내려면 queue·worker·quota를 어떻게 격리하고, backpressure·공정성·공급자 전환을 어떤 지표로 제어하겠습니까?
답변 예시 보기
물리적 격리와 동적 admission
priority 필드 하나를 둔 공용 큐만으로는 낮은 우선순위 backlog가 저장소와 worker를 고갈시킬 수 있습니다. OTP/보안, 거래, 마케팅을 별도 topic·consumer group·worker pool로 나누고 OTP에는 공급자 quota와 컴퓨트의 예약 용량을 둡니다. 채널·공급자·tenant별 token bucket을 계층적으로 적용해 한 캠페인이 전체 자원을 독점하지 못하게 합니다.
- queue age, 공급자 성공률·p95, 429 비율, OTP SLO burn rate를 feedback signal로 사용해 캠페인 release rate를 낮춥니다. 단순 큐 길이보다 oldest age가 사용자 지연을 더 잘 보여줍니다.
- 공급자 전환은 오류율과 비용만 보지 않고 발신 번호·템플릿 승인·지역 규정 호환성을 확인합니다. 회복 시 트래픽을 한꺼번에 되돌리지 않고 probe→10%→확대하는 방식으로 hysteresis를 둡니다.
- tenant weighted fair queueing으로 캠페인 간 공정성을 보장하고 작은 고객이 대형 고객 뒤에서 하루 종일 굶지 않게 합니다. 하루 내 완료가 불가능하다는 forecast가 나오면 조기에 캠페인 소유자에게 지연·채널 변경 선택지를 제공합니다.
용량 계획은 정상 평균이 아니라 공급자 한 곳 상실과 캠페인 동시 실행을 기준으로 합니다. 부하 테스트에서 OTP p99, 마케팅 예상 완료 시각, starvation, 복구 후 retry storm을 함께 확인합니다.
한국 사용자가 해외 여행 중이고, 마케팅 동의를 철회한 직후 이미 수백만 건의 예약 이벤트가 큐에 들어가 있습니다. 어느 시점의 동의·timezone·frequency cap을 적용할지, 정책 서비스 장애 시 fail-open/closed, 감사 증빙과 60개 계열사의 점진 마이그레이션을 어떻게 설계하겠습니까?
답변 예시 보기
정책의 기준 시점과 출처
마케팅 규정은 요청 접수 시점뿐 아니라 실제 발송 직전의 최신 동의를 기준으로 다시 평가합니다. 예약 이벤트에는 사용자 프로필 전체를 복사하지 않고 대상 userId, 캠페인 정책 version, 요청 근거를 담습니다. 발송 worker가 중앙 consent service의 versioned snapshot 또는 변경 stream으로 유지한 로컬 projection을 조회합니다.
- timezone은 계정 설정, 최근 신뢰 가능한 지역, 캠페인 기본값의 우선순위를 제품·법무와 정합니다. 불확실하면 더 보수적인 quiet hours를 적용하고 다음 허용 시각으로 재예약합니다.
- 정책 서비스 장애 때 OTP 같은 필수 거래 알림과 마케팅을 분리합니다. 마케팅은 fail-closed, 보안 알림은 법적 근거와 last-known-good age 한도 안에서 허용하는 정책표를 둡니다.
- 결정마다 consentVersion, ruleVersion, timezone source, 평가 결과를 감사 로그에 남깁니다. 철회 이벤트는 고우선으로 projection에 반영하고 backlog에서 아직 dispatch되지 않은 항목을 suppress합니다.
마이그레이션은 legacy adapter로 기존 동기 API를 수용하되 새 notificationId와 상태 의미로 변환합니다. 계열사별 shadow 평가로 기존/신규 정책 결과 차이를 비교하고, 작은 비율의 비중요 알림부터 전환합니다. 규정 위반 0건, suppress 차이, 지연과 문의량을 gate로 삼아 확대합니다.
개인화 소셜 홈 피드 설계
상황
조건
규모와 기능
- DAU 8천만, 일 2억 게시물, 홈 피드 피크 90만 RPS
- 일반 사용자 팔로우 중앙값 420명, 유명인 최대 8천만 팔로워
- 팔로우·추천·광고 혼합, 무한 스크롤, 3개 리전
- 뮤트·차단·삭제·친구 전용 공개 범위 지원
현재 문제와 의사결정 제약
- Write Amp유명인 게시물 fan-out 폭발
- Latencyranking feature timeout이 전체 응답 지연
- Pagination새 게시물 유입 시 중복·누락
- Privacy파생 timeline의 삭제·차단 반영 지연
꼬리 질문
fan-out on write와 fan-out on read 중 하나를 고르기보다 일반 계정·유명인·비활성 사용자를 다르게 처리한다면 분류 기준과 데이터 흐름을 설계해 주세요. 임계값 변경, 유명인 등극, 중복 후보 병합, fan-out backlog와 비용을 어떻게 운영할지도 설명해 주세요.
답변 예시 보기
hybrid를 선택하는 근거
일반 계정은 게시 시 활성 팔로워의 home timeline에 postId pointer를 push해 빠른 읽기를 제공하고, 유명인은 author outbox/stream에 한 번 저장한 뒤 조회 시 pull해 병합하겠습니다. 비활성 사용자는 모든 게시물을 미리 복제하지 않고 재방문 시 follow graph와 author stream에서 최근 후보를 재구성합니다.
- 분류는 팔로워 수만 보지 않고 게시 빈도×최근 활성 팔로워 수로 예상 write amplification을 계산합니다. fan-out 완료 목표와 worker 비용을 넘으면 pull 대상으로 전환하며, hysteresis를 둬 전략이 자주 흔들리지 않게 합니다.
- 전략 version과 전환 시각을 author metadata에 기록합니다. 전환 경계의 게시물이 push timeline과 author stream 양쪽에 있을 수 있으므로 postId로 dedup하고, 누락 가능 기간은 backfill job으로 메웁니다.
- fan-out queue는 author/tenant별 quota와 shard를 두어 유명인 하나가 다른 게시물을 막지 않게 합니다. queue age, 활성 사용자 freshness, post당 복제 수, 읽기 병합 비용을 함께 측정합니다.
정답인 임계값은 고정 숫자가 아니라 실제 팔로워 활성률과 인프라 비용에서 나옵니다. 유명인 게시·계정 팔로워 급증·worker 절반 장애를 재현해 freshness SLO와 읽기 p99가 동시에 유지되는지 확인합니다.
추천 후보 생성, feature 조회, ranking 모델, 광고 서비스 중 하나가 느려져도 300ms를 지켜야 합니다. 단계별 latency budget, 부분 결과, timeout·circuit breaker, fallback 품질, 실험 모델의 장애 격리를 어떻게 설계하고 어떤 지표로 확대·롤백하겠습니까?
답변 예시 보기
다단계 피드와 시간 예산
팔로우 후보, 추천 후보, 광고 후보를 병렬로 수집하고 lightweight filter→feature fetch→rank→policy filter 순으로 좁힙니다. 예를 들어 전체 300ms 중 gateway 30ms, 후보 70ms, feature 60ms, ranking 50ms, hydration 60ms, 여유 30ms처럼 예산을 두고 각 호출 timeout은 상위 예산보다 짧게 설정합니다.
- 추천이 늦으면 사전 계산된 후보, 지역 인기 콘텐츠, 순수 팔로우 최신순으로 단계적 fallback합니다. 광고 장애는 빈 슬롯 또는 자연 게시물로 채우며 홈 피드 전체 실패로 전파하지 않습니다.
- 사용자·요청별 deadline을 downstream에 전파하고 deadline 이후 결과는 버립니다. bulkhead와 모델 version별 worker pool/circuit breaker로 실험 모델의 메모리 누수나 긴 tail이 control 모델을 고갈시키지 않게 합니다.
- 캐시된 feature에는 생성 시각과 version을 포함하고 허용 stale 범위를 feature별로 다르게 둡니다. 차단·안전 정책 같은 feature는 오래됐으면 후보를 제거하지만 취향 점수는 제한적으로 stale을 허용할 수 있습니다.
확대 gate는 p99와 오류율뿐 아니라 fallback rate, 피드 빈 화면 비율, 세션당 소비·숨김·신고 같은 품질 지표를 함께 봅니다. shadow→1% canary→점진 확대하고 latency budget 또는 안전 지표를 넘으면 자동으로 control 모델로 돌립니다.
무한 스크롤 중 새 게시물과 삭제가 계속 발생하고 ranking 점수도 변합니다. offset pagination이 만드는 중복·누락을 분석하고 cursor에 무엇을 담을지, snapshot 일관성·재랭킹·cursor 위변조·오래된 cursor를 어떻게 처리할지 설명해 주세요.
답변 예시 보기
페이지 계약 먼저 정하기
offset은 앞에 새 항목이 삽입되면 같은 게시물을 다시 읽거나 건너뜁니다. 완전히 고정된 snapshot은 일관되지만 장시간 세션마다 거대한 결과를 저장하는 비용이 큽니다. 이 제품에는 첫 요청의 feedSessionId, rankingVersion, 상한 시각, 마지막 정렬 키를 담은 opaque cursor를 사용하고, 세션 동안 가능한 한 같은 후보 snapshot과 모델 버전을 유지하겠습니다.
- 정렬 키는 score 하나가 동률·변경될 수 있으므로 score+eventTime+postId처럼 안정적인 tie breaker를 둡니다. cursor는 서버 서명하고 사용자·만료 시각을 묶어 변조와 다른 계정 재사용을 막습니다.
- 짧은 세션은 후보 postId 목록을 TTL cache에 저장하면 일관된 페이지를 제공할 수 있습니다. cache miss나 오래된 cursor는 410과 refresh를 반환하거나 상한 시각 기준으로 재구성하되, 어떤 중복 가능성이 있는지 UX와 합의합니다.
- 삭제·차단은 snapshot 일관성보다 우선하므로 다음 페이지 hydration 시 항상 최신 visibility를 필터합니다. 제거된 수만큼 후보를 추가 fetch하되 무한 refill을 막는 상한과 부분 페이지 허용을 둡니다.
중복/누락률을 synthetic session으로 측정하고 ranking 배포, 캐시 만료, 대량 삭제 중에도 cursor가 안전하게 실패하는지 검증합니다. 새 게시물은 기존 세션 중간에 억지로 삽입하기보다 ‘새 게시물 N개’ refresh UX로 분리할 수 있습니다.
8천만 팔로워의 timeline cache에 들어간 게시물이 법적 요청으로 삭제되고, 동시에 사용자가 작성자를 차단했습니다. 즉시 모든 파생 데이터를 지우는 비용과 읽기 시 필터 비용을 비교하고, 권한 변경·캐시·검색·추천 학습 데이터까지 포함한 반영 및 검증 절차를 설계해 주세요.
답변 예시 보기
접근 차단과 물리 정리를 분리
수천만 timeline을 동기 수정한 뒤 API를 성공시키면 지연·실패 범위가 지나치게 큽니다. 원본 post의 tombstone과 사용자별 block graph를 권위 있는 정책으로 만들고, 피드 hydration 직전에 최신 상태를 검사해 즉시 노출을 막습니다. 이후 비동기 cleanup이 timeline pointer, 검색 색인, 추천 candidate cache를 제거합니다.
- 법적 삭제 이벤트에는 deletionId와 version을 두고 각 파생 시스템이 처리 상태를 보고하게 합니다. 누락 consumer는 재시도/DLQ와 정기 대사로 찾고, 완료 보고가 없는 저장소를 삭제 완료로 간주하지 않습니다.
- block은 사용자별 관계이므로 거대한 timeline 재작성을 피하고 조회 필터와 후보 생성 단계에 적용합니다. block graph 장애 시에는 개인정보 우선으로 fail-closed하되, last-known-good와 지역별 복제 전략을 마련합니다.
- 학습 데이터는 재현성과 삭제 요구가 충돌합니다. 원문·식별 feature를 삭제하고 향후 학습에서 제외하며, 이미 학습된 모델에 대한 제거 수준과 재학습 시점을 법무·ML팀과 사전 정의합니다.
삭제 SLO는 API 승인, 모든 온라인 조회 차단, 물리 삭제 완료를 별도로 측정합니다. 캐시 purge 실패와 오래된 recommendation worker를 주입하고, 삭제된 postId를 전 저장소에서 canary query로 추적합니다.
대용량 클라우드 파일 동기화·공유 서비스 설계
상황
조건
규모와 기능
- 사용자 6천만, 기업 tenant 12만, 총 35PB·일 2PB 업로드
- 최대 10GB, 버전 이력 180일, 휴지통 30일
- 다중 기기 offline sync, 폴더 공유·공개 링크
- PostgreSQL metadata + multi-region object storage
현재 문제와 의사결정 제약
- Resume대용량 재전송으로 네트워크·고객 비용 증가
- Orphanmetadata와 blob 사이 partial failure
- Conflictoffline 동시 수정이 last-write-wins로 유실
- Securitydedup·공유 캐시가 tenant 경계를 위협
꼬리 질문
10GB 파일을 불안정한 모바일 네트워크에서도 재개할 수 있도록 upload session과 chunk protocol을 설계해 주세요. chunk 크기, 순서 무관·중복 업로드, checksum, pre-signed URL, session 만료, 최종 commit의 멱등성과 악성 클라이언트의 자원 고갈까지 설명해 주세요.
답변 예시 보기
재시작 가능한 프로토콜
클라이언트는 파일 크기·이름·전체 hash 선택값으로 upload session을 만들고 서버는 uploadId, chunk 크기, 만료 시각, pre-signed URL 묶음을 반환합니다. 8~64MB 같은 chunk 크기는 RTT·병렬성·메모리·작은 파일 오버헤드를 측정해 조정합니다. 각 part는 partNumber, length, checksum과 함께 object storage에 직접 올려 API 서버가 byte stream 병목이 되지 않게 합니다.
- 동일 part 재전송은 같은 checksum이면 성공으로, 다르면 명시적 충돌로 처리합니다. status API가 이미 수신된 part 목록을 반환해 순서가 달라도 누락만 보낼 수 있게 합니다.
- complete 요청은 manifest와 전체 크기를 검증하고 uploadId+manifest hash로 멱등하게 처리합니다. object storage의 multipart complete 후 metadata commit까지는 UPLOADING/VERIFYING/COMMITTED 상태 머신으로 추적합니다.
- pre-signed URL은 tenant·object prefix·part·짧은 만료에 묶고 최대 파일/part 수, 동시 session, 총 임시 저장량 quota를 적용합니다. 완료하지 않은 session은 grace period 후 scanner가 abort하되 진행 중 요청과 경쟁하지 않도록 lease/version을 확인합니다.
checksum CPU 비용, upload 성공률, 평균 재전송 byte, orphan 임시 용량을 관측합니다. 순서 뒤섞임, 같은 part 동시 업로드, URL 만료, complete 응답 유실을 테스트해 재시도가 결과를 바꾸지 않는지 확인합니다.
object storage 업로드와 metadata DB commit을 하나의 분산 트랜잭션으로 묶기 어렵습니다. blob만 남는 경우와 목록에는 보이지만 blob이 없는 경우 각각을 어떻게 예방·탐지·복구하고, 사용자에게 파일이 보이는 시점과 다중 리전 복제 완료 시점을 어떻게 정하겠습니까?
답변 예시 보기
가시성 상태와 보상 가능성
임시 namespace에 blob을 먼저 완성하고 checksum을 확인한 뒤 metadata revision을 COMMITTED로 조건부 갱신합니다. 목록 API는 COMMITTED만 보여 줍니다. DB commit 후 이벤트가 storage lifecycle과 복제 작업을 진행하게 하며, outbox로 이벤트 유실을 막습니다. 2PC 대신 각 단계가 멱등하고 대사 가능한 상태 머신을 택합니다.
- blob만 있고 metadata가 없으면 uploadId tag로 orphan scanner가 찾습니다. 즉시 삭제하면 늦은 DB 재시도와 경쟁할 수 있으므로 충분한 grace period와 session 상태를 확인하고 삭제합니다.
- metadata가 COMMITTED인데 blob HEAD/checksum이 실패하면 사용자에게 손상 파일을 성공처럼 주지 않고 TEMPORARILY_UNAVAILABLE로 격리합니다. 원본 임시 blob이나 다른 리전 replica로 복구하고, 불가능하면 운영 incident와 고객 통지를 트리거합니다.
- 업로드 성공의 기본 의미를 primary region에서 내구성 있게 읽을 수 있음으로 정의할지, 다중 리전 복제까지 포함할지는 고객 tier와 RPO에 따라 나눕니다. 복제 전에는 region failure 위험을 상태·SLO로 추적합니다.
DB→object와 object→DB 양방향 inventory 대사를 주기적으로 수행하고 orphan 수·손상 수·복제 lag·복구 시간을 지표로 둡니다. complete 직후 프로세스 종료, DB deadlock, object copy timeout을 주입해 수렴 여부를 검증합니다.
노트북과 태블릿이 version 10을 기반으로 오프라인에서 같은 파일을 수정한 뒤 동시에 동기화합니다. 텍스트·Office 문서·바이너리 파일별 충돌 정책, base revision과 optimistic concurrency, 자동 병합 실패 UX, rename/delete 충돌을 어떻게 다룰지 설명해 주세요.
답변 예시 보기
유실 방지가 기본 원칙
업로드 commit에 baseRevision을 포함하고 현재 head가 같을 때만 새 revision으로 CAS합니다. 첫 기기가 version 11을 만들면 두 번째 기기는 409 conflict와 최신 revision 정보를 받습니다. last-write-wins는 단순하지만 앞선 내용을 조용히 잃으므로 이 제품 요구에는 기본값으로 적합하지 않습니다.
- 일반 텍스트는 base, local, remote를 이용한 3-way merge를 시도하고 충돌 marker가 남으면 사용자가 선택하게 합니다. Office 형식은 포맷별 merge 엔진 지원 여부와 손상 위험을 평가하며, 임의 바이너리는 두 버전을 보존한 conflict copy가 안전합니다.
- rename은 안정적인 fileId와 parentId를 사용해 내용 변경과 독립적으로 병합할 수 있습니다. delete 대 수정은 tombstone version을 비교해 ‘삭제된 파일 복원’ 또는 conflict copy 선택지를 주며 자동 영구 삭제하지 않습니다.
- 동일 device의 재시도는 operationId로 멱등 처리하고, sync log/changes cursor로 각 기기가 적용한 revision을 추적합니다. 충돌 파일 이름, 사용자 알림, 관리자 감사 기록까지 제품 흐름에 포함합니다.
충돌률, 자동 병합 성공률, conflict copy 복구율과 사용자 문의를 측정합니다. clock skew가 있어도 revision 관계로 판단하며, 동시 rename/delete/modify 조합을 property test로 검증합니다.
저장 비용을 25% 줄이기 위해 content hash 기반 중복 제거를 도입하려 합니다. tenant 간 dedup이 파일 존재 여부를 노출할 수 있고 암호화 키·삭제·retention과 충돌할 때 dedup 범위, chunking, reference count, 공유 링크 철회를 어떻게 설계하겠습니까?
답변 예시 보기
보안 경계 안에서 비용 절감
전체 서비스 cross-tenant dedup은 업로드 시간·용량 차이로 특정 파일 존재를 추론하게 하고 tenant별 암호화·삭제를 어렵게 합니다. 기본은 tenant 내부 dedup으로 제한하고, 높은 기밀 등급은 dedup을 끌 수 있게 하겠습니다. 고정 chunk는 작은 수정에도 나머지를 재사용할 수 있지만 삽입 시 경계가 밀리므로 content-defined chunking의 CPU·색인 비용과 비교합니다.
- chunkHash→encrypted blob과 tenant별 manifest를 분리하고, 참조 증감은 이벤트와 대사 가능한 상태로 관리합니다. reference count 0 즉시 삭제 대신 grace period와 pending upload/법적 hold를 확인해 경쟁 조건으로 인한 유실을 막습니다.
- 암호화는 tenant key로 data key를 wrap하는 방식 등을 검토합니다. 동일 평문을 같은 ciphertext로 만드는 convergent encryption은 dedup에는 좋지만 사전 공격 위험이 있으므로 위협 모델과 규정을 통과해야 합니다.
- 공유 링크는 blob 주소가 아니라 shareId와 권한 version을 거치는 authorization endpoint로 만듭니다. 철회 이벤트를 edge deny-list에 빠르게 전파하고, 짧은 signed download URL의 최대 수명을 1분 SLO와 맞춥니다.
실제 중복 분포를 tenant별로 샘플링해 절감률이 복잡도에 맞는지 먼저 검증합니다. reference count drift, 삭제 대기 용량, 철회 후 다운로드 성공률을 관측하고 대사에서 참조가 불명확하면 삭제하지 않는 보수적 정책을 택합니다.
멀티테넌트 대규모 메트릭·알림 플랫폼 설계
상황
조건
규모와 기능
- 초당 1,200만 data point, 활성 series 4억, 피크 2배
- 3개 리전 agent/collector, durable log, TSDB, object storage
- 실시간 대시보드·recording rule·알림·장기 추세
- 최근 6시간 hot, 30일 raw, 13개월 5분 downsample
현재 문제와 의사결정 제약
- Cardinality잘못된 label 하나가 공용 자원 고갈
- Backpressure수집 장애가 애플리케이션에 역전파
- Query광범위 쿼리가 ingester와 object store 폭주
- Storm중복·연쇄 알림으로 핵심 원인 은폐
꼬리 질문
application agent부터 지역 collector, durable log, stream 처리, TSDB와 query frontend까지 데이터 흐름을 설계해 주세요. 각 구간 장애가 애플리케이션에 전파되지 않도록 buffer·backpressure·drop 우선순위를 어떻게 정하고, 중복·순서 역전·리전 단절을 어떻게 다룰지 설명해 주세요.
답변 예시 보기
격리된 수집 경로
애플리케이션 agent는 비차단 batch 전송과 작은 bounded buffer를 사용하며 collector 응답을 업무 요청이 기다리지 않게 합니다. 로컬 buffer가 가득 차면 무한 메모리 증가 대신 명시된 우선순위에 따라 debug성 custom metric을 sampling/drop하고, process up·오류율 같은 핵심 신호는 더 오래 보존합니다. 손실 자체를 dropped_points 지표로 노출합니다.
- 지역 collector가 인증·정규화·tenant quota를 적용하고 durable log에 append한 시점을 수집 ACK 기준으로 둡니다. processor는 metric/tenant hash로 partition해 series별 순서를 대부분 유지하되 event timestamp와 허용 out-of-order window로 늦은 sample을 처리합니다.
- ingester는 WAL과 replica/quorum으로 최근 데이터를 보호하고 block을 object storage에 flush합니다. query frontend는 hot ingester와 장기 store 결과를 시간 범위에 따라 병합하며 중복 sample은 series label+timestamp 규칙으로 제거합니다.
- 리전 단절 시 agent는 로컬 상한까지만 버퍼하고 collector는 지역 log에 적재합니다. 중앙 복구 후 backfill을 rate limit해 현재 트래픽을 밀어내지 않게 하고, 오래된 데이터의 알림 재평가 여부를 별도 정책으로 둡니다.
구간별 ingest lag, buffer saturation, drop rate, WAL fsync, flush 실패를 추적합니다. storage outage와 네트워크 partition을 주입해 앱 latency 변화, 핵심 metric 손실, 복구 후 backlog 소진 시간을 검증합니다.
requestId·userId가 label로 들어와 한 tenant의 series가 분당 수백만 개씩 늘고 있습니다. cardinality를 어디서 탐지하고 어떤 단계로 제한하겠습니까? 무조건 drop했을 때의 진단 손실, 기존 series, tenant 공정성, 개발자에게 원인을 보여주는 UX까지 설명해 주세요.
답변 예시 보기
예방·탐지·격리의 다층 방어
SDK lint와 문서에서 metric label은 bounded dimension이고 requestId는 log/trace로 보내야 함을 안내하지만 예방만 믿지 않습니다. collector에서 tenant→metric→label별 신규 series 생성률과 추정 distinct count를 계산하고, ingester에서도 메모리·series quota를 최종 방어선으로 둡니다.
- soft limit에서는 경고·비용 preview·상위 cardinality label 샘플을 제공하고, hard limit에서는 기존 series update는 허용하되 새 series 생성을 rate limit하거나 문제 label을 제거한 aggregate metric으로 격하합니다. 무작위 drop보다 핵심 시계열 연속성을 지킬 수 있습니다.
- quota는 tenant 전체뿐 아니라 metric별·team별로 나눠 한 잘못된 계측이 tenant의 모든 신호를 막지 않게 합니다. burst allowance와 증가율 제한을 두고, 계약된 대형 고객은 격리 shard나 별도 tier를 검토합니다.
- 자동으로 label을 제거하면 서로 다른 값을 합쳐 통계 의미가 바뀔 수 있으므로 metric metadata에 degraded 표시를 남기고 소유 팀에 정확한 drop 이유·예시·수정법을 제공합니다.
active series, churn, bytes/point, tenant별 reject와 ingester memory를 관측합니다. 제한 정책을 shadow mode로 실행해 어떤 정상 metric이 막힐지 확인하고, 한 tenant의 폭증이 다른 tenant p99와 알림 평가에 영향을 주지 않는지 부하 테스트합니다.
사용자가 13개월 전체의 고해상도 데이터를 정규식으로 조회해 cluster가 느려집니다. hot/warm/cold 저장, downsampling·retention, query planner·cache, tenant별 query budget을 어떻게 설계하고 원본과 집계 결과의 의미 차이를 어떻게 설명하겠습니까?
답변 예시 보기
저장 계층과 조회 비용을 함께 설계
최근 6시간은 ingester/SSD hot store, 30일 raw block은 object storage+index, 이후 13개월은 5분 downsample block으로 분리합니다. compactor가 immutable block을 병합·downsample하고 retention marker 후 grace period를 거쳐 삭제합니다. 평균만 저장하면 최대치가 사라지므로 count/sum/min/max와 필요 시 histogram을 함께 보존합니다.
- query frontend가 시간 범위와 step에 맞는 resolution을 선택하고 일별 shard로 병렬화하되 최대 fan-out을 제한합니다. exact raw가 필요하면 예상 bytes·시간을 보여주고 비동기 export로 전환합니다.
- label matcher selectivity를 추정해 광범위 regex를 거절하거나 범위를 좁히도록 안내합니다. tenant별 동시 쿼리, scan bytes, CPU token budget과 weighted queue로 dashboard/alert query를 ad-hoc 분석에서 격리합니다.
- result cache는 query normalization, block generation, time boundary를 key에 포함하고 immutable 과거 구간만 길게 캐시합니다. 최근 구간은 짧은 TTL로 두며 늦은 sample 반영 때문에 오래된 오답이 남지 않게 합니다.
사용자에게 resolution과 잠정/확정 범위를 응답 metadata로 제공합니다. scanned bytes, queue wait, partial response, cache hit를 tenant별로 공개해 비용을 예측할 수 있게 하고, alert evaluator에는 별도 예약 용량과 더 엄격한 쿼리 검증을 둡니다.
공통 DB 장애로 3천 서비스의 지표가 동시에 악화되어 8천 개 알림이 발생합니다. 단순 임계치 외에 alert rule 평가, flap 방지, grouping·dedup·inhibition, 의존성 기반 원인 추정, 당직자 ACK·escalation을 어떻게 설계하고 오탐 감소가 탐지 지연을 키우지 않는지 검증해 주세요.
답변 예시 보기
신호 품질과 전달을 분리
rule evaluator는 tenant/rule shard로 분산하고 평가 상태를 checkpoint해 failover 중 중복 알림을 수용하되 alert fingerprint로 dedup합니다. 일시 spike에는 N회 연속 위반, for duration, hysteresis와 cooldown을 적용합니다. 모든 규칙을 같은 지연으로 만들지 않고 page 수준은 SLO burn-rate의 fast/slow window를 조합해 빠른 대형 장애와 느린 소진을 모두 탐지합니다.
- alertmanager 계층에서 service, cluster, symptom 기준으로 group하고 같은 fingerprint의 반복을 억제합니다. DB_ROOT_CAUSE가 firing이면 명시된 dependency graph의 downstream latency 알림을 inhibit하되, graph 오류로 중요한 독립 장애가 가려지지 않도록 대표·억제 목록을 함께 보여줍니다.
- 심각도와 소유권에 따라 chat→push→전화로 escalation하고 ACK lease가 만료되면 다음 당직자로 넘깁니다. 알림에는 최근 배포, dashboard, runbook, 영향 tenant와 상관 신호를 붙여 진단 시간을 줄입니다.
- 알림 파이프라인 자체의 queue age와 notification provider 상태를 독립적으로 모니터링하며, primary 관측 시스템 장애 때 사용할 dead-man switch와 외부 경로를 둡니다.
정책 변경은 과거 incident trace replay와 shadow evaluation으로 탐지 시각, page 수, 놓친 incident를 비교합니다. 운영 지표는 MTTD뿐 아니라 actionable 비율, pages/incident, ACK·resolve 시간, 억제 오판을 함께 봅니다.
글로벌 API Rate Limiter 설계
상황
조건
시스템 구성과 목표
- 평균 80만 RPS, 프로모션 최대 200만 RPS, 6개 리전 active-active
- 무료 100 RPS, 기업 고객 2만 RPS와 월 사용량 한도; 결제 API는 별도 burst 허용
- Gateway p99 추가 지연 5ms 이하, 정책 변경 30초 이내 전파
- 기존 Redis Cluster는 서울 리전에 있고 왕복 지연 45~180ms
현재 문제와 제약
- Boundary Burst분 경계에서 순간적으로 2배 요청 통과
- Hot Tenant상위 3개 고객이 카운터 shard의 62% 점유
- Partition리전 단절 시 글로벌 quota를 정확히 알 수 없음
- 잘못된 정책 배포는 매출 손실로 이어지므로 즉시 롤백과 감사 추적 필요
꼬리 질문
이 서비스에서 하나의 알고리즘을 모든 제한에 적용하지 않는다면, 서버 보호용 순간 제한과 고객 계약·과금용 사용량 제한을 각각 어떤 방식으로 구현하겠습니까? Fixed Window, Sliding Window, Token Bucket 등의 선택 근거뿐 아니라 경계 버스트, 시계 오차, 메모리 비용과 고객에게 보일 응답까지 설명해 주세요.
답변 예시 보기
요구를 두 종류의 제어로 분리
먼저 “다운스트림을 순간 폭주로부터 보호하는가”와 “계약량을 정확히 집계하는가”를 나눕니다. 결제 API 보호에는 일정 burst를 허용하면서 장기 평균을 제한하는 token bucket이 자연스럽습니다. 예를 들어 2만 RPS 고객에게 2초 분량의 bucket을 주되, 결제 DB가 감당할 수 있는 리전 전체 concurrency 제한을 그 위에 별도로 둡니다. 반면 월 사용량은 요청 경로의 limiter만 과금 원장으로 삼지 않고 내구성 있는 사용 이벤트를 집계해 확정합니다.
알고리즘과 상태 모델
- 단순 fixed window는 저렴하지만 경계에서 두 window의 quota가 연속 통과합니다. 이 영향이 작은 관리자 API에는 사용할 수 있고, 외부 공개 API에는 sliding window counter나 token bucket을 우선 검토합니다.
- 정확한 sliding log는 요청별 timestamp 때문에 메모리가 커집니다. 초 단위 bucket을 합산하는 근사 counter는 비용을 줄이는 대신 허용 오차를 수치로 합의해야 합니다. 서버 시간에 의존한다면 단조 시계와 Redis 서버 시간을 사용하고, 클라이언트 시각은 믿지 않습니다.
- 사용자·조직·API 정책은 독립 bucket을 평가해 가장 먼저 소진된 제한을 반환합니다. 다만 매 요청마다 여러 원격 호출을 하지 않도록 한 스크립트나 로컬 평가 단위로 묶습니다. 응답에는 429, 적용된 정책 ID, 재시도 가능 시각을 주되 내부 quota 값이나 다른 고객 정보는 노출하지 않습니다.
- 결정은 “어떤 알고리즘이 정답”이 아니라 burst 허용량, 제한 정확도, 상태 크기, 장애 시 고객 영향의 조합입니다. 실제 트래픽의 inter-arrival 분포를 replay하여 정상 요청 차단률과 다운스트림 보호 효과를 함께 검증합니다.
모든 요청이 서울의 중앙 Redis를 조회하면 지연과 단일 장애점 문제가 생깁니다. 각 리전에서 5ms 안에 판단하면서도 기업 고객의 글로벌 2만 RPS를 최대한 지키려면 quota를 어떻게 배분하고 회수하겠습니까? 트래픽이 한 리전으로 급격히 이동하거나 리전 간 네트워크가 단절되는 경우의 초과 허용 상한도 포함해 주세요.
답변 예시 보기
로컬 판단과 글로벌 정산
요청 경로는 리전 로컬 limiter가 처리하고, 중앙 조정 계층은 짧은 유효기간의 token lease를 리전별로 배분하게 합니다. 처음부터 2만 개를 6등분하면 트래픽 이동 때 한 리전은 차단되고 다른 리전의 token은 놀 수 있으므로 최근 사용률과 예측치를 기준으로 기본 할당과 공유 예비분을 둡니다. 리전은 예를 들어 1초 단위 lease를 선취하고 70~80% 소진 시 비동기로 추가 요청합니다.
오차와 재분배를 명시적으로 관리
- 네트워크 단절 시 이미 임대한 token까지만 사용하면 전역 초과량은 “리전 수 × 미사용 lease” 이내로 제한됩니다. lease 크기를 작게 하면 정확해지지만 조정 트래픽과 단절 시 가용성이 나빠집니다. 결제·인증과 무료 검색에 서로 다른 lease 크기와 안전 여유를 둘 수 있습니다.
- 한 리전으로 트래픽이 이동하면 중앙이 저사용 리전의 다음 lease를 축소하고 고사용 리전에 배분합니다. 이미 발급된 token을 강제로 회수하려 하면 복잡성과 지연이 커지므로 만료를 짧게 하고 자연 회수하는 편을 검토합니다.
- 대형 tenant는 전용 counter shard나 별도 limiter pool로 격리하고, 일반 tenant는 consistent hashing으로 분산합니다. 정책 캐시에는 version과 만료를 두어 오래된 높은 quota가 무기한 유지되지 않게 합니다.
- 검증에서는 리전 장애와 100% 트래픽 전환을 주입해 최대 초과율, 정상 차단률, 재분배 수렴 시간을 측정합니다. 계약상 hard quota가 절대 조건이면 해당 고객만 중앙 합의 비용을 감수하거나 단일 home region으로 라우팅하는 선택도 제시합니다.
Limiter 저장소가 500ms 이상 지연되거나 정책 스트림이 끊겼을 때 fail-open과 fail-closed 중 하나를 일괄 선택하기 어렵습니다. API 위험도와 고객 등급에 따라 어떤 degraded mode를 설계하고, 장애가 복구된 뒤 초과 사용량과 정상 고객 오차단을 어떻게 처리하겠습니까?
답변 예시 보기
위험 기반 정책
결정 기준은 limiter 자체의 가용성이 아니라 제한이 사라졌을 때의 피해입니다. 로그인·비밀번호 찾기처럼 공격과 비용 위험이 큰 API는 보수적인 로컬 제한으로 fail-closed에 가깝게 운영하고, 이미 인증된 기업 고객의 조회처럼 중단 비용이 큰 API는 last-known-good 정책과 작은 비상 bucket으로 제한적 fail-open을 허용할 수 있습니다. 결제 생성은 중복 방지와 다운스트림 용량을 함께 보므로 무제한 통과가 아니라 로컬 concurrency cap을 반드시 유지합니다.
장애 모드와 복구
- 원격 판단 호출에는 1~2ms 수준의 엄격한 timeout을 두고, 각 gateway의 로컬 token bucket으로 즉시 전환합니다. 정책 snapshot은 서명·version·절대 만료 시각을 가지며 너무 오래되면 더 보수적인 기본값으로 단계적으로 낮춥니다.
- degraded 요청에는 decision source와 정책 version을 로그로 남기고, 고객별 우회 사용량을 비동기 usage event로 기록합니다. 장애 중 상태 기록 자체가 실패할 수 있으므로 로컬 bounded buffer와 손실 지표도 필요합니다.
- 복구 직후 밀린 차액을 한꺼번에 제한해 고객 트래픽을 절벽처럼 끊지 않습니다. 계약 정책에 따라 사후 과금, 다음 시간대 quota의 완만한 차감, 초과분 면제 중 운영·영업 기준을 사전에 합의합니다.
- 전환율, 우회 RPS, 차단률, downstream 포화도를 함께 경보하고, 비상 모드는 시간 제한과 자동 종료 조건을 둡니다. 정기 게임데이로 정책 캐시 만료와 Redis 장애를 검증합니다.
운영자가 고객 ID를 잘못 입력해 전체 무료 고객에게 기업용 quota가 배포되거나, 반대로 핵심 고객을 1 RPS로 제한하는 사고를 막아야 합니다. 정책 저장·배포·관측·롤백을 어떤 흐름으로 만들고 “Limiter가 정확히 동작한다”는 것을 어떻게 검증하겠습니까?
답변 예시 보기
정책을 코드 수준의 배포물로 취급
정책에는 tenant 범위, API pattern, 알고리즘, rate, burst, 적용 기간, 우선순위와 schema version을 명시합니다. 저장 전 정적 검증으로 음수·비현실적 값, 겹치는 규칙, 광범위 wildcard를 차단하고 변경 영향 tenant 수와 예상 차단량을 preview합니다. 대형 범위나 90% 이상 축소는 2인 승인과 예약 적용을 요구합니다.
점진 전파와 결과 검증
- 불변 policy revision을 만들고 소수 gateway에서 shadow evaluation을 수행해 기존·신규 정책의 결정 차이를 기록합니다. 이후 1% → 한 리전 → 전체 순으로 확대하며 오차단률, 429 비율, downstream 오류율을 중단 기준으로 둡니다.
- gateway는 revision을 원자적으로 교체하고 last-known-good를 보관합니다. rollback은 새 정책을 수정하는 대신 이전 revision을 재활성화해 감사 가능성을 유지합니다.
- 단위 테스트만으로는 분산 오차를 검증할 수 없습니다. 실제 burst trace replay, 시계 이동, Redis failover, 리전 분할, hot tenant를 포함한 부하·장애 테스트로 허용량의 상·하한과 p99 판단 지연을 확인합니다.
- 고객 문의 대응을 위해 request ID로 어떤 정책과 counter가 차단 결정을 내렸는지 설명 가능한 decision log를 표본 저장합니다. 개인정보와 저장 비용을 고려해 전체 요청 원문은 남기지 않습니다.
멀티리전 분산 Key-Value Store 설계
상황
조건
규모와 데이터 특성
- 유효 데이터 50TB, 3개 리전, 평균 70만/최대 120만 read·write QPS
- 키 10억 개, 값 평균 8KB·최대 1MB, TTL 30분~90일
- 세션 p99 15ms, 장바구니 p99 50ms, 가용성 99.99%
- 리전 간 RTT 80~190ms, 리전 내부는 2ms 이내
현재 문제와 결정 제약
- Skew일부 이벤트 키가 단일 shard 쓰기의 35% 차지
- Conflictactive-active 수정 시 last-write-wins로 상품 유실
- Repair Storm장애 복구 후 동기화가 사용자 I/O와 경쟁
- 결제 재고·쿠폰은 강한 일관성이 필요하므로 이 저장소의 범위에서 분리 가능
꼬리 질문
노드 추가·제거 때 전체 데이터를 이동하지 않으면서 용량이 다른 노드에 데이터를 고르게 배치하려면 partition ring과 virtual node를 어떻게 구성하겠습니까? 실제 키 분포가 균등하지 않고 특정 이벤트 키가 hot key가 되는 경우까지 포함해 재배치 절차와 실패 시 롤백을 설명해 주세요.
답변 예시 보기
배치와 장애 도메인
consistent hashing은 노드 변경 시 인접 범위만 이동하게 하지만 그 자체로 균등 배치를 보장하지는 않습니다. 물리 노드마다 여러 virtual node를 두고 디스크·CPU·네트워크 용량에 따라 vnode 수나 token 범위에 가중치를 줍니다. 복제본은 같은 rack·zone에 몰리지 않도록 topology-aware placement를 적용하고, metadata ring의 epoch을 합의된 control plane에서 관리합니다.
재배치와 skew 대응
- 새 노드는 먼저 replica로 range를 streaming하고 checksum·샘플 읽기로 검증한 뒤 routing table을 단계 전환합니다. 전환 중 write는 기존·신규 owner에 전달하거나 변경 로그를 catch-up하여 누락 창을 닫습니다.
- streaming bandwidth와 compaction을 제한하고 foreground p99 또는 disk queue가 임계값을 넘으면 자동 중단합니다. ring epoch이 뒤처진 클라이언트에는 redirect와 짧은 refresh를 제공하되 무한 redirect를 막습니다.
- vnode는 key 수의 균형에는 도움되지만 하나의 hot key를 쪼개지 못합니다. 읽기 hot key는 replica·cache와 request coalescing을 활용하고, 쓰기 hot key는 도메인이 허용하면 suffix sharding 후 집계하거나 전용 partition으로 격리합니다. 장바구니처럼 키 단위 원자성이 필요하면 무리한 salting보다 단일 키 처리량 상한을 명확히 합니다.
- 롤백은 이전 ring epoch과 owner가 데이터를 일정 기간 유지하도록 해 routing만 되돌릴 수 있게 합니다. capacity 변화, 이동 예상량, 복제 미달 range를 사전 시뮬레이션합니다.
복제 계수 N=3을 가정할 때 세션과 장바구니에 동일한 R·W quorum을 적용하시겠습니까? 리전 간 지연, sloppy quorum, hinted handoff, read repair가 있는 환경에서 어떤 일관성을 실제로 보장할 수 있으며 네트워크 분할 중 어떤 요청을 허용할지 설명해 주세요.
답변 예시 보기
데이터 등급별 선택
R+W>N은 복제 집합과 membership이 안정적이고 최신 version을 정확히 비교한다는 가정 아래 겹치는 replica를 만들 뿐, 분할 환경의 선형화 가능성을 자동 보장하지 않습니다. 세션의 최근 접속 시각은 stale read를 허용해 local R=1, async W=1~2로 가용성을 높일 수 있습니다. 장바구니는 사용자에게 쓰기 직후 자신의 변경이 보여야 하므로 session stickiness, write version을 포함한 read-your-writes 또는 local quorum을 검토합니다.
장애 중 동작과 복구
- 정상 시 각 리전 local replica에 동기 복제하고 원격 리전은 비동기로 전파하면 지연을 줄일 수 있습니다. 대신 리전 소실 시 RPO가 생기므로 상품 재고·결제 상태는 이 저장소에 두지 않거나 합의 기반 별도 경로를 사용합니다.
- sloppy quorum은 원래 replica 대신 임시 노드에 쓰므로 가용성을 높이지만 R과 W의 교집합 가정을 약화시킵니다. hinted handoff에는 대상, version, 만료와 디스크 상한을 두고 장기 장애에서는 무제한 hint 축적 대신 운영 경보·백필 절차로 전환합니다.
- read repair는 요청 경로에 과도한 데이터를 쓰지 않도록 비동기와 rate limit을 적용하고, anti-entropy tree로 전체 range 차이를 주기적으로 찾습니다. tombstone을 너무 빨리 지우면 삭제 데이터가 부활할 수 있어 최대 단절 기간보다 긴 grace period가 필요합니다.
- 면접에서는 “가용성”을 요청 성공률로만 말하지 않고 stale 허용 범위, RPO, 충돌률과 복구 수렴 시간으로 표현합니다.
유럽 사용자가 장바구니에 상품 A를 추가하는 동안 미국 기기에서 상품 B를 추가하고 A의 수량도 변경했습니다. 리전 단절 후 두 버전이 만났을 때 충돌을 어떻게 탐지하고 병합하겠습니까? LWW, version vector, CRDT, 애플리케이션 병합의 장단점과 삭제·수량 변경의 의미를 포함해 주세요.
답변 예시 보기
충돌을 숨기지 않고 인과관계를 보존
물리 시각 기반 LWW는 구현이 단순하지만 시계 오차로 최신 의도가 사라지고, 동시에 서로 다른 상품을 추가했는데 한 장바구니 전체가 덮어써질 수 있습니다. version vector 또는 compact한 dotted version vector로 한 버전이 다른 버전을 포함하는지, 서로 concurrent인지 구분하고 concurrent sibling을 보존합니다. 다만 replica·클라이언트 수만큼 metadata가 커지는 문제와 오래된 기기 정리 정책이 필요합니다.
도메인 연산으로 병합
- 장바구니를 통째로 replace하지 않고 item 단위 add/remove/set-quantity 연산과 operationId를 기록합니다. 서로 다른 상품 추가는 union할 수 있지만 “수량을 1 증가”와 “수량을 1로 설정”은 의미가 달라 API에서 구분해야 합니다.
- add-wins 또는 remove-wins set 중 어떤 정책인지 정합니다. 사용자가 삭제한 상품이 부활하는 것이 더 나쁘다면 remove tombstone을 일정 기간 유지하고, 동일 item의 concurrent 수량 변경은 최대값·합산·최근 의도 중 제품 정책을 선택합니다. 고가 상품은 사용자 확인을 요구할 수도 있습니다.
- CRDT는 수학적 수렴을 주지만 비즈니스적으로 올바른 결과를 자동 보장하지 않습니다. 쿠폰·재고 예약처럼 전역 불변식이 있는 상태는 장바구니 표시와 분리해 결제 시 authoritative service에서 재검증합니다.
- 충돌률과 자동 병합 결과를 표본 감사하고, 병합이 불가능한 경우 두 버전을 보존해 사용자에게 선택하게 합니다. 재전송된 operation은 operationId로 멱등하게 처리합니다.
한 zone이 6시간 중단된 뒤 복구되어 12TB의 데이터와 hint를 따라잡아야 합니다. 복구 트래픽이 정상 요청을 다시 장애로 몰아넣지 않도록 bootstrap, anti-entropy, compaction을 어떻게 조절하겠습니까? 데이터 손상과 조용한 누락을 탐지하는 운영 체계도 제시해 주세요.
답변 예시 보기
복구를 별도 워크로드로 제어
복구 노드를 즉시 정상 routing에 넣지 않고 JOINING 상태에서 snapshot을 받고 이후 변경 로그를 catch-up합니다. range별 checksum과 Merkle tree 비교로 다른 부분만 전송하되, 네트워크·디스크 I/O token bucket을 두어 foreground 지연 예산 안에서 속도를 조절합니다. 모든 노드가 동시에 repair하지 않도록 coordinator가 rack·range 단위 concurrency를 제한합니다.
안전한 정상화
- hint replay, SSTable compaction, replica streaming이 같은 디스크에서 경쟁하지 않게 스케줄을 분리합니다. p99, disk queue depth, compaction backlog가 임계치를 넘으면 복구를 자동 감속합니다.
- snapshot 시점 이후의 mutation log 위치를 명확히 잡고 fencing된 ring epoch을 사용해 이전 owner의 늦은 쓰기가 섞이지 않게 합니다. 충분한 replica 수와 샘플 read 비교가 통과한 뒤 canary range부터 읽기, 다음 쓰기를 허용합니다.
- key count만 비교하면 동일 개수의 손상을 놓칩니다. range checksum, version 분포, tombstone 수, replication deficit, 최근 쓰기의 synthetic probe를 지속 관측하고 정기 restore drill로 백업이 실제 복구 가능한지 확인합니다.
- 복구 완료 기준은 “노드 up”이 아니라 모든 range의 복제 수준, 최대 버전 지연, hint backlog와 오류율이 SLO 안으로 돌아온 상태입니다.
실시간 검색 자동완성 및 트렌드 제안 설계
상황
조건
규모와 품질 목표
- DAU 3천만, 최대 50만 query suggestion RPS, 입력당 평균 5회 호출
- 한글 초성·띄어쓰기·영문 오타, 한국어·영어·일본어 지원
- p50 25ms, p99 100ms, 최대 10개 제안
- 트렌드 5분 이내, 검색 전환율과 zero-result rate를 품질 지표로 사용
현재 문제와 제약
- Memory모든 prefix와 top-N을 메모리에 두면 리전당 1.8TB
- Abuse신규 계정 3천 개가 순위를 조작한 이력
- Privacy희귀·개인 검색어가 global 후보에 유입될 위험
- 앱·CDN 캐시 때문에 정책 삭제가 최대 20분 늦게 반영됨
꼬리 질문
Trie, FST, 검색 인덱스의 prefix query, prefix별 precomputed top-N을 비교해 조회 경로를 설계해 주세요. 한글 초성·오타 후보까지 넣으면서 메모리와 p99를 지키려면 무엇을 사전 계산하고 무엇을 요청 시점에 처리하며, 업데이트 중 일관성은 어떻게 다루겠습니까?
답변 예시 보기
계층화한 후보 생성
최상위 인기 prefix는 압축된 Trie/FST 또는 prefix별 top-N snapshot을 리전 메모리에 두어 한 번의 lookup으로 반환합니다. 롱테일과 필터가 복잡한 후보는 검색 인덱스에 맡기고 엄격한 timeout 뒤 메모리 결과만으로도 응답할 수 있게 합니다. 모든 문자열을 Trie node에 중복 저장하기보다 normalized term ID와 score를 분리하고 top-K만 materialize합니다.
정규화와 업데이트
- 원문을 Unicode 정규화, 대소문자·공백·자모 규칙으로 표준화하되 표시 문자열은 별도로 보존합니다. 한글 초성 인덱스와 영문 typo 후보는 별도 필드나 후보 사전으로 만들고, 요청 시 편집거리 전체 탐색이 폭주하지 않도록 prefix 길이·후보 수를 제한합니다.
- 빈번한 prefix는 메모리, 드문 prefix는 인덱스로 보내는 기준을 query volume과 cache hit로 주기 조정합니다. 빈 입력이나 한 글자 요청은 후보 폭이 크고 공격에 취약하므로 지역·카테고리별 curated set을 우선합니다.
- 새 snapshot은 불변 version으로 빌드하고 checksum·금칙어 검증 후 canary 서버에 load합니다. 포인터를 원자 교체하고 이전 version은 롤백용으로 남깁니다. 실시간 delta는 작은 overlay index에 반영하고 주기적으로 base와 merge합니다.
- 평가에서는 p99뿐 아니라 relevance·전환율·다양성과 typo 오교정률을 언어별로 측정합니다. 빠른 구조가 정답이 아니라 update 빈도와 품질 요구를 만족하는 혼합 경로가 핵심입니다.
“지난 10분 급상승”과 “최근 30일 안정적 인기”를 함께 반영하고 싶습니다. 검색·클릭 이벤트의 중복과 지연 도착, 봇 트래픽, 지역별 편향을 고려해 점수를 어떻게 만들고 온라인 후보에 반영하겠습니까? 실시간 집계와 야간 배치가 다른 결과를 낼 때의 처리도 설명해 주세요.
답변 예시 보기
실시간 신호와 기준선 분리
검색 이벤트를 내구성 있는 로그에 저장하고 event time window로 term·지역·카테고리별 unique 사용자 수, 검색 증가율, 클릭 전환을 집계합니다. 단순 검색 횟수는 봇과 반복 입력에 취약하므로 계정·기기별 rate cap과 대략적 distinct count를 사용합니다. 30일 batch baseline 대비 최근 window의 비율과 절대 사용자 수를 함께 보아 작은 모수의 10배 증가가 과대평가되지 않게 합니다.
품질·정합성 제어
- watermark를 두고 예를 들어 2분 이내 지연 이벤트는 window를 갱신하며, 이후 이벤트는 다음 보정이나 batch에 반영합니다. eventId 또는 producer sequence로 중복을 줄이고 원본 로그는 재처리를 위해 보존합니다.
- 봇 필터는 IP 하나만 보지 않고 계정 연령, 반복 간격, 클릭·구매 후속 행동, ASN 등을 조합합니다. 의심 점수는 바로 삭제하기보다 가중치를 낮추고 검토 queue로 보내 오탐에 의한 실제 트렌드 억제를 줄입니다.
- score는 장기 인기도, 시간 감쇠된 상승도, 전환·품질 신호와 정책 penalty로 구성하고 version을 붙입니다. 실시간 overlay를 base snapshot과 합칠 때 동일 term의 중복과 score scale을 맞춥니다.
- 야간 확정치와 online 결과를 자동 대사해 허용 차이를 넘으면 모델 확대를 중단합니다. 실시간 값은 잠정 ranking용이지 과금 원장이 아니며, replay로 새 집계 로직을 shadow 검증한 뒤 전환합니다.
전역 인기, 지역·기기 맥락, 사용자 검색 이력을 조합해 개인화하되 다른 사용자에게 민감 검색어가 노출되면 안 됩니다. 후보 생성과 ranking을 어떻게 나누고, 익명 사용자·로그아웃·가족 공유 기기에서 개인정보 경계를 어떻게 설정하겠습니까?
답변 예시 보기
공개 후보와 개인 후보의 경계
전역·지역 후보는 k-anonymity 수준의 최소 distinct 사용자 수와 정책 검사를 통과한 term만 포함합니다. 개인 이력은 사용자 전용 저장소에서 가져와 요청 시에만 별도 후보로 합치며 global 집계나 공유 cache key에 절대 섞지 않습니다. 개인 후보를 cache한다면 user-scoped key, 짧은 TTL, 암호화와 삭제 이벤트를 적용합니다.
단계적 조합과 실패 시 fallback
- 빠른 global 후보를 먼저 가져오고 제한 시간 내 개인 feature를 조회해 re-rank합니다. 개인화 service가 20ms 예산을 넘으면 global+지역 결과로 fallback하여 전체 p99를 보호합니다.
- 익명 상태에는 기기 기반 장기 이력보다 세션 내 입력 정도만 사용하고, 로그인 전후 이력 병합은 명시적 정책과 동의를 따릅니다. 가족 공유 기기에서는 개인 이력을 잠금 화면에 노출하지 않는 UX도 시스템 요구에 포함합니다.
- 전화번호·이메일·주문번호 패턴, 희귀 term, 사용자 신고를 ingest 단계와 serve 단계에서 모두 필터링합니다. 모델이 만든 후보도 동일한 최종 policy filter를 통과해야 합니다.
- 개인화 효과는 CTR만 보지 않고 민감 후보 노출률, 다양성, latency와 opt-out 사용자 결과를 함께 봅니다. 로그에는 원문 대신 가능한 범위에서 hash·term ID를 사용하고 접근 통제를 둡니다.
법무팀이 특정 문구를 30초 안에 모든 결과에서 제거하라고 요청했지만 이미 리전 메모리, CDN, 앱 캐시에 퍼져 있습니다. false positive로 정상 브랜드가 함께 차단될 위험까지 고려해 긴급 차단, 전파 확인, 복구 절차를 설계해 주세요.
답변 예시 보기
데이터 갱신보다 앞선 정책 계층
대규모 Trie를 재빌드할 때까지 기다리지 않고 작은 고우선 deny/allow 정책 집합을 모든 응답 직전에 적용합니다. 정책은 exact, normalized exact, 제한된 pattern을 구분해 과도한 substring 차단을 피하고, revision·사유·만료·승인자를 기록합니다. 긴급 exact 차단은 1인 실행 후 사후 승인, 광범위 pattern은 2인 승인을 요구하는 식으로 위험도를 나눌 수 있습니다.
전파와 복구
- 서명된 정책 이벤트를 리전 gateway와 suggestion server에 push하고 ACK된 revision 비율을 관측합니다. stream이 끊긴 노드는 짧은 polling으로 보완하며, 너무 오래된 revision이면 해당 범주의 결과를 보수적으로 숨깁니다.
- CDN cache key에 policy revision을 포함하거나 purge API를 호출하고 TTL을 짧게 둡니다. 앱의 장기 캐시는 서버에서 받은 minimumPolicyVersion보다 낮으면 사용하지 않도록 설계해야 합니다.
- 전 세계 synthetic probe로 정확한 문구와 혼동 가능한 정상 문구를 동시에 조회해 차단·오탐을 확인합니다. 미전파 리전과 앱 version 비율을 대시보드로 추적합니다.
- 오탐 복구도 새 allow revision으로 남기며, 원본 후보 삭제와 snapshot 재빌드는 비동기로 수행합니다. 차단 SLO는 정책 publish가 아니라 실제 serve 결과 기준으로 측정합니다.
대규모 상품 웹 크롤러 설계
상황
조건
규모와 구성
- 발견 URL 최대 10억/일, 실제 fetch 예산 2억/일, 원문 8PB 보관
- HTML fetcher 2만 대, JS renderer 2천 대; 페이지 평균 600KB
- 상위 제휴몰 가격 신선도 p95 10분, 일반 사이트 24시간
- 사이트별 허용 RPS 0.2~100, robots 정책 갱신 주기 6시간
현재 문제와 위험
- Politeness분산 worker가 같은 host 제한을 독립 판단
- Trap필터 조합·세션·달력 URL이 무한 생성
- DuplicateURL은 달라도 내용이 같은 페이지가 57%
- robots 변경·삭제 요청과 기존 저장 데이터의 보존 정책도 추적 필요
꼬리 질문
URL frontier를 단순 우선순위 큐 하나로 구현하면 인기 사이트가 독점하거나 같은 host에 동시 요청이 몰릴 수 있습니다. 비즈니스 우선순위와 host별 politeness를 동시에 만족하도록 partition·scheduling하고, worker 장애·DNS 변경·robots 갱신 중에도 제한을 지키는 흐름을 설명해 주세요.
답변 예시 보기
두 단계 frontier
URL을 canonical host 기준으로 partition하여 같은 host의 요청은 하나의 논리적 host queue와 rate state를 공유하게 합니다. 앞단 priority queue가 상품 가치·변경 가능성·마지막 수집 시각으로 host를 고르고, 뒤의 host scheduler는 nextFetchAt과 허용 concurrency가 된 host만 worker에 lease합니다. 이 구조는 전역 중요도와 외부 사이트별 간격을 분리합니다.
실패·운영 제어
- worker에는 짧은 lease와 fetchId를 주고 완료 ACK 전에는 상태를 남깁니다. 타임아웃 후 재할당은 가능하지만 외부 요청이 이미 실행됐을 수 있으므로 host rate 계산에는 시도 시점부터 포함합니다.
- robots.txt는 user-agent별 allow/disallow, crawl-delay와 가져온 시각을 versioned cache로 보관합니다. 만료 후 갱신에 실패하면 계약 사이트는 합의된 정책, 불명확한 사이트는 보수적 정지 등 법무 기준을 명시합니다.
- DNS cache로 lookup 폭주를 줄이되 IP 변경과 SSRF를 고려해 사설·금지 대역을 매 연결 시 검증합니다. 429·503은 host별 exponential backoff와 circuit breaker로 전체 시스템 retry storm을 막습니다.
- host별 실제 RPS·동시 연결·429 비율과 robots revision을 기록하고 synthetic test site로 다중 worker에서도 제한 상한이 지켜지는지 검증합니다. 한 대형 host의 backlog가 작은 host를 굶기지 않도록 weighted fair scheduling을 둡니다.
URL 문자열 중복, 추적 파라미터만 다른 URL, 내용이 거의 같은 상품 페이지를 각각 어느 단계에서 제거하겠습니까? canonicalization, Bloom filter, content hash/SimHash의 오탐·누락 비용을 비교하고 잘못된 규칙이 중요한 페이지를 영구 누락시키는 상황에 대비해 주세요.
답변 예시 보기
중복의 종류별 처리
수집 전에는 scheme·host 대소문자, 기본 port, fragment 제거와 사이트별 안전한 query parameter 규칙으로 URL을 정규화합니다. 이미 본 URL 확인은 분산 exact store가 기준이며 Bloom filter를 앞단에 두어 부하를 줄일 수 있습니다. 다만 Bloom false positive가 “fetch하지 않음”으로 이어지면 신규 상품을 놓치므로, 중요 URL은 exact 확인하고 일반 발견 URL만 허용 오차를 적용합니다.
내용 중복과 검증
- fetch 후 canonical tag·redirect와 본문 정규화 hash로 완전 동일 내용을 묶습니다. 템플릿·광고만 다른 근사 중복은 SimHash/MinHash 후보를 만들되 가격·옵션 영역 차이를 보존하도록 도메인 parser 결과도 비교합니다.
- 중복 page라도 링크 discovery source로서 가치는 있을 수 있으므로 원문 저장 생략과 link 추출 생략을 분리합니다. 대표 URL 선택은 사이트 canonical, 안정성, HTTPS와 상품 ID 근거를 남깁니다.
- canonical 규칙은 site·version별로 배포하고 샘플 shadow run에서 merge 비율과 서로 다른 상품 ID가 합쳐지는 비율을 봅니다. 급격한 dedup 증가 시 자동 롤백합니다.
- 오탐 때문에 건너뛴 URL을 표본 exact audit하고 Bloom filter는 기간별 rotation·재구축합니다. 운영자가 URL의 판단 경로와 대표 문서를 추적하고 재수집할 도구가 필요합니다.
fetch 예산이 하루 2억 회로 고정되어 있을 때 새 상품, 가격이 자주 변하는 상품, 여러 번 실패한 페이지, 오래 안정적인 페이지의 재수집 주기를 어떻게 정하겠습니까? 사이트 장애로 대량 실패한 뒤 backlog가 생기는 경우 신선도 SLO를 어떻게 회복할지도 포함해 주세요.
답변 예시 보기
변경 가능성과 가치 기반 예산
모든 페이지를 같은 주기로 polling하지 않고 최근 변경 간격, 클릭·매출 가치, 재고 변동성, 마지막 성공 시각과 fetch 비용으로 nextFetchAt을 계산합니다. 신규·이벤트 상품에는 탐색 예산을 예약하고, 오래 변하지 않은 페이지는 지수적으로 주기를 늘리되 최대 간격을 둡니다. 변경을 놓친 비용과 외부 요청 비용으로 우선순위를 설명할 수 있어야 합니다.
실패와 학습의 안전장치
- 페이지 404 같은 영구 실패와 timeout·503 같은 일시 실패를 분리합니다. 일시 실패는 jitter backoff, 사이트 전체 장애는 host circuit breaker로 묶어 수백만 URL의 개별 재시도를 막습니다.
- 복구 후 backlog는 최근 인기·SLO 위반 페이지부터 처리하고 저가치 URL을 늦춥니다. backlog를 한꺼번에 방출하지 않고 외부 host의 정상 허용률 안에서 catch-up합니다.
- 예측 모델이 있다면 단순 주기 정책과 shadow 비교하고, 특정 소형 사이트를 굶기지 않도록 최소 예산과 탐색 비율을 둡니다. sitemap, partner feed, 변경 알림을 이용하면 polling 예산을 줄일 수 있습니다.
- 사이트·상품 등급별 age distribution, 변경 발견 지연, 변경 없는 fetch 비율을 관측합니다. 과거 trace replay로 동일 예산에서 신선도가 개선되는지 검증하고 모델 rollback을 준비합니다.
일반 HTTP fetch, headless browser 렌더링, 로그인 세션이 필요한 페이지가 섞여 있습니다. 비용이 큰 renderer를 어디에 적용하고 악성 페이지의 자원 고갈·내부망 접근을 어떻게 막겠습니까? parser 변경으로 가격이 0원으로 오인되는 데이터 품질 사고의 탐지와 롤백도 설계해 주세요.
답변 예시 보기
단계적 fetch와 격리
먼저 저렴한 HTTP fetch·정적 parser를 시도하고 상품 필드가 없거나 JS 의존 site로 분류된 경우에만 renderer queue로 승격합니다. 사이트 profile과 과거 성공률로 경로를 결정하며 renderer 예산·tenant별 concurrency를 둡니다. 브라우저는 짧은 수명의 sandbox에서 CPU·메모리·시간·응답 크기를 제한하고, 사설 IP·metadata endpoint·파일 시스템 접근을 네트워크 계층에서 차단합니다.
데이터 품질과 운영 검증
- 로그인 credential은 site별 vault와 최소 권한으로 분리하고 세션·쿠키가 다른 사이트나 로그에 섞이지 않게 합니다. CAPTCHA 우회는 기술 문제가 아니라 계약·법무 경계로 분리합니다.
- parser output에는 parserVersion, source URL, fetch time과 근거 fragment를 붙입니다. 가격 범위, 통화, 전일 대비 변화율, 필드 null 비율을 검사해 이상 값은 바로 공개하지 않고 quarantine합니다.
- 새 parser는 기존 parser와 같은 원문을 shadow 처리해 차이 분포를 비교한 뒤 사이트 일부에 canary합니다. 0원 비율·parse 실패율·상품 수 급변을 rollback 조건으로 둡니다.
- 원문을 일정 기간 불변 보관하면 parser 버그 수정 후 외부 사이트를 다시 때리지 않고 replay할 수 있습니다. renderer timeout과 sandbox violation도 사이트별로 관측합니다.
글로벌 VOD·라이브 동영상 스트리밍 설계
상황
조건
서비스 규모
- VOD 200만 편, 신규 원본 1PB/일, 최대 4K 25Mbps
- 라이브 동시 1천만, peak egress 80Tbps, 3개 CDN 사용 가능
- 6개 bitrate ladder, HLS/DASH와 DRM, 40개국 권리 제한
- 재생 시작 p95 2초, rebuffer ratio 0.5% 이하, 라이브 지연 5초
현재 문제와 제약
- Thundering Herd인기 live segment miss가 origin에 집중
- Pipelinetranscode 일부 rendition 실패를 전체 실패로 처리
- QoE낮은 latency와 안정적 buffer가 상충
- DRM·지역 권리 검사는 우회할 수 없고 multi-CDN 비용 차이가 큼
꼬리 질문
원본 ingest부터 transcoding, packaging, origin 저장, CDN 재생까지의 흐름을 설계해 주세요. 6개 rendition 중 하나가 실패하거나 같은 작업이 재시도될 때 전체 영상을 처음부터 처리하지 않으면서도 manifest가 존재하지 않는 segment를 가리키지 않게 하려면 어떤 상태와 원자성을 두겠습니까?
답변 예시 보기
불변 원본과 단계별 작업
ingest는 multipart 업로드와 checksum으로 원본을 object storage에 확정한 뒤 assetId·sourceVersion 이벤트를 내구성 있는 queue에 발행합니다. probe, rendition별 transcode, segment/package, DRM encrypt, publish를 독립적이고 멱등한 task로 나눕니다. task key를 assetId+sourceVersion+profileVersion+segment 범위로 만들어 재시도가 같은 object를 안전하게 덮거나 이미 완료 결과를 재사용하게 합니다.
부분 실패와 publish 경계
- 각 rendition은 PENDING/RUNNING/READY/FAILED 상태와 입력·출력 checksum을 가집니다. 특정 고화질 실패 시 저화질만으로 공개할지, 필수 ladder를 기다릴지는 콘텐츠 등급과 SLA로 정합니다.
- segment를 임시 namespace에 쓰고 존재·길이·decode 검증이 끝난 rendition만 manifest 후보에 포함합니다. 새 immutable manifest를 작성한 뒤 asset의 activeManifestVersion 포인터를 원자 교체합니다.
- poison source와 일시 worker 오류를 구분해 retry budget 후 DLQ·운영 검토로 보냅니다. CPU/GPU pool은 라이브·신규 공개·백필을 분리해 오래된 VOD 재인코딩이 라이브를 막지 않게 합니다.
- golden media와 실제 샘플로 영상·음성 sync, segment 연속성, DRM 재생을 자동 검증하고 pipeline stage 지연·실패율·비용을 profile version별로 관측합니다.
결승전 시작 직후 같은 live segment에 전 세계 요청이 몰리고 CDN 하나가 장애를 일으켰습니다. multi-CDN, origin shield, cache key, pre-warming과 failover를 어떻게 구성하며, 급격한 트래픽 이동이 남은 CDN과 origin을 연쇄 장애로 만들지 않도록 어떤 제어를 하겠습니까?
답변 예시 보기
계층 캐시와 origin 보호
edge miss가 직접 object origin으로 가지 않게 지역별 origin shield를 두고 동일 segment 요청을 collapse합니다. VOD segment는 content-addressed immutable URL과 긴 TTL, live segment는 sequence/version을 포함한 짧은 TTL을 사용합니다. 경기 전 player·리전 수요를 예측해 manifest, 첫 segment, DRM metadata를 pre-warm하되 모든 rendition을 무차별 warm하는 비용은 피합니다.
점진적 multi-CDN 전환
- DNS만으로 느리게 전환하기보다 player steering 또는 traffic director가 국가·ISP별 throughput, 오류, startup QoE를 보고 세션 단위로 CDN을 선택합니다. 재생 중 잦은 전환은 cache와 연결을 잃으므로 연속 실패나 buffer 위험 때만 바꿉니다.
- 장애 CDN의 100%를 즉시 한 곳으로 보내지 않고 남은 CDN의 계약 용량과 실시간 포화를 고려해 가중치를 단계 조정합니다. 신규 세션 admission, 저우선 preview 화질 제한 등 load shedding도 준비합니다.
- origin에는 signed request, shield별 bandwidth cap, request coalescing과 stale manifest 정책을 둡니다. origin 장애 때 이미 캐시된 segment는 계속 재생하고 새 live segment는 명확한 재시도·안내 정책을 가집니다.
- CDN 로그가 늦으므로 player QoE beacon과 synthetic probe를 함께 사용합니다. 정기적으로 CDN 장애와 50% traffic shift를 연습해 전환 시간, origin peak, 재생 실패율을 검증합니다.
segment를 줄이면 라이브 지연은 낮아지지만 요청 수와 rebuffer 위험이 커집니다. 서로 다른 네트워크·기기에서 ABR이 bitrate와 buffer를 어떻게 선택하게 하고, 5초 지연을 회복하기 위한 catch-up이 사용자 경험을 악화시키지 않도록 어떤 지표와 정책을 사용하겠습니까?
답변 예시 보기
단일 목표가 아닌 QoE 최적화
2~6초 segment와 low-latency chunk를 비교하며 CDN·player 지원 범위부터 확인합니다. player는 최근 throughput만으로 화질을 올리지 않고 buffer occupancy, 변동성, device decoder 능력을 함께 봅니다. 시작 시 보수적 rendition으로 빠르게 재생하고 충분한 buffer가 쌓인 뒤 단계적으로 올려 급격한 oscillation을 줄입니다.
지연 유지와 검증
- live edge에서 멀어지면 buffer가 충분할 때만 1.02~1.05배 playback catch-up을 적용하고, 임계 이상이면 최신 segment로 점프할지 사용 사례에 따라 결정합니다. 스포츠는 장시간 지연보다 짧은 jump를 선호할 수 있지만 사용자 고지가 필요합니다.
- 저대역폭에서는 지연 목표보다 연속 재생을 우선하는 degraded mode를 두며 data saver·low latency 선택도 제공합니다. rendition ladder는 실제 네트워크 분포와 화질 효용을 기준으로 설계합니다.
- startup time, rebuffer frequency·duration, average bitrate, switch rate, live latency와 재생 실패를 국가·ISP·기기별로 봅니다. 평균 화질 상승이 이탈률을 악화시키지 않는지 확인합니다.
- 네트워크 trace replay와 실제 device lab에서 ABR version을 shadow/A-B 테스트하고 QoE guardrail을 넘으면 롤백합니다. 플레이어 beacon 손실과 표본 편향도 고려합니다.
DRM license service가 한 리전에서 장애를 일으키고 manifest 인증 서비스도 느려졌습니다. 콘텐츠 권리와 보안을 우회하지 않으면서 이미 시청 중인 사용자와 신규 재생 요청의 가용성을 어떻게 다르게 처리하고, 토큰 공유·지역 우회·재생 URL 유출을 어떻게 제한하겠습니까?
답변 예시 보기
control plane과 media plane 분리
media segment는 CDN에서 대규모로 제공하고 권한 판정·license 발급은 작은 control plane으로 분리합니다. 재생 시작 시 짧은 수명의 playback session을 발급해 contentId, user/device, region, entitlement와 만료를 서명합니다. CDN은 URL마다 원격 인증을 호출하기보다 edge에서 서명을 검증해 인증 장애가 segment마다 증폭되지 않게 합니다.
장애와 남용 대응
- 이미 유효한 license·session을 가진 사용자는 만료 범위까지 재생을 지속합니다. 신규 session은 다른 리전으로 failover하되 데이터 residency와 권리 규칙을 동일하게 적용합니다. 검증할 수 없을 때 무조건 fail-open하지 않습니다.
- license는 device-bound key, 동시 재생 한도와 offline 만료를 사용하고 갱신은 조기에 시도합니다. token URL은 짧게 만료하고 session·콘텐츠 범위를 제한해 공유 피해를 줄입니다.
- 지역은 IP만 신뢰하지 않고 계정 국가·결제·VPN 위험 신호를 정책적으로 조합하되 오탐 이의제기 흐름을 둡니다. key·token 원문은 로그에 남기지 않습니다.
- license p99, 발급 실패, 기존/신규 재생 성공률을 분리 관측합니다. 리전 failover와 key rotation drill에서 재생 지속 시간, 권리 위반 0건, 복구 후 중복 session 정리를 검증합니다.
실시간 차량 호출·배차 시스템 설계
상황
조건
규모와 목표
- 온라인 기사 300만 명, 위치 3초 간격·최대 100만 update RPS
- 평균 1만/최대 5만 호출 RPS, 8개 국가 30개 도시
- 후보 조회 p99 300ms, 최초 제안 3초, 30초 내 최종 배차 95%
- ETA 모델, 기사 선호·차종·안전 정책과 동적 요금 연계
현재 문제와 제약
- Hot Region공항·역 cell이 평균의 50배 쓰기 처리
- Stale GPS지하·터널에서 위치 지연과 순서 역전
- Racetimeout·중복 명령으로 동일 기사 이중 배차
- 운행 안전과 가격 동의는 latency를 위해 생략할 수 없는 규칙
꼬리 질문
기사 위치를 Geohash, H3, quadtree 같은 공간 인덱스에 어떻게 배치하고 주변 후보를 어떤 순서로 확장 탐색하겠습니까? 밀집 지역의 hot partition, cell 경계, 늦거나 튀는 GPS, 기사가 offline된 뒤 남은 stale 위치를 함께 처리해 주세요.
답변 예시 보기
위치 write와 후보 read 분리
ingest는 driverId별 sequence·eventTime을 검사해 이전 위치가 최신 상태를 되돌리지 못하게 하고, 비현실적 속도·정확도 반경이 큰 좌표는 별도 품질 flag를 붙입니다. 최신 상태는 driverId store가 기준이며 공간 인덱스에는 cell→driverId 후보를 둡니다. 위치 이동은 새 cell 등록과 이전 cell 제거가 완전한 분산 트랜잭션이 아니어도 조회 시 최신 상태·version을 재검증해 유령 후보를 제거할 수 있습니다.
탐색과 hotspot 제어
- H3/Geohash cell에서 시작해 ring 단위로 넓히되 직선거리만으로 결정하지 않고 도로 ETA service에 전달할 후보 상한을 둡니다. 경계의 이웃 cell을 반드시 포함하고 도심·교외 밀도에 따라 resolution을 다르게 할 수 있습니다.
- 밀집 cell은 driverId hash로 sub-shard하고 읽을 때 병렬 병합하거나 더 작은 cell로 동적 분할합니다. 빈번한 위치 write와 배차 claim은 별도 저장소·resource pool로 분리해 인덱스 지연이 예약 일관성을 깨지 않게 합니다.
- heartbeat TTL, 앱 상태, 마지막 위치 age를 필터링하고 정확도가 낮으면 탐색 반경·ETA 불확실성을 높입니다. GPS 한 번 누락으로 즉시 offline 처리하기보다 상태를 AVAILABLE→STALE→OFFLINE으로 전이합니다.
- 도시·cell별 update lag, 후보 수, stale 제거율과 실제 픽업 ETA 오차를 관측합니다. 실제 이동 trace replay와 특정 cell 50배 부하 테스트로 p99와 분할 수렴을 검증합니다.
두 matching worker가 같은 기사에게 거의 동시에 제안하거나, 기사가 수락했지만 ACK가 유실되어 승객 요청이 다른 기사에게 넘어갈 수 있습니다. 기사 claim과 승객 요청의 상태 머신, lease·fencing·멱등키를 사용해 이중 배차와 영구 잠금을 어떻게 막겠습니까?
답변 예시 보기
두 자원의 상태 전이를 명시
승객 요청은 SEARCHING→OFFERED→MATCHED/CANCELLED, 기사는 AVAILABLE→OFFERED→ASSIGNED로 전이하며 matchId와 version을 가집니다. worker는 기사 레코드를 조건부 갱신해 AVAILABLE이고 version이 예상값일 때만 짧은 lease와 fencing token을 얻습니다. 승객 요청에도 한 개의 active offer만 허용할지 동시 제안을 허용할지 정책을 명시합니다.
결과 불명과 늦은 응답 처리
- 제안·수락 명령은 matchId+attemptId로 멱등 처리하고 같은 요청의 재전송은 기존 결과를 반환합니다. 기사 수락은 token과 lease 만료를 검사하는 조건부 전이로 두 상태를 확정하거나 coordinator/saga가 보상합니다.
- lease가 만료되어 새 기사가 확정된 뒤 이전 기사의 늦은 수락은 fencing token으로 거부하고 명확한 앱 메시지를 보냅니다. 단순 TTL만 쓰면 정지됐던 worker가 뒤늦게 쓰는 zombie 문제를 막지 못합니다.
- 순차 제안은 기사 피로를 줄이지만 지연이 늘고, 여러 명 동시 제안은 빠르지만 수락 취소가 발생합니다. 수요·공급과 기사 경험에 따라 소규모 batch를 제안하되 첫 atomic claim 성공자만 확정하는 방식을 검토합니다.
- 상태 불일치 scanner가 오래된 OFFERED, 기사 ASSIGNED/승객 미확정 쌍을 찾아 멱등 보상합니다. 중복 배차율, orphan lease, 늦은 수락률과 매칭 시간으로 정책을 검증합니다.
가까운 기사만 고르면 외곽 승객이 계속 기다리고, 모든 후보를 정교하게 평가하면 3초를 넘습니다. ETA, 기사 수락 확률, 승객 대기, 기사 공정성, 목적지·차종 제약을 어떤 단계로 평가하고 수요 폭증 때 품질을 어떻게 의도적으로 낮출지 설명해 주세요.
답변 예시 보기
후보 생성과 ranking 분리
공간 인덱스는 수백 명의 거친 후보를 빠르게 만들고 차량 상태·차종·안전·법적 규칙 같은 hard filter를 먼저 적용합니다. 그 다음 도로 ETA, 수락 확률, 승객 대기 시간, 기사 연속 거절·수익 공정성 등을 score로 결합해 상위 소수에게 제안합니다. 규칙과 모델 feature에 version을 붙여 실제 결정 재현이 가능하게 합니다.
시간 예산과 폭주 모드
- 후보 조회 300ms, ETA batch 500ms, ranking 100ms처럼 단계별 timeout을 둡니다. ETA service가 느리면 격자 간 사전 계산 또는 직선거리 보정값으로 fallback하고 부분 feature 누락으로 전체 요청을 실패시키지 않습니다.
- 폭주 시 탐색 ring·후보 수·모델 복잡도를 제한하고 cached feature를 사용합니다. 다만 안전·차종 규칙은 degrade하지 않으며, 근사 결과와 필수 규칙을 구분합니다.
- 오래 기다린 승객의 priority를 높이고 특정 지역·기사군에 불리한 결과를 관측합니다. 전역 평균 배차율만 최적화하면 외곽 지역이 희생될 수 있으므로 wait-time percentile과 취소율을 지역별로 봅니다.
- offline replay는 상호작용 편향이 있어 online canary가 필요합니다. 배차 시간, 수락·취소, 픽업 ETA 오차, 기사 유휴 이동거리와 공정성 guardrail로 모델 확대·롤백을 결정합니다.
호출 확인 화면에서는 12,000원이었는데 8초 뒤 배차 시점에 15,000원으로 계산되었습니다. 지역별 수요·공급을 이용한 동적 요금을 실시간 계산하면서도 사용자 동의, 재시도 멱등성, 이상 가격·조작, 모델 장애를 어떻게 다루겠습니까?
답변 예시 보기
가격 quote를 독립된 계약으로 모델링
수요·공급 집계가 만든 surge multiplier는 지역·시간 window별 versioned snapshot으로 배포합니다. 사용자가 호출을 확인할 때 quoteId, 통화, 구성 항목, multiplier, 유효기간을 발급하고 명시적으로 동의받습니다. 배차 요청은 quoteId를 참조하며 유효기간 안에는 이후 모델 값이 바뀌어도 동의한 가격 규칙을 유지하거나 변경 시 재동의를 받습니다.
안전장치와 검증
- 동일 호출 재시도는 idempotency key로 같은 quote·request를 사용합니다. 할인·통행료처럼 사후 변동 가능한 항목은 예상과 확정의 경계를 화면과 API에 명확히 합니다.
- 지역 cell의 최소 표본, 상·하한, 변화율 제한과 인접 지역 smooth를 적용해 GPS 조작이나 소수 이벤트가 극단 가격을 만들지 않게 합니다. 비정상 계정·기기 신호는 집계에서 감산합니다.
- pricing stream 장애 시 last-known-good를 제한 시간 사용한 뒤 기본 요금으로 fallback할 수 있습니다. 수요 폭증에서 요금 계산 불가가 전체 배차 장애가 되지 않되 국가별 규제는 항상 적용합니다.
- 가격 version·입력 집계·quote·동의를 감사 로그로 연결하고, 도시별 가격 분포·quote 변경·취소·민원율을 경보합니다. 과거 이벤트 replay와 작은 지역 canary로 상한 위반과 매출·배차 효과를 검증합니다.
멀티테넌트 분산 메시지 큐 설계
상황
조건
규모와 기능
- 20만 topic-partition, 평균 400만/최대 1천만 msg/s, 평균 2KB
- 3개 zone, replication factor 3, 기본 7일·최대 90일 보존
- consumer group 3만 개, offset rewind와 schema registry 제공
- 중요 topic RPO 0 목표, publish p99 30ms, 가용성 99.99%
현재 문제와 제약
- Skew나쁜 partition key와 대형 message로 broker 편중
- Duplicateretry·failover·consumer crash의 중복 부작용
- Lag느린 consumer와 장기 보존이 disk headroom 소진
- 결제와 분석의 내구성·지연 요구가 달라 단일 정책 적용 불가
꼬리 질문
partition 수와 key를 어떻게 정하고 leader/follower replication으로 어느 범위의 순서와 내구성을 보장하겠습니까? leader failover, ISR 축소, rack 장애 중에도 결제 이벤트 RPO 0을 지키려 할 때 지연·가용성·운영 비용이 어떻게 달라지는지 설명해 주세요.
답변 예시 보기
보장의 경계를 partition으로 제한
전역 순서가 아니라 같은 업무 key의 partition 내 append 순서를 제공합니다. 주문 상태라면 orderId를 key로 하여 동일 주문 이벤트가 한 partition에 가게 하되, tenantId만 쓰면 대형 tenant가 hot partition이 될 수 있습니다. 처리량, consumer 병렬성, 예상 보존량으로 초기 partition 수를 정하고 늘린 뒤 key mapping이 바뀌면 기존·신규 partition 간 순서가 깨질 수 있음을 계약에 포함합니다.
복제와 장애 선택
- replication factor 3을 서로 다른 zone에 배치하고 중요 topic은 producer ack=all, min ISR=2와 idempotent producer를 요구합니다. ISR이 1로 줄면 RPO를 지키기 위해 publish를 거부할지 가용성을 택할지 topic 등급별로 정합니다.
- leader epoch과 high watermark로 승인되지 않은 tail을 구분하고 unclean leader election은 중요 topic에서 금지합니다. 분석 topic은 약간의 손실을 허용해 다른 정책을 쓸 수 있습니다.
- partition이 너무 많으면 file handle, election, controller metadata와 rebalance 비용이 커집니다. broker별 leader·bytes·disk 균형과 예상 90일 보존량을 placement에 반영하고 이동은 foreground latency로 throttle합니다.
- zone loss, leader process kill, network partition 테스트로 승인된 메시지 유실 0, failover 시간, duplicate 범위를 검증합니다. 단순 broker up이 아니라 under-replicated partition과 ISR 수렴을 복구 기준으로 봅니다.
producer가 ACK를 받지 못해 재전송하고, consumer는 쿠폰 DB 커밋 후 offset commit 전에 죽습니다. idempotent producer, transaction, consumer inbox/outbox를 구분해 end-to-end 중복 부작용을 어떻게 줄이며 “exactly once”라고 말할 수 있는 범위를 어디까지로 제한하겠습니까?
답변 예시 보기
각 실패 창을 따로 닫기
producer에는 producerId·epoch·partition sequence를 주어 broker가 같은 session의 재전송을 dedup합니다. 이는 producer 재시도 중 로그 중복을 줄일 뿐, upstream DB commit과 publish 사이를 원자화하지 않습니다. 주문 DB 변경과 outbox 행을 같은 로컬 트랜잭션에 쓰고 relay가 발행하는 패턴이 그 실패 창을 다룹니다.
소비와 외부 부작용
- queue 안의 read-process-write가 모두 같은 플랫폼이면 input offset과 output append를 transaction으로 묶어 중간 중복 노출을 막을 수 있습니다. 외부 쿠폰 DB·이메일 공급자는 이 transaction에 자동 포함되지 않습니다.
- 쿠폰 consumer는 eventId 또는 업무 idempotency key를 inbox/unique constraint에 기록하고 쿠폰 원장 변경과 같은 DB transaction으로 커밋합니다. 이후 offset commit 실패로 재수신해도 기존 결과를 반환합니다.
- 이메일처럼 외부가 idempotency를 지원하지 않으면 at-least-once 재시도와 중복 발송 위험, at-most-once 유실 위험을 업무와 합의합니다. timeout으로 결과를 모르는 상태를 별도 UNKNOWN으로 운영합니다.
- 중복 이벤트를 강제로 주입하고 consumer를 DB commit 직후 kill해 부작용이 한 번인지 검증합니다. transaction abort·fencing, inbox 증가율, duplicate hit와 처리 지연을 관측하며 dedup 보존 기간 이후 replay 정책도 정합니다.
한 tenant가 20MB 메시지와 90일 보존을 요청하고 다른 tenant는 key 없이 높은 트래픽을 보냅니다. 중요 결제 topic의 지연을 보호하도록 quota, placement, storage, admission control을 어떻게 설계하고 어느 시점에 전용 cluster로 분리하겠습니까?
답변 예시 보기
여러 자원 차원의 격리
메시지 수만 제한하면 대형 payload와 장기 보존을 막지 못합니다. tenant/topic별 ingress bytes, request rate, message size, partition 수, retained bytes, connection과 fetch bandwidth quota를 둡니다. broker는 token bucket으로 producer를 throttle하고 명확한 retry-after를 주며, 절대 크기 상한을 넘는 payload는 object storage에 두고 참조만 전달하도록 유도합니다.
placement와 승격 기준
- 중요·일반 workload를 broker pool과 disk class로 분리하고 controller·network thread까지 bulkhead를 둡니다. replication·retention까지 반영한 disk forecast로 topic 생성 시 quota를 예약합니다.
- partition key cardinality와 분포를 schema/샘플로 사전 검사하고 broker별 bytes·leader skew를 지속 탐지합니다. hot partition 자동 이동은 단일 key 처리량을 해결하지 못하므로 producer key 변경이나 application sharding이 필요합니다.
- 지속적으로 클러스터의 일정 비율을 점유하거나 별도 보존·보안·업그레이드 정책이 필요한 tenant는 전용 cluster로 분리합니다. 이동 시 dual publish와 offset mapping, 검증 기간을 둡니다.
- disk headroom, quota throttle, produce/fetch p99와 중요 topic SLO를 tenant별로 관측합니다. 초대형 message·retention 급증·disk 한 대 소실을 부하 테스트해 admission이 실제로 핵심 workload를 보호하는지 검증합니다.
consumer bug 때문에 6시간치 이벤트를 재처리해야 하지만 현재 실시간 처리도 계속되어야 합니다. offset rewind, replay isolation, schema evolution, DLQ를 어떻게 운영하며 잘못된 replay가 외부 부작용을 다시 일으키거나 broker를 포화시키지 않도록 검증·승인 절차를 설계해 주세요.
답변 예시 보기
재처리를 별도 실행으로 모델링
기존 group offset을 즉시 되감기보다 새 replay group과 runId를 만들고 시작·종료 offset, 대상 partition, 코드·schema version을 불변 계획으로 기록합니다. 별도 worker pool과 fetch/produce quota를 사용해 실시간 consumer와 자원을 격리합니다. 먼저 dry-run 또는 shadow sink에서 결과 수와 checksum을 비교한 뒤 범위를 확대합니다.
부작용·스키마·운영 통제
- DB projection은 eventId 멱등 upsert 또는 새 table 재구축 후 atomic swap을 사용합니다. 이메일·결제 같은 외부 side effect는 replay 모드에서 기본 차단하고 명시적으로 승인된 보정 command만 발행합니다.
- schema registry는 backward/forward compatibility를 검사하지만 오래된 event를 최신 consumer가 이해한다는 보장은 별도입니다. event version별 adapter와 golden replay test를 유지하고 해석 불가 이벤트는 이유와 원문 참조를 DLQ에 둡니다.
- DLQ는 영구 저장소가 아니라 소유자, retry 가능 분류, 보존·재주입 도구가 있는 운영 queue입니다. 재주입에도 새 eventId를 무작정 만들지 않고 원본 lineage와 시도 횟수를 보존합니다.
- 승인자는 예상 메시지 수·부작용·완료 시간·중단 기준을 확인합니다. replay lag, broker disk/network, 결과 count·금액 대사를 관측하고 canary partition 실패 시 즉시 중지합니다.
감사 가능한 대규모 결제 원장 시스템 설계
상황
조건
시스템 구성·규모
- 평시 2만 TPS, 이벤트 시 최대 8만 TPS
- 국내외 PG 6곳, 12개 통화, 부분 취소·환불·차지백 지원
- 원장 PostgreSQL, Kafka 이벤트, Redis 조회 캐시
- 7년 보존, 일 2회 PG·은행 정산 파일 대사
현재 문제·목표
- API 재시도와 webhook 중복으로 이중 부작용 발생
- 원장 잔액과 조회용 잔액 projection이 최대 20분 불일치
- PG 결과 불명 거래 약 0.03%, 수동 확인에 평균 2일 소요
- RPO 0에 가까운 금액 보존, 장애 복구 RTO 30분 목표
꼬리 질문
승인·취소·부분환불·수수료·판매자 정산을 표현하는 원장을 설계하십시오. 불변 원장과 빠른 잔액 조회를 함께 제공할 때 어떤 불변식을 어디에서 강제하고, projection 장애나 지연이 발생해도 잘못된 금액을 사용하지 않도록 어떻게 하겠습니까?
답변 예시 보기
모델과 불변식
먼저 “결제 상태”와 “돈의 이동”을 같은 행의 가변 상태로 다루지 않겠습니다. 하나의 business transaction 아래 승인·환불 같은 operation을 두고, 각 operation이 append-only journal entry들을 생성하게 합니다. 고객 미수금, PG 수취 예정금, 판매자 지급 예정금, 수수료 수익처럼 의미가 다른 계정을 분리하고, 거래 단위로 동일 통화의 debit 합계와 credit 합계가 0이라는 불변식을 DB 트랜잭션과 제약으로 강제합니다. 환불이나 운영 보정은 과거 entry 수정이 아니라 원거래를 참조하는 반대 entry로 기록해 이력을 보존합니다.
조회와 실패 격리
- 현재 잔액은 원장을 매번 합산하지 않고 account별 balance projection과 주기적 snapshot으로 제공합니다. 원장 커밋과 projection 이벤트 발행 사이의 유실은 transactional outbox 또는 원장 commit log 기반 CDC로 닫습니다.
- projection에는 마지막 반영 journal sequence를 저장합니다. 중복 이벤트는 entryId로 멱등 처리하고, 역순 이벤트는 account sequence 또는 원장 버전으로 거부·보류합니다. 재구축 시 snapshot 이후 entry를 재생해 결과가 원장 합계와 같은지 검증합니다.
- 잔액을 단순 표시하는 화면에는 수 초의 지연을 허용할 수 있지만, 출금 가능 금액·환불 한도처럼 금전 부작용을 만드는 판단은 pending hold를 포함한 강한 일관성 경로를 사용합니다. 어느 기능이 어느 잔액을 읽는지 계약으로 구분해야 합니다.
- 원장 합계와 projection의 차이, outbox lag, 계정별 sequence gap을 상시 대사하고 임계치를 넘으면 해당 계정의 출금을 보수적으로 차단합니다. 성능 최적화가 금액 불변식을 우회하지 못하게 합니다.
고객이 결제 버튼을 눌렀고 PG 승인 요청은 타임아웃됐지만 실제 승인 여부는 알 수 없습니다. 고객의 반복 클릭, 서버 재시도, 늦게 도착한 webhook이 겹치는 상황에서 API 계약과 상태 머신을 어떻게 설계하겠습니까? ‘중복 결제’와 ‘결제 유실’ 중 어느 위험을 언제 감수할지도 설명하십시오.
답변 예시 보기
결과 불명 상태를 모델에 포함
타임아웃을 실패로 번역하지 않고 UNKNOWN 또는 PENDING_CONFIRMATION이라는 정상 상태로 다룹니다. 클라이언트가 주문 단위 idempotency key를 보내면 서버는 요청 payload hash와 함께 저장하고, 동일 키·동일 payload에는 기존 paymentId와 상태를 반환합니다. 같은 키로 금액이 다른 요청은 오류로 거부합니다. 따라서 브라우저 반복 클릭과 API gateway 재시도가 새 승인 요청을 만들지 않습니다.
- PG가 idempotency key를 지원하면 내부 operationId를 그대로 전달합니다. 지원하지 않으면 승인 요청을 무조건 반복하기보다 PG 거래 조회 API, webhook, 정산 파일 순서로 사실을 확인하고, 확인 전에는 고객에게 “처리 확인 중”과 조회 가능한 paymentId를 제공합니다.
- webhook은 서명·timestamp를 검증하고 providerEventId로 중복 제거합니다. 다만 webhook 순서가 보장되지 않으므로 상태 전이 표와 PG 거래 버전을 이용해 이미 환불된 거래가 늦은 승인 이벤트로 되돌아가지 않게 합니다.
- 디지털 콘텐츠처럼 중복 청구가 치명적이면 불명 상태에서 추가 승인을 막는 쪽을 택합니다. 좌석 예약처럼 기회 손실도 큰 업무라면 일정 시간 hold를 연장하고 확인 경로를 빠르게 하되, 동일 주문에 새 payment attempt를 열 조건과 기존 attempt 취소 절차를 명확히 합니다.
- UNKNOWN 체류 시간, PG별 확인 성공률, webhook 지연을 관측하고 시간 초과 건은 exception queue로 보냅니다. 운영자는 원장을 직접 수정하지 않고 승인 확인 또는 보정 operation을 승인 절차와 함께 실행합니다.
내부 원장에는 성공, PG에는 취소, 은행 정산 파일에는 출금으로 기록된 거래가 발견됐습니다. 수백만 건을 자동 대사하면서 잘못된 자동 보정이 더 큰 사고를 만들지 않도록 매칭·예외 처리·운영 승인 절차를 어떻게 설계하겠습니까?
답변 예시 보기
대사를 별도의 통제 시스템으로 설계
먼저 내부 paymentId, PG transactionId, merchant reference를 생성 시점부터 함께 보존해 exact match 비율을 높입니다. 금액·통화·승인일·카드사·거래 유형을 이용한 규칙 기반 매칭은 신뢰도 점수를 부여하고, 1:1뿐 아니라 부분환불·합산 정산의 1:N 관계도 표현합니다. 원천 파일과 파싱 결과를 불변으로 보관해 규칙 변경 후에도 재현할 수 있게 합니다.
- 차이를 missing internal, missing external, amount mismatch, state mismatch, timing difference로 분류합니다. 단순 정산일 차이는 유예 window 뒤 재확인하고, 금액 차이나 중복 청구는 즉시 지급 보류 대상으로 격상합니다.
- 완전히 결정적인 소액·빈번 패턴만 자동 보정하고, 보정도 기존 entry를 수정하지 않고 correction journal로 남깁니다. 금액·고객 영향·규칙 신뢰도에 따라 1인 또는 2인 승인을 요구하며 모든 판단 근거와 전후 잔액을 감사 로그에 기록합니다.
- 대사 job 자체가 중복 실행돼도 reconciliation case와 correction key가 유일하도록 합니다. 원본 파일 checksum, 처리 버전, 마지막 offset을 저장해 부분 실패부터 재개합니다.
- 미대사 금액 총액과 건수, aging, PG별 mismatch rate를 SLO로 관리합니다. 새 규칙은 과거 데이터 shadow run으로 오탐률을 평가한 뒤 적용하고, 자동화 비율보다 잘못된 보정 0건을 우선합니다.
원장 DB의 한 리전이 20분간 단절되고 Kafka 소비도 지연되는 복합 장애가 발생했습니다. 무조건 가용성을 유지할지 결제를 제한할지 판단하고, 장애 조치·복구·검증 계획과 평소 관측해야 할 지표를 설명하십시오.
답변 예시 보기
금액 경로의 실패 정책
네트워크 분할에서 두 리전이 동일 계정 잔액을 독립 갱신하면 나중에 단순 병합할 수 없으므로, 원장 쓰기는 합의된 단일 writer 또는 quorum을 유지하는 리전에만 허용하겠습니다. quorum을 잃으면 신규 승인 접수는 제한하거나 pending으로만 저장하고 금액 확정은 fail-closed합니다. 결제 수단과 사업 영향에 따라 조회·비금전 기능은 last-known state로 계속 제공할 수 있습니다.
- 동기 복제 범위와 RPO, 승격 조건을 미리 정의하고 fencing token으로 이전 primary의 늦은 쓰기를 막습니다. DNS 전환만으로 split brain이 해결된다고 가정하지 않습니다.
- Kafka 지연 시 원장 커밋은 유지하되 projection이 stale임을 표시하고, 출금 판단은 원장 또는 보수적 hold를 사용합니다. lag가 한도를 넘으면 처리량을 줄여 복구 backlog가 무한히 커지지 않게 합니다.
- 복구 후에는 journal sequence gap, debit-credit 합, outbox 미발행, projection checksum을 검사한 뒤 단계적으로 트래픽을 엽니다. PG 승인 건과 원장 누락 건도 장애 시간대로 별도 대사합니다.
- 평소 replica lag, commit latency, quorum 상태, UNKNOWN 거래 aging, projection lag, 계정별 불변식 실패를 관측하고 primary 강제 종료·네트워크 분할·소비자 중단 훈련으로 RTO를 검증합니다.
실시간 광고 클릭·과금 집계 시스템 설계
상황
조건
시스템 구성·규모
- 평균 150만, 피크 500만 events/sec
- Kafka 800 partitions, Flink 집계, object storage 원본 보관
- campaign·creative·매체·국가별 다차원 조회
- 대시보드 p95 2초, 집계 신선도 60초 목표
현재 문제·목표
- 이벤트 95%는 20초 내, 0.5%는 6시간 뒤 도착
- SDK retry·브로커 재전달로 약 1.2% 중복
- 상위 캠페인 하나가 전체 클릭의 18%를 차지
- 실시간 잠정치와 D+1 확정치의 차이를 0.3% 이내로 축소
꼬리 질문
event time 기반 1분 집계에서 watermark와 late event 허용 시간을 어떻게 정하겠습니까? 빠른 화면과 정확한 청구가 서로 다른 요구를 가질 때 window를 닫고 수정하는 정책, 사용자에게 수치를 표현하는 방식까지 설명하십시오.
답변 예시 보기
지연 분포를 기준으로 정책 분리
고정된 “정답 watermark”를 먼저 정하지 않고 매체·SDK 버전·지역별 event-time과 ingestion-time 차이 분포를 측정합니다. 대시보드는 예를 들어 p99 지연을 포함하는 2~5분 watermark로 빠르게 잠정 집계하고, 이후 도착한 이벤트는 update/retraction을 지원하는 sink로 보정합니다. 청구는 더 긴 24시간 grace period와 별도 확정 절차를 둘 수 있습니다.
- 이벤트 payload에는 단말 시각뿐 아니라 SDK sequence와 서버 수신 시각을 보존합니다. 비정상 단말 시각은 허용 범위로 clamp하거나 별도 분류해 오래된 window를 무한히 다시 열지 않게 합니다.
- window 상태 보존 기간은 늦은 이벤트의 사업 가치와 state 비용을 비교해 정합니다. 허용 시간 이후 이벤트는 버리지 않고 side output에 보관해 D+1 재계산 또는 분쟁 조사에 사용합니다.
- 대시보드에는 ‘잠정/확정’, 마지막 반영 시각, 보정 여부를 표시합니다. 이미 보고된 수치가 바뀔 수 있다는 제품 계약을 숨기지 않고, 큰 변화는 감사 가능한 revision으로 남깁니다.
- 매체별 late rate와 보정 폭을 관측해 watermark를 동적으로 검토하되, 데이터 지연 자체가 악화된 것을 watermark 확대로 가리는 상황을 경계합니다.
“exactly-once 과금”이라는 요구를 받았습니다. 수집 SDK, Kafka, 스트림 처리기, 집계 저장소 각각의 실패 구간을 분석하고, 중복 제거 키·보존 기간·재처리 방식을 어떻게 설계할지 설명하십시오. exactly-once라고 표현할 수 있는 범위도 명확히 하십시오.
답변 예시 보기
끝단의 결과가 한 번 반영되도록 설계
네트워크 전체에서 이벤트가 물리적으로 한 번만 전달된다는 보장은 현실적이지 않습니다. producer retry와 consumer 재시작으로 전달은 반복될 수 있으므로, “정의한 eventId에 대한 과금 효과가 집계 결과에 한 번만 반영된다”를 목표로 좁힙니다. eventId는 campaignId와 timestamp를 조합한 추정 키가 아니라 SDK installationId+monotonic sequence 또는 서버가 발급한 impression token처럼 안정적으로 생성해야 합니다.
- 수집 계층은 같은 eventId의 payload가 다르면 위변조 또는 SDK 오류로 격리합니다. Kafka idempotent producer는 브로커 중복을 줄이지만 SDK 자체 중복까지 해결하지 않는다는 범위를 밝힙니다.
- 스트림 처리기는 checkpoint와 source offset, state를 일관되게 복원하고, sink가 transaction/conditional upsert를 지원하면 checkpoint와 결과 commit을 결합합니다. 지원하지 않으면 windowId+dimension+revision의 멱등 upsert와 원본 replay로 보완합니다.
- dedup state TTL은 최대 재전송·late-event 기간보다 길어야 하지만 500만 EPS에서는 비용이 큽니다. 실시간 계층은 제한된 TTL, 확정 배치는 원본 로그 전체에서 재중복 제거하는 계층적 전략을 검토합니다.
- 브로커 장애, checkpoint 직전·직후 종료, sink timeout을 주입해 replay 후 집계 checksum이 같은지 검증합니다. “Flink exactly-once” 같은 제품 기능 이름만으로 외부 sink까지 보장된다고 결론내리지 않습니다.
특정 캠페인이 전체 트래픽의 18%를 차지해 한 파티션과 집계 task만 지연됩니다. campaignId 순서를 유지하면서 hot key를 완화할지, 순서를 포기하고 병렬 집계할지 판단하십시오. 파티션 증설과 재분배 중 발생할 수 있는 문제도 포함하십시오.
답변 예시 보기
필요한 순서 보장부터 축소
클릭 수의 합산은 대개 교환·결합 법칙이 성립하므로 캠페인 전체의 엄격한 순서가 필요하지 않습니다. fraud 규칙처럼 순서에 의존하는 처리와 단순 집계를 분리하고, 집계는 campaignId에 salt/shard를 붙여 여러 파티션에서 부분 합산한 뒤 2단계로 병합하겠습니다. shard 수는 캠페인 트래픽 등급에 따라 정하고 메타데이터 버전으로 관리합니다.
- 무작위 salt는 재처리 결과가 달라질 수 있으므로 eventId의 deterministic hash를 사용합니다. 부분 집계 key에 window와 shard를 포함하고 최종 합산도 revision 기반 멱등 upsert로 만듭니다.
- hot campaign만 동적으로 shard 수를 늘릴 때 기존·신규 routing version이 같은 window에 공존할 수 있습니다. effective time을 정하고 최종 집계가 두 버전을 읽게 하거나 window 경계에서 전환합니다.
- Kafka partition 증설은 기존 key의 partition 매핑을 바꾸므로 동일 key의 전후 순서가 끊어질 수 있습니다. 순서가 필요한 스트림은 새 topic으로 이관하거나 epoch를 두고 drain 후 전환합니다.
- partition별 input rate, processing lag, state size, backpressure를 관측하고 특정 캠페인 throttling이나 별도 cluster 격리도 비용 대비 검토합니다.
실시간 대시보드는 100만 클릭, 다음 날 확정 배치는 93만 클릭으로 계산했습니다. 두 파이프라인 중 하나를 정답으로 가정하지 않고 원인을 좁히고, 장기적으로 수치 신뢰와 재현성을 높이기 위한 데이터·규칙·운영 구조를 제안하십시오.
답변 예시 보기
동일 원본과 설명 가능한 차이
우선 campaign·매체·시간대·SDK 버전별로 차이를 drill-down해 late event, dedup, bot filtering, timezone, join 누락 중 어디서 갈라지는지 찾습니다. 양쪽이 같은 immutable raw event와 eventId를 사용하게 하고, 각 단계의 입력·출력 count와 reject reason을 남기면 “어디서 7만 건이 빠졌는지” 재현할 수 있습니다.
- 실시간과 배치가 별도 구현한 비즈니스 규칙은 시간이 지나며 달라집니다. 공통 rule definition과 version을 사용하고 모든 집계에 ruleVersion을 저장해 규칙 변경 전후 결과를 비교합니다.
- 확정치는 원본 replay로 생성하고 실시간 결과와 자동 대사합니다. 허용 임계치를 넘으면 청구 확정을 보류하며, 보정된 dashboard revision과 사유를 광고주에게 노출합니다.
- 봇 탐지 모델은 나중에 판정이 바뀔 수 있으므로 원본 삭제 정책 안에서 feature와 모델 버전, 판정 근거를 보존합니다. 과거 청구를 새 모델로 조용히 덮어쓰지 않고 재평가 정책을 명시합니다.
- 장기적으로 단일 스트리밍 파이프라인 재처리(Kappa) 또는 공통 계산 라이브러리를 검토할 수 있지만, 운영 복잡도와 독립 검증 장치를 잃는 비용도 비교합니다.
대규모 주변 장소·매장 검색 시스템 설계
상황
조건
시스템 구성·규모
- 전 세계 장소 8억 개, 일 변경 2천만 건
- 평균 20만 QPS, 피크 60만 QPS
- 반경 500m~50km, 30개 필터, 다국어 검색
- OpenSearch 기반 검색과 RDB 원본, CDN·Redis 캐시
현재 문제·목표
- 도심·축제 지역의 공간 partition 편향
- cell 경계 후보 누락과 과도한 이웃 cell 조회
- 주소·영업시간 변경 반영 p99 40분
- 검색 p95 200ms, 긴급 장소 상태 1분 내 반영 목표
꼬리 질문
Geohash, quadtree, H3/S2, R-tree 등의 대안을 비교해 분산 공간 인덱스와 partition key를 선택하십시오. 도심 밀집, 극지방, cell 경계, 반경이 크게 달라지는 검색을 어떻게 처리하고 선택을 어떤 데이터로 검증하겠습니까?
답변 예시 보기
제품 조회 패턴과 분산 방식으로 선택
단일 자료구조의 우열보다 저장소와 조회 패턴을 봅니다. key-value/검색 클러스터에 수평 분산하기 쉽고 이웃 cell 계산이 중요한 경우 H3/S2나 Geohash cell 기반 후보 생성이 현실적입니다. R-tree는 정교한 범위 질의에 유리하지만 고빈도 분산 갱신과 shard 배치 방식까지 확인해야 합니다. 전 세계 균일 cell이 실제 장소 밀도를 균일하게 만들지는 않습니다.
- 검색 반경에 따라 cell resolution을 선택하고 목표 원을 덮는 cell들과 경계 이웃을 조회한 뒤, 최종 단계에서 Haversine 거리로 정확히 필터링합니다. cell 중심만 검사해 경계 장소를 누락하지 않습니다.
- 밀집 cell은 더 작은 하위 cell로 분할하거나 cell+bucket으로 shard합니다. 희소 지역은 상위 resolution을 사용해 fan-out을 줄입니다. 메타데이터가 각 지역의 active resolution과 routing version을 알려 주게 합니다.
- 대반경 검색은 수천 cell fan-out 대신 계층별 요약/인기 후보나 별도 regional index를 사용하고 결과 수 상한을 둡니다. 극지방·날짜 변경선·좌표 오류 테스트도 포함합니다.
- 실제 위치 분포를 replay해 후보 recall, 조회 cell 수, shard 편향, p99를 함께 측정합니다. 거리 정확도만 높고 한 shard가 과부하되는 설계는 운영상 실패입니다.
거리 후보에 영업 여부·평점·가격·개인화·광고를 조합하면서 p95 200ms를 지켜야 합니다. 후보 생성부터 최종 ranking까지 단계를 나누고 각 단계의 지연 예산, 캐시, 외부 feature 장애 시 fallback을 어떻게 정하겠습니까?
답변 예시 보기
싼 필터에서 비싼 계산 순으로
첫 단계는 공간 cell과 카테고리·영구 폐업 같은 indexable filter로 수십만 후보를 수천 개 이하로 줄입니다. 두 번째는 거리, 기본 품질, 인기처럼 사전 계산 가능한 feature로 수백 개를 고르고, 마지막에만 실시간 영업 상태·개인화·광고 정책을 적용합니다. 각 단계에 예를 들어 60/50/60ms의 budget과 전체 deadline을 전달해 느린 하위 호출이 누적되지 않게 합니다.
- 장소 정적 메타데이터와 인기 feature는 지역·cell 단위 캐시, 실시간 좌석·충전기 상태는 짧은 TTL 또는 별도 status store로 분리합니다. query 전체를 오래 캐시해 안전 속성을 stale하게 만들지 않습니다.
- feature service가 deadline을 넘으면 기본 품질 순위로 fallback하고 partial result를 허용합니다. 병원 “진료 중”을 확인할 수 없을 때는 과거 값을 사실처럼 노출하지 않고 ‘정보 확인 필요’로 표시하는 도메인별 fail policy를 둡니다.
- 광고 후보는 자연 검색 관련성의 최소 기준과 노출 정책을 통과시킨 뒤 별도 슬롯에 합성하고, 광고 장애가 전체 검색을 실패시키지 않도록 bulkhead와 timeout을 둡니다.
- p95뿐 아니라 후보 recall, zero-result rate, fallback rate, 외부 feature별 latency 기여를 관측합니다. 성능 개선이 검색 품질이나 안전 정보 정확도를 얼마나 훼손했는지 A/B와 offline replay로 검증합니다.
사업자가 주소와 영업시간을 수정했지만 검색 인덱스, 상세 조회 캐시, 추천 결과에 서로 다른 버전이 보입니다. 일반 장소는 5분, 병원 휴업은 1분 내 반영해야 할 때 변경 파이프라인과 신선도 보장을 어떻게 차등 설계하겠습니까?
답변 예시 보기
속성별 신선도 계약
RDB를 원본으로 두고 transaction과 변경 이벤트 사이를 outbox/CDC로 연결합니다. 이벤트에는 placeId, entityVersion, 변경 속성, source 신뢰도를 포함하고 indexer는 versioned upsert로 역순 이벤트가 새 값을 덮지 않게 합니다. 검색 색인 성공과 캐시 무효화는 독립 consumer로 처리하되 각자의 진행 version을 추적합니다.
- 병원 휴업·안전 공지 같은 critical update는 고우선 topic과 전용 worker, 짧은 TTL, push invalidation을 사용합니다. 일반 설명·사진은 배치 병합으로 비용을 줄일 수 있습니다.
- 상세 응답과 검색 문서에 sourceVersion/updatedAt을 두고, 중요 속성은 응답 직전 authoritative status store에서 재검증합니다. 인덱스 전체가 갱신될 때까지 위험한 상태를 임시 overlay로 차단하는 방법도 검토합니다.
- DLQ만 쌓아 두지 않고 version gap scanner와 RDB-index checksum 대사를 운영합니다. 누락 시 단건 재색인, 범위 replay, 전체 blue-green 재색인의 복구 단계를 둡니다.
- source 자체가 틀릴 수 있으므로 복수 제보와 운영자 검증, 만료 시각, 정보 신뢰도도 모델링합니다. ‘빠르게 반영’과 ‘사실임을 검증’ 사이의 정책을 장소 유형별로 정합니다.
지역 축제 당일 평소의 50배 검색이 한 구역에 집중되고 위치 feature store가 장애 났습니다. 전체 서비스로 장애가 확산되지 않도록 용량·격리·저하 모드를 설계하고, 개인정보를 과도하게 저장하지 않으면서 문제를 진단할 관측 방법을 제안하십시오.
답변 예시 보기
지역과 기능 단위로 격리
cell을 partition key로 사용하더라도 인기 지역은 hot shard가 되므로, read replica와 multi-bucket, 사전 계산된 지역 인기 목록을 준비합니다. gateway에서 사용자·IP·지역별 rate limit을 적용하고 검색 fan-out과 최대 반경을 일시 제한합니다. feature store는 별도 pool과 circuit breaker로 격리해 장애 시 거리+기본 인기 순위로 저하합니다.
- 인기 query/cell 결과는 짧은 TTL과 stale-while-revalidate로 제공하고 요청 병합으로 cache stampede를 막습니다. 단, 영업 상태처럼 위험한 필드는 별도 검증 또는 불확실 표시를 유지합니다.
- 지역별 concurrency와 shard CPU를 기준으로 admission control을 동적으로 조절하고, 다른 지역의 예약 용량을 침범하지 않게 tenant/region quota를 둡니다.
- 정확한 사용자 좌표를 장기 로그에 남기지 않고 coarse cell, query category, 익명화된 request id로 latency와 오류를 집계합니다. 디버깅용 상세 위치는 명시적 접근 통제와 짧은 보존 기간을 적용합니다.
- 축제 트래픽 replay와 feature store 완전 차단 실험으로 p99, fallback 품질, zero result를 검증하고 저하 모드 진입·해제 기준을 runbook으로 둡니다.
멀티테넌트 분산 작업 스케줄러 설계
상황
조건
시스템 구성·규모
- 2만 tenant, 일 1.5억 job, 피크 dispatch 10만/sec
- 실행 시간 1초~24시간, worker 20만 개
- DAG 최대 1만 node, cron·우선순위·deadline 지원
- PostgreSQL metadata, Kafka dispatch, Kubernetes worker
현재 문제·목표
- 리더 전환과 ACK 유실 때 중복 dispatch
- worker 종료를 15분 뒤 탐지, 좀비 결과가 정상 결과를 덮음
- 대규모 retry storm과 한 tenant의 worker 독점
- dispatch p99 5초, control plane 가용성 99.99%
꼬리 질문
수억 개의 실행 예정 job을 여러 scheduler가 조회하고 중복 없이 dispatch하도록 설계하십시오. DB conditional claim, 시간 bucket·partition, leader/lease, durable queue 중 어떤 조합을 선택하며 리더 전환과 clock skew를 어떻게 다루겠습니까?
답변 예시 보기
예약 결정과 실행 전달을 분리
모든 scheduler가 거대한 due-job table을 스캔하지 않도록 scheduledAt 기준 time bucket과 tenant hash로 partition합니다. scheduler instance는 partition lease를 얻고 DB의 server time을 기준으로 가까운 window의 job을 조건부 갱신합니다. SCHEDULED → ENQUEUED 전이와 outbox 기록을 한 트랜잭션으로 묶고, dispatcher가 outbox를 Kafka에 전달합니다.
- lease는 작업 소유권 최적화일 뿐 유일한 정확성 장치로 보지 않습니다. lease 만료와 갱신이 겹쳐도 DB의 state/version compare-and-set과 jobRunId unique key가 중복 run 생성을 막게 합니다.
- 리더 교체 중 outbox가 재발행될 수 있으므로 consumer는 jobRunId로 멱등 수신합니다. queue ACK 유실 뒤 재전달도 같은 run으로 취급합니다.
- cron은 timezone·DST 정책과 nextRunAt 계산 버전을 저장합니다. scheduler 로컬 시각이 아니라 DB/합의된 시각을 사용하고, 일시적 clock jump 때 과거 job을 폭발적으로 생성할지 skip할지 정책화합니다.
- partition별 due lag, scan rows/claimed rows, lease churn, duplicate dispatch를 관측하고 scheduler 강제 종료와 DB failover 경계에서 중복·누락 불변식을 시험합니다.
worker가 6시간 job을 수행하다 heartbeat를 잃었습니다. 종료된 worker와 느리거나 네트워크만 단절된 worker를 구분할 수 없는 상황에서 언제 재할당하고, 이전 worker의 늦은 결과가 새 결과를 덮지 않도록 어떻게 하겠습니까?
답변 예시 보기
불확실성을 lease와 fencing으로 제한
heartbeat가 없다는 사실만으로 프로세스 종료를 증명할 수 없으므로 run마다 lease expiry와 단조 증가 attempt/fencing token을 발급합니다. worker는 주기적으로 lease를 갱신하고 progress checkpoint를 기록합니다. expiry 뒤 scheduler는 새 token으로 재할당할 수 있지만, 결과 저장소와 부작용 API가 현재 token보다 작은 쓰기를 거부해야 좀비 worker를 막을 수 있습니다.
- timeout은 전역 5분이 아니라 job type의 실행 분포, checkpoint 가능성, 비용에 따라 설정합니다. 긴 GC·일시 네트워크 단절을 고려해 suspect와 expired 단계를 나누고 재할당 전에 유예할 수 있습니다.
- 플랫폼이 통제하는 object write는 attempt별 임시 경로에 쓰고, 성공한 current token만 atomic publish합니다. 이메일·외부 결제처럼 fencing을 지원하지 않는 부작용은 job 작성자에게 idempotency key를 제공하거나 자동 재시도 정책을 더 보수적으로 둡니다.
- checkpoint가 있으면 새 attempt가 마지막 확정 지점부터 재개하고, checkpoint 자체도 run/version에 종속시킵니다. 부분 결과 cleanup과 보존 기간을 정의합니다.
- heartbeat miss rate, 재할당 후 원 worker 생존율, 중복 부작용을 관측해 lease를 조정합니다. 네트워크 partition을 주입해 old token의 결과가 실제로 거부되는지 검증합니다.
외부 스토리지 장애로 300만 job이 동시에 실패했습니다. 오류가 복구되더라도 일제 재시도가 다시 장애를 일으킬 수 있습니다. 자동 재시도, DLQ, circuit breaker, tenant 공정성을 어떻게 조합하고 운영자가 안전하게 재개하도록 하겠습니까?
답변 예시 보기
재시도를 새로운 부하로 관리
실패 횟수만 보고 동일 간격으로 재시도하지 않고 오류를 transient, throttling, permanent, unknown으로 분류합니다. transient에는 exponential backoff와 full jitter를 적용하고 전체·dependency·tenant별 retry budget을 둡니다. dependency circuit이 열리면 새 실행과 retry를 지연시켜 queue에 내구성 있게 보관합니다.
- 재시도 시각을 같은 초에 몰리지 않게 분산하고 정상 신규 job을 굶기지 않도록 fresh/retry queue와 가중치를 나눕니다. tenant별 concurrency·CPU quota로 큰 고객 하나가 복구 용량을 독점하지 않게 합니다.
- 인증 오류·잘못된 입력은 반복해도 성공하지 않으므로 빠르게 terminal/DLQ로 보냅니다. DLQ replay도 원 queue에 전량 투입하지 않고 dry-run, 범위 선택, 속도 제한, 중지 기능을 제공합니다.
- 복구 시 dependency 성공률과 latency를 보며 admission rate를 단계적으로 올립니다. backlog age와 deadline을 고려해 이미 가치가 없는 job은 취소 또는 skip하고 고객에게 정책을 알립니다.
- retry amplification ratio, dependency별 오류, queue age, tenant별 starvation을 관측합니다. 대규모 장애 시뮬레이션으로 breaker가 control plane까지 보호하는지 확인합니다.
DAG의 상위 job 1개가 실패하거나 수동 재실행될 때 하위 수천 job의 상태를 어떻게 결정하겠습니까? 부분 재실행, backfill, 취소가 진행 중 실행과 충돌하지 않도록 상태 모델과 운영자 도구를 설계하십시오.
답변 예시 보기
DAG 정의와 실행 인스턴스를 분리
버전이 있는 DAG definition과 특정 logical date의 DAG run, 각 task run/attempt를 분리합니다. 하위 task는 모든 필수 dependency가 성공한 동일 run에서만 READY가 되며, upstream failure에는 BLOCKED·SKIPPED·continue-on-failure 같은 명시적 정책을 적용합니다. 상태 전이는 이벤트에서 파생하되 DB version으로 경쟁 갱신을 막습니다.
- 부분 재실행은 기존 성공 이력을 조용히 변경하지 않고 새 repair run 또는 새 attempt로 생성합니다. downstream 결과가 upstream version에 의존하므로 영향 범위를 계산하고 재사용 가능한 결과의 조건을 보여 줍니다.
- backfill은 시간 범위와 예상 job 수·비용을 preview하고 별도 quota/priority로 실행합니다. 운영 실시간 job을 밀어내지 않으며 pause·resume·cancel과 진행률을 제공합니다.
- 취소는 cooperative signal이므로 이미 외부 부작용이 끝났을 수 있습니다. CANCEL_REQUESTED와 CANCELLED를 구분하고, 보상 가능한 task와 수동 확인이 필요한 task를 표시합니다.
- 운영 UI에는 “왜 실행되지 않았는지” dependency chain, attempt별 로그·token, 재실행 영향도를 노출하고 모든 수동 변경을 감사 로그로 남깁니다.
오프라인을 지원하는 실시간 문서 공동편집 시스템 설계
상황
조건
시스템 구성·규모
- MAU 2천만, 동시 연결 300만
- 문서당 평시 20명, 최대 500명 동시 편집
- WebSocket gateway, document shard server, operation log/object storage
- 텍스트·표·댓글·presence, 7일 오프라인 편집
현재 문제·목표
- 재접속 replay 중 중복 operation과 커서 위치 오류
- hot document shard의 CPU·broadcast 병목
- snapshot 중 편집 중단과 로그 무한 증가
- 로컬 입력 반영 50ms, 원격 반영 p95 300ms 목표
꼬리 질문
OT와 CRDT 중 하나를 선택하라는 요구를 받았습니다. 중앙 서버 의존성, 오프라인 기간, 복합 문서 모델, 메타데이터·저장 비용, 팀의 구현·검증 역량을 기준으로 선택 과정을 설명하고, 선택하지 않은 접근이 더 적합해지는 조건도 말하십시오.
답변 예시 보기
알고리즘 이름보다 필요한 보장과 운영 모델
현재처럼 중앙 document server가 있고 대부분 온라인이며 서버가 authoritative order를 제공할 수 있다면 OT는 operation을 작게 유지하고 중앙에서 변환을 통제하기 쉽습니다. 반면 7일 오프라인과 다중 기기 peer-like merge가 핵심이면 CRDT의 commutative/idempotent merge가 매력적이지만, element identifier와 tombstone, 복합 타입별 CRDT 설계 비용이 큽니다.
- 텍스트 삽입·삭제뿐 아니라 표 행 이동, 댓글 anchor, 권한 변경의 의미를 operation별로 정의하고 동시 연산 표를 property test로 검증합니다. 한 알고리즘을 문서 전체에 강제하기보다 텍스트와 presence를 다른 모델로 처리할 수 있습니다.
- OT를 택하면 server revision과 transform history 보존, 오프라인 client rebase 실패 UX가 중요합니다. CRDT를 택하면 stable unique ID, tombstone GC의 causal stability, 악성 client가 metadata를 폭증시키는 문제를 다룹니다.
- 어느 쪽도 사용자 의도를 항상 보존하지 않으므로 충돌이 큰 구조 변경은 conflict copy, 변경 비교, undo 같은 UX로 보완합니다.
- 실제 동시 편집 trace와 랜덤 operation 순열로 convergence, 저장량, CPU, 오프라인 merge 시간을 측정한 뒤 결정합니다. 팀이 수정·감사할 수 없는 복잡도도 주요 선택 기준입니다.
클라이언트가 3일간 오프라인에서 2천 개 operation을 만든 뒤 재접속했고, 서버 문서는 이미 5만 revision 앞서 있습니다. 누락·중복 없이 동기화하고 충돌을 사용자에게 설명하는 프로토콜을 설계하십시오.
답변 예시 보기
식별·ACK·rebase가 있는 재접속
각 client replica에 stable clientId와 단조 증가 opSeq를 부여하고 operation에는 base server revision을 포함합니다. 서버는 clientId+opSeq로 멱등 적용하고 연속 ACK 범위를 저장합니다. 재접속 시 client는 마지막 ACK와 pending op 범위를 보내며, 서버는 그 revision이 보존 중이면 missing server operations를 반환한 뒤 client operation을 변환·병합합니다.
- revision이 compaction 경계보다 오래되면 최신 snapshot과 필요한 metadata를 내려받아 pending operation을 새 base에 rebase합니다. client가 전체 snapshot을 업로드해 서버를 덮어쓰게 하지 않습니다.
- 동일 operation 재전송은 이전 결과/ACK를 반환하고, op sequence gap은 적용하지 않고 누락 범위를 요청합니다. 재연결이 반복돼도 결과가 같아야 합니다.
- 삭제된 문단에 대한 편집, 표 schema 변경처럼 자동 의도 보존이 불가능한 경우에는 유실시키지 않고 복구 문서나 conflict panel로 보존하고 사용자에게 대상·사유를 보여 줍니다.
- sync bytes, rebase 실패율, pending op age, 중복 op 비율을 관측하고 네트워크 왕복 중 임의 연결 종료를 반복하는 property/integration test로 검증합니다.
operation log가 수 GB로 커져 신규 접속이 느립니다. 편집을 중단하지 않고 snapshot·compaction을 수행하면서 느린 오프라인 클라이언트의 동기화와 버전 이력을 어느 범위까지 지원할지 설명하십시오.
답변 예시 보기
일관된 revision 경계에서 압축
document shard가 revision R까지의 불변 snapshot을 생성하는 동안 R 이후 operation은 계속 새 log segment에 append합니다. snapshot은 checksum과 schema version을 검증한 뒤 manifest를 원자적으로 교체하고, 신규 client는 snapshot R+이후 delta만 받습니다. compactor가 실패해도 이전 manifest와 log가 유효해야 합니다.
- log 삭제는 snapshot 생성 성공만으로 결정하지 않습니다. 최소 지원 offline 기간, 연결된 client의 ACK revision, 감사·undo 보존 정책을 함께 봅니다. 오래된 client는 전체 snapshot 기반 재동기화로 전환합니다.
- CRDT tombstone은 모든 관련 replica가 deletion을 관측했다는 causal stability 없이 제거하면 삭제된 내용이 부활할 수 있습니다. 무기한 offline replica를 지원할 수 없다면 7일이라는 명시적 계약과 재초기화 정책이 필요합니다.
- 버전 이력은 모든 low-level operation을 영원히 replay하기보다 주기 snapshot과 구간 delta, 사용자 checkpoint를 계층 보관합니다. 법적 삭제 데이터는 백업 보존·키 폐기 정책까지 포함합니다.
- snapshot 생성 시간, delta 길이, 초기 접속 bytes/latency, 가장 오래된 ACK를 관측하고 snapshot 도중 shard failover 테스트를 수행합니다.
500명이 편집하는 hot document에서 broadcast가 폭증하고, 동시에 소유자가 한 사용자의 권한을 회수했습니다. 지연과 부하를 제어하면서 편집 operation, presence, 권한 변경의 일관성 수준을 어떻게 다르게 설계하겠습니까?
답변 예시 보기
데이터 중요도별 경로 분리
편집 operation은 documentId로 한 authoritative shard에 routing해 순서/merge를 결정하고 durable log에 기록한 뒤 ACK합니다. presence와 cursor는 손실 가능한 ephemeral channel로 분리해 coalescing·sampling하며, 느린 client에게 모든 cursor update를 쌓지 않습니다. hot document는 gateway fan-out tree나 regional relay로 broadcast CPU를 분산합니다.
- client별 outbound queue에 상한을 두고 느린 client는 presence를 drop한 뒤 operation gap이 생기면 snapshot/delta 재동기화합니다. 한 client의 backpressure가 shard 전체를 막지 않게 합니다.
- 권한 회수는 일반 편집보다 높은 우선순위의 versioned policy event로 전파하고, authoritative shard가 다음 operation 수락 전 최신 ACL version을 검사합니다. 연결 종료만으로 이미 큐에 있던 쓰기를 막을 수 없으므로 operation마다 권한을 검증합니다.
- 오프라인 복제본을 원격 삭제할 수 있다는 보장은 제한적입니다. 서버 재동기화·공유는 차단하고 로컬 암호화 key 회수, 관리형 기기 정책 등 제품·보안 경계를 명확히 합니다.
- 문서별 ops/sec, broadcast fan-out, slow-client queue, ACL propagation lag를 관측하고 500명 부하와 권한 회수 경쟁 테스트를 수행합니다.
전사 Feature Flag·점진 배포 플랫폼 설계
상황
조건
시스템 구성·규모
- flag 100만 개, 환경 4개, 일 평가 5조 회
- 서버·웹·모바일 SDK 12종, 25개 리전
- 조건식·비율 rollout·A/B 실험·kill switch
- 일 설정 변경 5만 건, 일반 전파 30초·긴급 10초 목표
현재 문제·목표
- 원격 평가가 서비스 latency·가용성에 결합
- 부분 전파와 SDK 버전 차이로 평가 불일치
- 잘못된 변경의 영향도 사전 확인 불가
- 소유자 없는 stale flag 35%, 의존성·감사 추적 부족
꼬리 질문
중앙 원격 평가와 SDK 로컬 평가를 비교하고 서버·브라우저·모바일별로 어떤 방식을 선택하겠습니까? 설정 배포, last-known-good, 규칙 기밀성, control plane 장애 시 동작까지 구체화하십시오.
답변 예시 보기
평가 경로를 서비스 요청에서 분리
서버 SDK는 versioned ruleset snapshot을 streaming 또는 polling으로 받아 메모리에서 평가하는 방식을 기본으로 하겠습니다. 요청마다 control plane을 호출하지 않아 latency와 장애 결합을 없애고, 서명·schema 검증을 통과한 snapshot만 last-known-good로 원자 교체합니다. stream이 끊겨도 일정 기간 기존 설정으로 평가합니다.
- 브라우저·모바일에는 secret rule이나 개인정보 segment를 보내지 않습니다. 공개 가능한 flag만 축소 snapshot으로 로컬 평가하고, 민감한 결정은 서버 평가 API에서 수행하되 호출 실패 정책을 기능별로 정합니다.
- snapshot에는 global version, flag version, 요구 SDK capability를 포함합니다. SDK가 지원하지 않는 operator가 있으면 임의 해석하지 않고 이전 version 또는 명시적 default를 사용하고 incompatibility를 보고합니다.
- fail-open/fail-closed는 전역 default 하나가 아니라 결제 차단, UI 장식, 보안 권한 등 flag 의미별로 정합니다. bootstrap 설정조차 없을 때의 code default도 배포물에 포함합니다.
- control plane availability와 별도로 data plane evaluation latency/error, config age, version adoption을 관측합니다. 네트워크 완전 차단과 손상 snapshot을 주입해 서비스가 last-known-good로 지속되는지 확인합니다.
10% rollout에서 같은 사용자가 계속 같은 군에 속하게 하면서 국가·앱 버전·기기 조건을 결합해야 합니다. 익명 사용자가 로그인하거나 rollout 비율·규칙 순서를 변경할 때 실험 오염과 사용자 이동을 어떻게 관리하겠습니까?
답변 예시 보기
결정적 할당과 변경 의미를 명시
stable subject key와 flag/experiment별 salt를 합쳐 hash한 뒤 고정 bucket에 매핑합니다. 동일 ruleset이면 플랫폼·리전과 무관하게 같은 결과가 나와야 하므로 hash·정규화·operator semantics를 명세하고 SDK contract test vector로 검증합니다. 조건 필터를 통과한 사용자에게만 bucket rollout을 적용합니다.
- 비율을 10%에서 20%로 늘릴 때 기존 10%가 유지되도록 연속 bucket range를 확장합니다. variant 비율 재분배로 기존 사용자가 이동하는지 preview하고, 실험 중에는 assignment record를 별도 저장하는 방식도 검토합니다.
- 익명 deviceId에서 login userId로 key가 바뀌면 군이 달라질 수 있습니다. 제품 목표에 따라 최초 assignment를 계정에 승계하거나 전환을 허용하되, 분석에서 identity transition을 표시합니다.
- 국가·버전 조건은 request attribute의 신뢰성과 결측 정책을 정의합니다. 클라이언트가 보낸 국가값으로 규제 기능을 결정하는 등의 잘못된 신뢰 경계를 피합니다.
- assignment churn, cross-SDK mismatch, exposure 없는 assignment, sample ratio mismatch를 관측해 평가 엔진 오류와 분석 오염을 탐지합니다.
운영자가 조건식을 반대로 입력하거나 전역 kill switch를 실수로 켤 수 있습니다. 변경 생성부터 전 세계 반영, 자동 중단과 롤백까지 어떤 안전장치를 두고 긴급 변경의 속도와 승인 절차를 어떻게 균형 잡겠습니까?
답변 예시 보기
설정 변경도 코드 배포처럼 취급
schema/type validation뿐 아니라 대상 사용자 수, 주요 segment, 이전 결과와 달라지는 비율을 실제 또는 샘플 트래픽으로 preview합니다. 결제·인증 같은 critical flag는 소유자와 승인자를 분리하고 2인 승인, 변경 사유·ticket, 시간 제한을 요구합니다. 긴급 경로도 승인 생략보다는 사전 지정 on-call과 break-glass 감사 절차를 둡니다.
- 한 번에 전 세계 publish하지 않고 내부→1%→한 리전→전체의 ring으로 배포하며 각 ring의 평가 오류·핵심 비즈니스 지표를 gate로 둡니다. kill switch 자체도 단계 적용과 최대 영향도 제한을 지원합니다.
- 모든 ruleset은 immutable version이고 롤백은 이전 version을 다시 배포하는 새 변경입니다. SDK의 현재 version과 ACK 비율을 추적해 ‘저장 성공’을 ‘전파 완료’로 착각하지 않습니다.
- error rate나 결제 성공률이 임계치를 넘으면 자동 rollback하되 지표 지연·동시 배포로 인한 오판 가능성을 고려합니다. 자동화가 불확실하면 rollout pause와 사람 확인을 선택합니다.
- 권한 최소화, 변경 감사 로그, tamper-evident 보관, 정기 복구 훈련을 포함합니다. 잘못된 snapshot을 이미 받은 SDK가 즉시 이전 version으로 돌아가는지 검증합니다.
100만 개 flag 중 35%가 소유자 없이 남아 있고, 서비스 코드·다른 flag·실험 분석이 서로 참조합니다. 런타임 성능을 해치지 않으면서 flag 수명주기, 의존성, 삭제 안전성, 비용과 책임을 어떻게 관리하겠습니까?
답변 예시 보기
생성 시점부터 종료 조건을 갖게 함
flag에 owner team, 목적, 생성일, 예상 만료일, failure default, criticality를 필수 메타데이터로 둡니다. release flag, experiment flag, ops kill switch, permission flag를 유형별로 분리해 retention과 승인 정책을 다르게 적용합니다. 영구 권한 정책을 임시 release flag처럼 관리하지 않습니다.
- SDK는 평가 시 flag key와 version, 호출 service를 비동기·샘플링 telemetry로 보내 사용 현황을 집계합니다. 원시 사용자 속성을 수집하지 않고 cardinality와 비용 상한을 둡니다.
- 정적 코드 검색, runtime evaluation, ruleset의 flag-to-flag reference를 합쳐 dependency graph를 만듭니다. 30일 미평가만으로 바로 삭제하지 않고 모바일 구버전, DR 환경, 드문 배치의 참조를 고려합니다.
- 삭제는 deprecated 표시→새 생성/수정 차단→SDK warning→고정값 전환→관찰 기간→ruleset 제거의 단계로 진행합니다. 복구 가능한 tombstone 기간도 둡니다.
- 팀별 stale flag 예산과 owner dashboard를 제공하고 만료 알림·ticket을 자동화합니다. flag 수보다 ruleset 크기, SDK 평가 CPU, 전파 payload도 비용 지표로 관리합니다.
대규모 이커머스 상품 검색·랭킹 시스템 설계
상황
조건
시스템 구성·규모
- 상품 8억, 40개 언어, 60만 search QPS
- 일 상품 변경 3억 건, 행사 중 재고 변경 150만/sec
- OpenSearch 2천 shard, feature store와 ML re-ranker
- facet·오타·동의어·개인화·광고·deep pagination 지원
현재 문제·목표
- CDC 누락·역순 반영과 색인 backlog
- 전량 재색인에 30시간, 전환 검증 기준 부재
- 가격·재고 신선도 p99 35분, 목표 1분
- ranking dependency 장애와 인기 query cache stampede
꼬리 질문
상품 DB의 가격·카테고리·판매 상태 변경을 검색 인덱스에 누락·역순 없이 반영하고, 스키마 변경을 위한 전체 재색인을 무중단으로 수행하십시오. CDC, versioned upsert, 검증, alias 전환, 롤백의 실패 시나리오를 포함하십시오.
답변 예시 보기
증분 변경과 전체 재구축을 함께 설계
상품 DB transaction과 outbox를 묶거나 DB log 기반 CDC를 사용해 dual write 유실을 피합니다. 이벤트에는 productId와 aggregateVersion을 포함하고 indexer는 기존보다 큰 version만 upsert해 역순·중복을 무해하게 합니다. 관련 도메인이 분리돼 있다면 product document assembler가 가격·재고·카탈로그 각각의 source version을 보존합니다.
- 새 schema의 index B를 생성해 snapshot 시점 데이터를 bulk load하는 동안 그 이후 CDC를 별도 offset부터 계속 반영합니다. backlog가 따라잡힌 뒤 document count뿐 아니라 샘플·계층별 checksum, query golden set, 가격·재고 version lag를 비교합니다.
- 일부 트래픽을 B에 shadow/canary query해 latency, zero-result, ranking 차이를 확인하고 read alias를 원자 전환합니다. 전환 후에도 A와 CDC offset을 일정 기간 유지해 빠르게 rollback할 수 있게 합니다.
- mapping 오류나 poison document는 전체 파이프라인을 막지 않도록 격리하되, DLQ 방치로 검색 누락이 굳어지지 않게 error budget과 자동 replay를 둡니다.
- 주기적으로 DB와 index를 bucket checksum/샘플 대사하고 version lag p99를 SLO로 관리합니다. CDC broker 장애, bulk load 중 재시작, alias 전환 직전 실패를 훈련합니다.
키워드 관련성, 판매량, 신선도, 개인화, 광고를 250ms 안에 조합해야 합니다. retrieval·ranking 단계를 어떻게 나누고 feature 조회가 느리거나 모델이 장애일 때 품질과 가용성 사이에서 어떤 fallback을 선택하겠습니까?
답변 예시 보기
다단계 ranking과 명시적 latency budget
역색인에서 lexical/semantic 조건과 필수 정책 필터로 수천 후보를 retrieval하고, 저비용 정적 feature로 수백 개를 light-rank한 뒤 상위 수십 개만 개인화 모델로 re-rank합니다. 전체 250ms 안에서 검색 100ms, feature 50ms, re-rank 50ms, 조립 30ms 등 deadline을 배분하고 downstream에 남은 시간을 전달합니다.
- 판매량·품질은 인덱스에 사전 계산하고, 사용자·실시간 feature는 bulk 조회·지역 캐시로 가져옵니다. per-item RPC를 피하고 feature timestamp와 default 의미를 모델 학습·서빙에서 맞춥니다.
- feature store timeout 시 개인화 없는 기본 rank, 모델 오류 시 검증된 이전 모델 또는 rule score로 fallback합니다. circuit breaker와 별도 thread/pool로 검색 자체를 보호합니다.
- 광고는 별도 auction 결과를 관련성·재고·정책 기준을 통과한 슬롯에 합성하고 명확히 표시합니다. 광고 장애가 자연 검색을 막지 않고, 수익 최적화가 최소 품질을 침범하지 않게 guardrail을 둡니다.
- offline NDCG만 아니라 click/conversion, zero result, 품절 노출, latency, fallback rate를 함께 봅니다. 모델 rollout은 shadow와 소량 canary로 확대하고 품질 저하 시 즉시 이전 버전으로 전환합니다.
인기 검색어 결과를 캐시해 부하를 줄이면서도 가격과 재고를 1분 내 반영해야 합니다. query 결과, 상품 문서, 가격·재고 중 무엇을 어디에 캐시하고, 행사 중 event invalidation 폭주와 cache stampede를 어떻게 막겠습니까?
답변 예시 보기
안정적인 후보와 변동 속성을 분리
query cache에는 완성된 상품 응답을 오래 저장하기보다 후보 productId와 비교적 안정적인 rank score/version을 짧게 보관합니다. 가격·재고·배송일은 별도 high-throughput status store 또는 짧은 TTL cache에서 batch hydrate하고, 주문 직전에는 주문 도메인이 최종 검증합니다. 검색은 발견 도구이지 재고 예약 원장이 아닙니다.
- 가격·재고 이벤트마다 모든 query key를 역추적해 삭제하면 fan-out이 폭발합니다. 상품 단위 status cache를 갱신하고 query 후보는 응답 조립 시 최신 status와 결합합니다. 품절 후보는 여분 후보로 대체할 수 있게 query cache에 더 넓은 목록을 둡니다.
- 인기 key 만료에는 jitter, stale-while-revalidate, single-flight/request coalescing을 적용해 동시에 원본 검색으로 몰리지 않게 합니다. 지역·사용자별 변형으로 cache cardinality가 폭증하지 않도록 key 차원을 제한합니다.
- 행사 때 재고 event를 무조건 개별 처리하지 않고 상품별 최신 version으로 coalesce하되, 중간 상태가 업무상 필요한지 확인합니다. consumer lag가 커지면 TTL 축소나 cache bypass보다 원본이 감당 가능한지 먼저 판단합니다.
- hit rate뿐 아니라 cached candidate age, status version lag, 품절 노출률, stampede 시 origin QPS를 관측하고 인기 key 동시 만료 실험을 수행합니다.
행사 중 색인 backlog가 20분으로 늘고 ranking feature service도 절반의 요청에서 timeout됩니다. 검색을 중단하지 않으면서 고객 피해를 제한할 저하 모드, 우선순위, 관측·복구 절차를 설계하십시오. 정상화 후 누락 데이터를 어떻게 검증할지도 설명하십시오.
답변 예시 보기
신선도와 개인화를 독립적으로 저하
ranking feature 장애는 circuit breaker를 열어 개인화를 빠르게 생략하고 검증된 기본 관련성+인기도 rank로 응답합니다. 색인 backlog는 모든 속성이 같은 위험이 아니므로 가격·재고·판매 중지 이벤트를 고우선 topic/worker로 분리하고 설명·이미지 변경은 지연시킵니다. 위험 상품은 overlay denylist/status store로 검색 응답에서 즉시 제외할 수 있습니다.
- version lag가 한도를 넘은 상품에는 가격 확정 문구를 표시하거나 상세/주문 단계에서 authoritative 가격을 강제 조회합니다. 차이가 있을 때 고객에게 불리하게 조용히 변경하지 않고 사업 보상 정책과 연결합니다.
- 검색 요청은 전체 deadline, feature pool 격리, bulkhead로 보호합니다. 인기 query stale 결과는 제공하되 status hydrate가 실패한 상품은 품절 가능성을 명시하거나 노출 우선순위를 낮춥니다.
- 복구는 consumer 수를 무작정 늘려 검색 cluster를 쓰기 부하로 다시 쓰러뜨리지 않고, index CPU·merge pressure를 보며 rate를 단계 확대합니다. 최신 version만 필요한 update는 key별 coalesce합니다.
- 정상화 후 CDC offset gap, DLQ, DB-index version 분포, 상품 bucket checksum을 대사하고 행사 시간대 query replay로 품절·가격 오류를 측정합니다. incident에는 매출뿐 아니라 잘못 노출된 고객 수와 복구 완료 근거를 남깁니다.
대규모 모놀리스의 MSA 전환·서비스 경계·데이터 소유권 설계
상황
조건
시스템 구성·규모
- 회원 3천만, 상품 1억 2천만, 주문 피크 2만 5천 TPS
- 모놀리스 480만 LOC, Oracle 11TB·1,850개 테이블·스토어드 프로시저 730개
- 외부 연동 34개, 야간 배치 260개, 사내 직접 조회 도구 70개
- 월 2회 전체 배포, 전체 회귀 테스트 11시간·평균 롤백 48분
현재 문제·목표
- 공통 모델·DB lock·배포 결합으로 작은 변경도 전체 장애 위험
- 공유 테이블 쓰기와 분산 트랜잭션 경계가 불명확
- 3년 안에 핵심 변경의 70%를 독립 배포 가능한 도메인으로 전환
- 주문 성공률 99.99%, 데이터 보정률 0.01% 이하를 유지하며 점진 이관
꼬리 질문
어디부터 어떤 크기로 서비스를 분리할지 결정해야 합니다. 코드 패키지나 테이블 수가 아니라 비즈니스 경계와 변경·장애 특성을 근거로 후보를 찾고, 첫 이관 대상을 선택하는 과정을 설명하십시오. 조직 구조와 지나친 세분화 위험도 함께 다루십시오.
답변 예시 보기
변경과 불변식이 함께 움직이는 범위를 먼저 찾기
우선 “마이크로서비스 개수”를 목표로 두지 않고 현재 결합을 가시화하겠습니다. 최근 1년의 커밋·배포·장애 이력에서 함께 변경되는 모듈, 런타임 call graph, 테이블별 읽기·쓰기 주체, lock과 배치 의존성을 수집하고 주문 생성·가격 결정·재고 예약·정산처럼 업무 언어가 다른 흐름을 이벤트 스토밍으로 정리합니다. 같은 트랜잭션에서 반드시 지켜야 하는 불변식과 같은 팀이 자주 함께 바꾸는 모델은 한 경계에 남기고, 용어의 의미와 변화 주기가 다르며 API로 계약하기 쉬운 영역을 bounded context 후보로 봅니다. 처음부터 주문 핵심을 잘게 나누기보다, 비즈니스 가치가 있으면서 의존 방향이 비교적 단순한 배송 조회나 알림 같은 후보로 플랫폼·배포·관측 역량을 검증한 뒤 가격·재고로 확대하겠습니다.
- 후보별로 변경 빈도, 장애 피해 반경, 확장 패턴, 데이터 결합도, 규제 요구, 팀 역량을 점수화합니다. CPU를 많이 쓰는 상품 검색처럼 독립 확장 가치가 큰 영역과 잦은 정책 변경으로 배포 병목을 만드는 프로모션은 우선순위가 높지만, 테이블 수가 적다는 이유만으로 분리하지 않습니다.
- 경계 밖에는 비즈니스 의미가 있는 coarse-grained API와 이벤트를 두고 내부 엔티티를 그대로 CRUD로 노출하지 않습니다. 너무 작은 서비스는 한 사용자 요청에 RPC와 배포 단위를 늘려 지연·장애·조정 비용만 키우므로, 독립적으로 변경·소유·복구할 이유가 생길 때 분리합니다.
- 각 서비스는 코드와 DB만이 아니라 온콜·SLO·비용·로드맵을 책임지는 제품팀에 귀속합니다. 현재 화면 단위 팀을 즉시 전면 개편하기 어렵다면 첫 도메인에 장기 담당자를 묶고 platform enablement 팀이 공통 배포·보안·관측 기반을 제공하되 비즈니스 로직을 가져가지는 않게 합니다.
- 첫 이관의 성공 기준을 서비스 생성 개수가 아니라 독립 배포 lead time, 변경 실패율, 장애 반경, 원본 DB 직접 접근 수, 요청당 원격 호출 수로 정합니다. 2~3개 이관 뒤 실제 지표가 나빠지면 경계를 다시 합치거나 modular monolith로 남기는 선택도 허용합니다.
- 아키텍처 fitness function으로 금지된 cross-schema write, 순환 API 의존, 내부 패키지 참조를 CI에서 감지하고, 분기마다 dependency map을 갱신합니다. 문서상 경계와 런타임 경계가 달라지는 것을 방치하지 않는 것이 핵심입니다.
신규 재고 서비스가 자신의 DB를 가져야 하지만 주문·상품·정산·CS가 현재 재고 테이블을 직접 읽고 쓰고 있습니다. 공유 DB를 끊으면서 조회 성능과 데이터 정합성을 지키기 위한 데이터 소유권, API·이벤트·CDC, 읽기 모델 전환 방안을 설계하십시오.
답변 예시 보기
한 데이터의 쓰기 권한은 한 경계에만 두기
목표 상태에서는 재고 서비스만 재고의 가용 수량·예약·해제 상태를 변경하고 다른 서비스는 명시적인 command API나 이벤트 계약을 사용하게 하겠습니다. 그러나 기존 소비자를 한 번에 바꾸지 않고 먼저 DB audit와 query log로 테이블·컬럼별 실제 읽기·쓰기 주체를 찾아 목록화합니다. 재고 로직을 모놀리스 내부 facade 뒤로 모으는 branch by abstraction을 선행해 신규 코드의 직접 SQL을 차단하고, 기존 프로시저와 배치에는 소유자·종료일을 붙입니다. 모놀리스가 계속 같은 행을 쓰는 상태에서 신규 서비스에 DB를 복제하는 방식은 두 진실을 만들기 때문에, 이관 단계마다 어느 시스템이 authoritative writer인지 하나로 명시해야 합니다.
- 초기에는 Oracle의 변경을 CDC로 신규 재고 저장소에 복제해 backfill과 읽기 검증을 수행하되 CDC를 업무 command로 오해하지 않습니다. snapshot 기준 LSN/SCN을 기록하고 이후 log를 이어 붙이며 상품별 수량·예약 합계·version을 대사합니다. 역순 이벤트는 aggregate version으로 거부하고 삭제와 schema 변경도 계약에 포함합니다.
- 쓰기 전환 시에는 모놀리스 facade가 재고 command API를 호출하도록 바꾸고, 네트워크 성공 여부가 불명확한 경우를 위해 reservationId를 멱등키로 사용합니다. DB unique constraint와 상태 전이 조건으로 같은 예약이 두 번 차감되지 않게 하며, 타임아웃을 곧바로 실패로 간주하지 않고 조회 API로 결과를 확인할 수 있게 합니다.
- 상품 화면·CS처럼 낮은 지연과 조인이 필요한 조회는 서비스별 필요 필드만 담은 local read model을 재고 이벤트로 갱신합니다. 정산처럼 정확한 시점 스냅샷이 중요한 소비자는 version·event time과 재처리 가능한 원장을 사용합니다. 모든 조회를 동기 API로 바꾸면 재고 장애와 latency가 전사로 전파되고, 반대로 CDC 테이블 복제를 영구 API로 쓰면 내부 schema 결합이 남는다는 trade-off를 설명합니다.
- legacy SQL 소비자는 호환 view나 read-only replica로 짧게 유예하되 쓰기 권한부터 회수하고 접근 로그를 owner dashboard에 공개합니다. 유예 계층에는 종료일과 신선도 한계를 표시하고 신규 의존 생성을 차단합니다. 데이터팀·CS와 함께 필드 의미를 계약으로 고정해야 테이블만 옮긴 뒤 의미가 달라지는 문제를 막을 수 있습니다.
- 운영 지표로 미전환 direct write 수, CDC lag, command 중복률, source-target 불일치 bucket, API timeout 뒤 unknown 상태 비율을 봅니다. 쓰기 전환 전 shadow command는 부작용 없이 판정 결과만 비교하고, 전환 후에는 소량 tenant→한 리전→전체 순으로 확대하며 불일치가 임계치를 넘으면 writer route를 되돌립니다.
기능 개발을 멈추지 않고 기존 모놀리스 트래픽을 신규 서비스로 옮겨야 합니다. Strangler 패턴을 적용한 라우팅, 데이터 backfill, shadow·dual read, 점진 전환과 롤백을 설계하고, dual write가 필요한 경우 실패 창을 어떻게 제한할지 설명하십시오.
답변 예시 보기
코드·데이터·트래픽 전환을 서로 다른 단계로 관리
API gateway에서 URL만 갑자기 신규 서비스로 돌리기 전에 모놀리스 안의 호출부를 anti-corruption facade로 감싸 legacy 모델과 신규 도메인 모델의 차이를 한곳에서 번역하겠습니다. 그 뒤 데이터는 기준 시점 snapshot을 backfill하고 CDC로 따라잡되, 아직 writer는 모놀리스 하나로 유지합니다. 신규 서비스는 production 요청을 shadow로 받아 판정 결과와 응답 schema, latency를 비교하지만 결제·예약 같은 부작용은 실행하지 않습니다. 이렇게 코드 추출, 데이터 동기화, 읽기 전환, 쓰기 전환을 별도의 feature flag와 runbook으로 관리해야 한 단계의 실패가 전체 롤백을 요구하지 않습니다.
- 읽기는 내부 사용자나 1% tenant부터 신규 결과를 반환하고 나머지는 legacy를 사용합니다. dual read는 사용자 응답 경로에서 두 시스템 중 느린 쪽을 기다리지 않고, 기존 결과를 반환하면서 비동기 비교합니다. 금액·상태 같은 필드는 단순 JSON equality가 아니라 시간 허용 범위와 업무상 동등성 규칙으로 비교합니다.
- 쓰기 전환의 최선은 하나의 writer를 옮기고 결과 이벤트로 legacy read model을 갱신하는 것입니다. 불가피한 dual write를 요청 스레드에서 순차 호출하면 두 번째 실패 시 반쪽 성공이 남으므로, 기존 DB transaction에 outbox를 기록해 relay가 신규 서비스에 멱등 command를 전달합니다. 이 기간에는 신규 쪽 기능을 legacy가 이해할 수 있는 범위로 제한하고 dual-write 종료 날짜를 둡니다.
- 라우팅 키는 사용자보다 주문·상품 같은 aggregate key를 사용해 한 객체의 요청이 양쪽 writer로 갈라지지 않게 합니다. canary 비율을 늘릴 때 기존 aggregate의 귀속을 고정하고, 귀속 version을 감사할 수 있어야 합니다. 모바일 구버전·배치·관리자 API처럼 gateway를 우회하는 경로도 사전 목록화합니다.
- 롤백은 바이너리 배포를 되돌리는 것만으로 끝나지 않습니다. 쓰기 전환 뒤 신규 데이터가 생겼다면 legacy가 읽을 수 있는 역동기화 또는 forward-fix 계획이 필요합니다. schema는 expand-and-contract로 먼저 호환 필드를 추가하고 양쪽이 새·구 버전을 읽는 기간을 거친 뒤 제거합니다. 비가역 변환은 별도 승인과 복구 snapshot을 요구합니다.
- 각 단계의 gate는 응답 차이율, p95/p99, 오류율, CDC lag, 미처리 outbox, 데이터 보정률, CS 문의량입니다. 장애 훈련에서는 relay 중단, 중복 전달, 신규 DB failover, gateway route rollback을 주입하고 RTO 안에 어떤 시스템이 writer인지 운영자가 혼동 없이 판별하는지 확인합니다.
주문 생성은 쿠폰 사용, 재고 예약, 결제 승인까지 이어지고 기존에는 하나의 DB 트랜잭션으로 처리했습니다. 서비스 분리 후에도 고객에게 모순된 상태를 보이지 않도록 일관성 모델과 Saga, outbox, 보상·재처리·운영 절차를 설계하십시오.
답변 예시 보기
전역 ACID 대신 업무 상태와 복구 가능한 불변식을 설계
먼저 모든 단계를 강한 일관성으로 묶어야 한다는 전제를 버리되, 어떤 불변식도 포기하지 않습니다. 주문 ID를 먼저 만들고 PENDING 상태에서 쿠폰 hold, 재고 reservation, 결제 authorization을 각각 만료 시간이 있는 자원 예약으로 수행한 뒤 모두 성공하면 CONFIRMED로 전환합니다. 고객에게는 “처리 중”을 명시하고 완료되지 않은 주문을 성공으로 표시하지 않습니다. 2PC는 이질적 DB·외부 PG가 참여하기 어렵고 장애 시 lock과 가용성 문제가 크므로 기본 선택으로 삼지 않으며, 실패를 상태 머신과 보상으로 다루는 Saga를 선택하겠습니다.
- 단계와 분기, 업무 보상 순서가 복잡하고 운영자가 현재 위치를 알아야 하므로 주문 서비스의 orchestration을 우선 검토합니다. 단순 이벤트 choreography는 결합이 없어 보이지만 흐름이 여러 consumer에 흩어져 순환·중복·장애 원인 파악이 어려워질 수 있습니다. orchestrator 자체는 상태를 durable DB에 저장하고 active-active 경쟁은 version 조건으로 막습니다.
- 각 서비스는 자신의 상태 변경과 outbox event 저장을 한 로컬 transaction으로 묶습니다. relay의 at-least-once 전달을 전제로 eventId/commandId와 aggregate version을 inbox 또는 업무 unique key에 저장합니다. “exactly once”라는 이름보다 결제 승인·쿠폰 사용이 중복 호출되어도 결과가 하나임을 DB 제약과 provider idempotency key로 증명하는 것이 중요합니다.
- 보상은 DB rollback이 아니라 별도 업무 행위입니다. 결제 승인 취소가 실패할 수 있고 쿠폰 해제 전에 만료될 수 있으므로 보상도 retry·멱등·감사 대상이며, 자동 처리 기한을 넘으면 manual review queue로 보냅니다. 결제는 성공했지만 응답을 잃은 unknown 상태에서는 재승인하지 않고 PG 조회·webhook·대사로 결과를 확정합니다.
- 사용자 조회는 단계별 내부 상태를 그대로 노출하지 않고 주문 관점의 처리 중·완료·실패와 예상 시간을 제공합니다. 오래 걸린 PENDING 주문, reservation 만료 임박, 보상 실패, event lag를 알리고 운영 화면에서 원본 command/event, 재시도 횟수, 외부 거래 ID를 추적하되 임의 상태 수정 대신 승인된 보정 command를 실행합니다.
- 검증은 정상 happy path보다 단계별 timeout·프로세스 종료·중복 event·역순 event를 fault injection하여 수행합니다. 불변식은 “확정 주문마다 유효한 결제와 재고가 하나”, “취소 완료 후 hold가 남지 않음”처럼 대사 query로 만들고 일별 자동 reconciliation을 둡니다. Saga 완료 시간 분포, unknown 상태 수, 자동 보상 성공률, 수동 보정률을 SLO로 관리합니다.
MSA 동기 호출 연쇄 장애·서비스 디스커버리·관측성과 복원력 설계
상황
조건
시스템 구성·규모
- 서비스 86개, pod 2,400개, 평시 32만·피크 85만 RPS
- 동기 호출 깊이 최대 14, 요청당 fan-out 최대 36
- Kubernetes DNS·service mesh·API Gateway·공용 Redis 사용
- 팀별 Java·Kotlin·Go, tracing 0.1% head sampling·로그 보존 7일
현재 문제·목표
- stale endpoint·중첩 retry·무제한 queue로 장애가 연쇄 증폭
- 호출별 timeout 합이 사용자 deadline보다 크고 취소가 전파되지 않음
- 서비스·버전·AZ별 golden signal과 의존성 가시성 부족
- 핵심 경로 p99 800ms, 오류율 0.1% 이하·MTTR 20분 목표
꼬리 질문
가격 서비스의 작은 지연이 왜 전체 장애로 커졌는지 진단하고, 14단계 동기 호출 구조의 의존성과 latency budget을 어떻게 재설계하겠습니까? 동기·비동기·로컬 복제 중 무엇을 어디에 적용할지 판단 기준을 설명하십시오.
답변 예시 보기
호출 그래프와 대기 자원의 증폭부터 끊기
사후에는 “가격 서비스가 느렸다”에서 멈추지 않고 gateway에서 가격 DB까지 critical path를 재구성하겠습니다. 각 hop의 요청률·동시성·timeout·retry 주체·thread/connection pool 대기·queue 길이를 시간축으로 맞춰 최초 지연과 증폭 시점을 찾습니다. Little’s Law 관점에서 latency가 100ms에서 4초로 늘면 같은 RPS에서도 in-flight가 40배가 되고, bounded되지 않은 queue와 공용 pool은 관련 없는 API까지 굶깁니다. service mesh와 SDK가 각각 세 번 재시도했다면 실제 amplification factor를 access log·mesh metric·trace로 계산하고, pod 감소보다 downstream QPS 증가가 먼저였는지 확인합니다.
- 사용자 p99 목표 800ms에서 gateway 여유와 응답 조립 시간을 제외하고 critical path에 deadline budget을 배분합니다. 모든 downstream은 절대 deadline 또는 남은 시간을 전달받아 이미 의미 없는 작업을 시작하지 않고, 호출 취소가 DB query와 coroutine까지 전파되게 합니다. 각 hop timeout을 800ms로 설정하는 방식은 14단계에서 목표를 지킬 수 없습니다.
- 호출 그래프에서 사용자 응답에 즉시 필요한 command와 사전 계산 가능한 query를 분리합니다. 결제 승인처럼 즉시 결과가 필요한 작업은 동기로 남기되 깊이를 줄이고, 알림·분석·추천 갱신은 outbox event로 비동기화합니다. 회원 등급·상품 속성처럼 변경 빈도가 낮고 약간의 지연이 허용되는 값은 이벤트로 local read model을 만들어 원격 fan-out을 줄입니다.
- 가격·권한처럼 신선도와 규칙 version이 중요한 데이터는 무조건 캐시하지 않고, 허용 가능한 staleness와 실패 의미를 업무가 결정합니다. 서버가 미리 발급한 versioned price quote나 entitlement token으로 기준을 고정할 수 있는지도 검토합니다. 이는 임의 stale fallback보다 감사와 만료 정책이 명확합니다.
- fan-out은 병렬화만으로 해결하지 않습니다. 최대 동시 호출, 응답에 필요한 quorum, partial result 의미를 정하고, 한 하위 서비스가 느릴 때 전체를 기다리지 않게 합니다. API composition이 반복된다면 BFF나 aggregation service를 두되 새로운 중앙 모놀리스와 single point가 되지 않도록 경계와 소유자를 둡니다.
- 변경 전후 요청당 hop 수, amplification factor, critical path p99, queue wait, pool saturation, deadline exceeded 비율을 비교합니다. 대표 호출 그래프를 CI 성능 시험과 load shedding 실험에 넣고, 한 dependency를 4초 지연시켜도 비관련 API의 SLO가 유지되는지를 acceptance criteria로 삼습니다.
autoscaling·rolling update·노드 종료 중 stale endpoint로 요청이 전달되지 않도록 서비스 디스커버리와 load balancing을 설계하십시오. DNS·registry·service mesh의 역할, readiness·draining·TTL, AZ 장애와 control plane 장애 시 동작을 포함하십시오.
답변 예시 보기
등록보다 안전한 제외와 마지막 정상 상태가 중요
Kubernetes 환경에서는 endpoint registry를 별도로 중복 구축하기 전에 Service/EndpointSlice를 권위 있는 membership source로 두고, mesh data plane이 이를 받아 client-side load balancing하도록 하겠습니다. DNS는 안정적인 service identity를 찾는 데 사용하되 pod IP 목록을 애플리케이션이 긴 TTL로 캐시하게 두지 않습니다. discovery가 알려 주는 것은 “존재하는 endpoint”일 뿐 실제 요청 처리 가능성을 완전히 보장하지 않으므로 startup·readiness·liveness 의미를 분리하고 readiness가 DB pool·필수 초기화·warm-up 완료를 반영하게 합니다. 과도한 dependency 연동 readiness는 하위 장애 때 모든 pod가 동시에 빠지는 위험이 있으므로 자신의 처리 가능성만 측정합니다.
- 배포와 종료 시 먼저 readiness를 false로 바꾸고 EndpointSlice와 proxy에서 제외된 것을 확인한 뒤 preStop/drain 기간에 신규 요청을 막고 기존 HTTP/2·gRPC stream을 마칩니다. termination grace는 p99 요청 시간과 장기 stream에 근거해 정하며, SIGTERM을 무시하는 애플리케이션을 contract test로 탐지합니다. 즉시 pod를 죽여 connection reset을 retry로 메우는 운영을 피합니다.
- load balancer는 연결 수나 latency를 반영하고 outlier detection으로 연속 실패 endpoint를 일시 제외하지만, 작은 replica 수에서 모두 eject되지 않도록 최대 비율과 복귀 probe를 둡니다. passive health만 믿지 않고 synthetic probe를 사용하되 실제 업무 트랜잭션을 과도하게 만들지 않습니다. locality-aware routing은 같은 AZ를 선호하여 비용과 지연을 줄이되 AZ capacity가 줄면 다른 AZ로 안전하게 spillover합니다.
- DNS TTL, JVM DNS cache, mesh endpoint 갱신 시간, keep-alive connection 수명을 각각 측정합니다. endpoint가 registry에서 삭제돼도 기존 pooled connection이 남을 수 있으므로 drain signal과 connection max age가 필요합니다. 70초 stale이 DNS인지 control plane watch 지연인지 proxy ACK 누락인지 version별 지표로 구분합니다.
- discovery control plane 장애 시 data plane은 last-known-good endpoint set으로 기존 트래픽을 계속 처리하고 설정 fetch를 요청 경로에 두지 않습니다. 다만 오래된 목록의 endpoint가 모두 사라지는 경우를 대비해 failover DNS나 제한된 direct resolution 경로를 검토하며, control plane 복구 후 한꺼번에 reconnect하지 않도록 jitter를 둡니다.
- 검증은 rolling update, node drain, AZ 네트워크 단절, registry watch 중단을 staging과 소량 production에서 주입합니다. endpoint add/remove-to-proxy ACK 시간, stale endpoint error, connection reset, AZ별 분산, no-ready-endpoint 시간을 SLO로 관리하고 배포 controller가 임계치 초과 시 rollout을 멈추게 합니다.
timeout, retry, circuit breaker, bulkhead, rate limit, load shedding과 fallback을 서비스별로 어떻게 조합하겠습니까? 결제처럼 재시도 위험이 큰 호출과 읽기 호출을 구분하고, service mesh 공통 정책과 애플리케이션 업무 정책의 경계를 설명하십시오.
답변 예시 보기
복원력 패턴을 계층별로 중복 적용하지 않기
공통 설정을 일괄 배포하기 전에 dependency catalog에 호출의 멱등성, 최대 허용 지연, 사용자 중요도, fallback 가능성, 용량을 기록하겠습니다. connect timeout은 빠르게, request timeout은 상위 deadline보다 짧게 두고 모든 hop이 남은 deadline을 존중합니다. retry는 transient 오류이고 남은 시간과 retry budget이 있을 때만 수행하며 exponential backoff와 jitter를 적용합니다. 각 계층이 독립적으로 retry하면 곱셈 증폭이 생기므로 gateway·mesh·SDK 중 원칙적으로 한 계층을 소유자로 정하고 나머지는 비활성화합니다.
- GET 조회라도 비용이 크거나 overload 응답이면 무조건 retry하지 않습니다. 동일 호출을 안전하게 반복할 수 있는 경우에만 한두 번 시도하고, 전체 요청의 일정 비율만 retry하도록 token budget을 둡니다. 결제 승인·재고 차감은 idempotency key와 provider 조회가 없는 한 자동 retry하지 않으며, timeout 뒤 unknown 상태를 별도 상태로 저장해 대사로 확정합니다.
- circuit breaker는 최근 오류율·slow-call 비율로 열고 half-open에서 제한된 probe만 통과시킵니다. 모든 instance가 동시에 열고 닫혀 파동을 만들지 않게 jitter와 최소 표본을 둡니다. breaker는 실패를 해결하지 않고 빠르게 격리하는 장치이므로 open 상태의 업무 응답을 명확히 설계해야 합니다.
- bulkhead는 중요 API·dependency·tenant별 concurrency와 thread/connection pool을 분리하여 추천 지연이 결제 pool을 먹지 못하게 합니다. queue는 반드시 bounded하고 포화 시 오래 기다리게 하지 말고 우선순위 기반으로 429/503 또는 저하 응답을 빠르게 반환합니다. 입구 rate limit과 adaptive concurrency/load shedding으로 이미 포화된 서비스에 추가 작업이 들어오는 것을 막습니다.
- fallback은 애플리케이션이 데이터 의미를 알아야 합니다. 혜택 문구는 생략하고 추천은 인기 목록으로 대체할 수 있지만, 권한 검증을 무조건 fail-open하거나 결제 성공을 추정하면 안 됩니다. 플랫폼/mesh는 timeout·retry budget·outlier detection과 telemetry라는 mechanism을 제공하고, 서비스팀은 fallback·멱등성·오류 mapping이라는 policy를 소유합니다.
- 정책은 config version으로 관리하고 canary 적용합니다. fault injection으로 latency, connection reset, 429, partial response를 주입해 downstream QPS가 몇 배가 되는지, breaker open까지 걸리는 시간, 핵심/비핵심 API 오류율, recovery oscillation을 측정합니다. retry 성공률만 높아도 원본 부하와 tail latency가 악화되면 실패한 정책으로 판단합니다.
다음 장애에서는 최초 원인을 20분 안에 찾고 배포 버전·AZ·의존 서비스까지 피해 범위를 좁혀야 합니다. metric·log·trace·상관관계 ID, 동적 sampling, SLO·경보, 장애 대응과 복원력 검증 체계를 설계하십시오.
답변 예시 보기
요청 단위 증거와 서비스 책임을 같은 운영 모델로 연결
모든 서비스에 request/trace ID와 W3C trace context를 전파하고, gateway가 받은 client deadline·tenant class·release version을 context와 구조화 로그에 남깁니다. metric은 RED(request rate, error, duration)와 USE(utilization, saturation, errors)를 서비스·operation·status class·AZ·version 차원으로 표준화합니다. userId·raw URL·orderId 같은 무제한 값을 metric label로 쓰지 않고, 구체적인 개별 요청은 trace와 제한된 로그에서 찾습니다. 로그 오류 코드와 span status의 taxonomy도 맞춰 같은 timeout이 팀마다 500·200 fallback·UNKNOWN으로 흩어지는 문제를 줄입니다.
- 0.1% 고정 head sampling은 희귀 오류와 긴 tail을 놓칩니다. 정상 요청은 낮게 샘플하되 error, deadline 초과, 특정 canary version, 고지연 trace는 collector의 tail-based sampling으로 보존합니다. tail 결정 전 buffer 비용과 개인정보를 관리하고, 심각 장애 때 전체 sampling을 올려 telemetry backend를 다시 쓰러뜨리지 않도록 상한과 우선순위를 둡니다.
- service map은 trace만으로 추정하지 않고 mesh traffic과 배포 metadata를 결합해 호출량·오류·p99와 owner를 표시합니다. 한 trace에서 client span과 server span이 이어지는지 contract test를 두고 Kafka 등 비동기 구간도 message correlation ID와 producer/consumer span으로 연결하되 payload 개인정보를 기록하지 않습니다.
- 경보는 CPU 단일 임계치보다 주문 SLI의 multi-window burn rate를 사용해 빠른 대형 장애와 느린 error budget 소진을 구분합니다. symptom alert가 on-call을 깨우고 원인 후보인 saturation·retry amplification·endpoint churn은 진단 대시보드에 연결합니다. 모든 서비스에는 SLO, owner, runbook, dependency tier와 dashboard 링크를 등록합니다.
- incident command 체계에서 한 명은 조정, 서비스 owner는 완화, 기록 담당은 timeline을 맡습니다. 먼저 retry 차단·비핵심 load shedding·rollout 중단으로 피해를 줄이고 원인 분석과 분리합니다. 상태 페이지와 내부 공지에는 추정과 확인 사실을 구분하며, 복구 선언은 오류율뿐 아니라 queue/backlog, data reconciliation, breaker 상태가 정상화된 뒤 합니다.
- 매 배포는 version·config를 telemetry에 태깅하고 canary SLO comparison으로 자동 중단합니다. 분기별 game day에서 stale endpoint, 한 AZ 지연, trace collector 장애, dependency 5초 지연을 주입해 탐지 시간·완화 시간·데이터 손실을 측정합니다. 회고 action은 “주의”가 아니라 retry owner 단일화, dashboard gap 보완처럼 검증 가능한 항목과 기한·소유자를 가져야 합니다.
24×365 보험 계약 플랫폼의 점진적 교체와 API 계약 이관
상황
조건
시스템 구성·규모
- 활성 계약 3,200만 건, 평시 7천 RPS·갱신일 최대 4만 RPS
- Java EE 모놀리스 14개 노드, Oracle RAC, 야간 배치 620개
- SOAP 160개·HTTP 90개 endpoint, 문서화되지 않은 오류 코드 70여 개
- 신규 Kubernetes 환경은 멀티 리전이나 레거시는 단일 주 리전에서 운영
현재 문제·목표
- 호출자와 실제 계약 의미를 알 수 없어 변경 영향도 산정 실패
- 주소 변경 canary에서 응답은 성공했으나 후속 배치 부작용 발생
- 구·신 경로 동시 쓰기와 rollback 후 데이터 소유권이 불명확
- 12개월 안에 계약 조회·변경의 50%를 신규 경로로 안전하게 이관
꼬리 질문
어떤 기능부터 Strangler Fig 방식으로 분리할지 결정하고, 레거시의 숨은 동작과 실제 호출자를 파악해 경계를 만드는 첫 90일 계획을 설명하십시오. 단순 CRUD 난이도가 아니라 실패 반경·데이터 소유권·조직 준비도를 어떻게 평가하겠습니까?
답변 예시 보기
기능 목록보다 관측 가능한 업무 절편에서 시작
첫 단계는 코드를 곧바로 떼는 것이 아니라 런타임 사실을 수집하는 것입니다. Gateway 또는 레거시 앞단 프록시에 endpoint·호출자 인증서·앱 버전·요청/응답 크기·오류·지연·후속 DB 변경을 기록하고, 배치와 DB job까지 포함한 dependency map을 만듭니다. 개인정보 본문은 저장하지 않고 schema fingerprint와 필드 존재 여부를 남깁니다. 동시에 실제 장애·수동 보정·민원 자료로 계약 상태와 금액, 효력일 같은 불변식을 찾아 characterization test와 대표 계약 golden dataset으로 고정합니다. 문서에 적힌 업무 흐름보다 운영자가 엑셀로 보정하는 예외가 경계 선정에 더 중요할 수 있습니다.
- 후보별로 변경 빈도, 독립 배포 가치, 동기 의존 수, 공유 테이블 수, 배치 결합도, 규제 위험, rollback 용이성, 담당 팀의 운영 역량을 점수화합니다. 읽기 전용이라는 이유만으로 쉬운 것은 아닙니다. 조회 결과가 견적이나 지급 판정에 쓰이면 stale 허용 범위가 작고, 반대로 쓰기라도 독립 데이터와 명확한 보상 절차가 있으면 더 좋은 첫 절편일 수 있습니다.
- 경계에는 anti-corruption layer를 두어 레거시의 상태 코드·기본값·날짜 의미를 canonical domain model로 번역합니다. 이 계층의 목적은 구 모델을 새 서비스 전체에 누출하지 않는 것이며, 임시 어댑터별 owner와 제거 조건을 backlog에 둡니다. 신규 서비스가 초기에는 레거시를 호출해도 관측·계약·소유권 경계를 먼저 세운다는 의미가 있습니다.
- 90일은 0~30일 호출·데이터·배치 가시화, 31~60일 후보 하나의 계약 테스트와 shadow read, 61~90일 내부 사용자·저위험 tenant canary 및 복구 훈련으로 나눕니다. 완료 기준은 코드량이 아니라 호출자 95% 식별, golden case 동등률, 미분류 side effect 0건, 15분 내 routing rollback과 데이터 복구 runbook 검증입니다.
- 아키텍처 팀만 경계를 정하지 않고 보험 업무 담당자, 야간 운영자, 제휴 연동 담당자를 함께 owner로 둡니다. 모르는 호출자와 예외는 migration risk register에 명시하고 해당 영역은 트래픽을 올리지 않습니다. 이렇게 하면 분리 속도보다 학습 속도와 실패 반경을 제어할 수 있습니다.
제휴사와 구버전 앱을 수정하지 않은 채 요청을 신규 서비스로 보내야 합니다. 레거시 API의 문서화되지 않은 의미까지 보존하면서 새 계약을 발전시키기 위한 Gateway·어댑터·버전 전략과 계약 검증 방법을 설계하십시오.
답변 예시 보기
wire compatibility와 도메인 계약을 분리해 관리
외부 호출자가 보는 legacy facade는 URL, SOAP action, 필드 누락과 null의 차이, 오류 코드와 재시도 가능성까지 기존 wire contract를 보존합니다. facade 뒤의 adapter가 이를 명시적인 canonical command/query로 변환하고 신규 서비스는 그 계약만 받습니다. 예를 들어 레거시의 빈 주소가 ‘변경하지 않음’인지 ‘삭제’인지 호출자별로 다르다면 단순 JSON mapping으로 숨기지 않고 adapter policy와 테스트 사례로 명문화합니다. 신규 API는 명시적 version과 idempotency key, correlation ID, 상태 조회 계약을 제공하되 구 호출자에게 새 의미를 억지로 노출하지 않습니다.
- 30일 정도의 실제 트래픽에서 민감값을 제거한 요청/응답 형태와 상태 전이를 수집해 consumer별 contract corpus를 만듭니다. 정상 happy path뿐 아니라 잘못된 날짜, 중복 요청, timeout 후 재호출, 필드 순서, unknown enum, 응답의 공백 처리까지 replay합니다. Consumer-driven contract test와 provider schema compatibility check를 CI gate로 두고, 발견되지 않은 호출자는 별도 legacy route에 남깁니다.
- shadow는 조회 요청부터 시작해 구·신 응답을 의미 단위로 비교합니다. 타임스탬프·정렬처럼 허용할 차이와 금액·계약 상태처럼 허용하지 않을 차이를 field policy로 구분하며, 단순 byte equality가 아닌 business equivalence를 측정합니다. 쓰기는 부작용 없는 validation/dry-run endpoint를 통해 판정 결과만 비교하고 같은 명령을 양쪽에 실행하지 않습니다.
- 새 필드는 additive하게 추가하고 호출자가 모르는 enum을 받을 때의 fallback을 정의합니다. 제거·의미 변경은 usage telemetry로 실제 소비자가 사라진 것을 확인한 뒤 deprecation 기간과 계약 버전 종료 일정을 운영합니다. Gateway 변환 규칙도 소스 관리, 리뷰, 버전·감사 로그, 자동 테스트를 적용해 운영 화면에서 임의로 바뀌는 또 다른 레거시가 되지 않게 합니다.
- 검증 지표는 HTTP 성공률만이 아니라 consumer별 의미 차이율, adapter fallback 비율, unknown field/enum, 재시도율, 후속 배치 결과를 포함합니다. 구·신 계약이 다른 경우 어느 쪽이 ‘정답’이라고 즉시 가정하지 않고 업무 owner가 기대 의미를 판정하고 canonical 계약에 반영합니다.
주소 변경처럼 쓰기와 후속 배치 부작용이 있는 기능을 1%에서 100%까지 이관하십시오. 동일 사용자가 안정적으로 같은 경로를 쓰게 하는 기준, shadow/canary 단계, 자동 중단 지표와 트래픽 확대 승인 조건을 구체적으로 제시하십시오.
답변 예시 보기
요청 비율이 아니라 업무 단위 소유권을 점진 이관
쓰기 요청을 매번 무작위 1%로 나누면 한 계약이 구·신 경로를 오가며 상태와 재시도 의미가 깨집니다. contractId 또는 tenantId를 안정적인 hash bucket에 배정하고, 이관된 계약의 해당 capability는 신규 서비스만 authoritative writer가 되도록 routing registry에 epoch를 기록합니다. 장기 workflow가 진행 중인 계약은 완료 후 전환하거나 command 시작 시의 owner epoch를 끝까지 유지합니다. 단순 요청 라우팅과 데이터 소유권 이관을 같은 것으로 취급하지 않아야 rollback 후 누가 최종 writer인지 판단할 수 있습니다.
- 단계는 production replay와 read shadow→직원/테스트 제휴사→한 상품·한 지역 0.1%→1%→5%→20%→50%→100%로 설계합니다. 각 ring은 최소 한 번의 야간 배치와 월말 경계를 지나야 하며, 다음 단계 승인은 기술 owner와 업무 owner가 golden state transition, p99, 수동 보정, 민원 선행지표를 함께 확인합니다. 일정 때문에 관찰 창을 줄이는 결정도 명시적으로 위험 승인을 받습니다.
- 가드레일은 오류율·latency 외에 구·신 판정 불일치율, 계약 상태 stuck 시간, 중복 감사 이력, 잘못된 우편 생성, 보정 queue 증가를 포함합니다. 오류 예산을 초과하면 자동으로 확대를 pause하고 심각한 불변식 위반에는 현재 bucket을 legacy로 되돌리기보다 먼저 write freeze를 걸어 추가 모순을 막습니다. route version과 build version을 모든 로그·감사 이벤트에 남겨 영향 계약을 즉시 추출합니다.
- 신규 서비스는 legacy downstream을 호출하는 동안 timeout budget, circuit breaker, bulkhead를 갖고 실패 시 무조건 레거시로 재실행하지 않습니다. 결과 불명 요청은 idempotency key와 operation status로 조회해 중복 변경을 막습니다. 재시도 가능 오류와 사람 확인이 필요한 오류를 계약으로 나눕니다.
- 확대 전에는 신규 리전 장애, Gateway 설정 오류, 배치 결과 지연, 레거시 DB 포화, 메시지 중복을 game day로 재현합니다. 성공 기준은 전환 비율뿐 아니라 고객 오류·수동 보정 건수가 baseline보다 악화되지 않고 on-call이 15분 안에 중단·영향 계약 식별·복구 결정을 수행하는 것입니다.
신규 경로가 이미 일부 계약을 변경한 뒤 치명적 결함이 발견되었습니다. 라우팅만 되돌리는 rollback이 왜 불충분한지 설명하고, write ownership·보상·감사·재전환까지 포함한 운영 절차를 설계하십시오.
답변 예시 보기
코드 rollback과 업무 상태 복구를 별도 절차로 다룸
Gateway를 레거시로 되돌려도 신규 DB의 변경, 발행된 우편·알림, 레거시로 전달 중인 이벤트, 외부 제휴사의 재시도가 사라지지 않습니다. 따라서 incident commander는 먼저 rollout을 pause하고 해당 capability의 신규 command를 freeze한 뒤 route epoch·배포 버전·시간 범위로 영향 계약과 side effect 목록을 확정합니다. 읽기는 안전한 snapshot이나 레거시로 제공할 수 있지만, 두 시스템이 동시에 보정 쓰기를 수행하지 않도록 계약별 authoritative writer와 recovery owner를 하나로 유지합니다.
- 모든 command에 operationId, before/after business version, route epoch, actor, 판정 근거를 기록하고 외부 side effect에는 같은 correlation key를 전달합니다. 이 정보로 ‘요청을 받음’, ‘신규 DB 반영’, ‘레거시 반영’, ‘우편 생성’ 단계를 구분합니다. timeout을 실패로 간주해 원 요청을 재실행하지 않고 상태 조회와 멱등한 repair command를 사용합니다.
- 데이터가 잘못됐으면 DB backup을 통째로 되감지 않고 업무적으로 승인된 보상 명령을 생성합니다. 주소를 이전 값으로 복원할 때 그 사이 고객이 다시 수정했는지 version 조건으로 확인하며, 이미 발송된 우편은 취소·재발송 같은 별도 보상이 필요합니다. 자동 복구, 업무 담당자 승인, 고객 통지가 필요한 범위를 위험 등급으로 나누고 dry-run 결과와 예상 건수를 먼저 검토합니다.
- 복구 완료는 두 DB의 row count가 아니라 계약별 핵심 상태·효력일·감사 이력과 외부 side effect를 대사해 판정합니다. 불일치 queue는 reason code, owner, SLA를 갖고 잔여 건수와 oldest age를 공개합니다. 규제 감사에는 원래 변경과 보상 변경을 모두 append-only로 남기고 잘못된 이력을 삭제해 흔적을 감추지 않습니다.
- 재전환은 결함 수정만으로 시작하지 않습니다. 영향 데이터 repair, 원인 사례의 회귀 테스트, 같은 실패를 잡는 canary guardrail, rollback game day를 완료한 뒤 새 route epoch로 작은 bucket부터 다시 시작합니다. RTO는 라우팅 복원 시간, 업무 정합성 복구 시간, 고객 보상 완료 시간을 별도로 보고해 ‘5분 만에 롤백’이라는 잘못된 성공 표현을 피합니다.
공유 Oracle DB와 Stored Procedure 중심 정산 시스템의 무중단 분리
상황
조건
시스템 구성·규모
- 일 주문 8천만 건, 월말 정산 journal 12억 행, DB redo 9TB/day
- Oracle RAC 6노드, PL/SQL 180만 줄, batch 900개·직접 SQL client 120개
- 신규 정산 서비스는 PostgreSQL과 Kafka 기반, multi-AZ 운영
- 정산 결과는 파트너 38만 곳과 회계 시스템으로 익일 오전 7시까지 전달
현재 문제·목표
- 테이블·프로시저 호출 관계와 금액 불변식이 문서화되지 않음
- 공유 DB dual write 시 부분 성공과 순서 역전 위험
- CDC snapshot/stream gap, 대량 redo와 schema change 대응 필요
- 18개월 내 파트너 정산 도메인을 독립 DB로 분리하고 공유 계정 제거
꼬리 질문
Stored Procedure와 공유 테이블에 숨은 업무 규칙을 어떻게 식별하고 파트너 정산 도메인의 경계를 정하겠습니까? 호출 그래프, characterization test, 데이터 소유권과 직접 SQL 차단까지 단계별 실행안을 설명하십시오.
답변 예시 보기
코드 해석보다 실행 증거와 금액 불변식을 먼저 확보
PL/SQL 소스의 정적 dependency, DBA audit의 실제 호출자, AWR/SQL trace의 실행 빈도와 p95, redo에서 관찰한 변경 테이블을 합쳐 procedure-to-table-to-caller graph를 만듭니다. dynamic SQL, synonym, trigger, DB link는 정적 분석에서 빠지므로 대표 월말 기간의 런타임 trace로 보완합니다. 각 노드에는 담당 팀, 입력·출력, commit 여부, 재실행 가능성, 금액 영향, 배치 window를 붙이고 ‘소유자 없음’을 숨기지 않고 migration blocker로 관리합니다. 드물지만 금액이 큰 예외 프로시저는 빈도만으로 우선순위를 낮추지 않습니다.
- 실제 과거 정산 기간을 비식별화한 golden dataset으로 고정하고 프로시저의 journal 합계, 파트너별 지급액, 세금·쿠폰 보존식, 상태 전이를 snapshot test합니다. 결과 row만 비교하지 않고 생성된 회계 분개와 재실행 후 변화까지 검증해 비멱등 동작을 찾습니다. 업무 담당자가 수동 보정하는 case도 테스트 입력에 포함합니다.
- 경계는 기존 package 구조가 아니라 업무 불변식과 변경 권한을 기준으로 정합니다. 파트너 정산 서비스가 payout schedule과 settlement journal을 소유하고 주문·쿠폰은 immutable fact 또는 명시적 계약으로 받습니다. 여러 도메인이 한 테이블의 컬럼을 나눠 쓰면 새 owner를 먼저 지정하고, 필요한 소비자는 API/event/read model로 전환합니다.
- 직접 SQL을 한 번에 차단하지 않고 DB proxy/audit로 caller를 식별한 뒤 개인 계정 제거→service account별 최소 권한→view/wrapper package만 허용→쓰기 권한 회수 순으로 좁힙니다. 허용되지 않은 새 SQL을 CI와 DB audit에서 막고, exception에는 owner·만료일·호출량을 부여합니다. 이렇게 해야 분리 후에도 synonym을 통한 숨은 쓰기가 되살아나지 않습니다.
- 프로시저는 가장자리 규칙을 신규 코드로 옮기고 anti-corruption wrapper가 구·신 호출을 선택하도록 합니다. 한 번에 번역하지 않고 동등성 검증, shadow 계산, 소량 authoritative 실행을 거칩니다. 진척은 변환한 PL/SQL 줄 수가 아니라 소유권 확정 테이블 비율, 차단한 직접 writer 수, golden case 동등률, 수동 보정 감소로 평가합니다.
Oracle의 기존 데이터를 PostgreSQL로 초기 적재하면서 이후 모든 변경을 CDC로 이어받아야 합니다. 대량 snapshot, SCN 경계, transaction 순서, DDL, poison event와 lag 폭증을 처리하는 파이프라인을 설계하십시오.
답변 예시 보기
일관된 기준점과 재시작 가능한 단계로 snapshot과 stream을 연결
먼저 대상 테이블과 컬럼을 무작정 모두 복제하지 않고 신규 정산 모델에 필요한 source contract를 확정합니다. Oracle의 일관된 SCN에서 snapshot을 시작하고 같은 SCN 이후 redo를 CDC가 보존하도록 합니다. 대량 테이블은 PK/range로 병렬 추출하되 각 chunk에 source SCN, row count, checksum, 마지막 key를 기록해 중단 후 재개합니다. snapshot 완료 후 CDC를 처음부터 다시 받는 방식이 아니라 버퍼링한 변경을 source commit 순서와 transaction boundary를 보존해 적용하고, 대상 row에는 source version/SCN을 저장해 중복과 역순을 거부합니다.
- Oracle의 여러 row transaction을 Kafka 개별 메시지로 풀어낼 때 소비자가 부분 상태를 보지 않도록 transaction metadata와 commit marker를 사용하거나 staging에 모두 적재한 뒤 원자 적용합니다. 같은 aggregate의 순서가 필요한 범위를 정의해 partition key를 정하며, 전체 DB의 전역 순서를 흉내 내느라 처리량을 희생하지 않습니다. 삭제·truncate·bulk update의 의미도 별도 이벤트로 명시합니다.
- DDL은 예고 없는 자동 전파를 허용하지 않습니다. schema registry의 호환성 검사와 expand/contract 절차를 거치고, 알 수 없는 컬럼은 보존 또는 무시 정책을 계약별로 둡니다. 호환되지 않는 type change는 새 컬럼/새 event version으로 이행합니다. DB patch와 CDC connector upgrade를 월말 전에 staging redo replay로 검증합니다.
- 변환 오류 한 건이 partition을 영구 정지시키지 않도록 원본 payload·SCN·schema version을 quarantine에 보관하지만, 금액 이벤트를 DLQ로 보내고 성공 처리한 척하지 않습니다. 영향 aggregate를 읽기/정산 보류 상태로 표시하고 owner와 SLA를 가진 repair queue에서 수정 후 원래 순서를 고려해 재처리합니다. 재처리 도구에는 dry-run, 중복 방지, 승인 기록을 둡니다.
- redo 생성률, connector read SCN, Kafka lag, apply lag, snapshot ETA, source-target checksum을 함께 관측합니다. 월말 폭증에는 source DB I/O를 보호하는 throttle과 중요 테이블 우선순위를 두고, lag SLO를 넘으면 전환을 중단합니다. connector 장애·archive log 유실·snapshot 도중 schema 변경·PostgreSQL 복구를 game day로 재현해 필요한 redo 보존 기간과 RPO를 실제 수치로 확정합니다.
공유 DB에서 신규 DB로 writer를 옮기는 동안 dual write의 부분 실패를 어떻게 피하고, 금액 데이터가 동일함을 무엇으로 증명하겠습니까? shadow 계산, 단일 writer 전환, 연속 대사와 불일치 복구 절차를 제시하십시오.
답변 예시 보기
동시 writer가 아니라 계산 비교 후 소유권을 한 번씩 넘김
애플리케이션이 Oracle과 PostgreSQL에 순차 dual write하면 어느 한쪽 timeout에서 결과 불명과 금액 분기가 생깁니다. 따라서 초기에는 Oracle만 authoritative writer로 유지하고 commit된 변경을 redo CDC로 신규 DB에 반영합니다. 신규 엔진은 같은 입력으로 정산을 shadow 계산하되 지급·세금계산서 발행 같은 side effect는 만들지 않고 결과를 comparison store에 저장합니다. 두 계산의 차이를 반올림, 효력일, late event, 기준 데이터 version 같은 reason으로 분류해 규칙 차이인지 pipeline 누락인지 먼저 구분합니다.
- 대사는 계층적으로 수행합니다. transaction/event 수와 SCN gap, 주문별 journal debit-credit 합, 파트너·영업일·통화별 지급액, 총계와 회계 control total을 비교합니다. 전체 합이 같아도 서로 다른 파트너의 오차가 상쇄될 수 있으므로 bucket checksum과 business key별 비교가 필요합니다. 불일치율 0만 보지 않고 금액 절대 차, oldest mismatch age, 미분류 reason 건수를 gate로 둡니다.
- golden period 여러 회차와 월말 peak를 통과하면 신규 경로에 특정 파트너 cohort의 writer lease/ownership epoch를 부여합니다. 그 cohort의 명령은 신규 서비스만 받고, Oracle이 필요한 동안에는 신규 DB transaction과 outbox를 함께 커밋한 뒤 adapter가 legacy read model을 갱신합니다. 구 프로시저의 해당 파트너 쓰기 권한 또는 route를 차단해 split brain을 구조적으로 막습니다.
- timeout 후에는 같은 settlementOperationId와 aggregate version으로 결과를 조회하고 conditional write·unique constraint로 중복 지급을 막습니다. CDC/outbox는 at-least-once임을 인정해 대상에도 eventId 처리 이력을 둡니다. 금액 차이가 난 cohort는 자동 확대를 pause하고 아직 실행되지 않은 지급을 보류하되, 이미 외부로 나간 이체는 승인된 reversal/adjustment journal로 보상합니다.
- repair는 원본 row를 임의 UPDATE하지 않고 reason code가 붙은 재계산 또는 조정 분개로 수행하며 실행 전 예상 변화와 영향 파트너를 dry-run합니다. 해결 후 동일한 대사 계층을 다시 통과하고 감사 로그에 원인·승인자·이전/이후 control total을 남깁니다. Oracle을 즉시 폐기하지 않고 제한된 rollback window 동안 CDC offset과 읽기 가능한 snapshot을 유지하되, rollback 시에도 ownership epoch를 명시적으로 되돌려 두 writer가 생기지 않게 합니다.
기존 리포트와 프로시저를 유지한 채 컬럼 타입·키 구조를 바꾸고 최종적으로 공유 테이블을 제거해야 합니다. expand/contract, online backfill, read/write 전환, rollback과 삭제 증거까지 포함한 무중단 스키마 전환 계획을 설명하십시오.
답변 예시 보기
호환 가능한 확장과 사용 증거 기반 수축을 여러 배포로 분리
기존 컬럼의 타입을 제자리 변경하거나 이름을 바꾸지 않고 새 nullable 컬럼·새 키 mapping table을 먼저 추가합니다. 이 expand 단계는 구 코드가 새 schema에서도 그대로 동작하고 신규 코드가 구 schema 상태를 처리하도록 forward/backward compatibility를 CI에서 검사합니다. DDL lock과 table rewrite 위험은 운영 데이터 복제본에서 측정하고 online DDL이라도 metadata lock 대기와 replica/CDC 영향을 관측합니다. 월말에는 위험한 DDL을 금지하고 kill switch와 lock timeout을 둡니다.
- backfill은 PK range별 checkpoint, rate limit, pause/resume을 지원하며 업무 트래픽의 DB CPU·redo·replication lag budget 안에서 실행합니다. 각 chunk는 새 값이 비어 있거나 source version이 같을 때만 조건부 갱신해 동시 온라인 쓰기를 덮지 않습니다. old-to-new 변환 version과 checksum을 기록하고 실패 row는 reason과 함께 격리합니다. 완료는 전체 count가 아니라 null 잔여, 변환 오류, bucket checksum, 핵심 쿼리 결과로 판정합니다.
- 전환 중 새 쓰기는 애플리케이션의 한 transaction 안에서 구·신 컬럼을 같이 갱신하거나 DB 내부 trigger를 임시로 사용할 수 있지만, 두 경로의 precedence와 종료일을 명시합니다. trigger는 숨은 로직을 늘리므로 가능하면 짧게 유지하고 metrics를 노출합니다. 읽기는 shadow compare→소량 cohort에서 새 컬럼 우선+구 컬럼 fallback→100% 새 읽기로 옮기며 fallback 비율이 0에 수렴하는지 봅니다.
- rollback은 단계별로 다릅니다. 새 read 오류는 feature flag로 구 read로 되돌리고, 새 write가 시작된 뒤에는 reverse sync가 실제로 구 schema를 최신으로 유지하는지 검증해야 합니다. lossy type 변환이나 키 재편은 되돌릴 수 없을 수 있으므로 원본 값 보존, mapping table, point-in-time snapshot과 보상 도구를 전환 전에 준비합니다. rollback 가능 시간을 지나기 전 구 컬럼 쓰기를 끊지 않습니다.
- contract 단계는 audit 로그, AWR, code search, 리포트 실행 이력으로 3개월간 구 컬럼·테이블 read/write가 0임을 증명하고 소비자 owner의 승인을 받습니다. 먼저 권한 회수와 deprecation view로 숨은 사용을 fail-fast하게 발견한 뒤 보존 snapshot과 복구 runbook을 확인하고 삭제합니다. synonym·DB link·프로시저·배치·모니터링까지 dependency graph에서 함께 제거하며, 성공 지표는 schema 변경 완료가 아니라 공유 계정 0개, 구 writer 0개, 대사 통과와 월말 두 회차 무사고입니다.
Kafka 파티션·Consumer Group 운영 안정화 설계
상황
sellerId를 key로 쓰는 주문 topic의 한 partition만 초당 7만 건을 받았습니다. 해당 partition의 consumer lag는 52분까지 늘었으나 group 전체 평균 lag 경보는 울리지 않았고, 배송 알림은 이미 출고된 뒤 도착했습니다. 운영자가 consumer pod를 24개에서 60개로 늘렸지만 partition은 24개뿐이라 처리량은 늘지 않았습니다. 이어서 배포 때 모든 pod가 동시에 재시작하면서 11분 동안 네 차례 rebalance가 반복됐고, poll loop에서 외부 배송사 API를 동기 호출하던 consumer들이 max.poll.interval.ms를 넘겨 다시 group에서 축출되는 악순환이 발생했습니다. [플랫폼 책임자의 요청] “평균 처리량을 맞춘 구성도가 아니라, 업무별 순서 보장의 범위와 partition key의 수명주기, hot key가 발생했을 때의 완화 방법, 배포·장애 중 consumer group의 안정성, lag를 업무 지연으로 해석하는 운영 체계를 제안해 주세요. 기존 producer와 40개 consumer 팀을 한 번에 바꿀 수 없다는 제약도 반영해야 합니다.”조건
시스템 구성·규모
- Kafka 18 brokers·3개 AZ, 주문 topic 24 partitions·RF 3, 일평균 35억 events
- 평균 18만/행사 최대 95만 events/sec, 평균 payload 1.4KB
- 주문·재고·배송 consumer group 40개, Kubernetes에서 300여 consumer pod 운영
- 동일 주문의 상태 순서 보장, 결제·재고 반영 업무 지연 p99 30초 이내 목표
장애 이력·조직 제약
- Hot Partition최대 partition 처리량이 중앙값의 22배, broker network 96% 도달
- Rebalance Storm배포와 느린 poll이 겹쳐 11분간 partition 소유권 불안정
- Blind Laggroup 합계·평균 경보만 있어 단일 주문군의 52분 지연을 놓침
- producer 17개와 consumer 40개를 단계적으로 이관해야 하며 행사 전 freeze 기간은 2주
꼬리 질문
sellerId key가 판매자별 순서는 보장하지만 인기 판매자를 hot partition으로 만들었습니다. 주문 상태의 실제 순서 요구를 다시 정의하고, 새로운 partition key와 partition 수를 어떻게 결정하겠습니까? key 변경과 partition 증설 과정에서 기존·신규 이벤트의 순서가 섞이는 문제까지 설명해 주세요.
답변 예시 보기
먼저 순서가 필요한 업무 단위를 축소
판매자 전체 주문이 하나의 직렬 스트림일 필요가 있는지부터 확인합니다. 일반적으로 CREATED→PAID→ALLOCATED→SHIPPED 같은 전이는 같은 orderId 안에서만 순서가 중요하고 서로 다른 주문은 병렬 처리할 수 있습니다. 따라서 기본 key는 orderId가 더 자연스럽습니다. 재고가 상품 단위로 직렬화돼야 한다면 모든 이벤트의 key를 sellerId로 타협하기보다 재고 예약용 topic을 skuId로 분리하고, 판매자 단위 집계는 순서에 의존하지 않는 멱등 집계로 설계합니다. 이처럼 한 topic에 상충하는 ordering domain을 억지로 넣지 않고 업무 불변식별 스트림을 나누는 것이 첫 선택입니다.
용량과 이관을 함께 설계
- partition 수는 단순히 consumer pod 수에 맞추지 않고 피크 bytes/sec, partition당 producer·consumer 처리량의 부하 테스트 결과, 향후 2~3배 성장, broker별 leader 수와 장애 시 재복제 여유를 함께 계산합니다. partition을 과도하게 늘리면 controller metadata, file handle, recovery와 rebalance 비용이 늘므로 처리량 목표와 운영 상한을 문서화합니다.
- Kafka에서 partition 수를 늘리면 기본 partitioner의 key→partition 매핑이 달라질 수 있고, 같은 key의 과거 이벤트와 새 이벤트가 서로 다른 partition에 존재할 수 있습니다. 따라서 운영 중 단순 증설만 하고 “key 순서가 유지된다”고 말하지 않습니다. 새 topic version을 충분한 partition 수로 만들고 producer가 일정 시점부터 dual-write하거나 outbox relay가 두 topic에 발행하게 하며, 이벤트에 orderId·aggregateVersion·eventId를 포함합니다.
- consumer는 신규 topic을 shadow로 읽어 주문별 결과와 누락·역전 수를 비교합니다. 전환 barrier 이전 offset을 모두 처리했다는 watermark를 확인한 뒤 신규 projection으로 전환하거나, 전환 기간에는 aggregateVersion으로 오래된 전이를 거부·보류합니다. dual-write 자체도 부분 실패할 수 있으므로 원본 outbox와 발행 상태에서 복구 가능해야 합니다.
- 검증은 key별 빈도 상위 N, partition별 records/bytes in·produce latency·consumer 처리량의 p50/p95/max 비율을 봅니다. 과거 행사 분포를 replay해 최대 partition이 평균의 예를 들어 2~3배 안으로 들어오는지, broker 한 대 장애 후에도 지연 SLO와 replication headroom을 지키는지 확인합니다.
행사 당일 특정 skuId 자체가 주문의 40%라서 orderId로 분산한 주문 스트림은 안정적이지만, SKU별 재고 차감 스트림은 다시 한 partition에 몰립니다. 강한 SKU 순서를 유지하면서 hot key를 완화하는 방법과, 순서를 완화할 수 있을 때의 대안을 비교해 주세요. 장애 중 즉시 취할 조치와 장기 개선을 구분해 설명해 주세요.
답변 예시 보기
순서와 처리량의 물리적 한계를 인정
하나의 skuId에 대해 모든 차감을 엄격히 직렬 처리해야 한다면 해당 key는 결국 한 partition·한 consumer task의 처리량을 넘을 수 없습니다. broker나 consumer 수를 늘리는 것만으로 해결되지 않습니다. 먼저 재고 불변식이 “이벤트을 전부 순서대로 실행”인지, 아니면 “최종 판매량이 가용 수량을 넘지 않음”인지 구분합니다. 후자라면 원장형 예약, 조건부 DB 갱신, 구간별 재고 토큰처럼 순서가 아닌 불변식으로 문제를 바꾸어 병렬성을 얻을 수 있습니다. 강한 순서가 정말 필요한 구간은 짧게 유지하고 외부 API·알림 같은 느린 작업은 그 뒤의 별도 topic으로 분리합니다.
완화 선택과 운영 절차
- 즉시 조치로는 해당 producer의 요청량을 admission control하고 중요 주문 topic과 broker quota를 격리합니다. hot partition leader가 있는 broker의 다른 leader를 이동해 network·disk 여유를 확보하되, 행사 중 대규모 reassignment가 복제 트래픽으로 장애를 키우지 않도록 throttle과 중단 기준을 둡니다. consumer 내부 batch 처리·DB bulk write를 적용하고 외부 호출을 poll thread에서 제거해 단일 task의 처리량부터 높입니다.
- 순서를 완화할 수 있다면
skuId + bucket으로 여러 partition에 분산하고, 각 bucket consumer가 재고 예약 토큰의 일부만 사용하도록 사전 할당합니다. bucket 잔량은 빠르게 다를 수 있으므로 중앙 조정 없이 oversell하지 않는 토큰 총량 불변식과 회수·재분배 절차가 필요합니다. 또는 이벤트를 병렬 계산한 뒤 skuId별 sequence barrier에서 확정하는 2단계 구조를 쓸 수 있지만 buffer와 tail latency가 늘어납니다. - 강한 전역 순서를 유지해야 한다면 인기 SKU 전용 topic·partition과 전용 consumer resource를 격리하는 편이 현실적입니다. 이는 처리량 상한을 없애지 않지만 noisy neighbor 전파를 막고 해당 경로에 최적화된 batch·저장소를 적용하게 합니다. 미리 판매 이벤트를 감지해 hot SKU를 전용 경로로 routing하려면 전환 시점과 aggregateVersion을 기록해야 합니다.
- hot-key 후보는 top-key sketch, partition별 records/bytes, consumer processing time, broker request queue로 미리 탐지합니다. 완화 후에는 단순 lag 감소뿐 아니라 oversell 0, 예약 거절률, bucket별 잔여 편차, 주문별 처리 순서 오류를 chaos·replay 테스트로 검증하고 정상 경로 복귀 절차까지 훈련합니다.
Kubernetes rolling deployment 중 consumer group이 반복해서 rebalance되고, 외부 배송사 API가 90초 걸린 consumer는 partition을 빼앗긴 뒤에도 이전 작업의 결과를 DB에 기록했습니다. consumer group의 assign/revoke 흐름, poll과 처리 분리, static membership·cooperative rebalancing을 활용해 중단과 중복·zombie write를 어떻게 줄이겠습니까?
답변 예시 보기
리밸런스를 없애기보다 작업 소유권 변경을 안전하게
consumer는 poll을 오래 막지 않고 records를 bounded worker queue로 넘기되, partition별 처리 순서와 완료 offset을 추적합니다. queue가 가득 차면 해당 partition을 pause하고 heartbeat·poll은 계속하며, 처리 완료 후 resume합니다. 무작정 max.poll.interval.ms만 10분으로 늘리면 장애 consumer의 감지가 늦어지고 배포 정지 시간도 커집니다. 배송사 API 같은 장기 I/O는 비동기 command topic으로 분리하거나 timeout·bulkhead를 적용해 Kafka partition 소유 기간과 외부 처리 시간을 결합하지 않는 것이 우선입니다.
소유권·배포·commit 규칙
- partition revoke 시 신규 작업 수신을 멈추고 짧은 drain budget 안에 완료된 연속 offset까지만 동기 commit합니다. 뒤 offset이 먼저 끝났더라도 gap을 건너뛰어 commit하지 않습니다. budget을 넘긴 작업은 취소하거나 멱등 저장소에서 새 owner가 다시 실행하게 하며, revoke callback 안에서 무제한 외부 호출을 기다리지 않습니다.
- cooperative sticky assignor는 모든 partition을 한꺼번에 회수하는 stop-the-world를 줄이고 필요한 partition만 점진 이동시킵니다. 안정적인 pod identity와
group.instance.id를 쓰는 static membership은 짧은 재시작에 불필요한 재할당을 줄이지만, 중복 instance id와 종료된 멤버의 session timeout 동안 복구가 지연되는 trade-off가 있습니다. PodDisruptionBudget, 충분한 termination grace, readiness와 한 번에 교체할 pod 수를 함께 조정합니다. - 이전 owner가 늦게 DB를 쓰는 zombie 문제는 consumer group 세대만 믿지 않고 업무 레코드에 eventId 멱등키 또는 aggregateVersion 조건을 둡니다. 외부 요청에는 operation id를 전달하고 결과 저장은 예상 version일 때만 성공시킵니다. Kafka offset commit과 외부 DB를 원자화할 수 없으므로 재실행 가능한 효과를 만드는 것이 핵심입니다.
- consumer별 poll interval, heartbeat 실패, rebalance 횟수·소요 시간·partition 이동 수, revoke drain 초과, duplicate 처리율을 배포 단위로 봅니다. staging에서 pod kill, API 120초 지연, network pause를 주입해 처리량이 안정화되는 시간과 오래된 owner의 쓰기가 실제로 거부되는지를 검증합니다.
현재 대시보드는 consumer group의 총 lag와 평균 lag만 보여 주어 단일 partition의 52분 지연을 놓쳤습니다. offset lag를 실제 업무 지연과 복구 시간으로 해석하려면 어떤 지표와 경보가 필요합니까? lag가 증가했을 때 원인을 분류하고 scale-out·throttle·replay 중 무엇을 실행할지 운영 절차를 설계해 주세요.
답변 예시 보기
lag 하나를 SLO로 착각하지 않기
offset lag는 partition마다 메시지 유입률과 처리 비용이 달라 같은 10만 건도 의미가 다릅니다. group-topic-partition별 current/end offset 차이와 최대값을 기본으로 보고, oldest unprocessed event의 event time 또는 broker append time을 이용한 lag age를 함께 측정합니다. 여기에 최근 소비율과 생산율을 이용해 backlog가 실제로 줄고 있는지, 현재 순처리율로 몇 분 뒤 회복되는지 drain ETA를 계산합니다. 업무 관점에서는 “결제 승인 이벤트의 99%가 30초 안에 반영됨”처럼 source event time부터 DB 반영 완료까지 end-to-end freshness를 synthetic marker 또는 처리 완료 timestamp로 관측합니다.
원인별 대응 runbook
- partition별 produce rate와 consume rate, record 처리 시간, fetch latency, rebalance, pause 시간, 실패·retry·DLQ, downstream DB/API latency를 한 화면에서 연결합니다. 한 partition만 증가하면 hot key나 poison message를, 전 partition이면 downstream·배포·broker 문제를, 소비율은 정상인데 lag age만 크면 timestamp 품질도 의심합니다.
- partition 수보다 consumer가 적고 모든 partition이 균등하게 밀리며 downstream 여유가 있을 때만 scale-out이 유효합니다. consumer 수가 이미 partition 수와 같거나 hot partition 하나라면 pod 증설은 답이 아닙니다. 이때는 batch 최적화, hot key 격리, producer quota가 후보입니다. DB가 포화됐다면 consumer를 늘리지 않고 pause·rate limit로 DB를 보호합니다.
- 경보는 group 평균 대신 중요 topic의 max lag age, SLO 위반 이벤트 비율, drain ETA 악화와 no-progress를 사용하고 배포·행사라는 change context를 붙입니다. lag 급증 자체와 계획된 replay를 구분해 경보 소음을 줄이되, replay가 broker disk·network budget을 넘으면 자동 감속합니다.
- 복구 완료는 offset lag 0만으로 보지 않습니다. 처리 결과 count·금액·주문 상태의 source 대사, DLQ와 retry backlog, downstream 오류율이 정상인지 확인합니다. 사건 후에는 누락된 top-key 지표와 용량 모델을 보완하고 과거 행사 trace로 경보가 실제로 몇 분 일찍 울렸는지 재검증합니다.
Kafka 전달 보장·스키마 진화·재해복구 설계
상황
조건
시스템 구성·목표
- 주 리전 12 brokers·보조 리전 9 brokers, RF 3, 중요 topic 72 partitions
- 평균 6만/최대 25만 승인 events/sec, 14개 consume-process-produce topology
- Schema Registry와 Avro 사용, producer 23개·consumer 68개 팀이 독립 배포
- 승인 이벤트 RPO 0에 근접, 리전 장애 RTO 30분, 결과 반영 p99 5초 목표
장애 이력·제약
- False EOSidempotent producer를 end-to-end exactly-once로 오해해 DB 이중 차감
- Txn Abort긴 외부 호출로 transaction abort·read_committed 지연 47분
- Schema Incident필드 의미 변경이 호환성 검사를 통과했지만 6개 consumer 오해석
- 양 리전 active-active 쓰기는 허용하지 않으며 금융 부작용의 자동 replay는 금지
꼬리 질문
ACK 유실과 재시도, broker leader failover가 있는 환경에서 idempotent producer가 해결하는 중복과 해결하지 못하는 중복을 구분해 주세요. 주문 DB 변경과 Kafka 발행 사이의 유실까지 막기 위해 producer 설정, key·event identity, transactional outbox를 어떻게 조합하고 무엇을 모니터링하겠습니까?
답변 예시 보기
보장 범위를 실패 창별로 분리
idempotent producer는 producer id·epoch와 partition별 sequence를 이용해 동일 producer session의 재시도가 broker log에 중복 append되는 것을 줄입니다. 적절한 ack와 retry 설정, 순서가 뒤집히지 않는 in-flight 정책을 함께 확인해야 하며 producer가 새 identity로 다시 시작해 업무 이벤트를 재생성하거나 애플리케이션이 같은 승인을 두 번 호출하는 문제까지 제거하지는 않습니다. 특히 주문 DB commit 후 프로세스가 죽어 Kafka 발행이 누락되는 dual-write 실패와, 반대로 발행 후 DB rollback되는 문제는 producer idempotence의 범위 밖입니다.
원본 트랜잭션과 발행을 연결
- 주문·승인 DB 변경과 outbox row를 하나의 로컬 DB 트랜잭션에 기록합니다. outbox에는 재시도마다 바뀌지 않는 eventId, aggregateId, aggregateVersion, schema version, payload와 생성 시각을 둡니다. relay는 미발행 row를 반복 읽어 Kafka에 발행할 수 있고, 발행 확인 전 장애가 나면 중복될 수 있으므로 downstream도 eventId·version으로 멱등 처리합니다.
- Kafka key는 aggregateId로 두어 같은 승인의 상태 전이를 한 partition에 모으고, producer idempotence와
acks=all, 중요 topic의min.insync.replicas를 사용합니다. ISR 부족 때 RPO를 우선해 publish를 거부할지 정책을 명시합니다. relay 여러 인스턴스가 같은 outbox를 잡지 않도록 DB claim/lease를 쓰되 lease 만료 후 재실행은 안전해야 합니다. - Kafka transaction으로 여러 topic 발행을 원자화할 수 있지만 원본 RDB 트랜잭션과 Kafka transaction을 하나로 만들지는 않습니다. outbox relay가 transaction을 사용하더라도 event identity가 최종 안전망이며, “로그에 한 번”과 “업무 효과 한 번”을 구분해 API 계약에 씁니다.
- outbox oldest age·미발행 건수·relay retry, producer error/timeout, ISR 축소, duplicate eventId 수, aggregateVersion gap을 관측합니다. DB commit 직후 kill, ACK 응답 유실, leader failover를 주입하고 source row 수와 Kafka의 unique eventId, 최종 projection checksum이 일치하는지 검증합니다.
Kafka를 소비해 상태 저장소를 갱신하고 결과 topic 두 개를 발행하는 topology에는 transactional consume-process-produce를 적용하려 합니다. 처리 중 외부 PostgreSQL과 30초짜리 모델 API도 호출해야 할 때 exactly-once의 경계, transaction timeout·abort, read_committed, zombie instance를 어떻게 다루겠습니까?
답변 예시 보기
Kafka 내부 원자성과 외부 효과를 분리
consume-process-produce transaction은 입력 consumer offset과 Kafka 결과 record, Kafka 기반 state changelog를 같은 transaction으로 commit해 장애 후 입력을 다시 읽더라도 abort된 결과가 read_committed consumer에게 보이지 않게 할 수 있습니다. 그러나 외부 PostgreSQL commit이나 모델 API의 부작용은 Kafka transaction에 참여하지 않으므로 end-to-end exactly-once라고 부르지 않습니다. DB 한도 차감은 eventId를 unique key로 갖는 inbox와 업무 갱신을 하나의 DB 트랜잭션에 묶거나, topology가 DB command를 별도 topic에 발행하고 전담 consumer가 멱등 적용하도록 경계를 바꿉니다.
짧고 예측 가능한 transaction
- 30초 모델 호출을 Kafka transaction 안에 두면 timeout, 열린 transaction 구간, abort 후 전체 batch 재처리가 커집니다. 모델 결과를 사전 계산·캐시하거나 request/result topic의 비동기 단계로 분리하고, transaction에는 결정적이고 짧은 변환과 Kafka 쓰기만 둡니다. 외부 조회가 꼭 필요하면 엄격한 timeout과 bulkhead, 작은 batch를 사용하고 실패 시 전체 transaction을 반복할 비용을 계산합니다.
- 각 인스턴스는 안정적으로 고유한 transactional.id를 가지며 새 producer epoch가 이전 zombie producer를 fence하도록 합니다. 재시작한 두 pod가 같은 id를 동시에 쓰거나 매번 무한히 새 id를 만들어 transaction metadata를 누적하지 않도록 배포 identity와 수명주기를 관리합니다.
read_committedconsumer는 열린 transaction 이후의 record가 이미 복제돼도 last stable offset 때문에 보지 못할 수 있으므로 일반 offset lag만 보면 원인을 놓칩니다. transaction duration, commit/abort rate, abort 원인, open transaction age, last stable offset과 high watermark 차이를 함께 경보합니다.- process kill, transaction timeout, broker failover, 모델 API 지연을 단계별로 주입하고 결과 topic 두 개와 offset이 모두 보이거나 모두 보이지 않는지 확인합니다. DB sink는 같은 입력을 반복 적용해도 unique eventId와 예상 accountVersion으로 한 번만 차감되는지 검증하며, 처리량을 위해 transaction 범위를 넓혔을 때 abort 재작업 비용과 p99를 부하 테스트합니다.
Schema Registry의 backward compatibility 검사는 통과했지만 producer가 기존 amount 필드의 통화 의미를 바꾸면서 구버전 consumer가 잘못 처리했습니다. 독립 배포하는 수십 개 팀에서 스키마의 구조뿐 아니라 의미를 안전하게 진화시키고, 오래된 이벤트를 수년 뒤 재처리할 수 있도록 어떤 계약과 배포 절차를 만들겠습니까?
답변 예시 보기
문법 호환성과 의미 호환성을 별도로 관리
Registry의 backward/forward 검사는 필드 추가·삭제와 기본값 등 직렬화 가능성을 확인하지만 “amount가 원화 소수 단위에서 결제 통화 minor unit으로 바뀌었다”는 의미 변화까지 판단하지 못합니다. 기존 필드의 단위·통화·nullable 의미를 재사용하지 않고 amountMinor, currency, amountSemanticsVersion처럼 새 필드를 추가합니다. 이벤트 이름과 필드별 불변식, 단위, enum의 unknown 처리, PII 등급, producer가 값을 확정하는 시점을 schema와 함께 관리하는 데이터 계약이 필요합니다.
확장 후 수축과 소비자 증거
- 먼저 consumer가 신·구 필드를 모두 읽고 명시적 우선순위와 불일치 metric을 내도록 배포합니다. 그 다음 producer가 두 필드를 함께 기록하는 기간을 두고 shadow 비교로 금액·통화가 같은지 검증합니다. 모든 중요 consumer의 지원 version과 마지막 소비 시점을 registry/catalog에서 확인한 뒤 구필드 쓰기를 중단하며, 필드 삭제는 보존 기간과 replay 요구까지 고려해 별도 major topic version으로 처리할 수 있습니다.
- consumer-driven contract test와 대표 과거 payload의 golden corpus를 CI에서 실행합니다. 단순 deserialize 성공이 아니라 금액 계산·상태 전이 결과를 비교하고, 미지원 enum은 조용히 잘못 매핑하지 말고 격리하거나 안전한 unknown 경로를 택합니다. producer canary의 새 schema id별 처리 오류와 DLQ를 관측해 자동 중단합니다.
- 이벤트는 당시 사실을 재현할 수 있게 schema id뿐 아니라 필요한 정책·모델 version과 원본 단위를 보존합니다. 최신 코드는 version별 adapter를 통해 canonical domain model로 변환하고, 지원 종료 version은 원문을 quarantine하며 변환 근거 없이 추측하지 않습니다. 오래된 consumer binary 또는 변환 code도 재현 가능하게 artifact와 설정을 보관합니다.
- 스키마 소유자, 영향 consumer, 승인과 deprecation 기한을 명확히 하고 계약 위반 건수·구필드 사용률·unknown enum·schema별 lag를 대시보드화합니다. 사고 시 잘못 해석된 offset 범위를 schema id와 배포 version으로 찾고, 원본을 보존한 상태에서 수정 consumer를 shadow replay해 결과 대사 후 교체합니다.
주 리전 전체 장애 후 보조 리전으로 전환하고, 복구 뒤 장애 시간대 이벤트 일부를 다시 처리해야 합니다. topic 복제만으로 충분하지 않은 이유를 설명하고, consumer offset·transactional id·중복·순서, failback과 금융 부작용을 고려한 DR 및 통제된 replay 절차를 설계해 주세요.
답변 예시 보기
DR의 단위를 데이터 복제가 아닌 처리 상태로 확장
리전 간 비동기 topic 복제는 record를 옮기지만 source와 target의 partition 수·offset은 같다는 보장이 없고, consumer group offset, schema, ACL, topic config, transaction metadata와 애플리케이션 state store까지 자동으로 복구한다고 가정할 수 없습니다. 먼저 RPO를 측정할 replication watermark와 source eventId를 두고, 보조 리전 topic·partition 수, schema/ACL/config를 평소 동기화합니다. active-active writer는 금지하므로 전환 결정 시 주 리전 producer를 fencing하거나 네트워크·권한으로 쓰기를 차단한 증거를 확보한 뒤 보조 producer를 엽니다.
전환·재처리·복귀의 단계화
- consumer 시작점은 offset 숫자를 단순 복사하지 않고 replicated source offset mapping 또는 event timestamp·eventId checkpoint로 결정합니다. 마지막 확정 업무 checkpoint보다 약간 앞에서 시작해 중복을 허용하되 DB inbox의 eventId와 aggregateVersion, 외부 PG operation id로 부작용을 막습니다. 새로운 리전의 transactional.id namespace와 instance identity를 미리 정하고, 이전 리전 zombie producer가 되살아나 쓰지 못하게 epoch 외에 인프라 fencing도 적용합니다.
- 트래픽 개방 전 canary partition 또는 읽기 전용 projection을 실행해 schema 해석, record count, key별 sequence gap, 원장 합계를 비교합니다. RPO gap이 있으면 원본 outbox/업무 DB에서 누락 eventId를 재발행하되 새 사건으로 위장하지 않고 원본 lineage를 보존합니다. 복구 완료 기준은 broker healthy가 아니라 승인 원본과 결과의 대사, lag age와 DLQ 정상화입니다.
- replay는 기존 group offset을 즉시 rewind하지 않고 runId, 입력 topic·partition별 start/end checkpoint, code/schema/model version을 고정한 별도 group과 전용 quota로 실행합니다. 결제·문자·이메일 같은 side effect는 기본 차단하고 shadow sink에서 금액·건수·checksum을 비교합니다. 승인된 보정만 원거래 eventId를 참조하는 별도 correction command로 수행해 이중 차감을 막습니다.
- failback도 두 번째 migration으로 취급합니다. 주 리전을 먼저 catch-up하고 양쪽 결과를 비교한 뒤 보조 writer를 fencing하고 명시적 barrier 이후 주 리전을 엽니다. 분기마다 리전 격리, replication 지연, offset mapping 누락, 주 리전의 늦은 재기동을 주입해 실제 RPO·RTO를 측정하고 수동 승인 단계와 연락망까지 runbook에 반영합니다.