한동안 구축했던 오픈고시 사이트를 보기만 하고 관리하지 못하다가 검색 결과 조회가 너무 오래 걸리는 현상을 계속 보게 되어 개선을 하게 되었다. 사이트에서는 여러 시청과 정부 사이트가 제공하는 여러 소식들을 모아서 한 군데서 볼 수 있게 하는 것이 주요 기능인데 데이터가 쌓이다 보니 어느 순간부터 최소 2~3초가 걸리고 있는 것을 볼 수 있었다.
어차피 사이트가 아직 트래픽이 없기 때문에 현시점에서는 큰 문제가 없지만 나중을 위해서라도 그리고 기다리기 답답해서 검색 성능을 개선하게 되었다.
현황 파악
현재 MongoDB를 사용하고 있으며 정보 제공을 위해 2개의 컬렉션으로 구성되어 있다.
나라장터 입찰공고 데이터는 하루에도 끝없이 올라오고 기존 공고가 잘못 작성되면 수정하지 않고 새로운 공고로 올리기 때문에 꾸준히 쌓이는 것을 확인할 수 있다.
이름 | 개수 | 검색 시간 | 비고 |
나라장터 입찰공고 소식 | 약 143,000개 | 평균 10초 소요 | 최대 60초 소요 😱 |
시청 및 정부 사이트 소식 | 약 21,000개 | 평균 5초 소요 |
검색 성능이 느린데에는 다양한 이유가 있지만 일단 다음과 같은 방향으로 잡았다.
- 인덱스를 잘못 생성하는 경우
- 인덱스를 타지 않는 쿼리가 사용되는 경우
- 인덱스를 타지만 너무 많아서 느린 경우
- 동적인 쿼리가 많아서 Compound 인덱스를 사용하지 못하는 경우
각 방향을 기준으로 조사를 한 결과, 다음과 같은 문제점을 확인할 수 있었다.
- 나라장터 입찰공고에 존재하지 않은 필드를 인덱싱 하고 있었다.
- 검색어 또는 사이트를 필터링하거나 일부 소식에 대한 필터링으로 인해 인덱스를 타지 않고 있었다.
- 검색어를 포함하는 경우, text 인덱스를 타야 하는데 타지 않고 있었다.
- 조건에 따라 생성해야하는 인덱스가 많아서 생성하지 않았었다.
개선
일부 소식에 대한 필터링 처리 개선
기존에는 일부 소식에 대한 필터링을 별도 DB에 보관하여 쿼리 후 MongoDB에 쿼리 할 때 해당 소식을 제외하는 방식으로 필터링을 구현해 왔었다.
# 제외할 소식 가져오기 (RDB)
exclude_news = ExcludeNews.objects.filter(...)
# 제외할 소식 필터링 (MongoDB)
news_documents = NewsDocument.objects.filter(id__nin=[x.id for x in exclude_news])
이렇게 처리한 건 당시 개발 시간이 부족하고 제외할 소식을 추가할 일이 거의 없을 것으로 판단하여 작성하였다.
다만 이렇게 되면 RDB에 쿼리를 해야 하고 나온 결과를 이용해 다시 필터링해야 하기 때문에 쿼리를 한번 더 하여 시간이 걸리는 이슈가 생긴다. 데이터 양이 적다면 큰 차이를 못 느끼게 되지만 많아지면 많아질수록 쿼리에 부담이 될 여지가 있었다.
따라서 이를 해결하기 위해 기존 MongoDB 문서에 필드를 하나 사용하고 Django의 시그널 기능을 사용했다.
Django의 시그널 기능은 특정 이벤트가 발생했을 때 전후로 정의한 작업을 실행시킬 수 있는 기능이다.
Signals | Django 문서 | Django (djangoproject.com)
먼저 기존 문서에는 이미 필터링 처리를 하기 위한 목적의 필드가 있다.
거의 사용할 일이 없어서 대부분의 문서는 정의만 돼있고 실제로는 해당 필드가 있지는 않았는데 이를 다음과 같이 적용하였다.
로직
- Django 관리자에서 제외할 소식 작업 (추가/수정/제거) 진행
- 작업 진행 시 post_save 이벤트가 발생하고 제외된 소식의 ID로 문서 객체를 가져옴
- 해당 문서 객체에 필드 업데이트
위와 같이 변경 후 필터링 처리를 하는 코드는 다음과 같이 변경하였다.
# 제외할 소식 필터링 (MongoDB)
news_documents = NewsDocument.objects.filter(is_hidden__ne=True)
기존에는 RDB를 통해서 제외할 소식 목록을 한번 가져온 다음 다시 해당 목록을 이용해 소식을 필터링하였으나 현재는 관리자 페이지에서 업데이트하면 조회 로직에서는 is_hidden이라는 필드로 조회하도록 하여 검색 성능을 개선하였다.
개선 결과
비즈니스 로직에서 두번의 쿼리를 통해 필터링하던 과정을 한 번으로 줄이고 Django에서 제공하는 좋은 기능을 활용하여 성능 개선을 이루었다. 특히 시그널 기능은 이미 알고 있던 기능임에도 불구하고 딱히 사용할 생각을 안하고 있었는데 이번 기회로 사용하게 되었다.
이름 | 기존 검색 시간 | 1차 개선 검색 시간 | 최종 검색 시간 | 비고 |
나라장터 입찰공고 소식 | 평균 10초 소요 | 평균 2초 소요 | 평균 200ms | |
시청 및 정부 사이트 소식 | 평균 5초 소요 | 평균 1초 소요 | 평균 100ms |
다만 검색 성능에 여전히 부족함을 느껴 다른 개선안도 적용하였고 최종적으로 단순 조회 시에는 각각 200ms, 100ms 이내의 응답속도를 얻게되었다. 현재 개선된 검색 성능에 상당한 만족을 하고 있다.
적용된 다른 개선안은 다음에 작성 예정 😎
'Developer > Python' 카테고리의 다른 글
운영 중인 웹사이트 MongoDB 검색 성능 개선기 - 2 (0) | 2023.12.18 |
---|