배경
챗봇 시스템 운영 중, 이용자 급증 시 메시지 전송 API의 응답 속도가 저하되고 시스템 전체가 버벅이는 현상에 대해 제보를 받았다. API에 대해 분석을 해보니 메시지 전송 시 금칙어를 필터링하는 로직이 포함되어 있었다. 금칙어 필터링 결과에 따라 경고 문구 전송 및 세션 강제 종료 등의 처리로 이어질 수 있는 메시지 전송 API에 포함된 중요한 기능이다.
문제 파악 및 분석
정확한 문제 파악을 위해 부하테스트, APM 도구를 활용하여 분석을 진행하였다.
-
병목 지점 분석: 부하테스트와 트레이스를 통해 분석한 결과, 요청 처리 시간의 대부분이 Elasticsearch를 호출하는 특정 Span에 집중되어 있음을 확인하였다.
-
스레드 고갈: 메시지 전송 API에 동시 요청이 증가할 경우, 스레드 안에서 Elasticsearch의 형태소 분석 과정에서 네트워크 I/O 대기시간이 급증했다. 그로 인해 Elasticsearch의 서버 부하와 애플리케이션 내 스레드 고갈로 이어져 시스템 전체가 버벅이는 현상을 유발했다.
- Elasticsearch의 사이드 이펙트 : 금칙어 데이터의 변경 발생시 Elasticsearch의 인덱스를
close/open하는 과정이 포함되어open요청이 실패했을 경우에 대한 복구 처리가 없었고 대한 리스크와 복잡도 이슈가 남아있었다.
해결방안 결정 과정
- 대안 탐색 및 결정:
-
처음에는 Elasticsearch 서버 튜닝과 캐싱을 고려했지만 ES 서버 튜닝은 작업량 대비 효율이 나올지가 의문이었고, 캐싱은 채팅에서 사용자 입력 메시지가 예측이 되지 않아 효과가 미비할 것이라 판단하였다.
-
“문자열 매칭에 무거운 형태소 분석기가 필수적일까?”라는 의문을 바탕으로 접근하여, Elasticsearch 의존성을 제거하여 시스템 복잡도 및 병목 제거를 목표로 설계 재검토하기로 했다.
-
금칙어 처리하는 여러 실무 사례를 분석하였고, 그 결과 별도의 검색 엔진 없이 인메모리 기반으로 의 검색 성능을 낼 수 있는 아호코라식(Aho-Corasick) 알고리즘 도입을 최종 결정(참고 사례: 배달의민족 기술블로그))하였다.

-
해결
-
1. 아호코라식 도입 :
- 애플리케이션 실행시점에 금칙어를 Trie 자료구조로 메모리에 로드하여, 외부 통신 없이 금칙어 탐색 후 필터링하도록 아키텍쳐 전환 (라이브러리:
org.ahocorasick)하였다. - 하지만 관리자 페이지에서 금칙어 변경 시, 분산 환경의 특성상 요청을 처리한 서버 외에는 Trie를 갱신하지 못하는 정합성 불일치라는 새로운 문제가 발생했다.

- 애플리케이션 실행시점에 금칙어를 Trie 자료구조로 메모리에 로드하여, 외부 통신 없이 금칙어 탐색 후 필터링하도록 아키텍쳐 전환 (라이브러리:
-
2. 이벤트 브로드캐스팅:
- 관리자 페이지에서 금칙어 변경 시, RabbitMQ Fanout Exchange를 활용해 모든 서버 인스턴스에 변경 이벤트를 브로드캐스팅하여 인메모리 Trie 재구축하도록 실시간 동기화를 적용했다.
.drawio.png)
개선 후기
로그가 아닌 부하테스트와 모니터링 도구를 통해 병목을 진단하는 과정을 통해, 트러블슈팅의 새로운 세계를 경험했다. 그리고 여러 해결방안을 고민하는 과정에서 시스템의 특징과 작업량 대비 임팩트를 제대로 비교하고 결정하는 것이 중요하다는 것을 이 글을 정리하면서 새삼 깨달았다.