티스토리 뷰
지난 포스팅에서 나는 기존 팀원이 짜놓은 코드를 리팩토링 했다.
단순 라이브러리를 이해하고 리팩토링 한 것 뿐아니라, 파일로 읽던 금칙어 목록들을 DB로 이전시켰다.
📂 파일로 금칙어를 읽어 처리하는데의 문제점
파일로 금칙어를 읽으면 몇가지 불편한 문제점이 발생했다.
[문제점 1] 금칙어 목록을 Continuous Delivery 과정에서 jar 파일 뿐 아니라 금칙어 파일도 S3에 같이 저장해줘야 했다. 그래야 application 실행 시점에 메모리에 읽을 수 있으니깐.
[문제점 2] 금칙어를 추가하거나, 삭제하려면 다시 배포를 해야한다. 그러면, 서브모듈 관리도 해야하고, 버전 관리에 금칙어 텍스트 파일 관리가 포함되게 된다.
따라서, 코드가 아닌 단순 목록 관리가 버전 관리에 포함되게 되는 문제가 발생한다.
[문제점 3] 금칙어에 대한 추가적인 요구사항이 발생했을때 확장성이 떨어진다. 예를들어, 사람들이 자주 입력하는 금칙어에 대한 통계정보가 필요한 경우 어떻게 기능추가를 할 수 있을까?
그러면 어차피 DB로 테이블을 분리해야하기 때문에, 처음부터 파일이 아닌 DB로 분리하는게 맞다고 판단했다.
👍 이렇게 해결했어요!
따라서, 나는 DB로 금칙어 목록을 관리하는게 맞다고 판단했고, 다음과 같이 금칙어에 대한 변동사항이 생기면 서버를 껐다가 다시 키는게 아닌, api를 통해 동적으로 관리할 수 있도록 구조를 변경했다.
아래와 같은 4가지 api를 추가했다.
그리고 아래와 같이 문장에서 발견된 금칙어에 대해 사용횟수도 올려주어 통계정보로 사용할 수 있게 만들었다.
또한, 기존 파일로 관리되던 금칙어 목록을 Client에서 필요 할 수도 있기 때문에, 영향을 최소화하기 위해서 CSV로 금칙어에 대한 정보를 추출 할 수 있게 만들었고,
api 호출 시마다 CSV 파일을 만드는 것은 비효율적이며 서버에 많은 부하를 줄 수 있기 때문에 아래와 같이 CSV 파일을 만들어 S3에 저장하고, 금칙어에 대한 변경사항이 생긴 시간 (LastModifiedDate 컬럼 중 가장 최근 값)과 다운로드 시간을 비교해 변동사항이 없다면 기존 생성해 둔 CSV 경로를 메모리에 저장해두고 리턴하게 만들었다. (CSV 생성은 apache common csv 라이브러리를 이용했다)
그리고 아래와 같이 금칙어에 대한 변동사항이 있을때, Concurrent Radix Tree의 이점을 최대한 활용하여 trie또한 업데이트 시켜주었다.
그런데, 이 Concurrent Tree 라이브러리를 사용하면 아호코라식 알고리즘을 사용하지 못한다.
어떻게 하면 좋을까?
👀 아호코라식 알고리즘을 도입해야할까? 라이브러리들을 비교해보자!
기존에 사용하고 있던 ConcurrentInvertedRadixTree는 금칙어 개수에 상관없이(n) 입력받는 문자열 d의 길이 * log(키워드 평균길이)의 시간복잡도를 가진다.
https://github.com/robert-bor/aho-corasick
반면, 아호 코라식 알고리즘을 제공하는 위 라이브러리를 확인해보면, 다음과 같은 말을 확인할 수 있다.
얼마나 많은 키워드가 주어지더라도, O(n)이다.
즉, 라이브러리를 비교해보면 시간복잡도 측면에 있어서는 아호코라식 알고리즘을 사용하는게 조금 더 나을 것으로 보인다.
하지만 위 라이브러리의 문제점은, 트라이 생성에 있는데, 아래와 같이 Trie 생성이 한번 생성되면 고정되고, 변경할 수가 없다.
따라서 동적으로 금칙어를 추가/삭제할 때 Trie에 추가하는게 아닌 Trie 자체를 다시 만들어야 한다.
그러면 Trie를 금칙어 추가 / 삭제 기능이 일어날때마다 Trie를 다시 만들어줘야하고, 그런 삭제 / 변경이 빈번할 경우 동시성 문제도 고려해야하고, 매번 다시 메모리에 생성하는데 비용이 발생한다.
가장 크게 아호코라식 알고리즘을 제공하는 라이브러리를 사용하는 것을 멈춘 이유는 금칙어는 상대적으로 긴 단어가 없다. 길어도 5글자가 대부분이라 시간복잡도는 거의 차이가 나지 않을 것으로 보인다.
따라서 기존 라이브러리로도 충분한 성능을 낼 것으로 기대되었다.
✨ 성능 개선 결과
성능을 측정하기 위해서 Spring에서 제공하는 StopWatch를 local에서만 사용할 수 있도록 AOP를 이용한 어노테이션으로 만들어서 메서드 시간 측정을 진행했다.
전역에서 금칙어 필터링을 DTO에서 처리하기 위해서 Custom annotation으로 만들어 검증했는데 해당 `isValid()` 메서드 stopwatch를 붙혀 결과를 측정했다.
DB에서 가지고와 List를 모두 순회할 경우
메모리에 있는 Trie를 사용해 순회할 경우
위 결과를 보면 Trie를 사용할 경우 응답 시간이 약 2.5배 이상 단축되는 것을 확인할 수 있었다
Reference
https://techblog.woowahan.com/15764/
'Programming' 카테고리의 다른 글
👨🏻💻 금칙어 필터링 기능을 분석하고 리팩토링 해보자! (1) - Trie 자료구조 및 라이브러리들 분석 (0) | 2024.05.15 |
---|---|
🚀 [프로그래머스 백엔드 데브코스] Space Club 최종 프로젝트 회고 (4) | 2024.05.09 |
🎨 디자인 패턴 적용기 - 팩토리 메서드 패턴, 전략 패턴 ✨ (feat. 템플릿 메서드, 템플릿 콜백) (0) | 2024.02.03 |
스트랭글러 패턴 (2) | 2023.05.31 |
Clean Code - 냄새와 휴리스틱 (일반) (0) | 2022.11.01 |
- Total
- Today
- Yesterday