Next.js 리드·아키텍트 데이터베이스 면접

Next.js 리드 · 아키텍트 (10년+) 데이터베이스 5문항 조회수 111 · 2026-08-05 (수) 12:11:47
1 데이터 모델링 및 스키마 설계
Hard

Q. Next.js 기반 전자상거래 플랫폼을 설계하는데, 상품은 다양한 옵션(색상, 사이즈 등) 조합을 가지며 각 조합마다 재고와 가격이 다릅니다. 또한 향후 동적으로 새로운 속성이 추가될 가능성이 높습니다. EAV(Entity-Attribute-Value) 모델과 JSON 컬럼 방식, 그리고 정규화된 관계형 모델의 트레이드오프를 비교하고, 각 접근법에서 재고 차감 트랜잭션의 동시성 제어와 쿼리 성능 측면의 장단점을 설명해주세요. 실무에서 어떤 하이브리드 전략을 선택하시겠습니까?

각 모델링 방식이 스키마 유연성, 쿼리 복잡도, 인덱싱 효율성, 트랜잭션 격리 측면에서 어떻게 다른지 생각해보세요.

A. 모범답안

EAV 모델은 스키마 유연성이 높지만 JOIN 연산이 과도하게 발생하고 타입 안정성이 떨어지며, 복합 조건 검색 시 성능이 급격히 저하됩니다. PostgreSQL의 JSONB 컬럼은 GIN 인덱스를 통해 특정 속성 검색이 가능하지만, 부분 업데이트 시 전체 JSON을 다시 쓰는 오버헤드가 있고 트랜잭션 격리 수준에서 행 단위 잠금만 가능해 속성별 동시성 제어가 어렵습니다. 정규화된 관계형 모델은 쿼리 성능과 데이터 무결성이 우수하나 스키마 변경 비용이 높습니다. 실무에서는 핵심 속성(가격, 재고)은 정규화된 컬럼으로, 가변 속성은 JSONB로 관리하는 하이브리드 방식을 선택하며, 재고 차감은 SELECT FOR UPDATE로 행 잠금을 확보한 후 원자적 업데이트를 수행합니다. 검색 성능이 중요한 속성은 Generated Column으로 추출해 인덱스를 생성하는 전략을 병행합니다.

핵심 포인트
  • • EAV는 유연하지만 JOIN 과다와 쿼리 복잡도 증가
  • • JSONB는 GIN 인덱스 지원하나 부분 업데이트와 행 단위 잠금 제약
  • • 정규화 모델은 성능과 무결성 우수하나 스키마 변경 비용 높음
  • • 하이브리드 전략: 핵심 속성은 컬럼, 가변 속성은 JSONB
  • • Generated Column으로 검색 속성 추출 및 인덱싱
답변에 넣으면 좋은 키워드
EAV JSONB GIN 인덱스 Generated Column SELECT FOR UPDATE 스키마 유연성 트랜잭션 격리
실무에서는

이커머스나 SaaS 플랫폼에서 다양한 고객의 커스텀 필드 요구사항을 수용하면서도 핵심 비즈니스 로직의 성능을 유지해야 할 때 사용됩니다.

Follow-up 질문

JSONB 컬럼에 저장된 중첩된 배열 구조에서 특정 조건을 만족하는 항목을 효율적으로 검색하려면 어떤 인덱스 전략을 사용하시겠습니까?

2 쿼리 최적화 및 실행 계획
Hard

Q. Next.js API에서 사용자별 대시보드 데이터를 조회하는 쿼리가 있습니다. 이 쿼리는 5개 테이블을 JOIN하고 WHERE 조건에 날짜 범위와 상태 필터가 있으며, 결과를 페이지네이션합니다. EXPLAIN ANALYZE 결과 Seq Scan이 발생하고 있지만, 해당 컬럼에는 이미 인덱스가 존재합니다. 인덱스가 사용되지 않는 주요 원인들을 설명하고, Cardinality와 Selectivity가 옵티마이저 판단에 미치는 영향, 그리고 Covering Index와 Index-Only Scan을 활용한 최적화 전략을 제시해주세요.

옵티마이저가 인덱스 비용을 시퀀셜 스캔보다 높게 평가하는 상황과 통계 정보의 역할을 고려해보세요.

A. 모범답안

인덱스가 있어도 사용되지 않는 주요 원인은 첫째, WHERE 조건에 함수나 타입 변환이 적용되어 인덱스 컬럼이 변형된 경우, 둘째, 조건의 Selectivity가 낮아 옵티마이저가 전체 행의 상당 부분을 읽어야 한다고 판단한 경우(일반적으로 15-20% 이상), 셋째, 통계 정보가 오래되어 실제 데이터 분포를 반영하지 못하는 경우입니다. Cardinality는 고유 값의 수로 인덱스 효율성을 결정하며, Selectivity는 조건을 만족하는 행의 비율로 옵티마이저가 인덱스 스캔과 시퀀셜 스캔 중 선택하는 기준이 됩니다. Covering Index는 쿼리에 필요한 모든 컬럼을 인덱스에 포함시켜 테이블 접근을 제거하며, PostgreSQL의 INCLUDE 절로 구현 가능합니다. 실무에서는 ANALYZE 명령으로 통계를 갱신하고, 복합 인덱스의 컬럼 순서를 Selectivity 높은 순으로 배치하며, 날짜 범위와 같이 자주 변경되는 조건은 파티셔닝과 병행하는 전략을 사용합니다.

핵심 포인트
  • • 함수 적용, 낮은 Selectivity, 오래된 통계가 인덱스 미사용 원인
  • • Selectivity 15-20% 이상이면 옵티마이저가 Seq Scan 선호
  • • Covering Index로 테이블 접근 제거하여 Index-Only Scan 유도
  • • 복합 인덱스 컬럼 순서는 Selectivity 높은 순으로 배치
  • • ANALYZE로 통계 갱신, 파티셔닝과 병행 전략
답변에 넣으면 좋은 키워드
EXPLAIN ANALYZE Selectivity Cardinality Covering Index Index-Only Scan INCLUDE 통계 정보
실무에서는

대시보드나 리포트 쿼리가 느려질 때 실행 계획을 분석하고 인덱스 전략을 재설계하여 성능을 개선하는 상황에서 사용됩니다.

Follow-up 질문

복합 인덱스에서 컬럼 순서를 결정할 때, Equality 조건과 Range 조건이 혼재되어 있다면 어떤 원칙으로 순서를 정하시겠습니까?

3 트랜잭션 격리 수준 및 동시성
Hard

Q. Next.js 기반 예약 시스템에서 동일한 자원에 대해 여러 사용자가 동시에 예약을 시도합니다. Read Committed 격리 수준에서 Lost Update 문제가 발생할 수 있는 시나리오를 설명하고, Repeatable Read와 Serializable 격리 수준으로 올렸을 때 각각 어떻게 동작하는지 비교해주세요. 또한 PostgreSQL의 SSI(Serializable Snapshot Isolation)와 MySQL의 Gap Lock, Next-Key Lock의 차이점을 설명하고, 예약 시스템에서 오버부킹을 방지하면서도 동시성을 최대화하기 위한 낙관적 잠금과 비관적 잠금의 선택 기준을 제시해주세요.

각 격리 수준에서 발생 가능한 이상 현상과 DBMS별 구현 방식의 차이, 그리고 비즈니스 특성에 따른 잠금 전략을 고려해보세요.

A. 모범답안

Read Committed에서는 트랜잭션이 잔여 좌석을 읽고(10석), 예약 가능 판단 후 업데이트하는 사이 다른 트랜잭션이 먼저 커밋하면 Lost Update가 발생합니다. Repeatable Read는 첫 읽기 시점의 스냅샷을 유지하지만 PostgreSQL에서는 쓰기 충돌 시 먼저 커밋한 트랜잭션만 성공하고 나머지는 롤백되며, Serializable은 SSI를 통해 직렬화 가능성을 검증해 이상 현상을 완전히 차단하지만 재시도 로직이 필요합니다. MySQL의 Gap Lock은 범위 내 존재하지 않는 레코드까지 잠그고, Next-Key Lock은 레코드와 그 앞 갭을 함께 잠가 Phantom Read를 방지하지만 동시성이 저하됩니다. PostgreSQL의 SSI는 잠금 없이 충돌을 감지해 동시성이 높지만 재시도 오버헤드가 있습니다. 예약 시스템에서는 충돌 빈도가 높으면 SELECT FOR UPDATE로 비관적 잠금을 사용하고, 충돌이 드물면 버전 컬럼 기반 낙관적 잠금으로 동시성을 높이며, 대기열 시스템과 결합해 피크 시간대 부하를 분산하는 전략을 병행합니다.

핵심 포인트
  • • Read Committed는 Lost Update 취약, Repeatable Read는 스냅샷 격리
  • • PostgreSQL SSI는 직렬화 검증, MySQL은 Gap/Next-Key Lock 사용
  • • SSI는 동시성 높지만 재시도 필요, Gap Lock은 동시성 저하
  • • 비관적 잠금은 충돌 빈번 시, 낙관적 잠금은 충돌 드물 때 유리
  • • 대기열 시스템으로 피크 부하 분산 전략 병행
답변에 넣으면 좋은 키워드
Lost Update Repeatable Read Serializable SSI Gap Lock Next-Key Lock 낙관적 잠금 비관적 잠금
실무에서는

티켓 예약, 한정 수량 상품 구매, 회의실 예약 등 동시 접근이 많은 자원 관리 시스템에서 데이터 정합성을 보장하면서 성능을 유지해야 할 때 사용됩니다.

Follow-up 질문

낙관적 잠금 방식에서 재시도 로직을 구현할 때, Exponential Backoff를 적용하는 이유와 최대 재시도 횟수를 결정하는 기준은 무엇입니까?

4 데이터베이스 커넥션 관리
Medium

Q. Next.js API Routes와 Server Actions에서 Prisma를 사용해 PostgreSQL에 연결하는데, Serverless 환경(Vercel)과 Container 환경(ECS)에서 각각 다른 커넥션 풀 전략이 필요합니다. Serverless 환경에서 Cold Start 시 커넥션 풀이 초기화되는 문제와 Lambda 동시 실행으로 인한 커넥션 고갈 시나리오를 설명하고, Connection Pooler(PgBouncer, RDS Proxy)의 Transaction 모드와 Session 모드의 차이점을 비교해주세요. 또한 Prisma의 connection_limit와 pool_timeout 설정이 실제 커넥션 수에 미치는 영향과, 최적값을 결정하는 기준을 제시해주세요.

Serverless 특성상 인스턴스 생명주기와 DB 커넥션 한계, 그리고 Pooler의 모드별 트레이드오프를 고려해보세요.

A. 모범답안

Serverless 환경에서는 각 Lambda 인스턴스가 독립적으로 커넥션 풀을 생성하므로, 동시 실행 수가 100개면 connection_limit이 5일 때 최대 500개 커넥션이 생성되어 DB 한계를 초과할 수 있습니다. Cold Start 시 풀 초기화로 첫 요청 지연이 발생하며, 사용 후 유휴 커넥션이 Lambda 수명 동안 유지되어 비효율적입니다. PgBouncer의 Transaction 모드는 트랜잭션 단위로 커넥션을 반환해 풀 효율이 높지만 Prepared Statement와 Session 변수 사용이 제한되며, Session 모드는 클라이언트 세션 동안 커넥션을 유지해 호환성은 높지만 동시성이 낮습니다. Serverless에서는 connection_limit을 1-2로 최소화하고 PgBouncer를 Transaction 모드로 운영하며, Container 환경에서는 인스턴스 수 × connection_limit가 DB max_connections의 70%를 넘지 않도록 설정합니다. pool_timeout은 애플리케이션 응답 시간보다 짧게 설정해 빠른 실패를 유도하고, RDS Proxy는 IAM 인증과 자동 페일오버를 제공해 보안과 가용성을 강화합니다.

핵심 포인트
  • • Serverless는 인스턴스별 풀 생성으로 커넥션 수 급증 위험
  • • PgBouncer Transaction 모드는 효율 높지만 기능 제약
  • • Session 모드는 호환성 높지만 동시성 낮음
  • • Serverless는 connection_limit 1-2, Container는 총합 70% 이하
  • • pool_timeout은 응답 시간보다 짧게 설정해 빠른 실패
답변에 넣으면 좋은 키워드
Connection Pool PgBouncer RDS Proxy Transaction 모드 Session 모드 Cold Start connection_limit
실무에서는

Vercel이나 AWS Lambda에서 Next.js를 운영할 때 DB 커넥션 고갈로 인한 장애를 예방하고, 비용 효율적인 인프라를 설계할 때 사용됩니다.

Follow-up 질문

Prisma의 $transaction API를 사용할 때, Interactive Transaction과 Sequential Transaction의 커넥션 점유 방식과 타임아웃 처리는 어떻게 다릅니까?

5 데이터베이스 파티셔닝 전략
Hard

Q. Next.js 기반 분석 플랫폼에서 수억 건의 이벤트 로그를 저장하며, 대부분의 쿼리는 최근 30일 데이터를 조회합니다. Range Partitioning, List Partitioning, Hash Partitioning의 특성을 비교하고, 시계열 데이터에 적합한 파티셔닝 전략을 설명해주세요. 또한 파티션 프루닝(Partition Pruning)이 쿼리 성능에 미치는 영향과, 파티션 자동 생성 및 오래된 파티션 삭제(DROP vs TRUNCATE vs DELETE)의 성능 차이를 분석하고, 파티션 키 선택 시 고려해야 할 Cardinality와 쿼리 패턴의 관계를 설명해주세요.

시계열 데이터의 접근 패턴과 파티션 관리 자동화, 그리고 삭제 연산의 물리적 동작 차이를 생각해보세요.

A. 모범답안

Range Partitioning은 날짜나 숫자 범위로 분할해 시계열 데이터에 최적이며 파티션 프루닝 효과가 크고, List Partitioning은 카테고리별 분할에 적합하지만 분포 불균형 위험이 있으며, Hash Partitioning은 균등 분산이 가능하지만 범위 쿼리에서 모든 파티션을 스캔해야 합니다. 시계열 데이터는 월 단위 Range Partitioning을 사용하며, WHERE 절에 파티션 키가 포함되면 옵티마이저가 해당 파티션만 스캔하는 Partition Pruning으로 I/O를 획기적으로 줄입니다. 파티션 자동 생성은 pg_cron이나 애플리케이션 레벨에서 구현하며, 오래된 데이터 삭제 시 DROP PARTITION은 메타데이터만 제거해 즉각적이고 WAL 생성이 없어 가장 빠르며, TRUNCATE는 테이블 구조 유지하며 빠르고, DELETE는 행 단위 삭제로 WAL과 VACUUM 부담이 큽니다. 파티션 키는 쿼리의 WHERE 절에 자주 등장하고 Cardinality가 적절해야 하며(너무 높으면 파티션 과다, 너무 낮으면 불균형), 복합 파티셔닝(날짜+지역)으로 쿼리 패턴에 맞춰 최적화합니다.

핵심 포인트
  • • Range Partitioning은 시계열 데이터에 최적, Hash는 범위 쿼리 비효율
  • • Partition Pruning으로 불필요한 파티션 스캔 제거
  • • DROP PARTITION이 가장 빠르고 WAL 없음, DELETE는 부담 큼
  • • 파티션 키는 쿼리 WHERE 절 빈도와 적절한 Cardinality 고려
  • • 복합 파티셔닝으로 다차원 쿼리 패턴 최적화
답변에 넣으면 좋은 키워드
Range Partitioning Partition Pruning DROP PARTITION TRUNCATE Cardinality 복합 파티셔닝 시계열 데이터
실무에서는

로그 분석, 모니터링 시스템, IoT 데이터 수집 등 대용량 시계열 데이터를 효율적으로 저장하고 오래된 데이터를 빠르게 삭제해야 하는 시스템에서 사용됩니다.

Follow-up 질문

파티션된 테이블에서 글로벌 인덱스와 로컬 인덱스의 차이점은 무엇이며, PostgreSQL에서는 어떻게 처리됩니까?

댓글 0

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

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