Django 미드레벨 성능 최적화 면접
새 면접Q. Django ORM을 사용하는 API에서 응답 시간이 3초 이상 걸린다는 보고를 받았습니다. 데이터베이스 쿼리가 원인으로 의심될 때, 어떤 순서와 방법으로 병목을 진단하고 개선하시겠습니까?
Django가 제공하는 쿼리 분석 도구와 ORM의 대표적인 성능 문제를 떠올려보세요.
먼저 Django Debug Toolbar나 django-silk를 활용해 실행된 쿼리 개수와 실행 시간을 확인합니다. N+1 쿼리 문제가 가장 흔한 원인이므로 select_related()와 prefetch_related()를 적용해 관계된 객체를 미리 로드합니다. 쿼리 개수가 줄었다면 EXPLAIN ANALYZE로 각 쿼리의 실행 계획을 분석하고, 필요시 인덱스를 추가합니다. only()나 defer()로 불필요한 필드 로딩을 줄이고, values()나 values_list()로 딕셔너리/튜플 형태로 가져와 객체 생성 오버헤드를 줄일 수 있습니다. 마지막으로 복잡한 쿼리는 raw SQL이나 extra()를 고려하거나, 데이터베이스 레벨에서 집계 연산을 수행하도록 annotate()와 aggregate()를 활용합니다.
- • Django Debug Toolbar나 django-silk로 쿼리 분석
- • select_related/prefetch_related로 N+1 문제 해결
- • only/defer/values로 불필요한 데이터 로딩 최소화
- • 인덱스 추가 및 쿼리 실행 계획 분석
목록 API에서 외래키나 다대다 관계를 포함할 때 쿼리 개수가 폭발적으로 증가하는 문제를 해결할 때 사용합니다.
select_related와 prefetch_related의 동작 원리와 사용 시점의 차이를 설명해주세요.
Q. Django 프로젝트에서 사용할 수 있는 캐싱 레벨(계층)을 설명하고, 각각 어떤 상황에서 적용하는 것이 효과적인지 설명해주세요.
Django는 데이터베이스부터 뷰, 템플릿, 전체 사이트까지 다양한 레벨에서 캐싱을 지원합니다.
Django는 여러 캐싱 레벨을 제공합니다. 전체 사이트 캐싱은 MIDDLEWARE에 CacheMiddleware를 추가해 모든 응답을 캐싱하며, 익명 사용자 대상 정적 콘텐츠에 적합합니다. 뷰 레벨 캐싱은 cache_page 데코레이터로 특정 뷰의 결과를 캐싱하며, 동일 파라미터에 대해 같은 응답을 반환하는 API에 유용합니다. 템플릿 프래그먼트 캐싱은 템플릿 내 일부 블록만 캐싱해 동적 페이지의 정적 부분을 최적화합니다. 저수준 캐시 API는 cache.get/set을 사용해 데이터베이스 쿼리 결과나 외부 API 응답 같은 특정 데이터를 캐싱하며, 가장 세밀한 제어가 가능합니다. 백엔드로는 Redis나 Memcached를 사용하며, 개발 환경에서는 로컬 메모리 캐시를 사용합니다.
- • 전체 사이트, 뷰, 템플릿 프래그먼트, 저수준 API 등 다층 캐싱 지원
- • cache_page 데코레이터로 뷰 단위 캐싱
- • 저수준 API로 쿼리 결과나 외부 API 응답 캐싱
- • Redis/Memcached 백엔드 사용
자주 조회되지만 변경이 드문 상품 상세 정보나 카테고리 목록을 캐싱해 데이터베이스 부하를 줄입니다.
캐시 키 설계 시 주의할 점과 캐시 무효화 전략에 대해 설명해주세요.
Q. Django 애플리케이션에서 동시 접속자가 증가하면서 'too many connections' 에러가 발생했습니다. DATABASES 설정의 CONN_MAX_AGE와 커넥션 풀링 관점에서 원인과 해결 방법을 설명해주세요.
Django의 기본 커넥션 관리 방식과 persistent connection 설정을 고려해보세요.
Django는 기본적으로 요청마다 데이터베이스 커넥션을 생성하고 종료하는데, CONN_MAX_AGE를 0으로 설정하면 이 동작을 합니다. 동시 접속자가 많으면 커넥션 생성/종료 오버헤드가 크고 데이터베이스 최대 연결 수를 초과할 수 있습니다. CONN_MAX_AGE를 None이나 적절한 초 단위 값으로 설정하면 persistent connection을 유지해 재사용할 수 있습니다. 하지만 Django는 진정한 커넥션 풀링을 제공하지 않으므로, Gunicorn/uWSGI 워커 수와 스레드 수를 고려해 최대 커넥션 수를 계산해야 합니다. 더 나은 해결책은 django-db-geventpool이나 SQLAlchemy의 QueuePool 같은 외부 커넥션 풀 라이브러리를 사용하거나, PgBouncer 같은 커넥션 풀러를 데이터베이스 앞단에 배치하는 것입니다. 데이터베이스의 max_connections 설정도 함께 조정해야 합니다.
- • CONN_MAX_AGE 설정으로 persistent connection 활성화
- • 워커/스레드 수 × CONN_MAX_AGE = 최대 커넥션 수 계산
- • PgBouncer 등 외부 커넥션 풀러 도입
- • 데이터베이스 max_connections 설정 조정
트래픽이 급증하는 이벤트나 프로모션 기간에 데이터베이스 연결 고갈로 서비스 장애가 발생하는 것을 방지합니다.
PgBouncer의 pool_mode 중 transaction pooling과 session pooling의 차이와 Django에서 사용 시 주의사항을 설명해주세요.
Q. Django REST Framework API에서 페이지네이션이 적용된 대용량 목록 조회 시, 페이지 번호가 커질수록 응답 속도가 느려집니다. 원인과 해결 방법을 설명해주세요.
OFFSET 기반 페이지네이션의 데이터베이스 동작 방식을 생각해보세요.
PageNumberPagination 같은 OFFSET 기반 페이지네이션은 뒤 페이지로 갈수록 데이터베이스가 OFFSET만큼의 행을 건너뛰기 위해 스캔해야 하므로 느려집니다. 예를 들어 100페이지는 9900개 행을 스캔 후 버려야 합니다. 해결 방법으로 커서 기반 페이지네이션(CursorPagination)을 사용하면 마지막 조회 항목의 ID나 타임스탬프를 기준으로 WHERE 절을 사용해 일정한 성능을 유지합니다. 또는 LimitOffsetPagination 대신 Keyset Pagination을 직접 구현해 인덱스를 활용할 수 있습니다. 정렬 기준 컬럼에 인덱스가 없다면 추가해야 하며, 복합 인덱스를 고려할 수 있습니다. 무한 스크롤 방식의 UI라면 커서 페이지네이션이 가장 적합하며, 페이지 번호가 필요하다면 캐싱이나 검색 엔진(Elasticsearch) 도입을 고려합니다.
- • OFFSET 기반 페이지네이션은 뒤 페이지에서 성능 저하
- • CursorPagination으로 일정한 성능 유지
- • 정렬 기준 컬럼에 인덱스 추가
- • 무한 스크롤 UI에는 커서 방식이 적합
게시글이나 상품 목록처럼 수만 건 이상의 데이터를 페이지네이션할 때 후반 페이지 접근 성능을 개선합니다.
CursorPagination의 단점이나 제약사항은 무엇이 있을까요?
Q. Django 애플리케이션에서 특정 API 엔드포인트가 외부 결제 API를 호출하는데, 외부 API 응답이 느려지면서 전체 서버의 워커가 고갈되어 다른 정상 API까지 응답하지 못하는 상황이 발생했습니다. 이 문제를 어떻게 해결하시겠습니까?
동기 처리의 블로킹 특성과 비동기 작업 분리를 고려해보세요.
외부 API 호출이 블로킹되면서 워커 스레드가 대기 상태로 묶여 다른 요청을 처리하지 못하는 상황입니다. 즉각적인 해결책으로 외부 API 호출에 timeout을 설정해 무한 대기를 방지하고, requests 라이브러리의 connect timeout과 read timeout을 모두 지정합니다. 근본적으로는 Celery나 Django-Q 같은 태스크 큐를 도입해 외부 API 호출을 비동기 작업으로 분리하고, 결과는 웹훅이나 폴링으로 받습니다. Circuit Breaker 패턴을 적용해 외부 API가 계속 실패하면 일정 시간 호출을 중단하고 빠르게 실패 응답을 반환합니다. 또한 워커 수를 늘리거나 별도의 워커 풀을 외부 API 호출 전용으로 분리해 격리할 수 있습니다. 캐싱이 가능한 응답이라면 일정 시간 캐시해 외부 호출을 줄입니다.
- • 외부 API 호출에 timeout 설정으로 무한 대기 방지
- • Celery 등 태스크 큐로 비동기 처리 분리
- • Circuit Breaker 패턴으로 연쇄 장애 방지
- • 워커 풀 분리로 격리
결제, 알림톡, 외부 인증 등 외부 서비스 의존성이 있는 API에서 외부 장애가 내부로 전파되는 것을 방지합니다.
Circuit Breaker 패턴의 상태 전이(Closed, Open, Half-Open)와 각 상태에서의 동작을 설명해주세요.
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!