TypeScript 주니어 시스템 설계 기술면접
새 면접Q. 실시간 채팅 애플리케이션의 메시지 전송 API를 설계한다고 가정합니다. WebSocket과 REST API를 함께 사용하는 하이브리드 구조에서, 각각 어떤 기능을 담당하게 할 것이며, 메시지 송수신을 위한 TypeScript 인터페이스는 어떻게 정의하시겠습니까?
WebSocket은 실시간 메시지 송수신과 타이핑 인디케이터 등 즉각적인 양방향 통신이 필요한 기능을 담당하고, REST API는 메시지 히스토리 조회, 채팅방 생성/수정/삭제 등 일반적인 CRUD 작업을 담당합니다. 메시지 인터페이스는 공통 필드(id, content, senderId, timestamp)를 정의하고, WebSocket 이벤트 타입은 union type으로 'message.send', 'message.received', 'typing.start' 등을 구분합니다. 에러 처리를 위해 WebSocket 응답에도 success/error 상태를 포함하는 래퍼 타입을 정의합니다.
- • WebSocket은 실시간 양방향 통신, REST는 CRUD 작업으로 역할 분리
- • 공통 메시지 인터페이스 정의 및 타입 재사용
- • WebSocket 이벤트를 union type으로 타입 안전하게 관리
- • 에러 처리를 위한 응답 래퍼 타입 설계
WebSocket 연결이 끊어졌을 때 메시지 손실을 방지하기 위한 재연결 및 메시지 큐 전략을 어떻게 설계하시겠습니까?
Q. 소셜 미디어 플랫폼에서 게시글에 대한 '좋아요'와 '북마크' 기능을 구현하려고 합니다. 사용자가 누가 좋아요를 눌렀는지 확인할 수 있고, 좋아요 개수도 빠르게 조회해야 합니다. 이를 위한 테이블 설계와 TypeScript 타입 정의를 제시하고, 성능 최적화를 위해 고려해야 할 점을 설명해주세요.
Post 테이블에 likeCount, bookmarkCount 컬럼을 추가하여 집계 데이터를 비정규화하고, 별도로 PostLike, PostBookmark 중간 테이블을 생성하여 사용자별 관계를 저장합니다. PostLike 테이블은 postId와 userId를 복합 유니크 키로 설정하고, 두 컬럼에 인덱스를 생성합니다. TypeScript에서는 Post 타입에 likeCount를 포함하고, PostLike는 postId, userId, createdAt을 포함하는 별도 타입으로 정의합니다. 좋아요 토글 시 트랜잭션을 사용하여 PostLike 삽입/삭제와 Post의 likeCount 증감을 원자적으로 처리합니다.
- • 집계 데이터를 Post 테이블에 비정규화하여 조회 성능 최적화
- • 중간 테이블로 사용자별 관계 관리 및 복합 유니크 키 설정
- • 인덱스 전략으로 조회 성능 향상
- • 트랜잭션으로 카운트와 관계 데이터의 일관성 보장
좋아요가 수십만 개인 인기 게시글에서 '좋아요를 누른 사용자 목록'을 페이지네이션으로 조회할 때 발생할 수 있는 성능 문제와 해결 방안은 무엇인가요?
Q. 파일 업로드 기능을 제공하는 API를 설계할 때 고려해야 할 보안 사항들은 무엇인가요? 파일 타입 검증, 크기 제한, 저장 경로 설정 등을 TypeScript 코드 레벨에서 어떻게 구현하고, 악의적인 파일 업로드를 방지하기 위한 방법을 설명해주세요.
파일 확장자뿐 아니라 MIME 타입과 매직 넘버를 함께 검증하여 파일 타입을 확인하고, TypeScript enum으로 허용된 파일 타입을 정의합니다. 파일 크기는 미들웨어 레벨에서 제한하고, 업로드된 파일명은 UUID 등으로 변경하여 원본 파일명을 그대로 사용하지 않습니다. 파일은 웹 루트 외부의 별도 디렉토리에 저장하고, 다운로드 시에는 Content-Disposition 헤더를 설정합니다. 이미지의 경우 리사이징 과정에서 메타데이터를 제거하고, 바이러스 스캔 API를 통합하는 것도 고려합니다.
- • 확장자, MIME 타입, 매직 넘버 다중 검증으로 파일 타입 확인
- • 파일명 무작위화 및 웹 루트 외부 저장으로 경로 조작 방지
- • 크기 제한 및 TypeScript enum으로 허용 타입 명시적 관리
- • 메타데이터 제거 및 바이러스 스캔 통합
대용량 파일 업로드 시 멀티파트 업로드나 청크 업로드를 구현할 때, 중간에 실패한 업로드를 처리하는 전략과 TypeScript 타입 설계는 어떻게 하시겠습니까?
Q. 사용자별로 맞춤화된 뉴스피드를 제공하는 API가 있습니다. 각 사용자마다 다른 콘텐츠를 보여줘야 하지만, 일부 데이터는 여러 사용자에게 공통으로 사용됩니다. 이런 상황에서 효율적인 캐싱 전략을 설계하고, TypeScript로 캐시 키 생성 로직과 다층 캐시 구조를 어떻게 구현할지 설명해주세요.
공통 데이터(게시글 정보, 작성자 프로필)와 개인화 데이터(팔로우 관계, 읽음 여부)를 분리하여 캐싱합니다. 공통 데이터는 postId를 키로 사용하고 TTL을 길게 설정하며, 개인화 피드는 userId와 페이지 정보를 조합한 키를 사용하고 TTL을 짧게 설정합니다. TypeScript에서는 제네릭 CacheService 클래스를 만들어 키 생성 함수를 주입받고, 캐시 히트/미스 로직을 추상화합니다. 다층 캐시는 인메모리 캐시(빠른 접근)와 Redis(영속성)를 조합하여, 먼저 인메모리를 확인하고 없으면 Redis를 조회하는 waterfall 방식으로 구현합니다.
- • 공통 데이터와 개인화 데이터를 분리하여 캐시 효율성 극대화
- • 캐시 키 네이밍 전략과 TTL 차등 적용
- • 제네릭을 활용한 타입 안전한 캐시 서비스 추상화
- • 인메모리와 Redis 다층 캐시로 성능과 안정성 확보
실시간으로 좋아요나 댓글이 추가될 때 관련된 여러 캐시를 동시에 무효화해야 한다면, 어떤 패턴을 사용하여 캐시 일관성을 유지하시겠습니까?
Q. 예약 시스템에서 동시에 여러 사용자가 같은 시간대를 예약하려고 할 때 발생하는 동시성 문제를 해결해야 합니다. 낙관적 락과 비관적 락의 차이를 설명하고, TypeScript와 데이터베이스 레벨에서 각각을 어떻게 구현할 수 있는지, 그리고 어떤 상황에서 어느 방식을 선택해야 하는지 설명해주세요.
비관적 락은 트랜잭션 시작 시 SELECT FOR UPDATE로 행을 잠그고 다른 트랜잭션의 접근을 차단하는 방식이며, 동시성이 높고 충돌이 빈번한 경우 적합합니다. 낙관적 락은 version 컬럼을 추가하여 UPDATE 시 version을 확인하고 변경되었으면 실패시키는 방식이며, 충돌이 드물고 읽기가 많은 경우 적합합니다. TypeScript에서는 Reservation 타입에 version 필드를 추가하고, 업데이트 시 WHERE 조건에 version을 포함하여 affected rows가 0이면 재시도하거나 에러를 반환합니다. 예약 시스템처럼 충돌이 빈번한 경우 비관적 락을 사용하고, 트랜잭션을 최대한 짧게 유지하여 데드락을 방지합니다.
- • 비관적 락은 SELECT FOR UPDATE로 선점, 낙관적 락은 version으로 충돌 감지
- • 동시성이 높으면 비관적 락, 충돌이 드물면 낙관적 락 선택
- • TypeScript 타입에 version 필드 포함 및 업데이트 로직 구현
- • 트랜잭션 격리 수준과 데드락 방지 전략 고려
예약 시스템을 마이크로서비스로 분리하여 여러 인스턴스가 동작할 때, 분산 환경에서 동시성 제어를 위해 Redis 분산 락을 어떻게 구현하시겠습니까?
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!