시니어 백엔드 엔지니어 면접 질문
시니어 백엔드 엔지니어 면접의 핵심, 자주 나오는 질문, 그리고 즉시 AI 피드백으로 연습하는 방법.
시니어 레벨에서 기대되는 것
분산 시스템 설계, 신뢰성 책임, 팀 간 아키텍처가 요구됩니다.
백엔드 엔지니어 면접 질문 예시
- 기술 면접NoSQL 저장소보다 SQL 데이터베이스를 선택하는 경우는 언제이고 왜인가요?좋은 답변이 다루는 것
- ACID 트랜잭션 보장
- 정규화된 관계형 데이터 모델
- 복잡한 조인과 집계 쿼리
- 데이터 무결성 제약 조건
- 스키마 변경의 어려움
샘플 답변 보기
SQL 데이터베이스는 데이터가 구조화되어 있고 관계가 명확하며, ACID 트랜잭션으로 데이터 무결성을 보장해야 하는 경우에 적합합니다. 예를 들어 전자상거래 주문 처리, 금융 거래, ERP 시스템 등에서는 여러 테이블 간의 조인과 복잡한 비즈니스 로직이 필요하므로 SQL이 자연스러운 선택입니다. 또한 SQL은 강력한 쿼리 언어로 보고서 생성이나 분석에 유리합니다. 반면 NoSQL은 스키마가 유연하고 수평 확장이 쉬워 대량의 비정형 데이터나 빠른 읽기/쓰기가 필요한 경우에 좋습니다. 하지만 SQL은 수직 확장에 의존하는 경향이 있어 초대규모 트래픽에서는 NoSQL에 비해 확장성이 떨어질 수 있습니다. 실제로 많은 시스템에서 읽기 부하를 위해 캐싱과 읽기 전용 복제본을 도입하는데, 이는 NoSQL보다 SQL의 확장 비용이 더 크다는 것을 의미합니다. 결국 선택은 데이터 모델의 복잡성과 일관성 요구사항에 달려 있습니다.
- 기술 면접데이터베이스 인덱스는 어떻게 작동하며 트레이드오프는 무엇인가요?좋은 답변이 다루는 것
- B-트리 구조와 O(log n) 탐색
- 클러스터형 vs 비클러스터형 인덱스
- 커버링 인덱스와 인덱스 스캔
- 쓰기 성능 저하와 저장 공간 증가
- 인덱스 설계 시 선택도와 카디널리티
샘플 답변 보기
데이터베이스 인덱스는 흔히 B-트리 구조로 구현되며, 각 노드가 여러 키를 가지고 균형을 유지하여 탐색 시간을 O(log n)으로 보장합니다. 인덱스는 특정 컬럼에 대해 정렬된 구조를 미리 만들어 전체 테이블 스캔 없이 빠르게 레코드를 찾을 수 있게 합니다. 주요 트레이드오프는 쓰기 작업(INSERT, UPDATE, DELETE) 시 인덱스도 함께 갱신해야 하므로 성능이 저하된다는 점입니다. 또한 인덱스를 저장하기 위한 추가 디스크 공간이 필요하며, 인덱스가 많을수록 유지보수 비용이 커집니다. 특히 복합 인덱스에서 컬럼 순서가 중요하며, 선택도가 낮은 컬럼에 인덱스를 만들면 효율이 떨어집니다. 흔한 실수는 모든 컬럼에 인덱스를 생성하는 것인데, 이는 쓰기 성능을 크게 떨어뜨리고 쿼리 옵티마이저가 올바른 인덱스를 선택하기 어렵게 만듭니다. 따라서 인덱스는 자주 조회되는 컬럼과 WHERE, JOIN 조건에 선별적으로 생성해야 합니다.
- 기술 면접결제 엔드포인트를 멱등하게 만드는 방법을 설명하세요.좋은 답변이 다루는 것
- 멱등성 키(Idempotency Key) 도입
- 클라이언트에서 고유 키 전송
- 서버 측에서 키와 처리 상태 저장
- 데이터베이스 unique constraint 활용
- 중복 요청 시 동일 응답 반환
샘플 답변 보기
결제 엔드포인트를 멱등하게 만들기 위해 가장 일반적인 방법은 클라이언트가 요청마다 고유한 멱등성 키(예: UUID)를 헤더에 포함시켜 보내는 것입니다. 서버는 이 키를 받으면 먼저 데이터베이스에서 해당 키가 이미 처리되었는지 확인합니다. 만약 처리된 요청이라면 기존 응답을 그대로 반환하고, 그렇지 않으면 결제를 진행한 후 키와 함께 응답을 저장합니다. 이때 키에 unique constraint를 걸어 동시에 들어온 중복 요청이 두 번 처리되는 것을 방지합니다. 또한 결제 상태를 추적하기 위해 'pending' 상태를 도입하고, 분산 환경에서는 redis나 데이터베이스 락을 사용하여 race condition을 해결할 수 있습니다. 한 가지 주의할 점은 멱등성 키의 만료 정책입니다. 키가 영원히 유지되면 저장 공간이 커지므로 일정 시간 후에 삭제하되, 그 사이에 중복 요청이 올 수 없도록 적절한 TTL을 설정해야 합니다. 또한 멱등성 키는 반드시 서버에서 생성된 것이 아니라 클라이언트가 책임지고 고유함을 보장해야 합니다.
- 코딩O(1) get과 put을 갖는 LRU 캐시를 구현하세요.좋은 답변이 다루는 것
- 해시맵과 이중 연결 리스트 사용
- get: 존재 시 리스트 맨 앞으로 이동 O(1)
- put: 용량 초과 시 리스트 끝 제거 O(1)
- 리스트 노드와 맵의 키-노드 매핑
- Python collections.OrderedDict 대체 가능
샘플 답변 보기
LRU 캐시는 가장 최근에 사용된 항목을 순서대로 유지하고, 용량이 가득 차면 가장 오래된 항목을 제거합니다. O(1) get과 put을 위해 해시맵과 이중 연결 리스트를 결합합니다. 해시맵은 키를 노드에 매핑하여 빠른 접근을 제공하고, 이중 연결 리스트는 노드의 삽입과 삭제를 상수 시간에 수행합니다. get 연산은 해시맵에서 노드를 조회한 후, 해당 노드를 리스트의 맨 앞으로 이동시킵니다. put 연산은 키가 이미 있으면 값을 갱신하고 노드를 맨 앞으로 이동시키며, 없으면 새 노드를 생성하여 맨 앞에 추가합니다. 이때 용량이 초과되면 리스트의 맨 끝 노드를 제거하고 해시맵에서도 삭제합니다. 자주 하는 실수는 더미 헤드와 테일 노드를 사용하지 않아 경계 조건을 복잡하게 만드는 것입니다. 아래는 Python 구현입니다.
참고 코드python class ListNode: def __init__(self, key=0, value=0): self.key = key self.value = value self.prev = None self.next = None class LRUCache: def __init__(self, capacity: int): self.capacity = capacity self.cache = {} # 키 -> 노드 매핑 # 더미 헤드와 테일 self.head = ListNode() self.tail = ListNode() self.head.next = self.tail self.tail.prev = self.head def _remove_node(self, node): # 이중 연결 리스트에서 노드 제거 prev = node.prev nxt = node.next prev.next = nxt nxt.prev = prev def _add_to_head(self, node): # 가장 최근 사용 노드를 헤드 바로 뒤에 추가 node.prev = self.head node.next = self.head.next self.head.next.prev = node self.head.next = node def get(self, key: int) -> int: if key in self.cache: node = self.cache[key] self._remove_node(node) self._add_to_head(node) return node.value return -1 def put(self, key: int, value: int) -> None: if key in self.cache: node = self.cache[key] node.value = value self._remove_node(node) self._add_to_head(node) else: if len(self.cache) >= self.capacity: # LRU 항목 제거: 테일의 바로 앞 노드 lru_node = self.tail.prev self._remove_node(lru_node) del self.cache[lru_node.key] new_node = ListNode(key, value) self.cache[key] = new_node self._add_to_head(new_node) # 시간 복잡도: get O(1), put O(1) # 공간 복잡도: O(capacity) - 코딩이벤트 스트림이 주어질 때 API용 레이트 리미터를 설계하세요.좋은 답변이 다루는 것
- 슬라이딩 윈도우 카운터 (Redis Sorted Set 활용)
- 토큰 버킷 알고리즘 (고정 속도)
- 분산 환경에서의 원자적 증가 (Redis INCR)
- 레이트 리미터 계층 구조 (전역/사용자별)
- 백프레셔와 에러 응답 (429 Too Many Requests)
샘플 답변 보기
API 레이트 리미터를 설계할 때는 먼저 제한 기준(예: 초당 요청 수, 분당 요청 수)과 범위(전역 또는 사용자별)를 결정해야 합니다. 가장 간단한 구현은 슬라이딩 윈도우 카운터를 Redis Sorted Set으로 유지하는 것입니다. 각 요청이 들어올 때 현재 타임스탬프를 Set에 추가하고, 윈도우 밖의 타임스탬프는 제거합니다. 그 후 Set의 크기가 임계값을 초과하면 요청을 거부합니다. 이 방법은 시간 경계에서의 폭주를 방지할 수 있습니다. 다른 방법으로 토큰 버킷 알고리즘은 일정 속도로 토큰이 채워지는 버킷을 사용하여 요청 시 토큰을 소비합니다. 빈 버킷이면 요청을 거부합니다. 분산 환경에서는 Redis의 INCR 명령어와 TTL을 사용하여 각 사용자별 카운터를 관리할 수 있습니다. 또한 레이트 리미터는 여러 계층으로 구성하여 특정 API나 IP에 대해 세밀한 제어를 할 수 있습니다. 중요한 점은 레이트 리미터 자체가 병목이 되지 않도록 인메모리 캐시(예: 로컬 카운터와 Redis 동기화)를 고려해야 합니다. 마지막으로 429 상태 코드와 함께 Retry-After 헤더를 반환하여 클라이언트가 재시도 시간을 알 수 있도록 해야 합니다.
- 시스템 설계수십억 건의 리디렉션을 처리하는 URL 단축기를 설계하세요.좋은 답변이 다루는 것
- 해시 함수 (Base62 인코딩)로 단축 키 생성
- 데이터베이스 샤딩 (범위 또는 해시 기반)
- 캐싱 계층 (Redis)으로 읽기 성능 향상
- 리디렉션 시 301 vs 302 선택
- 장기적 확장성: ID 생성기 (Snowflake 등)
샘플 답변 보기
수십억 개의 URL을 처리하는 URL 단축기는 먼저 단축 키를 생성해야 합니다. 일반적으로 긴 URL을 해싱하여 Base62 인코딩으로 짧은 문자열(예: 7자)을 만듭니다. 충돌 방지를 위해 데이터베이스에 저장하기 전에 유일성을 확인하거나, 전역 고유 ID 생성기(예: Snowflake)로 ID를 할당한 후 Base62로 변환하는 방법도 있습니다. 데이터베이스는 수평 분할(샤딩)이 필요하며, 범위 기반 또는 해시 기반 샤딩을 고려할 수 있습니다. 읽기 성능을 높이기 위해 Redis와 같은 캐시를 도입하여 자주 접근되는 URL을 캐싱합니다. 리디렉션은 301 Moved Permanently(영구 리디렉션, 브라우저 캐싱) 또는 302 Found(임시 리디렉션, 분석 가능) 중 선택하며, 분석이 필요하면 302를 사용합니다. 또한 클릭 통계를 위해 이벤트 로그를 별도로 수집하고, 배치 처리로 집계합니다. 병목 지점은 ID 생성기와 데이터베이스 쓰기이며, ID 생성기를 분산 환경에서 유일하게 유지하기 위해 Zookeeper나 Redis 원자적 증가를 사용할 수 있습니다. 트래픽이 매우 많으면 CDN을 통해 리디렉션을 처리할 수도 있습니다.
- 행동 면접해결에 기여한 장애와 그 후 바꾼 점을 설명하세요.좋은 답변이 다루는 것
- STAR 기법: 상황, 과제, 행동, 결과
- 자신의 실수 인정과 학습 과정
- 구체적인 기술적 조치 (모니터링, 알람, 코드 리뷰)
- 팀 협업과 커뮤니케이션 개선
- 장애 후 시스템 안정성 향상
샘플 답변 보기
제가 기여한 장애는 프로덕션 배포 중 데이터베이스 마이그레이션 스크립트에서 인덱스를 잘못 추가한 것이었습니다. 상황은 온라인 서비스 중인 테이블에 대량의 인덱스를 생성하면서 쓰기 성능이 급격히 저하되어 전체 서비스 타임아웃이 발생했습니다. 제가 맡은 과제는 이 문제를 빠르게 인지하고 롤백하는 것이었습니다. 행동으로는 즉시 배포를 롤백하고 해당 인덱스를 삭제하여 서비스를 복구했습니다. 이후 장애 분석을 통해 인덱스 생성 시점을 피크 시간이 아닌 유지보수 시간으로 변경하고, 사전에 테스트 환경에서 성능 영향을 측정하는 프로세스를 도입했습니다. 또한 변경 사항에 대한 코드 리뷰 시 데이터베이스 영향 평가를 필수 단계로 추가했습니다. 결과적으로 이후 비슷한 장애가 발생하지 않았고, 팀 내에서 데이터베이스 변경에 대한 모범 사례가 공유되었습니다. 이 경험을 통해 대규모 시스템에서는 사소한 변경이라도 충분한 테스트와 모니터링이 필요하다는 것을 배웠습니다.
- 행동 면접어려운 데이터 일관성 트레이드오프를 내려야 했던 경험을 말해 주세요.좋은 답변이 다루는 것
- STAR 기법 사용
- 트레이드오프: 일관성 vs 가용성 (CAP 정리)
- 구체적인 예: 최종 일관성과 강한 일관성 선택
- 비즈니스 요구사항 기반 결정
- 기술적 해결책: 분산 락, 버전 벡터, 보상 트랜잭션
샘플 답변 보기
어려운 데이터 일관성 트레이드오프는 실시간 알림 시스템에서 발생했습니다. 요구사항은 사용자에게 알림을 보낼 때 중복을 최소화하면서도 높은 가용성을 유지하는 것이었습니다. 강한 일관성을 적용하면 알림 전송 전에 데이터베이스에서 확인하는 과정이 추가되어 지연이 발생하고, 서비스 장애 시 알림이 유실될 위험이 있었습니다. 반면 최종 일관성을 선택하면 중복 알림이 발생할 수 있었습니다. 저는 비즈니스 측면에서 가끔 중복 알림이 발생해도 사용자 경험에 큰 영향을 주지 않는다고 판단하여 최종 일관성을 선택했습니다. 구체적으로 알림 전송 이벤트를 메시지 큐에 발행하고, 컨슈머에서 중복 체크를 위해 Redis에 전송 기록을 임시 저장했습니다. Redis가 다운되면 일시적으로 중복이 발생할 수 있지만, 이후 보상 로직으로 중복 알림을 취소할 수 있도록 설계했습니다. 이 결정을 팀과 공유하고, 모니터링을 통해 중복 발생률을 추적했습니다. 결과적으로 시스템 가용성은 99.99%를 유지했고, 중복률은 0.01% 미만으로 비즈니스 요구사항을 만족했습니다. 이 경험으로 완벽한 일관성보다는 상황에 맞는 트레이드오프를 선택하는 것이 중요하다는 것을 깨달았습니다.
면접관이 평가하는 것
데이터 모델링
관계형 대 NoSQL, 인덱싱, 정규화, 트랜잭션.
API 설계
REST/GraphQL/gRPC, 멱등성, 페이지네이션, 버저닝.
동시성
락, 경쟁 상태, 큐, 최종 일관성.
시스템 설계
대규모에서의 캐싱, 샤딩, 복제, 장애 처리.
알고리즘
복잡도 분석과 실용적인 자료 구조 선택.
준비 방법
- 설계 전에 가정과 제약을 명시하세요 — 면접관은 범위 설정을 높이 평가합니다.
- 코딩 답변에서는 항상 시간 및 공간 복잡도를 분석하세요.
- 시스템 설계에서는 대화를 주도하세요: 요구사항, API, 데이터 모델, 규모, 장애 모드.
자주 묻는 질문
백엔드 면접에서 어떤 시스템 설계가 나오나요?
URL 단축기, 레이트 리미터, 뉴스 피드, 채팅 시스템 설계가 흔하며 캐싱·샤딩·일관성 논의를 동반합니다.
백엔드 면접에는 얼마나 많은 알고리즘 지식이 필요한가요?
일상 업무가 시스템 중심이더라도 대부분의 회사는 자료 구조와 복잡도에 초점을 둔 코딩 라운드를 한두 번 포함합니다.
백엔드 면접을 효과적으로 연습하려면?
코딩 연습과 말로 하는 시스템 설계 연습을 결합하고, 모의 면접으로 트레이드오프를 소리 내어 방어할 수 있게 하세요.
백엔드 엔지니어 질문을 AI의 즉각적인 피드백으로 연습
Offersly는 당신의 이력서와 목표 직무에 맞춘 모의 면접을 진행하고, 모든 답변을 관련성·깊이·명확성·정확성으로 채점합니다.