TypeScript 리드·아키텍트 데이터베이스 면접
새 면접Q. 수백만 사용자를 보유한 전자상거래 플랫폼에서 단일 테이블로 관리되던 주문 데이터를 주문 헤더와 주문 상세로 분리하는 스키마 변경을 무중단으로 수행해야 합니다. 기존 애플리케이션은 TypeScript로 작성되어 있으며 ORM(TypeORM 또는 Prisma)을 사용 중입니다. 이러한 대규모 스키마 마이그레이션을 안전하게 수행하기 위한 단계별 전략과, 각 단계에서 발생할 수 있는 데이터 정합성 이슈 및 TypeScript 타입 시스템을 활용한 안전장치를 설명해주세요.
Expand-Contract 패턴과 Dual Write 전략을 고려하고, 마이그레이션 중 양쪽 스키마를 모두 지원하는 과도기적 구현을 생각해보세요.
무중단 스키마 마이그레이션은 Expand-Contract(Parallel Change) 패턴을 따릅니다. 먼저 새로운 테이블(주문 헤더, 주문 상세)을 생성하고 기존 테이블과 병행 운영하며, 애플리케이션 레이어에서 Dual Write를 구현하여 양쪽에 동시 기록합니다. TypeScript에서는 Union 타입과 타입 가드를 활용해 구 스키마와 신 스키마를 모두 처리할 수 있는 Repository 패턴을 구현하고, Feature Flag로 읽기 소스를 점진적으로 전환합니다. 데이터 동기화는 별도의 백그라운드 작업으로 수행하며, 체크섬 검증으로 정합성을 보장합니다. 모든 데이터가 마이그레이션되고 신규 스키마로 완전히 전환된 후, 구 테이블과 Dual Write 로직을 제거하는 Contract 단계를 진행합니다. 각 단계마다 롤백 계획을 수립하고, TypeScript의 Discriminated Union을 활용해 컴파일 타임에 스키마 버전 불일치를 감지할 수 있도록 설계합니다.
- • Expand-Contract 패턴을 통한 단계적 마이그레이션
- • Dual Write 전략과 데이터 정합성 검증
- • TypeScript 타입 시스템을 활용한 스키마 버전 관리
- • Feature Flag 기반 점진적 전환 및 롤백 전략
대규모 서비스의 레거시 스키마 개선이나 샤딩 도입 시 무중단 마이그레이션이 필수적으로 요구됩니다.
Dual Write 과정에서 한쪽 데이터베이스 쓰기가 실패했을 때 어떻게 처리하시겠습니까? 보상 트랜잭션과 이벤트 소싱 중 어떤 접근을 선호하시나요?
Q. TypeScript 기반 B2B SaaS 플랫폼에서 다중 테넌트 아키텍처를 사용 중입니다. 각 테넌트는 수십만 건의 레코드를 보유하며, 대부분의 쿼리는 tenant_id로 필터링됩니다. 최근 특정 테넌트의 복합 검색 쿼리(날짜 범위, 상태, 카테고리 필터 조합)에서 성능 저하가 발생했습니다. 인덱스 전략을 재설계할 때 고려해야 할 요소들과, Covering Index와 Partial Index의 활용 방안, 그리고 TypeScript 코드 레벨에서 인덱스 힌트를 관리하는 방법을 설명해주세요.
다중 테넌트 환경에서 tenant_id의 카디널리티 특성과 복합 인덱스의 컬럼 순서가 쿼리 패턴과 어떻게 매칭되어야 하는지 생각해보세요.
다중 테넌트 환경에서는 tenant_id를 복합 인덱스의 선두 컬럼으로 배치하여 테넌트별 데이터 격리를 효율화합니다. 복합 검색 쿼리의 경우 실제 쿼리 패턴을 분석하여 (tenant_id, created_at, status) 또는 (tenant_id, category, status)처럼 자주 사용되는 조합별로 인덱스를 생성합니다. Covering Index를 활용하면 SELECT 절의 컬럼까지 인덱스에 포함시켜 테이블 액세스를 제거할 수 있습니다. Partial Index는 특정 상태(예: active)만 자주 조회되는 경우 WHERE status = 'active' 조건으로 인덱스 크기를 줄이고 성능을 향상시킵니다. TypeScript에서는 Repository 패턴에 쿼리 빌더를 추상화하고, 각 메서드에 JSDoc으로 사용되는 인덱스를 명시하여 개발자가 인덱스를 인지하도록 합니다. 또한 타입 레벨에서 필수 필터(tenant_id)를 강제하는 인터페이스를 설계하여 인덱스를 활용하지 못하는 쿼리를 컴파일 타임에 방지합니다.
- • 다중 테넌트 환경에서 tenant_id 기반 복합 인덱스 설계
- • 쿼리 패턴 분석을 통한 Covering Index와 Partial Index 활용
- • TypeScript 타입 시스템으로 인덱스 활용 쿼리 강제
- • Repository 패턴에서 인덱스 전략 문서화 및 추상화
B2B SaaS나 멀티테넌트 플랫폼에서 테넌트별 데이터 격리와 복합 검색 성능을 동시에 만족시켜야 하는 상황에서 필수적입니다.
인덱스가 많아질수록 INSERT/UPDATE 성능이 저하되는데, 읽기와 쓰기 성능 사이의 트레이드오프를 어떻게 판단하고 관리하시겠습니까?
Q. TypeScript로 구현된 재고 관리 시스템에서 동일 상품에 대한 동시 주문 처리 시 재고 부족 상태임에도 초과 판매가 발생하는 문제가 간헐적으로 보고되고 있습니다. 현재 PostgreSQL의 기본 격리 수준(Read Committed)을 사용 중이며, 재고 차감 로직은 SELECT로 재고 확인 후 UPDATE로 차감하는 방식입니다. 이 문제를 해결하기 위한 여러 접근법(격리 수준 변경, 비관적 락, 낙관적 락, 애플리케이션 레벨 락)의 장단점을 비교하고, TypeScript 환경에서 각 방법을 구현할 때의 고려사항과 추천하는 솔루션을 제시해주세요.
SELECT와 UPDATE 사이의 시간 간격에서 발생하는 Lost Update 문제와, 각 해결책의 성능 및 데드락 가능성을 고려해보세요.
재고 초과 판매는 Read Committed 격리 수준에서 SELECT-UPDATE 사이의 Lost Update 문제입니다. Serializable 격리 수준으로 변경하면 완벽한 격리를 보장하지만 동시성이 크게 저하되고 직렬화 오류가 빈번합니다. 비관적 락(SELECT FOR UPDATE)은 재고 조회 시점에 행 잠금을 획득하여 다른 트랜잭션을 대기시키므로 데이터 정합성을 보장하지만, 락 경합으로 인한 대기 시간과 데드락 위험이 있습니다. 낙관적 락(version 컬럼)은 충돌 시 재시도가 필요하며 충돌률이 높은 재고 시스템에서는 비효율적입니다. Redis 등 외부 저장소를 활용한 분산 락은 데이터베이스 부하를 분산하지만 복잡도가 증가합니다. 재고 시스템에서는 SELECT FOR UPDATE를 사용한 비관적 락이 가장 적합하며, TypeScript에서는 트랜잭션 범위를 최소화하고 락 획득 타임아웃을 설정하여 데드락을 방지합니다. 또한 재고 차감을 원자적 UPDATE(SET quantity = quantity - 1 WHERE quantity >= 1)로 변경하여 SELECT를 제거하는 방법도 효과적입니다.
- • Read Committed에서 발생하는 Lost Update 문제 이해
- • 비관적 락(SELECT FOR UPDATE)을 통한 동시성 제어
- • 원자적 UPDATE 연산으로 SELECT-UPDATE 간극 제거
- • 트랜잭션 범위 최소화 및 타임아웃 설정으로 데드락 방지
전자상거래, 티켓 예매, 한정 상품 판매 등 동시 접근이 많은 재고 관리 시스템에서 필수적으로 다뤄야 하는 문제입니다.
재고가 여러 창고에 분산되어 있고 분산 트랜잭션이 필요한 경우, Saga 패턴과 2PC 중 어떤 것을 선택하시겠습니까? 그 이유는 무엇인가요?
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!