대규모 시스템 설계 미드레벨 기술면접
새 면접Q. 실시간 채팅 서비스를 설계하려고 합니다. 동시 접속자 100만 명을 지원해야 하며, 메시지는 1:1 채팅과 그룹 채팅(최대 500명)을 모두 지원합니다. 메시지 전송의 실시간성을 보장하면서도 서버 부하를 최소화할 수 있는 아키텍처를 어떻게 설계하시겠습니까?
연결 유지 방식과 메시지 전달 패턴, 그리고 서버 간 통신 방법을 고려해보세요.
WebSocket을 사용하여 클라이언트와 서버 간 양방향 연결을 유지하고, Connection Server를 통해 사용자 연결을 관리합니다. 메시지는 Message Queue(Kafka, RabbitMQ)를 통해 비동기로 처리하며, 1:1 채팅은 직접 전송하고 그룹 채팅은 Fan-out 방식으로 각 멤버에게 전달합니다. Redis Pub/Sub을 활용해 여러 Connection Server 간 메시지 라우팅을 처리하고, 사용자의 온라인 상태와 현재 연결된 서버 정보를 Redis에 저장합니다. 메시지 영속성을 위해 Cassandra나 MongoDB 같은 NoSQL DB에 저장하며, 읽지 않은 메시지 카운트는 Redis로 빠르게 조회합니다. 로드밸런서는 Sticky Session을 사용하여 같은 사용자가 같은 서버에 연결되도록 하여 재연결 오버헤드를 줄입니다.
- • WebSocket 기반 실시간 양방향 통신
- • Message Queue를 통한 비동기 메시지 처리
- • Redis Pub/Sub으로 서버 간 메시지 라우팅
- • NoSQL DB를 통한 메시지 영속성 보장
카카오톡, 슬랙, 디스코드 같은 메신저 서비스의 핵심 아키텍처입니다.
사용자가 오프라인 상태일 때 메시지를 어떻게 처리하고, 다시 접속했을 때 어떻게 전달하시겠습니까?
Q. 사용자 활동 로그를 저장하는 테이블이 일 1억 건씩 증가하여 6개월 만에 180억 건이 되었습니다. 최근 30일 데이터 조회 쿼리가 점점 느려지고 있습니다. 테이블 파티셔닝을 적용한다면 어떤 전략을 사용하고, 기존 데이터는 어떻게 마이그레이션하시겠습니까?
시간 기반 데이터의 특성과 조회 패턴, 그리고 무중단 마이그레이션 방법을 생각해보세요.
Range Partitioning을 사용하여 날짜(created_at) 기준으로 일별 또는 주별 파티션을 생성합니다. 최근 데이터는 일별 파티션으로 세분화하고, 오래된 데이터는 월별로 통합하여 파티션 개수를 관리합니다. 마이그레이션은 새 파티션 테이블을 생성한 후, 애플리케이션 이중 쓰기(dual write)로 신규 데이터는 새 테이블에 저장하면서 배치 작업으로 과거 데이터를 시간대별로 나눠 복사합니다. 복사가 완료되면 읽기 트래픽을 점진적으로 새 테이블로 전환하고, 데이터 정합성 검증 후 기존 테이블을 제거합니다. 파티션 프루닝(Partition Pruning)으로 WHERE 절의 날짜 조건이 있을 때 필요한 파티션만 스캔하여 성능을 개선하고, 오래된 파티션은 DROP으로 빠르게 삭제할 수 있습니다.
- • 날짜 기준 Range Partitioning 적용
- • 이중 쓰기와 배치 복사를 통한 무중단 마이그레이션
- • 파티션 프루닝으로 쿼리 성능 개선
- • 오래된 파티션 DROP으로 효율적인 데이터 관리
로그, 이벤트, 시계열 데이터처럼 시간에 따라 급증하는 대용량 데이터를 효율적으로 관리하는 상황에서 사용됩니다.
파티셔닝 외에 오래된 데이터를 별도 Cold Storage로 이관하는 전략을 추가한다면 어떻게 설계하시겠습니까?
Q. 프로덕션 환경에서 특정 API 엔드포인트의 응답시간이 평소 200ms에서 갑자기 5초 이상으로 증가했습니다. 에러는 발생하지 않고 느리게 응답만 옵니다. CPU와 메모리 사용률은 정상이지만, DB Connection Pool이 거의 고갈 상태입니다. 어떤 순서로 원인을 진단하고 해결하시겠습니까?
DB 연결이 고갈된 원인과 느린 쿼리, 그리고 연결 반환 문제를 살펴보세요.
먼저 APM 도구나 슬로우 쿼리 로그로 실행 중인 쿼리를 확인하여 장시간 실행되는 쿼리가 있는지 파악합니다. DB의 SHOW PROCESSLIST나 pg_stat_activity로 현재 활성 세션과 Lock 대기 상태를 확인합니다. 만약 특정 쿼리가 Lock을 잡고 있다면 해당 트랜잭션을 분석하고, 인덱스 누락이나 Full Table Scan이 발생하는지 실행 계획을 확인합니다. 애플리케이션 코드에서 트랜잭션이 제대로 커밋/롤백되지 않거나 Connection이 반환되지 않는 부분이 있는지 검토합니다. 즉각적인 조치로는 문제가 되는 쿼리를 Kill하거나 해당 API를 일시적으로 Circuit Breaker로 차단하고, 근본 원인 해결을 위해 인덱스 추가, 쿼리 최적화, Connection Pool 크기 조정, 타임아웃 설정을 적용합니다.
- • 슬로우 쿼리 로그와 활성 세션 분석
- • Lock 대기와 트랜잭션 상태 확인
- • Connection 반환 누락 여부 검토
- • 즉각 조치와 근본 원인 해결 분리
트래픽 증가나 배포 후 DB 연결 고갈로 서비스 전체가 느려지는 장애 상황에서 빠른 원인 파악이 필요합니다.
이런 문제가 재발하지 않도록 사전에 모니터링하고 예방할 수 있는 방법은 무엇입니까?
Q. JWT 기반 인증 시스템을 구현할 때, Access Token과 Refresh Token을 함께 사용하는 이유와 각각의 적절한 만료 시간, 저장 위치, 그리고 Token 탈취 시 피해를 최소화하는 방법을 설명해주세요.
토큰의 수명과 보안, 그리고 사용자 경험 간의 트레이드오프를 고려해보세요.
Access Token은 API 요청마다 전송되므로 탈취 위험이 높아 짧은 만료 시간(15분~1시간)을 설정하고, Refresh Token은 Access Token 재발급 용도로만 사용하며 긴 만료 시간(7~30일)을 부여합니다. Access Token은 메모리나 SessionStorage에 저장하여 XSS 공격 시에도 지속성을 제한하고, Refresh Token은 HttpOnly, Secure, SameSite 속성의 쿠키에 저장하여 JavaScript 접근을 차단합니다. Refresh Token은 DB나 Redis에 화이트리스트로 관리하여 강제 로그아웃이나 탈취 감지 시 무효화할 수 있도록 합니다. 추가로 Refresh Token Rotation(RTR) 기법을 사용하여 재발급 시마다 새로운 Refresh Token을 발급하고 기존 것은 무효화하며, 이상 패턴(다른 IP, 여러 번 재사용) 감지 시 모든 세션을 강제 종료합니다.
- • Access Token은 짧은 만료 시간으로 탈취 피해 최소화
- • Refresh Token은 HttpOnly 쿠키로 XSS 방어
- • Refresh Token을 서버에서 화이트리스트 관리
- • Refresh Token Rotation으로 탈취 감지 및 차단
모바일 앱이나 SPA에서 안전하고 편리한 인증 시스템을 구현할 때 필수적인 설계입니다.
JWT의 Stateless 특성 때문에 강제 로그아웃이 어려운데, 이를 해결하기 위한 방법은 무엇입니까?
Q. 마이크로서비스 아키텍처에서 서비스 A가 서비스 B, C, D를 순차적으로 호출하여 응답을 조합합니다. 각 서비스는 평균 100ms가 걸려 전체 응답시간이 300ms입니다. 이를 100ms 이내로 줄이기 위한 최적화 방법을 단계별로 제시해주세요.
호출 방식, 데이터 조합 전략, 그리고 필요성 재검토 순서로 접근해보세요.
먼저 서비스 B, C, D 호출을 병렬로 전환하여 CompletableFuture나 비동기 HTTP 클라이언트를 사용해 동시에 요청하고 결과를 조합합니다. 이렇게 하면 이론적으로 100ms로 단축됩니다. 두 번째로 각 서비스가 정말 필요한 데이터만 반환하도록 API를 최적화하고, GraphQL이나 BFF(Backend For Frontend) 패턴으로 필요한 필드만 요청합니다. 세 번째로 자주 변경되지 않는 데이터는 Redis나 로컬 캐시에 저장하여 매번 서비스를 호출하지 않도록 합니다. 네 번째로 서비스 간 호출이 과도하다면 CQRS 패턴을 적용하여 조회용 Materialized View를 미리 만들어두거나, Event Sourcing으로 필요한 데이터를 서비스 A에 복제합니다. 마지막으로 Circuit Breaker와 Timeout을 설정하여 한 서비스의 지연이 전체에 영향을 주지 않도록 방어합니다.
- • 순차 호출을 병렬 호출로 전환
- • 필요한 데이터만 요청하도록 API 최적화
- • 캐싱과 데이터 복제로 서비스 간 호출 최소화
- • Circuit Breaker로 장애 전파 방지
MSA 환경에서 여러 서비스를 조합하는 API Gateway나 BFF 레이어의 성능을 개선할 때 사용됩니다.
서비스 간 데이터 정합성을 유지하면서 복제하는 방법은 무엇입니까?
Q. TCP와 UDP의 차이를 설명하고, 실시간 화상 회의 시스템에서 영상 스트리밍은 UDP를, 채팅 메시지는 TCP를 사용하는 이유를 네트워크 특성과 연결지어 설명해주세요.
신뢰성, 순서 보장, 오버헤드, 그리고 실시간성의 우선순위를 고려해보세요.
TCP는 연결 지향 프로토콜로 3-way handshake로 연결을 수립하고, 데이터 전송 순서를 보장하며, 패킷 손실 시 재전송으로 신뢰성을 제공합니다. 반면 UDP는 비연결형 프로토콜로 연결 수립 과정이 없고, 순서 보장이나 재전송 없이 빠르게 데이터를 전송합니다. 화상 회의의 영상 스트리밍은 실시간성이 중요하며, 일부 프레임이 손실되어도 다음 프레임으로 넘어가는 것이 재전송으로 지연되는 것보다 낫기 때문에 UDP를 사용합니다. 채팅 메시지는 한 글자도 빠짐없이 정확한 순서로 전달되어야 하므로 신뢰성과 순서 보장이 필요해 TCP를 사용합니다. WebRTC 같은 기술은 UDP 기반으로 영상을 전송하면서 자체적으로 FEC(Forward Error Correction)나 NACK(Negative Acknowledgement)로 품질을 보완합니다.
- • TCP는 신뢰성과 순서 보장, UDP는 빠른 전송
- • 영상은 실시간성이 우선이므로 UDP 사용
- • 채팅은 정확성이 중요하므로 TCP 사용
- • UDP 기반 실시간 통신은 별도 에러 보정 기법 적용
Zoom, Google Meet 같은 화상 회의 시스템이나 온라인 게임에서 프로토콜을 선택할 때 중요한 기준입니다.
UDP를 사용하면서도 신뢰성을 어느 정도 보장하려면 애플리케이션 레벨에서 어떤 방법을 사용할 수 있습니까?
Q. Kubernetes 환경에서 무중단 배포(Rolling Update)를 수행할 때, 새 버전 Pod가 준비되기 전에 트래픽을 받아 502 에러가 발생하는 문제가 있습니다. Readiness Probe, Liveness Probe, Startup Probe의 역할과 적절한 설정 방법을 설명해주세요.
각 Probe의 목적과 체크 시점, 그리고 애플리케이션 초기화 시간을 고려해보세요.
Readiness Probe는 Pod가 트래픽을 받을 준비가 되었는지 확인하며, 실패 시 Service의 Endpoint에서 제외하여 트래픽을 받지 않게 합니다. Liveness Probe는 Pod가 정상 동작 중인지 확인하고, 실패 시 Pod를 재시작합니다. Startup Probe는 애플리케이션 초기 시작 시간이 긴 경우 사용하며, 성공하기 전까지 Readiness/Liveness Probe를 비활성화합니다. 무중단 배포를 위해서는 Readiness Probe를 반드시 설정하고, 애플리케이션의 헬스체크 엔드포인트(/health)가 DB 연결, 캐시 연결 등 실제 준비 상태를 확인하도록 구현합니다. initialDelaySeconds는 애플리케이션 평균 시작 시간보다 약간 길게, periodSeconds는 5~10초로 설정하고, failureThreshold는 3회 정도로 설정하여 일시적 네트워크 문제에 민감하게 반응하지 않도록 합니다. RollingUpdate 전략의 maxUnavailable과 maxSurge를 조절하여 배포 속도와 안정성의 균형을 맞춥니다.
- • Readiness Probe로 트래픽 수신 준비 상태 확인
- • Liveness Probe로 비정상 Pod 자동 재시작
- • 헬스체크 엔드포인트에서 실제 의존성 확인
- • 적절한 타이밍 설정으로 오탐 방지
컨테이너 기반 서비스의 무중단 배포를 안정적으로 운영하기 위한 필수 설정입니다.
Blue-Green 배포와 Canary 배포의 차이는 무엇이며, 어떤 상황에서 각각 사용하는 것이 적절합니까?
Q. 결제 처리 로직을 테스트할 때 외부 결제 게이트웨이 API를 실제로 호출하지 않고 테스트하려고 합니다. Mock과 Stub의 차이를 설명하고, 어떤 상황에서 어떤 방식을 사용하는 것이 적절한지 예를 들어 설명해주세요.
검증 대상과 테스트의 목적, 그리고 상호작용 여부를 기준으로 생각해보세요.
Stub은 미리 정해진 응답을 반환하는 단순한 구현체로, 테스트 대상이 의존하는 객체의 동작을 대체합니다. Mock은 호출 여부, 호출 횟수, 전달된 인자 등 상호작용을 검증하는 객체입니다. 결제 금액 계산 로직을 테스트할 때는 결제 게이트웨이가 성공 응답을 반환한다고 가정하고 계산 결과만 확인하면 되므로 Stub을 사용합니다. 반면 결제 실패 시 재시도 로직을 테스트한다면, 결제 API가 정확히 3번 호출되었는지, 올바른 파라미터가 전달되었는지 검증해야 하므로 Mock을 사용합니다. Mockito 같은 프레임워크에서 when().thenReturn()은 Stub, verify()는 Mock의 특성입니다. 과도한 Mock 사용은 테스트가 구현에 강하게 결합되어 리팩토링을 어렵게 만들 수 있으므로, 상태 기반 테스트(Stub)를 우선 고려하고 상호작용 검증이 중요한 경우에만 Mock을 사용하는 것이 좋습니다.
- • Stub은 미리 정해진 응답 반환, Mock은 상호작용 검증
- • 상태 검증은 Stub, 행위 검증은 Mock 사용
- • 과도한 Mock은 테스트와 구현의 강결합 유발
- • 외부 의존성 격리로 빠르고 안정적인 테스트
외부 API, DB, 메시지 큐 등 의존성이 많은 비즈니스 로직을 빠르게 테스트할 때 필수적입니다.
통합 테스트에서는 실제 외부 API를 호출해야 할까요, 아니면 WireMock 같은 도구로 가짜 서버를 띄워야 할까요?
Q. 수백만 개의 URL을 크롤링하는 시스템에서 이미 방문한 URL을 중복 체크해야 합니다. HashSet을 사용하면 메모리가 부족하고, DB 조회는 너무 느립니다. Bloom Filter를 사용하면 어떻게 해결할 수 있는지, 동작 원리와 장단점, 그리고 False Positive 문제를 설명해주세요.
공간 효율성과 확률적 자료구조의 특성을 생각해보세요.
Bloom Filter는 비트 배열과 여러 개의 해시 함수를 사용하는 확률적 자료구조로, 원소의 존재 여부를 빠르고 공간 효율적으로 확인합니다. URL을 추가할 때 k개의 해시 함수로 k개의 인덱스를 계산하여 해당 비트를 1로 설정하고, 조회 시 모든 해시 인덱스의 비트가 1이면 존재한다고 판단합니다. 실제 데이터를 저장하지 않고 비트만 사용하므로 HashSet 대비 메모리를 1/10 이하로 줄일 수 있습니다. 단점은 False Positive가 발생할 수 있다는 것으로, 실제로는 없는데 있다고 판단할 수 있지만, False Negative는 발생하지 않습니다. 크롤링 시스템에서는 일부 URL을 중복 방문해도 크게 문제되지 않으므로 허용 가능하며, 정확성이 필요한 경우 Bloom Filter로 1차 필터링 후 DB로 2차 확인하는 방식을 사용합니다. 비트 배열 크기와 해시 함수 개수를 조절하여 False Positive 확률을 1% 이하로 낮출 수 있습니다.
- • 비트 배열과 다중 해시 함수로 공간 효율적 중복 체크
- • False Positive는 있지만 False Negative는 없음
- • 메모리를 크게 절약하며 O(1) 시간복잡도
- • 정확성이 중요하면 2단계 필터링 적용
웹 크롤러, 중복 제거, 스팸 필터, CDN 캐시 등 대용량 데이터의 빠른 존재 여부 확인이 필요한 곳에 사용됩니다.
Counting Bloom Filter는 일반 Bloom Filter와 어떻게 다르며, 어떤 경우에 사용합니까?
Q. 기술 부채가 쌓인 레거시 코드를 리팩토링해야 한다는 의견과 새 기능 개발을 우선해야 한다는 의견이 팀 내에서 충돌했던 경험이 있나요? 어떻게 의견을 조율하고 결정했으며, 그 결과는 어땠는지 구체적으로 말씀해주세요.
상황, 본인의 입장, 설득 과정, 결과, 배운 점 순서로 구성해보세요.
이 질문은 STAR 기법으로 답변하는 것이 좋습니다. 먼저 Situation에서 당시 상황과 기술 부채의 구체적인 문제(예: 배포 시간 증가, 버그 빈발)를 설명하세요. Task에서 본인이 리팩토링 또는 신규 개발 중 어느 입장이었는지, 왜 그렇게 생각했는지 이유를 밝히세요. Action에서는 데이터 기반 설득(배포 실패율, 개발 속도 저하 지표), 점진적 개선 방안 제시(스프린트의 20%를 기술 부채 해결에 할당), 프로토타입 구현 등 구체적인 설득 과정을 설명하세요. Result에서는 실제로 어떤 결정을 내렸고, 그 결과 어떤 지표가 개선되었는지(또는 예상과 달랐던 점) 공유하세요. 마지막으로 이 경험을 통해 기술 부채 관리의 중요성, 이해관계자 설득 방법, 트레이드오프 결정 기준 등 무엇을 배웠는지 정리하세요. 면접관은 논리적 사고, 커뮤니케이션 능력, 팀워크, 그리고 결과로부터 배우는 자세를 평가합니다.
- • STAR 기법으로 구조화된 답변
- • 데이터와 근거 기반의 의사결정
- • 팀 내 합의 도출 과정
- • 결과와 학습한 교훈
스타트업이나 빠른 성장 단계의 회사에서 기술 부채와 비즈니스 요구사항 사이의 균형을 맞춰야 하는 상황입니다.
만약 리팩토링을 진행했는데 예상보다 시간이 오래 걸려 일정이 지연된다면 어떻게 하시겠습니까?
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!