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

Laravel 미드레벨 (3~7년) 데이터베이스 7문항 조회수 31 · 2026-08-04 (화) 13:50:56
1 복합 인덱스 설계
Medium

Q. 사용자 활동 로그 테이블(user_id, action_type, created_at, status)에서 '특정 사용자의 최근 30일간 특정 액션 타입의 성공한 로그'를 자주 조회합니다. 이 쿼리를 위한 최적의 복합 인덱스 구성 순서를 제안하고, 인덱스 컬럼 순서가 성능에 미치는 영향을 설명해주세요. 또한 Laravel 마이그레이션에서 복합 인덱스를 생성하는 방법도 함께 제시해주세요.

WHERE 절의 등호 조건, 범위 조건, 선택도(Selectivity)를 고려하여 인덱스 컬럼 순서를 결정해야 합니다.

A. 모범답안

복합 인덱스는 (user_id, action_type, status, created_at) 순서로 구성하는 것이 최적입니다. 등호 조건(=)으로 필터링되는 컬럼을 앞에 배치하고, 범위 조건(BETWEEN)은 마지막에 위치시켜야 인덱스를 최대한 활용할 수 있습니다. user_id는 카디널리티가 높고 가장 많은 데이터를 걸러내므로 첫 번째에 위치하며, action_type과 status는 등호 조건이므로 중간에, created_at은 범위 조건이므로 마지막에 배치합니다. Laravel 마이그레이션에서는 $table->index(['user_id', 'action_type', 'status', 'created_at'], 'idx_user_activity')로 생성할 수 있습니다. 인덱스 순서가 잘못되면 부분 인덱스 스캔만 가능하거나 인덱스를 전혀 사용하지 못할 수 있으므로, 쿼리 패턴에 맞는 정확한 순서 설계가 중요합니다.

핵심 포인트
  • • 등호 조건 컬럼을 앞에, 범위 조건 컬럼을 뒤에 배치
  • • 카디널리티와 선택도를 고려한 순서 결정
  • • 인덱스 순서에 따른 성능 차이 이해
답변에 넣으면 좋은 키워드
복합 인덱스 카디널리티 선택도 인덱스 스캔 범위 조건 등호 조건
실무에서는

검색 조건이 복잡한 관리자 페이지나 대시보드에서 복합 인덱스 설계는 응답 속도를 수십 배 개선할 수 있습니다.

Follow-up 질문

만약 이 테이블에 WHERE status = 'success' AND created_at BETWEEN ... 조건만으로 조회하는 쿼리도 자주 발생한다면, 별도의 인덱스를 추가로 생성해야 할까요?

2 데드락 해결
Hard

Q. 주문 시스템에서 동시에 여러 주문이 발생할 때 재고 차감과 포인트 차감을 처리하는 과정에서 데드락이 자주 발생합니다. 데드락이 발생하는 원리를 설명하고, Laravel에서 트랜잭션 순서 제어, 락 타임아웃 설정, 데드락 재시도 로직을 구현하여 이를 해결하는 방법을 제시해주세요.

여러 트랜잭션이 서로 다른 순서로 자원을 잠그려고 할 때 데드락이 발생하며, 락 획득 순서를 일관되게 유지하는 것이 핵심입니다.

A. 모범답안

데드락은 트랜잭션 A가 재고를 잠그고 포인트를 기다리는 동안, 트랜잭션 B가 포인트를 잠그고 재고를 기다리는 상황에서 발생합니다. 해결을 위해서는 첫째, 모든 트랜잭션에서 자원을 동일한 순서(예: 항상 재고 먼저, 포인트 나중)로 잠가야 합니다. 둘째, Laravel에서 DB::transaction() 내부에서 lockForUpdate()를 사용할 때 테이블 접근 순서를 일관되게 유지합니다. 셋째, config/database.php에서 'options' => [PDO::ATTR_TIMEOUT => 3]으로 락 타임아웃을 설정합니다. 넷째, 데드락 발생 시 자동 재시도를 위해 DB::transaction(function() {...}, 3)처럼 재시도 횟수를 지정하거나, catch (QueryException $e)로 데드락 에러(1213)를 감지하여 수동으로 재시도 로직을 구현합니다. 추가로 트랜잭션 범위를 최소화하고 실행 시간을 단축하여 락 경합을 줄이는 것도 중요합니다.

핵심 포인트
  • • 자원 잠금 순서를 모든 트랜잭션에서 일관되게 유지
  • • 락 타임아웃 설정과 데드락 재시도 전략 구현
  • • 트랜잭션 범위 최소화로 락 경합 감소
답변에 넣으면 좋은 키워드
데드락 lockForUpdate 트랜잭션 순서 락 타임아웃 재시도 로직 QueryException
실무에서는

이커머스의 플래시 세일이나 한정 수량 상품 판매 시 데드락 해결은 서비스 안정성의 핵심입니다.

Follow-up 질문

SELECT ... FOR UPDATE와 SELECT ... FOR UPDATE SKIP LOCKED의 차이점과 각각의 적절한 사용 사례는 무엇인가요?

3 커버링 인덱스
Medium

Q. 게시글 목록 조회 API에서 SELECT id, title, author_name, created_at FROM posts WHERE category_id = ? ORDER BY created_at DESC LIMIT 20 쿼리가 자주 실행됩니다. 커버링 인덱스(Covering Index)의 개념을 설명하고, 이 쿼리에 커버링 인덱스를 적용했을 때의 성능 이점과 주의사항을 설명해주세요. Laravel에서 실제 구현 방법도 함께 제시해주세요.

커버링 인덱스는 쿼리에 필요한 모든 컬럼을 인덱스에 포함시켜 테이블 접근을 완전히 제거하는 기법입니다.

A. 모범답안

커버링 인덱스는 쿼리에서 필요로 하는 모든 컬럼을 인덱스 자체에 포함시켜, 인덱스만으로 쿼리를 완전히 처리할 수 있게 하는 기법입니다. 이 쿼리에는 (category_id, created_at, id, title, author_name) 순서로 복합 인덱스를 생성하면, WHERE 조건과 ORDER BY를 인덱스로 처리하고 SELECT 컬럼도 인덱스에서 직접 가져올 수 있어 테이블 접근(랜덤 I/O)이 완전히 제거됩니다. Laravel 마이그레이션에서 $table->index(['category_id', 'created_at', 'id', 'title', 'author_name'], 'idx_covering')으로 생성합니다. 성능 이점은 디스크 I/O가 크게 감소하여 응답 속도가 수배~수십 배 향상되지만, 인덱스 크기가 커져 INSERT/UPDATE 성능이 저하되고 메모리 사용량이 증가하는 단점이 있습니다. 따라서 조회가 압도적으로 많고 인덱스에 포함할 컬럼이 많지 않은 경우에 적합합니다.

핵심 포인트
  • • 쿼리 필요 컬럼을 모두 인덱스에 포함하여 테이블 접근 제거
  • • 랜덤 I/O 감소로 조회 성능 대폭 향상
  • • 인덱스 크기 증가와 쓰기 성능 저하 트레이드오프
답변에 넣으면 좋은 키워드
커버링 인덱스 랜덤 I/O 복합 인덱스 테이블 접근 인덱스 온리 스캔
실무에서는

대용량 게시판이나 상품 목록 API에서 커버링 인덱스는 페이지 로딩 속도를 극적으로 개선합니다.

Follow-up 질문

만약 author_name이 VARCHAR(255)처럼 큰 컬럼이라면, 커버링 인덱스 전략을 어떻게 조정해야 할까요?

4 트랜잭션 격리와 락
Hard

Q. 결제 시스템에서 사용자의 잔액 조회 후 차감하는 로직을 구현할 때, SELECT balance FROM wallets WHERE user_id = ? 후 UPDATE wallets SET balance = balance - ? WHERE user_id = ? 순으로 실행합니다. 이 과정에서 발생할 수 있는 동시성 문제를 설명하고, Pessimistic Lock과 Optimistic Lock 중 어떤 방식이 적합한지 판단 기준과 Laravel에서의 구현 방법을 설명해주세요.

조회와 수정 사이의 시간 간격에서 다른 트랜잭션이 데이터를 변경할 수 있으며, 락 방식 선택은 충돌 빈도에 따라 결정됩니다.

A. 모범답안

조회와 수정 사이에 다른 트랜잭션이 잔액을 변경하면 Lost Update 문제가 발생하여 잔액이 부정확해질 수 있습니다. Pessimistic Lock은 SELECT ... FOR UPDATE로 조회 시점에 행을 잠가 다른 트랜잭션의 접근을 차단하며, 충돌이 자주 발생하는 상황에 적합합니다. Laravel에서 Wallet::where('user_id', $userId)->lockForUpdate()->first()로 구현합니다. Optimistic Lock은 version 컬럼을 추가하여 UPDATE 시점에 version을 확인하고, 변경되었으면 재시도하는 방식으로 충돌이 드문 상황에 적합합니다. Laravel에서는 WHERE user_id = ? AND version = ? 조건으로 UPDATE하고 affected rows가 0이면 재시도합니다. 결제 시스템처럼 충돌 가능성이 높고 데이터 정합성이 중요한 경우 Pessimistic Lock이 적합하며, 읽기가 많고 쓰기가 드문 경우 Optimistic Lock이 성능상 유리합니다. 추가로 트랜잭션 격리 수준을 REPEATABLE READ 이상으로 설정해야 합니다.

핵심 포인트
  • • Lost Update 문제 이해와 동시성 제어 필요성
  • • Pessimistic Lock은 충돌 빈번, Optimistic Lock은 충돌 드문 경우
  • • lockForUpdate() 사용과 version 기반 제어 구현
답변에 넣으면 좋은 키워드
Pessimistic Lock Optimistic Lock lockForUpdate Lost Update version 동시성 제어
실무에서는

포인트 차감, 쿠폰 사용, 좌석 예약 등 정확한 수량 관리가 필요한 모든 시스템에서 필수적입니다.

Follow-up 질문

sharedLock()과 lockForUpdate()의 차이점과 각각의 사용 시나리오는 무엇인가요?

5 쿼리 최적화
Medium

Q. Laravel Eloquent에서 whereHas()를 사용하여 관계 데이터를 필터링할 때 성능 문제가 발생할 수 있습니다. whereHas()가 생성하는 SQL의 문제점을 설명하고, JOIN이나 whereIn() 서브쿼리로 최적화하는 방법을 비교 분석해주세요. 각 방법의 장단점과 적절한 사용 시나리오도 함께 설명해주세요.

whereHas()는 EXISTS 서브쿼리를 생성하여 각 행마다 서브쿼리를 실행하므로 대용량 데이터에서 비효율적일 수 있습니다.

A. 모범답안

whereHas()는 EXISTS 서브쿼리를 사용하여 각 메인 테이블의 행마다 서브쿼리를 실행하므로, 데이터가 많을수록 성능이 급격히 저하됩니다. JOIN 방식은 Post::join('comments', 'posts.id', '=', 'comments.post_id')->where('comments.status', 'approved')->select('posts.*')->distinct()->get()처럼 작성하며, 한 번의 조인으로 처리되어 빠르지만 1:N 관계에서 중복 제거를 위해 DISTINCT가 필요합니다. whereIn 서브쿼리 방식은 $commentedPostIds = Comment::where('status', 'approved')->pluck('post_id'); Post::whereIn('id', $commentedPostIds)->get()처럼 두 번의 쿼리로 나누어 실행하며, 서브쿼리 결과가 작을 때 효율적이고 코드 가독성이 좋습니다. 관계 테이블의 데이터가 적으면 whereIn이, 많으면 JOIN이 유리하며, 복잡한 조건이 있으면 whereIn으로 명확하게 분리하는 것이 유지보수에 좋습니다. 실제 성능은 데이터 분포와 인덱스 상태에 따라 다르므로 EXPLAIN으로 확인해야 합니다.

핵심 포인트
  • • whereHas()의 EXISTS 서브쿼리는 대용량에서 비효율적
  • • JOIN은 빠르지만 DISTINCT 필요, whereIn은 명확하지만 두 번의 쿼리
  • • 데이터 크기와 복잡도에 따른 최적 방법 선택
답변에 넣으면 좋은 키워드
whereHas EXISTS JOIN whereIn 서브쿼리 DISTINCT 쿼리 최적화
실무에서는

댓글이 있는 게시글, 주문이 있는 회원 등 관계 기반 필터링이 필요한 모든 목록 조회 API에서 성능 차이가 극명하게 나타납니다.

Follow-up 질문

만약 whereHas() 안에서 복잡한 조건(여러 AND, OR 조건)을 사용해야 한다면, 어떤 최적화 전략을 사용해야 할까요?

6 파티셔닝 전략
Hard

Q. 로그 데이터 테이블이 수억 건으로 증가하여 조회 및 관리가 어려워졌습니다. MySQL의 파티셔닝(Partitioning) 기능을 활용하여 created_at 기준으로 월별 파티션을 구성하려고 합니다. Range Partitioning의 동작 원리와 장점을 설명하고, 파티션 프루닝(Partition Pruning)이 무엇인지, 그리고 오래된 파티션을 삭제하여 데이터를 관리하는 전략을 Laravel과 함께 설명해주세요.

파티셔닝은 물리적으로 테이블을 분할하여 쿼리 성능을 향상시키고, 특정 파티션만 접근하거나 삭제할 수 있게 합니다.

A. 모범답안

Range Partitioning은 특정 컬럼(created_at)의 값 범위에 따라 데이터를 물리적으로 다른 파티션에 저장하는 기법입니다. 예를 들어 2024년 1월 데이터는 p202401 파티션에, 2월 데이터는 p202402 파티션에 저장됩니다. 파티션 프루닝은 쿼리의 WHERE 조건에 파티션 키가 포함되면 MySQL이 자동으로 해당 파티션만 스캔하여 성능을 크게 향상시키는 최적화 기법입니다. 예를 들어 WHERE created_at >= '2024-01-01' AND created_at < '2024-02-01' 조건이면 p202401 파티션만 접근합니다. 오래된 데이터 삭제는 ALTER TABLE logs DROP PARTITION p202301처럼 파티션 단위로 즉시 삭제 가능하며, DELETE 쿼리보다 수백 배 빠릅니다. Laravel에서는 마이그레이션에서 DB::statement()로 파티션 생성 DDL을 실행하고, 스케줄러로 매월 새 파티션 추가 및 오래된 파티션 삭제 작업을 자동화할 수 있습니다. 파티션 키는 반드시 WHERE 절에 자주 사용되는 컬럼이어야 하며, 프라이머리 키와 유니크 키에 파티션 키가 포함되어야 합니다.

핵심 포인트
  • • Range Partitioning으로 데이터를 시간 범위별로 물리적 분할
  • • 파티션 프루닝으로 필요한 파티션만 스캔하여 성능 향상
  • • 파티션 단위 DROP으로 빠른 데이터 삭제 및 관리
답변에 넣으면 좋은 키워드
파티셔닝 Range Partitioning 파티션 프루닝 DROP PARTITION created_at 데이터 관리
실무에서는

접속 로그, 주문 이력, 센서 데이터 등 시계열 대용량 데이터를 효율적으로 관리하고 오래된 데이터를 빠르게 삭제할 수 있습니다.

Follow-up 질문

파티셔닝된 테이블에 인덱스를 생성할 때 주의해야 할 점과, 로컬 인덱스와 글로벌 인덱스의 차이는 무엇인가요?

7 인덱스 힌트와 쿼리 플랜
Medium

Q. 복잡한 WHERE 조건을 가진 쿼리에서 MySQL 옵티마이저가 잘못된 인덱스를 선택하여 성능이 저하되는 상황이 발생했습니다. EXPLAIN 결과 분석 방법과 주요 지표(type, key, rows, Extra)의 의미를 설명하고, Laravel에서 쿼리 힌트(USE INDEX, FORCE INDEX)를 사용하여 옵티마이저의 선택을 강제하는 방법을 제시해주세요. 또한 쿼리 힌트 사용의 장단점과 주의사항도 설명해주세요.

EXPLAIN은 쿼리 실행 계획을 보여주며, 옵티마이저가 잘못된 판단을 할 때 힌트로 개입할 수 있지만 남용하면 안 됩니다.

A. 모범답안

EXPLAIN 결과에서 type은 접근 방식(const, ref, range, index, ALL 순으로 느려짐)을 나타내고, key는 실제 사용된 인덱스, rows는 예상 스캔 행 수, Extra는 추가 정보(Using index는 커버링 인덱스, Using filesort는 정렬 발생)를 보여줍니다. type이 ALL이거나 rows가 과도하게 크면 성능 문제가 있습니다. 옵티마이저가 통계 정보 부족이나 복잡한 조건으로 잘못된 인덱스를 선택하면, Laravel에서 DB::select(DB::raw('SELECT * FROM users USE INDEX (idx_name) WHERE ...'))->get()이나 Model::from(DB::raw('users FORCE INDEX (idx_name)'))->where(...)->get()처럼 힌트를 사용할 수 있습니다. USE INDEX는 제안, FORCE INDEX는 강제입니다. 장점은 즉각적인 성능 개선이지만, 단점은 데이터 분포 변경 시 오히려 성능이 악화될 수 있고 유지보수가 어려워집니다. 따라서 통계 정보 갱신(ANALYZE TABLE), 인덱스 재설계, 쿼리 구조 개선을 먼저 시도하고, 불가피한 경우에만 힌트를 사용하며 정기적으로 재검토해야 합니다.

핵심 포인트
  • • EXPLAIN의 type, key, rows, Extra로 쿼리 성능 분석
  • • USE INDEX, FORCE INDEX로 옵티마이저 선택 강제 가능
  • • 힌트는 최후의 수단이며 정기적 재검토 필요
답변에 넣으면 좋은 키워드
EXPLAIN 쿼리 플랜 USE INDEX FORCE INDEX 옵티마이저 type rows
실무에서는

복잡한 검색 쿼리나 리포트 쿼리에서 옵티마이저가 예상과 다른 실행 계획을 선택할 때 성능 문제를 해결할 수 있습니다.

Follow-up 질문

EXPLAIN ANALYZE와 일반 EXPLAIN의 차이점은 무엇이며, 실제 운영 환경에서 어떻게 활용해야 할까요?

댓글 0

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

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