본문 바로가기

Developer/Python

운영 중인 웹사이트 MongoDB 검색 성능 개선기 - 2

운영 중인 웹사이트 MongoDB 검색 성능 개선기 - 1 :: 더 나은 개발을 설계하는 사람 (tistory.com)

 

운영 중인 웹사이트 MongoDB 검색 성능 개선기 - 1

한동안 구축했던 오픈고시 사이트를 보기만 하고 관리하지 못하다가 검색 결과 조회가 너무 오래 걸리는 현상을 계속 보게 되어 개선을 하게 되었다. 사이트에서는 여러 시청과 정부 사이트가

torrang.tistory.com

 

이전에 진행했던 성능 개선을 통해 다음과 같이 최소 5배 이상의 성능 향상을 얻었다.

 

기존

  • 검색에서 제외할 소식을 관리하는 테이블을 생성
  • 관리자 페이지에서 제외할 소식을 추가 또는 제거
  • 검색할 때마다 제외할 소식들의 id를 가져와서 쿼리에 포함

변경

  • 검색에서 제외할 소식을 관리하는 테이블을 생성
  • 관리자 페이지에서 제외할 소식을 추가 또는 제거
  • 추가/제거 이벤트 발생 시 Django signal 기능을 이용해 해당되는 MongoDB 문서에 is_hidden 필드에 상태 업데이트
  • 검색할 때 is_hidden 필드가 true인 문서를 제외하여 쿼리

문서의 id를 이용해 동적으로 필터링하는 쿼리를 제거하고 boolean 타입의 필드를 문서에 포함시켜 해당 필드를 기준으로 필터링하기 때문에 성능 향상이 극적으로 될 수밖에 없다.

 

다음 개선점

첫번째 개선 이후 적용된 개선점을 동일하게 다시 적용한 사례로 is_hidden 필드를 동일하게 활용하여 역시 동적 쿼리를 제거하여 쿼리 성능을 높였다.

 

1번 개선  과정에서는 특정 소식들을 필터링하도록 구성되어 있는데 내부적으로는 특정 키워드가 포함된 경우 동일하게 필터링하도록 구성되어 있다. 이는 수집되는 소식 중 특정 개인에 한정된 소식이나 정보의 가치가 낮은 단순 공시는 표시하지 않기 위해 작성되었다. 시청 사이트에서는 특성상 이런 유형의 소식이 매우 많이 올라오는데 이런 소식이 수집되더라도 굳이 웹사이트에 표시할 필요는 없어서 추가한 기능이다.

 

따라서 현재 사이트에서는 두가지 유형의 필터링되는 소식이 있다.

  1. 특정 소식
  2. 특정 키워드가 포함된 소식 (대부분이 이에 해당)

 

두가지 모두 필터링되는 소식이기 때문에 기존에는 개선 이전과 동일하게 동적으로 필터링이 되고 있었고 쿼리는 다음과 같았다. 

# 1. 제외할 소식 필터링
queryset = queryset.filter(...)

# 2. 제목에 특정 키워드가 포함된 소식 필터링
queryset = queryset.filter(title__not=re.compile(f".*{'|'.join(exclude_keywords)}.*", re.IGNORECASE))

...

 

당연하게도 텍스트 검색이기 때문에 이를 위해 인덱스를 만들었으나 사용이 안 되는 이슈가 있었다.

따라서 "동적 쿼리 + 인덱스 안됨"이라는 두가지가 시너지를 만들어 최대 60초라는 검색 시간을 만들어낸 것이다...

 

1번 개선 후 이를 어떻게 개선할지 고민하다가 동일한 방식으로 최적화가 가능한 것으로 판단되어 다음과 같이 진행하였다.

 

개선방안

  1. 특정 키워드가 포함된 소식을 필터링하기 위해 키워드 등록/수정/삭제 작업 진행
  2. Django에서 키워드 테이블에 대한 post_save 이벤트가 발생
  3. is_hidden=True이면서 검색 제외 소식 ID에 포함되지 않은 문서를 조회
  4. 문서의 is_hidden 필드를 false로 업데이트
  5. 등록된 키워드가 포함된 문서를 조회
  6. 문서의 is_hidden 필드를 true로 업데이트

위와 같이 작업을 진행하는 경우, 필터링 처리된 특정 소식의 is_hidden 필드를 true로 유지하면서 다시 특정 키워드를 포함한 문서의 is_hidden를 true로 업데이트할 수 있다.

 

다만 이렇게 변경됨으로써 생긴 문제점으로는 기존의 is_hidden 필드가 true인 필드를 모두 조회하여 false로 변경하고 다시 모든 특정 키워드가 포함된 문서의 is_hidden 필드를 true로 업데이트하기 때문에 문서가 많아질수록 업데이트 시간이 많이 소요되는 단점이 있다.

 

이는 post_save 이벤트가 발생할 때마다 모든 키워드에 대해 업데이트하지 않고 변경된 특정 키워드의 상태만 업데이트하도록 하여 업데이트 시간을 개선할 예정이다. (*로직 상 문제가 없는지 확인 필요)

 

개선 결과

앞서 진행한 두개의 개선 작업을 통해 최종적으로는 각각 1초, 2초 이내의 검색 시간을 가지게 되었고 후술 할 나머지 개선 작업을 통해 최종적으로는 평균 검색 시간이 1초가 안 되는 성능을 얻을 수 있었다. (물론 이 검색 시간 또한 여전히 아쉬운 부분이 많아서 여유 있을 때마다 개선할 예정이다.)

이름 기존 검색 시간 1차 개선 검색 시간 최종 검색 시간 비고
나라장터 입찰공고 소식 평균 10초 소요 평균 2초 소요 평균 200ms  
시청 및 정부 사이트 소식 평균 5초 소요 평균 1초 소요 평균 100ms  

 

기타 개선점

위 두개의 개선 작업 외에도 다음과 같은 개선 작업을 진행하였다.

  • 인덱스 분리
    • 기존에는 한 개 인덱스에 6개 필드를 모아서 인덱싱하였으나 조건에 따라 특정 필드를 사용하지 않는 경우가 있어서 필수로 사용하는 필드와 사용하지 않는 필드를 기준으로 인덱스를 분리 생성하였다. 
  • 텍스트 필드에 대한 인덱스 변경?
    • 오픈고시 사이트에서는 키워드 검색을 지원하고 있는데 당연히 텍스트 검색이기 때문에 인덱스의 유형을 text로 설정하였으나 왜인지 사용하지 않는다.
    • 키워드 검색 시에는 위와 같이 정규표현식으로 Case-Insensitive 기준으로 검색하도록 되어있음에도 불구하고 생성한 text 유형의 인덱스를 사용하지 않는다.
    • 같은 필드를 asc, desc로 생성 시 후 검색 시에는 인덱스를 사용하는 것을 확인해서 우선 해당 방식으로 유지하고 있으나 실제 쿼리 성능 개선이 이루어졌는지 판단하기 어려워 여러 테스트와 조사를 진행 후 판단할 예정이다.

 

결론

  1. 필터링 시 최적화를 잘하자.
  2. 인덱스는 제대로 알아보고 생성하자.
  3. 사용하는 DB의 특징을 제대로 알아보자.

 

p.s. 개선기는 한 번씩 개선될 때마다 업데이트 예정

'Developer > Python' 카테고리의 다른 글

운영 중인 웹사이트 MongoDB 검색 성능 개선기 - 1  (0) 2023.12.16