TypeScript 미드레벨 데이터베이스 기술면접

TypeScript 미드레벨 (3~7년) 데이터베이스 3문항 조회수 28 · 2026-08-04 (화) 13:53:06
1 인덱스 설계
Medium

Q. 복합 인덱스(Composite Index)를 생성할 때 컬럼 순서가 쿼리 성능에 미치는 영향을 설명하고, WHERE 절에서 name='김철수' AND age=30 AND city='서울' 조건으로 자주 조회되는 users 테이블에 인덱스를 설계한다면 어떤 순서로 컬럼을 배치해야 하는지 카디널리티(Cardinality)와 선택도(Selectivity) 관점에서 설명해주세요. TypeScript ORM에서 이를 정의하는 방법도 함께 설명해주세요.

카디널리티가 높은 컬럼(고유값이 많은)을 앞에 배치하는 것이 일반적이지만, 실제 쿼리 패턴도 함께 고려해야 합니다.

A. 모범답안

복합 인덱스는 최좌측 컬럼부터 순차적으로 정렬되므로 컬럼 순서가 매우 중요합니다. 일반적으로 카디널리티가 높은 컬럼(고유값이 많은)을 앞에 배치하여 선택도를 높이는 것이 좋으며, name 컬럼이 가장 높은 카디널리티를 가질 가능성이 크므로 INDEX(name, age, city) 순서가 적합합니다. 하지만 실제로는 WHERE 절에서 등호(=) 조건으로 사용되는 빈도와 범위 검색 여부도 고려해야 하며, 만약 age나 city만으로 검색하는 쿼리가 있다면 해당 인덱스를 활용할 수 없습니다. TypeORM에서는 @Index(['name', 'age', 'city']) 데코레이터로, Prisma에서는 @@index([name, age, city])로 스키마에 정의할 수 있습니다. 인덱스 사용 여부는 EXPLAIN 명령어로 실행 계획을 분석하여 검증해야 합니다.

핵심 포인트
  • • 복합 인덱스는 최좌측 컬럼부터 순차 정렬되므로 순서가 중요
  • • 카디널리티가 높은 컬럼을 앞에 배치하여 선택도 향상
  • • 실제 쿼리 패턴과 WHERE 절 조건 사용 빈도 고려 필요
  • • 부분 인덱스 활용 불가능한 쿼리 패턴 파악 중요
답변에 넣으면 좋은 키워드
복합 인덱스 카디널리티 선택도 최좌측 접두사 EXPLAIN 실행 계획
실무에서는

대용량 사용자 테이블에서 다양한 조건으로 검색할 때 적절한 복합 인덱스 설계로 쿼리 성능을 수십 배 개선할 수 있습니다.

Follow-up 질문

만약 age > 25 AND city='서울' 처럼 범위 검색과 등호 조건이 섞여 있다면 인덱스 컬럼 순서를 어떻게 조정해야 할까요?

2 트랜잭션 격리 수준
Hard

Q. 전자상거래 시스템에서 재고 차감 로직을 구현할 때 Dirty Read, Non-Repeatable Read, Phantom Read가 각각 어떤 문제를 일으킬 수 있는지 구체적인 시나리오와 함께 설명하고, 이를 방지하기 위한 트랜잭션 격리 수준(Read Uncommitted, Read Committed, Repeatable Read, Serializable)별 트레이드오프를 설명해주세요. TypeScript에서 Prisma나 TypeORM을 사용할 때 격리 수준을 설정하는 방법과 낙관적 락(Optimistic Lock)을 함께 사용하는 전략도 설명해주세요.

각 격리 수준이 방지하는 현상과 성능 간의 트레이드오프를 고려하고, 재고 차감처럼 정확성이 중요한 경우 어떤 전략이 적합한지 생각해보세요.

A. 모범답안

Dirty Read는 커밋되지 않은 데이터를 읽어 재고가 롤백될 경우 과다 판매가 발생할 수 있고, Non-Repeatable Read는 같은 트랜잭션 내에서 재고를 두 번 읽었을 때 다른 값이 조회되어 로직 오류가 발생할 수 있으며, Phantom Read는 재고 집계 시 새로운 행이 추가되어 계산이 틀어질 수 있습니다. Read Committed는 Dirty Read만 방지하고, Repeatable Read는 Non-Repeatable Read까지 방지하며, Serializable은 모든 문제를 방지하지만 동시성이 크게 저하됩니다. 재고 차감처럼 정확성이 중요한 경우 Repeatable Read 수준에서 SELECT FOR UPDATE로 비관적 락을 사용하거나, Read Committed에서 version 컬럼을 활용한 낙관적 락을 사용하는 것이 일반적입니다. TypeORM에서는 queryRunner.startTransaction('REPEATABLE READ')로 격리 수준을 설정하고, Prisma에서는 isolationLevel: Prisma.TransactionIsolationLevel.RepeatableRead 옵션을 사용하며, 낙관적 락은 @VersionColumn() 데코레이터나 version 필드로 구현합니다.

핵심 포인트
  • • Dirty Read는 미커밋 데이터 읽기로 과다 판매 위험
  • • 격리 수준이 높을수록 동시성 저하, 낮을수록 정합성 위험
  • • 재고 차감은 비관적 락(SELECT FOR UPDATE) 또는 낙관적 락 사용
  • • 격리 수준은 트랜잭션 시작 시 명시적으로 설정 가능
답변에 넣으면 좋은 키워드
트랜잭션 격리 수준 Dirty Read Phantom Read 비관적 락 낙관적 락 SELECT FOR UPDATE version
실무에서는

동시에 여러 사용자가 같은 상품을 구매할 때 재고 부족 상태에서도 주문이 성공하는 오버셀링을 방지하기 위해 적절한 격리 수준과 락 전략이 필수입니다.

Follow-up 질문

낙관적 락과 비관적 락의 성능 차이는 어떻게 되며, 각각 어떤 트래픽 패턴에서 유리한가요?

3 쿼리 튜닝
Medium

Q. N+1 쿼리 문제가 무엇인지 설명하고, TypeScript ORM(TypeORM, Prisma)에서 User와 Post가 1:N 관계일 때 모든 사용자와 각 사용자의 게시글 목록을 조회하는 상황에서 N+1 문제가 발생하는 원인과 이를 해결하기 위한 방법들(Eager Loading, Join, DataLoader 패턴)을 비교 설명해주세요. 각 방법의 장단점과 적합한 사용 시나리오도 함께 설명해주세요.

반복문 안에서 개별 쿼리를 실행하는 패턴이 N+1 문제의 핵심이며, 데이터를 미리 한 번에 가져오거나 배치 로딩하는 방식으로 해결할 수 있습니다.

A. 모범답안

N+1 쿼리 문제는 N개의 엔티티를 조회한 후 각 엔티티의 연관 데이터를 가져오기 위해 N번의 추가 쿼리가 발생하여 총 N+1번의 쿼리가 실행되는 성능 문제입니다. TypeORM에서는 find 메서드에 relations 옵션을 사용하거나 leftJoinAndSelect로 JOIN 쿼리를 실행하여 한 번에 데이터를 가져올 수 있고, Prisma에서는 include 옵션으로 Eager Loading을 구현합니다. JOIN 방식은 단일 쿼리로 해결되지만 중복 데이터 전송이 발생할 수 있고, DataLoader 패턴은 GraphQL 환경에서 요청을 배치로 모아 처리하여 캐싱과 중복 제거를 제공하지만 구현 복잡도가 높습니다. 일반적으로 REST API에서는 JOIN이나 Eager Loading을, GraphQL에서는 DataLoader를 사용하며, 연관 데이터가 선택적으로 필요한 경우 Lazy Loading을 유지하고 필요 시에만 명시적으로 로딩하는 것이 좋습니다.

핵심 포인트
  • • N+1 문제는 N개 엔티티 조회 후 각각 연관 데이터를 개별 쿼리로 가져올 때 발생
  • • Eager Loading과 JOIN으로 단일 쿼리화 가능
  • • DataLoader는 배치 로딩과 캐싱으로 GraphQL 환경에 적합
  • • 과도한 JOIN은 중복 데이터 전송으로 오히려 성능 저하 가능
답변에 넣으면 좋은 키워드
N+1 쿼리 Eager Loading Lazy Loading JOIN DataLoader relations include
실무에서는

사용자 목록 API에서 각 사용자의 프로필 이미지, 최근 게시글 등을 함께 조회할 때 N+1 문제로 수백 번의 쿼리가 발생하여 응답 시간이 수 초까지 늘어나는 경우가 흔합니다.

Follow-up 질문

만약 User-Post-Comment처럼 3단계 이상 중첩된 관계에서 N+1 문제를 해결하려면 어떤 전략을 사용해야 할까요?

댓글 0

로그인 후 댓글을 작성할 수 있습니다.

아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!