파이콘 한국 2020 컨퍼런스에서의 Django ORM (QuerySet)구조와 원리 그리고 최적화전략 라는 세션을 듣게 되었다. 해당되는 내용들이 Django에서 ORM을 통해 개발을 할 때에 최적화하는 것에서 실제적으로 도입할 수 있는 중요한 원리와 팁들이 포함되어 큰 도움이 될 것 같아 세션 내용에 대해 정리하고 공유한다.

 

Lazy Loading

  • ORM에서는 필요한 시점에만 SQL을 호출한다. (lazy loading 지연로딩)
  • 정말로 사용하지않으면 Query setSQL을 호출하지 않는다.
  • user.objects.all()을 모든 유저를 정의하더라도, users[0]만 한다면 1명만 얻기 위해 LIMIT 1 옵션이 걸린 상태로 SQL을 호출한다.
  • 한번만 호출해서 가져와도 되지만, ORM은 이후 로직을 모르기 떄문에 필요한 시점에 필요한 것만 가져오기 때문에 비효율적
  • List(user) = list(users) -> 호출하는 순서만 바뀌는 것으로 쿼리셋 캐싱을 통해 개선가능. 따라서 쿼리셋 캐싱을 재사용하는 법으로 해결해야 함.

Eager Loading 

  • Eager Loading 즉시로딩 : N + 1 Problem
  • 즉시로딩을 하기위해, 즉, (N +1) 해결을 위해 django에서는 select_realated()prefetched_related() 메서드를 사용한다.
  • for문을 돌 때마다 조회할 때마다 sql이 계속 호출되는 문제를 N + 1 문제라고 함
  • (유저를 호출하는 sql 한번) + (유저 개수 N) = (N + 1 개의 쿼리)
  • QuerySet1개의 쿼리와 0~N개의 추리쿼리 셋으로 구성되어 있다.
  • prefetched_related : 추가쿼리 셋
  • 호출할 때 result cache(SQL의 결과를 저장해놓고 재사용) 에 원하는 데이터가 없으면 쿼리셋 호출

 

  • select_realated() : 조인을 통해 즉시로딩
  • prefetched_related() : 추가 쿼리를 사용하여 즉시로딩(정보를 전부 끌어오겠다)
  • 역참조는 select_related 옵션을 줄수 없음.django에서 제약이 있는 부분.
  • prefetched_related 에 선언한 속성 개수만큼 queryset이 추가로 호출됨
  • 테스트할 assertNumQueries()로 테스트케이스를 작성하지만 매번 체크를 해줘야되는 문제 (꼼꼼하게 보겠지만), N+1문제로 인한 크리티컬한 성능 이슈만 커버하기 위해, captureQueriesContext를 활용하는 것이 도움됨

 

- QuerySet 사용에서 실수하기 쉬운 점들 

  • prefetched_related()는 추가 쿼리셋에서 제어
  • filter()는 새로운 쿼리셋이 아니라 한 개 쿼리셋 안에서 제어
  • 혼용하는 문제를 해결을 위해 prefeted_related 옵션을 제거하거나 / prefetch()에 조건을 넣도록 한다.
  • annotate select_related filter prefetch_related 순서가 실제 SQL 순서와 가장 유사하므로 이 순서로 QuerySet을 작성하는 것이 추천된다.
  • Queryset 캐시를 재활용하지 못할 때가 있다. .all로 질의하면 캐시를 재활용하지만, 특정 상품을 찾으려고 하면 캐시를 재사용하지 않고 sql로 질의. 쿼리셋을 재호출하지않으려면 .all로 불러온 것에서 if 절을 활용하여 리스트 컴프리헨션으로 빼오는 것을 추천한다.
  • raw 쿼리셋은 쿼리셋의 또 다른 유형이기 때문에 prefetch_related(), Prefetch() 사용이 가능하다. NativeSQL이 아닌 이유는 위의 옵션을 사용할 수 있다는 점에서 볼 수 있음.
  • 서브 쿼리의 발생 조건이 두 가지 있다.
  • 쿼리셋 안에 쿼리셋이 존재할 때 -> 리스트로 감싸서 해결 가능
  • 역방향 참조모델에서(정방향은 해당X) exclude() 조건절에 조인이 되지않고 서브쿼리(슬로우쿼리)로 발생. JOIN으로 풀리는 것이 안되기 때문에, prefetched_related 옵션을 통해 해결.
  • values(), values_list() 사용시에 EagerLoading 옵션 무시(select_related, prefetched_related 옵션 무시) (DB raw 단위로 한줄 한줄로 데이터 반환 objectrelational간에 apping이 일어나지 않기 때문)
  • 복잡한 ORM이 있다면 NativeSQL로 작성하는 것을 망설이지 말자

요약 이미지

 

쿠키, 로컬 스토리지, 세션 스토리지

쿠키

  • 만료 기한이 있는 key-value 저장소
  • 4KB 용량 제한이 있고, 매 서버 요청마다 서버로 쿠키가 같이 전송된다.
  • HTTP request는 stateless이기 때문에 요청 자체만으로는 요청이 어디서 오는지 알 수 없다. 쿠키가 요청자에 대한 정보를 담아 서버로 보냄으로써 클라리언트를 파악한다.

로컬 스토리지

  • 사용자가 지우지 않는 이상 브라우저에 계속 남아있다. 영구적.
  • key-value 저장소. key, value는 문자열로 변환된다.
  • HTML5 에 도입되었다.

세션 스토리지

  • 로컬 스토리지와 다르게 데이터가 영구적으로 보관되지 않는다. 브라우저를 껐다가 키면 사라지는 저장소.
  • HTML5 에 도입되었다.

'Web' 카테고리의 다른 글

HTTP 프로토콜  (0) 2020.06.10
Webpack  (0) 2020.02.22

HTTP 프로토콜

HTTP 프로토콜은 stateless 프로토콜이다. stateless란 다시 말해, 각각의 데이터 요청이 독립적으로 관리가 되는 것이고, 세션 등을 서버가 따로 관리하지 않는 것을 의미한다. 연결을 끊는 순간 클라이언트와 서버의 통신이 끊기며, 상태 정보를 유지하지 않는다.

  • 장점 : 불특정 다수를 대상으로 하는 서비스에 적합
  • 단점 : client가 로그인을 하더라도 로그 정보를 유지할 수 없다. 따라서 서버는 세션 정보를 cookie를 이용하여 관리한다.

HTTP 프로토콜은 TCP/IP 통신 위에서 동작하며 80번 포트를 사용한다.

How HTTP works

HTTP 프로토콜은 Request를 보내고 Response를 받는 식으로 동작한다. 즉 Client-Server 형식이다.

HTTP를 통해 전달되는 자료는 http:// 로 시작하는 URL로 조회가 가능하다.

HTTP 요청 메소드

  • GET: URL가 가진 정보를 조회를 요청

  • POST: 클라이언트에서 서버로 entity를 제출함. 때에 따라서 create, update, delete도 가능.

  • PUT: 내용 갱신. 전체 자원을 업데이트 할 때 사용한다.

  • DELETE: 리소스를 삭제

  • PATCH: 리소스의 부분을 수정하는 데 사용.

  • HEAD: 헤더 정보만 요청. response body를 반환하지 않음. 자원의 존재 유무만 확인할 때 사용

  • OPTIONS: 서버 옵션을 확인하기 위한 요청. CORS에서 사용

HTTP 상태 코드

  • 2XX : 성공
    • 200 : 오류없이 전송 성공
  • 3XX : 리다이렉션
    • 304 : 클라이언트의 캐시에 문서가 저장되었고, 선택적인 요청에 의해 수행됨
  • 4XX : 클라이언트 에러
    • 400 : 요청 실패. 문법상 오류가 있어 서버가 요청을 이해하지 못함
    • 401 : 권한 없음. 인증이 필요함.
    • 403 : 금지 (접근 거부 or SSL 등)
    • 404 : 문서를 찾을 수 없음. 서버가 요청한 리소스를 찾지 못함.
    • 405 : 메서드 허용 안됨
  • 5XX : 서버 에러
    • 500 : 서버 내부 오류. 서버가 요청을 수행할 수 없음.
    • 503 : 외부 서비스가 멈췄거나, 이용할 수 없는 서비스 (일시적)

HTTP 요청 메세지 형식

Request Header + Empty Line + Request Body

  • Header

    • 요청 메소드 + 요청 URI + HTTP Protocol 버전
    • GET /background.png HTTP/1.0
    • Header 정보 (날짜, 웹서버 이름 및 버전, 콘텐츠 타입 및 길이, 캐시 제어 방식, keep-alive 설정 등)
      • keep-alive란 지정된 시간동안 연결을 끊지 않고 요청을 계속해서 보내는 방식. TCP는 3 way-hanshake 방식으로 연결을 확인하는데, 매 데이터 요청마다 연결을 맺고 끊으면 비효율적이기 때문에 필요한 방식
  • Empty Line

    • 요청에 대한 모든 meta-content가 전송되었음을 알림
  • Body

    • POST와 PUT만 존재하는 부분
    • 요청과 관련된 내용
      • POST는 이 Body 안에 entity를 넘기고, GET은 url로 요청하는 정보를 넘긴다

HTTP 응답 메세지 형식

Response Header + Empty Line + Response Body

  • Header

    • HTTP Protocol 버전 + 응답 상태코드 + 응답 메세지
    • HTTP/1.1 404 Not Found.
  • Empty Line

    • 요청에 대한 모든 meta-content가 전송되었음을 알림
  • Body

    • 실제 응답 리소스 데이터

'Web' 카테고리의 다른 글

쿠키, 로컬 스토리지, 세션 스토리지  (0) 2020.06.17
Webpack  (0) 2020.02.22

Nginx 를 이용한 Django 배포 (feat. Docker)

runserver로만 장고 서버를 구동하고 있었는데, 성능이나 안정성 이슈가 있다고 한다. django의 runserver 커맨드는 주로 테스트용으로 사용되며, production 용도로는 WAS(Nginx, gunicorn)과 연동하여 배포하는 것이 일반적인 방법이다. 따라서 프로젝트를 진행하면서 django에 웹 서버인 nginx를 연동하는 과정이 있었다.

Docker를 이용하여 nginx를 쉽게 연동하는 방법이 있어 소개해보자 한다.

기존 장고 프로젝트가 있는 상태를 가정하고 진행하겠다. 우분투 기준으로 설명하려한다.

  1. 가상환경으로 들어가서 gunicorn을 설치한다. (무거운 uWSGI 대신 gunicorn으로 nginx와 연결하였다)

    • 참고로 uWSGI나 gunicorn같은 웹서버를 쓸 때, static file을 모으지 않으면 css가 적용되지 않은 상태로 나온다. 해결을 위해서,

      1. settings.py에 아래 코드 추가

        STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
      2. collectstatic 명령어를 통해 static file을 모아줌

        python manage.py collectstatic
  2. source venv/bin/activate pip install gunicorn

  3. 도커 설치

    • 설치에 필요한 패키지들 설치
    • sudo apt-get update && sudo apt-get install \ apt-transport-https \ ca-certificates \ curl \ software-properties-common
    • ppa관련 에러가 뜬다면 아래 스크립트 실행
    • sudo add-apt-repository -r ppa:jonathonf/python-3.6 sudo apt update
    • 도커 설치
    • curl -fsSL https://get.docker.com/ | sudo sh
    • 설치된 도커는 아래 스크립트로 확인할 수 있다.permission denied가 난다면, /var/run/docker.sock 파일의 권한을 변경
    • sudo chmod 666 /var/run/docker.sock
    • docker version
  4. 프로젝트의 루트 디렉토리에 Dockerfile 이라는 파일을 추가해준다. 내용은 아래와 같다.

    • 파이썬 버전에 맞게 첫 줄을 작성해주고
    • 도커 내에서 /code 라는 이름의 폴더를 생성
    • 로컬의 requirements.txt 파일을 mount하고 패키지를 설치하는 과정
    • 현재 로컬의 모든 파일, 폴더를 /code/ 로 마운트
  5. FROM python:3.6 RUN mkdir /code WORKDIR /code ADD requirements.txt /code/ RUN pip install -r requirements.txt ADD . /code/

  6. nginx 폴더를 만들고, nginx.conf 파일 생성server_name에는 도메인 주소를 넣으면 된다. (ex> ec2-54-180-94-185.ap-northeast-2.compute.amazonaws.com)

  7. server { location / { proxy_pass http://web:8000/; } location /static/ { alias /static/; } listen 80; server_name <도메인 주소>; }

  8. 프로젝트의 루트 디렉토리에 docker-compose.yml 파일 생성<프로젝트이름> 는 wsgi.py가 속한 폴더의 이름 (커스텀하지 않는다면 프로젝트 이름)

  9. depends_on을 통해 순서가 있게 서비스가 실행될 것이다. (web -> nginx)

  10. version: '3' services: nginx: image: nginx:latest ports: - '80:80' volumes: - .:/code - ./nginx:/etc/nginx/conf.d - ./staticfiles:/static depends_on: - web web: build: context: . dockerfile: Dockerfile command: gunicorn <프로젝트이름>.wsgi:application --bind 0.0.0.0:8000 volumes: - .:/code - ./staticfiles:/staticfiles expose: - '8000'

  11. settings.py의 ALLOWED_HOSTS에 'web'을 추가

  12. ALLOWED_HOSTS = ['web']

  13. 도커를 실행하고, 브라우저에서 도메인명을 입력하여 정상 작동하는지 확인

    • docker-compose 설치
    sudo pip install docker-compose
    docker-compose up --build

    shell을 꺼도 작동을 하도록 백그라운드 실행을 원한다면 -d 옵션을 추가하면 된다.

  14. docker-compose up --build -d

캐싱 전략

  • cacheFirst (캐시를 네트워크 보다 먼저 요청할 것인지)

    • offline 페이지가 먼저 보여질 때 유리
  • cacheOnly (캐시만 볼 것인지)

    • static file가 해당
  • networkFirst (캐시보다 네트워크를 먼저 요청할 것인지)

    • UX에 좋지 않음
  • networkOnly (네트워크만 요청할 것인지)

    • 캐시가 필요없는 GET 메소드가 아닌 method
  • staleWhileRevalidate (캐시 먼저, 그 다음 네트워크에서 요청된 캐시 반환)

    • 주로 사용되는 방식
  • Cache then network (캐시, 네트워크에 요청을 동시에 하고, 캐시 data 먼저 표시, 이후 네트워크 data 표시)

    • workbox에는 없는 전략, networkFirst보다는 UX에 좋음

 

나는 PWA 웹앱 개발을 할 때 크롬 카나리아를 유용하게 사용하는데 모르는 사람들이 많을 것 같아 포스팅한다.

 

https://www.google.com/intl/ko/chrome/canary/

 

Chrome 카나리아

개발자용 나이틀리 빌드

www.google.com

Chrome 카나리아는 개발자용 크롬 브라우저라고 생각하면 좋다. 

구글에서는 웹의 최첨단 기능을 포함한 브라우저라고 광고하고 있다..
실제로 Portals( https://web.dev/hands-on-portals/ ) 등 구글에서 새롭게 나오는 기술을 테스트해볼 수 있는 유일한 브라우저이긴 하다. 

 

PWA 개발에 Chrome 카나리아를 쓰면 좋은 이유는,

다른 브라우저와 달리 일반적인 검색 용도 등으로 쓰일 일이 없어서

다른 도메인 사이트들의 service worker와 헷갈릴 일이 없다는 게 장점이다. 그뿐인 것 같긴한데.. 나름 유용. 

 

 

+ Recent posts