django.db.migrations.exceptions.InconsistentMigrationHistory: Migration account.0001_initial is applied before its dependency users.0001_initial on database 'default'.

 

OperationalError: no such table: user_user

 

커스텀 유저를 처음 설정할 때 발생할 수 있는 에러로서

 

해결법은 

python manage.py migrate --run-syncdb

 

명령어를 통해 마이그레이션을 하지 않고 테이블만 생성하여 해결할 수 있다.

 

class Task(APIView):
    def get(self, request):
        questions = Task.objects.filter(client=request.user)
        serializer = TaskSerializer(questions, many=True)
        return Response(serializer.data)

오타없이 제대로 작성한 것 같은데 위와 같은 에러가 뜬다면

위 예시처럼 views.py의 class명과 model 이름이 일치하기 때문에 발생하는 에러이다.

class명과 model name을 다르게 설정하면 제대로 동작한다.

models.py에서

user_foreign_key = models.ForeignKey("auth.User", on_delete=models.CASCADE, verbose_name="foreign_key")

위와 같이 user의 외부키 필드를 설정해놓고 

user_data = UserData.objects.get(data_foreign_key="something")

정작 views.py에서는 유저 모델의 일부 속성만으로 접근했을 때 뜨는 에러이다.

 

해결법은

위 model.objects.get으로 접근하기 전에 

user = User.objects.get(email=client_attribute)
user_data = UserData.objects.get(data_foreign_key=user)

위처럼 User.objects.get을 거쳐서 나온 객체를 비교값으로 넣어주면 제대로 작동된다.

pip install --upgrade --force-reinstall Django

위 커맨드로 쉽게 해결할 수 있는 에러이다.

 

이후

makemigrations 을 진행하면 된다.

파이콘 한국 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로 작성하는 것을 망설이지 말자

요약 이미지

 

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

+ Recent posts