티스토리 뷰
모름지기 웹 개발에 있어서, 웹 개발에서도 게시판을 개발하는데 있어서 가장 큰일 중에 하나는 Paginate라는 기능일 거다. 뭐, 검색기능도 있긴한데 그건 비즈니스 레이어(Business Layer: 데이터의 처리를 위한 서비스계층)에서 처리하는 거라기보다는 파시스턴스 레이어(Persistence Layer: 데이터의 영속성을 관리하는 계층)에서 처리하는 내용이라 난이도로 치면 아무래도 Paginate(이하 페이징)가 손이 많이 가고 할 일도 많고 귀찮은 일이 많다.
그래서 페이징을 먼저 처리하려고 생각해보면 아이러니하게도 검색부분이 먼저 선행되어야하는 이슈가 생긴다. 왜 그런지 잠깐 설명하자면 페이징의 기능을 차근차근 생각해보면 이해가 된다.
페이징을 만들때 가장 중요한 것은 게시글의 전체 카운트를 가져와야한다. 그래야 총 몇 페이지까지 게시글이 존재하는지 알기 때문인데 예를 들어 한 페이지당 20개의 게시물을 표시하는 게시판에 총 4068개의 게시물이 존재한다면 전체 페이지는 몇 페이지일까? 라는 산수 문제를 풀기 위해서는 아래의 공식을 이용하면 되겠다.
자, 여기서 중요한 것은 Total Count 영역이다.
내가 왕년에 Query 좀 짜봤다 하는 사람들은 전체 카운트를 얻기 위해 아래와 같이 쿼리를 짜겠지.
이렇게 짜면 게시물의 전체 카운트가 나오는거 아님? 하면서 즐거운 기분에 룰루랄라 콧노래를 부를지 모르겠다만. 여기서 우리가 쉽게 간과할 수 있는 아이러니를 알아보기 위해 아래와 같은 순서대로 게시판 타입을 가져온다고 생각해보자.
- 사용자가 검색을 한다.
- WHERE를 이용해 내용을 필터링한다.
- 정제된 게시글을 반환한다.
- 정제된 게시글의 전체 갯수는 200개고 한 화면에 20개씩 보여진다고 가정하자.
이 때 위에서 이야기한 Query로 전체 카운트를 가져온다면 검색 후 총 20페이지만 표시할 수 있는 컨텐츠임에도 204페이지까지 컨텐츠를 보여줄 수 있는 버그가 발생하게 되는거다.
이해가 안간다면 그냥 이런걸 만들 때는 페이징같은 건 나중에 하고 검색먼저 만드는게 나중에 고생을 덜 하게된다고 생각하면 정신건강에 이로우니 그냥 그렇게 믿자고 생각하면 편하다.
일단 검색과 관련해서 filtering 이라는 걸 좀 알아보자.
Restframework에서 Filtering과 관련한 기술문서를 보고 싶다면 http://www.django-rest-framework.org/api-guide/filtering/ 여기를 참고하면 된다.
이 Filtering과 관련하여 이용할 수 있는 기능은 크게 두가지 정도로 보면되는데 하나는 filtering backend 클래스를 적용하고 필드를 적용시키면 아주 간단하게 검색 기능을 적용시킬 수 있다.
지난번의 myhome 프로젝트에서 requirements/base.txt 파일을 아래와 같이 수정해보자.
django-filter를 추가하고 Initialize For Local을 실행한다.
이렇게 django-filter가 설치되는 것을 확인할 수 있다.
그리고 myhome/settings/base.py 파일에 있는 INSTALLED_APPS를 아래와 같이 수정한다.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'corsheaders',
'django_filters',
'hello.apps.HelloConfig',
'board_type.apps.BoardTypeConfig'
]
10번째 줄을 추가하면 일단 준비는 된거다. 이제 board_type/view.py를 수정한다.
from django.shortcuts import render
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
from rest_framework.filters import SearchFilter
from .serializers import BoardTypeSerializer
from .models import BoardType
# Create your views here.
class BoardListCreateView(ListCreateAPIView):
name = "board-list-create"
serializer_class = BoardTypeSerializer
queryset = BoardType.objects.all()
filter_backends = [SearchFilter, DjangoFilterBackend]
filter_fields = ['type']
search_fields = ['type', 'description']
class BoardRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView):
name = "board-retrieve-update-destroy"
serializer_class = BoardTypeSerializer
queryset = BoardType.objects.all()
보기는 좀 힘들겠지만 위의 내용을 추가한다. 그리고 서버를 기동한 뒤에 localhost:8080/board-type/ 으로 접속한다.
그러면 상단에 Filters 라고 하는 버튼이 추가되어있는 것을 확인할 수 있다. 저 버튼을 클릭해 보자.
이렇게 위에는 검색을 할 수 있고 아래쪽에는 Type을 찾아볼 수 있도록 만들어져 있다.
여기서 질문을 해볼 수 있는 것은 검색이면 검색이고 Filter면 Filter지 무슨 차이 때문에 이렇게 둘로 나뉘어져 있는가 하는 점이다. search_fields와 filter_fields는 각각 독특한 특징이 있다.
그 특징을 확인해보기 위해서 LOGGING 설정을 지정해보도록 하자.
myhome/settings/local.py 파일을 열어 아래의 설정을 추가한다.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
}
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'level': 'DEBUG',
},
}
}
위의 설정을 추가한 뒤에 서버를 재기동하면 아래의 이미지 처럼 Django의 모든 행동들을 로그로 찍어주게 될거다.
이렇게 만들어둔 뒤에 아까의 그 페이지로 돌아가 검색쪽에 아무 키워드를 입력해보자.
그리고 Search 버튼 클릭. 이후에 어떤 쿼리를 어떻게 보냈는지 로그를 한번 보자.
쿼리가 주르륵 흘러간 것을 확인해볼 수 있다. 저 쿼리를 보기좋게 만들어보면 아래의 이미지와 같다.
WHERE 절을 읽어보면 type과 description 컬럼에 동일하게 '%헬로%'라고 LIKE 검색을 수행한 것을 확인 할 수 있다. 아까 view.py 코드를 다시 읽어보면
search_fields 에 지정해 놓은 컬럼 두개를 LIKE 검색을 한 거다. 처음에 내가 이걸 보고 진짜 멘탈이 날아가 영혼마저 빨려나간 기분이 들었을 정도였으니 왜냐하면 일단 아까 검색을 수행했을때 요청한 URL을 읽어보자.
Query String 에 search를 키로 잡고 던진 값을 search_fields에 명시된 필드에 일일이 LIKE 검색을 수행한다는 소리인 거다. 이게 뭔 대수냐고 생각하는 사람이 있을까봐 하는 이야긴데 지금이야 필드가 두개뿐이니 상관은 없지만 나중에 테이블에 값이 약 10만개 정도 들어가있고 search_fields에 선언된 필드가 약 10개정도라고 가정해보자. 우리가 필요한 검색 필드는 1개지만 검색이 불필요한 필드 9개를 무의미하게 WHERE절에 포함시킨다는 소리다. 아참, LIKE 검색만 있는 것은 아니고 필드 앞부분에 접두사를 붙여 LIKE 검색 외에도 매칭검색, 앞부분만 %, 뒷부분만 %, 등등등을 수행할 수 있으니 Django 메뉴얼을 읽어보기 바란다.
뭐 어쨌든 이런 옵션들을 제공한다고 해도 불필요한 쿼리를 만들어 성능을 저하시킬 가능성이 다분이 높은 이 기능을 사용한다는건 머리에 총을 맞거나 혹은 당장 어떤 결과물을 제공해야하는 상황이 아니라면 절대로 해서는 안될 일이다.
이 사실을 알게된 나의 표정
이런걸 보고 Too Much Kindness 라고 한다지. 과도한 자동화때문에 발생하는 성능의 저하는 결국 없느니만 못한 기능이 되어버렸다. 그렇다면 filter_fields는 어떻게 동작할까?
filter_fields에 지정된 type 필드를 정확히 매칭검색으로 찾아주는 것을 확인할 수 있다. 그런데 내가 메뉴얼을 몇번을 읽어보고 알아봤지만..... LIKE 검색을 지원하지 않는다는 사실을 알게 되었다.
아니 하나가 멀쩡하면 하나가 없고 다른 하나가 멀쩡하면 멀쩡하던게 병신이 되는 이런 기능이라니........ 이런 사실을 알게된 나는 정말 실제로 프로젝트를 드롭하고 Django를 폐기한 뒤 더 늦기전에 어서 Java로 갈아타자고 소리를 쳤었다. 물론 나중에 이런 일들이 또 생기게 되는데 그건 페이징 기능할 때 알려주겠다.
암튼, 이런 사실들 때문에 나의 멘탈이 털려가고 있을 때 쯤 filters 의 또다른 기능을 발견하게 되었다. 그 이야기는 2부에서 계속하겠다.
'Dev > Python' 카테고리의 다른 글
Django API 연동하기 - 2. 검색과 페이징 #3 PageNumberPagination (0) | 2018.06.13 |
---|---|
Django API 연동하기 - 2. 검색과 페이징 #2 (2) | 2018.06.06 |
Django API 연동하기 - 1. 준비운동 (0) | 2018.06.01 |
Django CORS 설정과 API 연동 (0) | 2018.05.31 |
Django Model과 Migrate 그리고 Serializers. (0) | 2018.05.30 |
- TAG
- angular, API서버, api연동, Django, Filter, filter_fields, python, Python Django, restframework, search_fields, 장고