Python 시니어 데이터베이스 아키텍처 면접
새 면접Q. 사용자 수가 1억 명을 넘어서면서 단일 데이터베이스로는 트래픽을 감당할 수 없게 되었습니다. 데이터베이스 샤딩을 도입하려고 할 때, Range-based 샤딩과 Hash-based 샤딩의 차이점을 설명하고, 각각의 장단점을 비교해주세요. 또한 Python 애플리케이션 레벨에서 샤딩 로직을 구현할 때 고려해야 할 사항과, 샤드 간 JOIN이나 트랜잭션이 필요한 경우의 해결 전략을 설명해주세요.
샤드 키 선택 기준과 데이터 재분배(rebalancing) 시나리오를 중심으로 생각해보세요.
Range-based 샤딩은 특정 범위(예: user_id 1-1000만)로 데이터를 나누며 범위 쿼리에 유리하지만 핫스팟이 발생할 수 있고, Hash-based 샤딩은 해시 함수로 균등 분산하지만 범위 쿼리가 어렵습니다. 샤드 키는 쿼리 패턴과 데이터 분포를 고려해 선택해야 하며, 한 번 정하면 변경이 어려우므로 신중해야 합니다. Python에서는 SQLAlchemy의 horizontal sharding extension이나 커스텀 라우팅 로직을 구현하며, 샤드 정보를 메타데이터로 관리합니다. 샤드 간 JOIN은 애플리케이션 레벨에서 처리하거나 데이터 중복을 허용하는 비정규화 전략을 사용하며, 분산 트랜잭션은 2PC 대신 Saga 패턴이나 eventual consistency를 고려합니다. 샤드 재분배 시에는 일관된 해싱이나 가상 버킷을 활용해 데이터 이동을 최소화하고, 마이그레이션 중에도 서비스를 유지할 수 있는 dual-write 전략을 사용합니다.
- • Range-based vs Hash-based 샤딩의 트레이드오프 이해
- • 샤드 키 선택의 중요성과 변경의 어려움
- • 샤드 간 JOIN/트랜잭션 처리 전략 (Saga 패턴, eventual consistency)
- • 샤드 재분배 시 일관된 해싱과 dual-write 전략
대규모 소셜 미디어나 전자상거래 플랫폼에서 수억 건의 사용자 데이터를 효율적으로 분산 저장하고 조회할 때 필수적입니다.
기존 모놀리식 데이터베이스를 샤딩 구조로 마이그레이션할 때, 무중단으로 전환하기 위한 단계별 전략을 설명해주세요.
Q. 복합 인덱스(Composite Index)를 설계할 때 컬럼 순서가 쿼리 성능에 미치는 영향을 설명하고, Cardinality와 Selectivity 개념을 바탕으로 최적의 컬럼 순서를 결정하는 원칙을 제시해주세요. 또한 Covering Index와 Include Index의 차이점을 설명하고, PostgreSQL과 MySQL에서 인덱스 구현의 차이점(특히 클러스터링 인덱스 유무)이 Python 애플리케이션의 쿼리 설계에 어떤 영향을 주는지 설명해주세요.
인덱스 스캔 시 왼쪽 컬럼부터 순차적으로 필터링되는 원리를 생각해보세요.
복합 인덱스는 왼쪽 컬럼부터 순차적으로 사용되므로 WHERE 절에서 가장 자주 사용되고 Selectivity가 높은(고유값이 많은) 컬럼을 앞에 배치해야 합니다. Cardinality는 고유값의 개수, Selectivity는 전체 대비 고유값 비율로 Selectivity가 높을수록 필터링 효율이 좋습니다. Covering Index는 쿼리에 필요한 모든 컬럼을 인덱스에 포함시켜 테이블 접근 없이 인덱스만으로 결과를 반환하며, Include Index는 PostgreSQL의 INCLUDE 절로 비키 컬럼을 인덱스에 추가합니다. MySQL은 InnoDB의 클러스터링 인덱스로 인해 PK 순서대로 데이터가 저장되어 범위 스캔이 빠르지만, PostgreSQL은 힙 구조로 인덱스 스캔 후 테이블 접근이 필요해 Covering Index가 더 중요합니다. Python에서는 SQLAlchemy의 Index 객체로 복합 인덱스를 정의하고, explain analyze로 실행 계획을 분석해 인덱스 효율을 검증해야 합니다.
- • 복합 인덱스 컬럼 순서는 Selectivity 기준으로 결정
- • Covering Index로 테이블 접근 최소화
- • MySQL의 클러스터링 인덱스 vs PostgreSQL의 힙 구조 차이
- • 실행 계획 분석을 통한 인덱스 효율 검증
수백만 건 이상의 데이터에서 복잡한 조건의 쿼리를 최적화할 때 인덱스 설계가 성능을 좌우합니다.
인덱스가 많을수록 SELECT 성능은 좋아지지만 INSERT/UPDATE/DELETE 성능은 저하됩니다. 실무에서 인덱스 개수를 결정할 때 어떤 기준을 사용하시나요?
Q. PostgreSQL의 MVCC(Multi-Version Concurrency Control) 구조를 설명하고, Optimistic Locking과 Pessimistic Locking의 차이점 및 각각의 적합한 사용 시나리오를 제시해주세요. 또한 Python에서 SQLAlchemy를 사용할 때 SELECT FOR UPDATE와 version 컬럼을 활용한 낙관적 락 구현 방법을 비교하고, 재고 차감과 같은 동시성이 중요한 비즈니스 로직에서 데드락을 방지하기 위한 설계 원칙을 설명해주세요.
동시 트랜잭션이 많을 때와 충돌이 드물 때를 나누어 생각해보세요.
PostgreSQL의 MVCC는 각 트랜잭션이 데이터의 스냅샷을 보기 때문에 읽기와 쓰기가 서로 블로킹하지 않으며, 동시성이 높아집니다. Pessimistic Locking은 SELECT FOR UPDATE로 행을 미리 잠그며 충돌이 빈번한 경우 적합하고, Optimistic Locking은 version 컬럼으로 충돌 감지 후 재시도하며 충돌이 드문 경우 효율적입니다. SQLAlchemy에서는 with_for_update() 메서드로 비관적 락을, version_id_col 옵션으로 낙관적 락을 구현합니다. 재고 차감 같은 경우 동시 요청이 많으면 비관적 락이 안전하지만, 락 순서를 일관되게(예: PK 오름차순) 유지해야 데드락을 방지할 수 있습니다. 또한 트랜잭션 범위를 최소화하고, 타임아웃을 설정하며, 데드락 발생 시 재시도 로직을 구현해야 합니다.
- • MVCC로 읽기-쓰기 블로킹 최소화
- • Optimistic vs Pessimistic Locking의 적합한 시나리오 구분
- • 락 순서 일관성 유지로 데드락 방지
- • 트랜잭션 범위 최소화와 타임아웃 설정
전자상거래에서 한정 수량 상품 구매나 좌석 예약 시스템처럼 동시 요청이 많은 환경에서 데이터 정합성을 보장하는 데 필수적입니다.
분산 환경에서 여러 서버가 동일한 데이터베이스에 접근할 때, 애플리케이션 레벨의 락(Redis 분산 락)과 데이터베이스 레벨의 락을 어떻게 조합해서 사용하시겠습니까?
Q. Python 웹 애플리케이션에서 데이터베이스 연결 풀(Connection Pool)의 동작 원리를 설명하고, SQLAlchemy의 QueuePool 설정 파라미터(pool_size, max_overflow, pool_timeout, pool_recycle)의 의미와 각각이 성능 및 안정성에 미치는 영향을 설명해주세요. 또한 커넥션 풀 고갈(pool exhaustion) 문제가 발생했을 때 원인을 진단하고 해결하는 방법을 실무 경험을 바탕으로 설명해주세요.
동시 요청 수와 쿼리 실행 시간의 관계를 고려해보세요.
커넥션 풀은 데이터베이스 연결을 미리 생성해 재사용함으로써 연결 생성 오버헤드를 줄이고 성능을 향상시킵니다. pool_size는 기본 유지 연결 수, max_overflow는 추가로 생성 가능한 임시 연결 수, pool_timeout은 연결 대기 시간, pool_recycle은 연결 재생성 주기입니다. pool_size가 너무 작으면 대기 시간이 길어지고, 너무 크면 데이터베이스 부하가 증가하므로 동시 요청 수와 평균 쿼리 시간을 고려해 설정해야 합니다. 커넥션 풀 고갈은 장시간 실행되는 쿼리, 트랜잭션 미종료, 또는 애플리케이션 스레드/워커 수가 pool_size보다 많을 때 발생합니다. 해결 방법으로는 슬로우 쿼리 최적화, 명시적 트랜잭션 종료, pool_size 증가, 또는 비동기 처리 도입이 있으며, 모니터링 도구로 풀 사용률을 지속적으로 추적해야 합니다.
- • 커넥션 풀로 연결 재사용 및 오버헤드 감소
- • pool_size와 max_overflow 설정의 트레이드오프
- • 커넥션 풀 고갈 원인: 슬로우 쿼리, 트랜잭션 미종료
- • 모니터링을 통한 풀 사용률 추적
트래픽이 급증하는 이벤트나 프로모션 기간에 데이터베이스 연결 부족으로 서비스 장애가 발생하는 것을 방지합니다.
비동기 프레임워크(FastAPI, asyncio)에서 데이터베이스 연결 풀을 관리할 때 동기 방식과 어떤 차이가 있으며, asyncpg나 databases 라이브러리를 사용할 때 주의할 점은 무엇인가요?
Q. 프로덕션 환경에서 특정 API의 응답 시간이 점점 느려지고 있다는 모니터링 알림을 받았습니다. 데이터베이스 쿼리가 원인으로 의심될 때, PostgreSQL의 EXPLAIN ANALYZE 결과를 해석하는 방법을 설명하고, Seq Scan, Index Scan, Bitmap Index Scan의 차이점과 각각이 선택되는 조건을 설명해주세요. 또한 Python 애플리케이션에서 쿼리 성능 문제를 진단하기 위해 사용할 수 있는 도구와 접근 방법을 단계별로 제시해주세요.
실행 계획의 비용(cost)과 실제 실행 시간(actual time)의 차이를 주목하세요.
EXPLAIN ANALYZE는 쿼리의 실행 계획과 실제 실행 통계를 보여주며, cost는 예상 비용, actual time은 실제 소요 시간, rows는 처리 행 수를 나타냅니다. Seq Scan은 테이블 전체를 순차 스캔하며 인덱스가 없거나 테이블이 작을 때 사용되고, Index Scan은 인덱스를 통해 직접 행에 접근하며 소량의 행을 조회할 때 효율적입니다. Bitmap Index Scan은 여러 인덱스 결과를 비트맵으로 합친 후 테이블에 접근하며 중간 규모 결과셋에 적합합니다. Python에서는 SQLAlchemy의 echo=True로 실행 쿼리를 로깅하고, django-debug-toolbar나 Flask-DebugToolbar로 쿼리 수와 시간을 추적합니다. 단계별로는 슬로우 쿼리 로그 활성화 → EXPLAIN ANALYZE로 실행 계획 분석 → 인덱스 추가/수정 → 쿼리 리팩토링 → 다시 측정하는 순서로 진행하며, pg_stat_statements로 전체 쿼리 통계를 모니터링합니다.
- • EXPLAIN ANALYZE로 실행 계획과 실제 통계 분석
- • Seq Scan vs Index Scan vs Bitmap Index Scan 선택 조건
- • 슬로우 쿼리 로그와 쿼리 로깅 도구 활용
- • 단계별 진단: 로그 → 분석 → 최적화 → 측정
사용자가 증가하면서 초기에는 빨랐던 쿼리가 점점 느려질 때, 체계적인 진단과 최적화가 필요합니다.
쿼리 최적화 후에도 성능이 개선되지 않는다면, 데이터베이스 레벨이 아닌 아키텍처 레벨에서 어떤 해결책을 고려하시겠습니까?
Q. 데이터베이스 정규화의 1NF, 2NF, 3NF, BCNF의 개념과 각 단계에서 해결하는 이상(Anomaly) 현상을 설명하고, 실무에서 의도적으로 비정규화(Denormalization)를 선택하는 시나리오와 그에 따른 트레이드오프를 설명해주세요. 또한 Python 애플리케이션에서 읽기 성능을 위해 비정규화된 테이블을 사용할 때, 데이터 정합성을 유지하기 위한 전략을 제시해주세요.
정규화는 중복 제거, 비정규화는 조인 감소라는 핵심 목표를 생각해보세요.
1NF는 원자값 보장, 2NF는 부분 함수 종속 제거, 3NF는 이행 함수 종속 제거, BCNF는 모든 결정자가 후보키인 상태를 의미하며, 정규화는 삽입/갱신/삭제 이상을 방지합니다. 그러나 정규화가 높을수록 조인이 많아져 읽기 성능이 저하되므로, 읽기가 압도적으로 많은 경우 의도적 비정규화를 고려합니다. 예를 들어 게시글 테이블에 댓글 수를 중복 저장하면 매번 COUNT 쿼리를 실행하지 않아도 되지만, 댓글 추가/삭제 시 게시글도 업데이트해야 합니다. Python에서는 데이터베이스 트리거, 애플리케이션 레벨 트랜잭션, 또는 이벤트 기반 아키텍처로 정합성을 유지하며, 주기적인 배치 작업으로 불일치를 보정합니다. 캐시 무효화 전략도 함께 고려해야 하며, 읽기 성능 향상과 쓰기 복잡도 증가 사이의 균형을 맞춰야 합니다.
- • 정규화 단계별 이상 현상 제거
- • 비정규화는 읽기 성능 향상 vs 쓰기 복잡도 증가 트레이드오프
- • 데이터 정합성 유지: 트리거, 트랜잭션, 이벤트 기반
- • 주기적 배치로 불일치 보정
대시보드나 통계 페이지처럼 복잡한 집계 쿼리가 자주 실행되는 경우, 미리 계산된 비정규화 테이블을 사용해 성능을 개선합니다.
NoSQL 데이터베이스(MongoDB, DynamoDB)에서는 정규화 개념이 어떻게 적용되며, 문서 기반 모델링과 관계형 모델링의 차이점은 무엇인가요?
Q. PostgreSQL의 테이블 파티셔닝(Table Partitioning) 기법인 Range Partitioning, List Partitioning, Hash Partitioning의 차이점과 각각의 적합한 사용 사례를 설명해주세요. 또한 시계열 데이터(로그, 이벤트)를 저장하는 대규모 시스템에서 파티셔닝을 도입할 때, 파티션 관리 자동화(생성, 삭제, 아카이빙) 전략과 Python 애플리케이션에서 파티션을 고려한 쿼리 작성 방법을 설명해주세요.
파티션 프루닝(partition pruning)이 쿼리 성능에 미치는 영향을 생각해보세요.
Range Partitioning은 날짜나 숫자 범위로 분할하며 시계열 데이터에 적합하고, List Partitioning은 특정 값 목록으로 분할하며 지역/카테고리 같은 이산 값에 적합하며, Hash Partitioning은 해시 함수로 균등 분배하며 핫스팟 방지에 유리합니다. 시계열 데이터는 월별 또는 일별 Range Partitioning을 사용하며, 파티션 프루닝으로 불필요한 파티션 스캔을 제거해 성능을 향상시킵니다. 파티션 관리는 PostgreSQL의 pg_partman 확장이나 Python 스크립트로 자동화하며, 미래 파티션을 미리 생성하고 오래된 파티션은 삭제하거나 S3 같은 저렴한 스토리지로 아카이빙합니다. Python 애플리케이션에서는 쿼리에 파티션 키(날짜)를 WHERE 절에 반드시 포함시켜야 프루닝이 작동하며, SQLAlchemy에서는 명시적으로 파티션 테이블을 지정하거나 동적 쿼리를 생성합니다. 파티션 수가 너무 많으면 메타데이터 관리 오버헤드가 증가하므로 적절한 파티션 크기를 유지해야 합니다.
- • Range/List/Hash Partitioning의 적합한 사용 사례
- • 파티션 프루닝으로 쿼리 성능 향상
- • 파티션 자동 관리: 생성, 삭제, 아카이빙
- • 쿼리에 파티션 키 포함 필수
수억 건의 로그 데이터를 저장하는 시스템에서 최근 데이터만 빠르게 조회하고, 오래된 데이터는 효율적으로 관리하는 데 필수적입니다.
기존 대용량 테이블을 파티셔닝된 구조로 마이그레이션할 때, 서비스 중단 없이 안전하게 전환하는 방법을 단계별로 설명해주세요.
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!