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

Nginx 미드레벨 (3~7년) 데이터베이스 5문항 조회수 29 · 2026-08-29 (토) 07:41:17
1 인덱스 설계
Medium

Q. Nginx의 액세스 로그를 실시간으로 MySQL에 저장하는 시스템을 운영 중입니다. 하루 1억 건 이상의 로그가 쌓이고, IP별 요청 수, 특정 기간의 URL별 통계, User-Agent별 분석 쿼리가 자주 실행됩니다. 테이블 구조는 id, timestamp, ip, url, user_agent, status_code로 구성되어 있습니다. 어떤 인덱스 전략을 수립하시겠습니까?

조회 패턴별로 필요한 인덱스를 구분하고, 복합 인덱스의 컬럼 순서와 쓰기 성능 트레이드오프를 고려해보세요.

A. 모범답안

먼저 timestamp 컬럼에 단일 인덱스를 생성하여 기간별 조회를 최적화합니다. IP별 통계를 위해 (ip, timestamp) 복합 인덱스를 생성하되, 카디널리티가 높은 ip를 선두에 배치합니다. URL별 분석을 위해서는 (url, timestamp) 복합 인덱스를 추가하되, url이 긴 경우 prefix 인덱스를 고려합니다. 다만 하루 1억 건의 INSERT 성능을 고려하여 인덱스 개수를 3-4개로 제한하고, 자주 사용되지 않는 user_agent는 인덱스 없이 풀스캔을 허용하거나 별도 집계 테이블로 분리합니다. 파티셔닝(월별 또는 일별)을 적용하여 오래된 데이터의 인덱스 부담을 줄이는 것도 고려해야 합니다.

핵심 포인트
  • • 조회 패턴에 따른 복합 인덱스 설계 (카디널리티 순서 고려)
  • • 쓰기 성능과 읽기 성능의 트레이드오프 분석
  • • 파티셔닝을 통한 인덱스 크기 관리
답변에 넣으면 좋은 키워드
복합 인덱스 카디널리티 prefix 인덱스 파티셔닝 쓰기 성능 트레이드오프
실무에서는

대용량 로그 시스템에서 실시간 분석과 저장 성능을 동시에 만족시키는 인덱스 전략 수립 시 필수적입니다.

Follow-up 질문

인덱스가 많아질수록 INSERT 성능이 저하되는 이유를 B-Tree 구조 관점에서 설명해주세요.

2 쿼리 튜닝
Hard

Q. Nginx 뒤에 있는 애플리케이션 서버의 응답 시간을 분석하기 위해 response_time 테이블에서 평균 응답시간을 조회하는 쿼리가 있습니다. 이 쿼리가 30초 이상 걸리는데, EXPLAIN 결과 Using temporary와 Using filesort가 나타납니다. 1억 건의 데이터에서 최근 7일간 endpoint별 평균 응답시간을 구하는 쿼리입니다. 어떻게 튜닝하시겠습니까?

GROUP BY와 날짜 범위 조건의 인덱스 활용, 그리고 임시 테이블 생성을 줄이는 방법을 생각해보세요.

A. 모범답안

먼저 WHERE 절의 날짜 조건과 GROUP BY의 endpoint를 함께 고려한 (created_at, endpoint) 복합 인덱스를 생성합니다. 인덱스 순서는 범위 조건인 created_at을 먼저 배치하여 데이터를 필터링한 후 endpoint로 그룹핑하도록 합니다. Using filesort를 제거하기 위해 ORDER BY 절이 있다면 인덱스 순서와 일치시킵니다. 추가로 집계 쿼리의 특성상 커버링 인덱스를 만들기 위해 (created_at, endpoint, response_time) 인덱스를 고려할 수 있습니다. 근본적으로는 실시간 집계보다 매 시간/일 단위로 미리 집계한 summary 테이블을 별도로 유지하는 방식으로 아키텍처를 변경하는 것이 효과적입니다. 이 경우 Nginx 로그 수집 파이프라인에서 배치 집계 프로세스를 추가합니다.

핵심 포인트
  • • WHERE와 GROUP BY를 모두 고려한 복합 인덱스 설계
  • • 커버링 인덱스를 통한 테이블 접근 최소화
  • • 사전 집계 테이블(summary table)을 통한 아키텍처 개선
답변에 넣으면 좋은 키워드
EXPLAIN Using filesort Using temporary 커버링 인덱스 복합 인덱스 사전 집계
실무에서는

대용량 로그 데이터의 실시간 분석 대시보드 구축 시 쿼리 성능 최적화에 필수적인 기법입니다.

Follow-up 질문

커버링 인덱스를 사용할 때의 장점과 단점, 그리고 인덱스 크기가 커지는 문제를 어떻게 관리하시겠습니까?

3 트랜잭션과 락
Medium

Q. Nginx를 통해 들어오는 API 요청이 사용자의 rate limit을 체크하고 카운터를 업데이트하는 로직이 있습니다. 동일 사용자의 동시 요청이 많을 때 rate_limit_counter 테이블에서 데드락이 자주 발생합니다. 트랜잭션 격리 수준은 REPEATABLE READ이고, SELECT ... FOR UPDATE를 사용해 카운터를 읽고 업데이트합니다. 데드락의 원인과 해결 방법을 설명해주세요.

인덱스 락과 갭 락의 동작 방식, 그리고 트랜잭션 격리 수준의 영향을 고려해보세요.

A. 모범답안

REPEATABLE READ 격리 수준에서 SELECT FOR UPDATE는 레코드 락과 함께 갭 락을 획득하므로, 여러 트랜잭션이 서로 다른 순서로 같은 레코드를 잠그려 할 때 데드락이 발생할 수 있습니다. 해결 방법으로는 첫째, 트랜잭션 격리 수준을 READ COMMITTED로 낮춰 갭 락을 제거합니다. 둘째, 사용자 ID로 정렬하여 항상 동일한 순서로 락을 획득하도록 합니다. 셋째, SELECT FOR UPDATE 대신 UPDATE 문에서 직접 증감 연산(UPDATE SET count = count + 1)을 수행하여 락 보유 시간을 최소화합니다. 넷째, 근본적으로 Redis 같은 인메모리 저장소로 rate limit 카운터를 관리하여 DB 락 경합을 피하는 아키텍처 변경을 고려합니다. 마지막으로 트랜잭션을 최대한 짧게 유지하고 불필요한 로직을 트랜잭션 밖으로 빼냅니다.

핵심 포인트
  • • REPEATABLE READ의 갭 락이 데드락 원인이 될 수 있음
  • • 트랜잭션 격리 수준 조정과 락 획득 순서 통일
  • • 인메모리 저장소를 활용한 아키텍처 개선
답변에 넣으면 좋은 키워드
데드락 갭 락 SELECT FOR UPDATE REPEATABLE READ READ COMMITTED 트랜잭션 격리 수준
실무에서는

API Gateway나 rate limiting 시스템에서 동시성 제어와 데드락 방지는 핵심적인 설계 고려사항입니다.

Follow-up 질문

READ COMMITTED로 변경했을 때 발생할 수 있는 phantom read 문제를 rate limit 시나리오에서는 어떻게 처리하시겠습니까?

4 데이터 모델링
Medium

Q. Nginx upstream 서버들의 헬스체크 결과를 저장하는 데이터베이스를 설계합니다. 각 upstream 서버는 10초마다 체크되며, 결과(성공/실패), 응답시간, 에러메시지를 저장합니다. 서버는 100대, 30일치 데이터를 보관하며, 최근 상태 조회, 특정 서버의 시계열 조회, 장애 구간 분석 쿼리가 주로 실행됩니다. 어떤 테이블 구조와 전략을 제안하시겠습니까?

시계열 데이터의 특성과 조회 패턴, 그리고 데이터 증가율을 고려한 파티셔닝과 정규화 전략을 생각해보세요.

A. 모범답안

먼저 서버 정보와 헬스체크 결과를 분리하여 servers 테이블(id, hostname, ip)과 health_checks 테이블(id, server_id, checked_at, status, response_time, error_message)로 정규화합니다. health_checks 테이블은 하루 약 86만 건(100대 * 6회/분 * 1440분)이 쌓이므로 일별 파티셔닝을 적용하고, 30일 이후 데이터는 자동으로 DROP하는 파티션 관리 전략을 수립합니다. 최근 상태 조회를 위해 별도의 server_status 테이블을 두고 최신 상태만 UPDATE하는 방식으로 빠른 조회를 보장합니다. (server_id, checked_at) 복합 인덱스를 생성하여 특정 서버의 시계열 조회를 최적화합니다. 선택적으로 시계열 데이터에 특화된 TimescaleDB나 InfluxDB 같은 전문 데이터베이스 도입도 고려할 수 있습니다.

핵심 포인트
  • • 정규화를 통한 서버 정보와 이벤트 데이터 분리
  • • 시계열 데이터 특성에 맞는 파티셔닝 전략
  • • 최신 상태 조회를 위한 별도 테이블 운영
답변에 넣으면 좋은 키워드
파티셔닝 정규화 시계열 데이터 복합 인덱스 데이터 보관 정책
실무에서는

모니터링 시스템에서 대량의 시계열 헬스체크 데이터를 효율적으로 저장하고 조회하는 데 필수적인 설계입니다.

Follow-up 질문

파티션 테이블에서 여러 날짜에 걸친 범위 조회 시 성능을 어떻게 최적화할 수 있을까요?

5 락과 동시성
Hard

Q. Nginx 뒤의 배치 작업 시스템에서 job_queue 테이블의 작업을 여러 워커가 가져가 처리합니다. 각 워커는 트랜잭션 내에서 status='pending'인 작업을 하나 SELECT하고 status='processing'으로 UPDATE한 뒤 처리합니다. 그런데 같은 작업을 여러 워커가 중복으로 가져가는 문제가 발생했습니다. SELECT FOR UPDATE를 추가했더니 중복은 해결됐지만 워커 수를 늘려도 처리량이 증가하지 않습니다. 원인과 개선 방안을 제시해주세요.

락 경합으로 인한 직렬화와 스킵 락 메커니즘, 그리고 배치 처리 방식을 고려해보세요.

A. 모범답안

SELECT FOR UPDATE는 조건에 맞는 모든 행에 락을 걸려고 시도하므로 여러 워커가 동시에 같은 pending 작업들에 대해 락을 경합하게 되어 사실상 직렬화됩니다. 해결 방법으로 첫째, SELECT FOR UPDATE SKIP LOCKED를 사용하여 이미 락이 걸린 행은 건너뛰고 다른 행을 즉시 가져가도록 합니다. 둘째, LIMIT 1을 명시하여 한 번에 하나의 행만 처리하도록 하되, 배치 크기를 늘려 LIMIT 10 정도로 여러 작업을 한 번에 가져가 처리하는 방식으로 락 오버헤드를 줄입니다. 셋째, job_queue를 워커 수만큼 샤딩하여 각 워커가 특정 파티션만 처리하도록 분리합니다(예: id % worker_count). 넷째, 작업 큐를 Redis나 RabbitMQ 같은 전용 메시지 큐로 전환하여 DB 락 경합을 근본적으로 제거합니다. 마지막으로 낙관적 락(버전 컬럼)을 사용해 UPDATE 시점에만 충돌을 체크하는 방식도 고려할 수 있습니다.

핵심 포인트
  • • SELECT FOR UPDATE SKIP LOCKED로 락 경합 회피
  • • 배치 처리와 샤딩을 통한 동시성 향상
  • • 전용 메시지 큐 시스템으로의 아키텍처 전환
답변에 넣으면 좋은 키워드
SKIP LOCKED 락 경합 직렬화 샤딩 낙관적 락 메시지 큐
실무에서는

분산 워커 시스템에서 작업 큐를 DB로 구현할 때 동시성과 처리량을 최적화하는 핵심 기법입니다.

Follow-up 질문

SKIP LOCKED를 사용할 때 작업 처리 순서가 보장되지 않는 문제를 어떻게 해결하시겠습니까?

댓글 0

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

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