Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
⭐️ Issue Number
🚩 Summary
요구 사항의 변경에 따라 검색어 자동 완성 API 로직을 변경합니다.
기존에는 대부분의 검색 엔진의 검색어 자동 완성 로직과 동일하게 첫 번째 글자부터 마지막 검색 글자까지 모두 일치하는 요소에 대해서만 검색어 자동 완성 결과로 보여줬었는데, 검색 대상이 되는 데이터가 무수히 많은 검색 엔진과 다르게 저희 서비스에서 검색 대상이 되는 데이터는 그에 비하면 많이 적은 편이라 그렇게 Fit하게 검색했을 경우에 보여지는 자동 완성 결과가 충분하지 못한 문제가 있었습니다.
그래서 검색어 자동 완성의 기준을 조금 더 완화하여 String의 Contains() 메서드 로직과 같이 중간에 있더라도 자동 완성의 대상이 되는 것으로 변경하고자 했습니다.
구현 과정에서 아래 요구 사항을 충족시키는 기능을 Redis에서 구현하고 Refactoring까지 진행했음에도 불구하고 MySQL 보다 성능이 5배가량 떨어지는 이슈가 있어 Redis 관련 설정과 로직을 모두 제거하고 MySQL로 Migration 하였습니다.
결과적으로 MySQL로 변경하면서 Response time을 2.5초에서 0.05초로 1/50로 단축시켰습니다.
관련한 내용은 제 개인 기술 블로그에 자세히 기록해놓았습니다.
Redis는 MySQL보다 항상 빠른가요? 아니요.
🛠️ Technical Concerns
Full scan 상황에서 Redis VS MySQL
앞선 Summary에서 잠깐 언급했던 것처럼 Redis의 성능 이슈가 있었습니다. 사실 이건 Redis의 구조적인 문제인데 Key-Value 형식으로 저장되어 관리되다보니 특징적인 장단점이 명확했습니다. 이러한 장점이 요구 사항이 변경되기 전 자동 완성 로직에는 매우 적절해서 좋은 성능을 내줬지만 요구 사항이 바뀌면서 Case insensitive하게 특정 문자열 포함 여부를 조회하는 기능이 추가되었어야 했는데 Redis에는 이에 대한 내장 모듈이 없을 뿐만 아니라, MySQL에 비해서 현저히 떨어지는 performance를 낼 수 밖에 없는 구조적인 문제점을 지니고 있다는 것을 깨닫게 되었습니다.
왜냐하면, MySQL에는 인덱싱을 적용할 수 있기도하고 Repository 단에서부터 애초에 최대 10개로 Limit를 걸고 filtering해서 가지고 오기 때문에 추후 Service 로직에서 그 많은 모든 데이터에 대해 인스턴스화해서 가지고 있을 필요가 없습니다. 이에 따른 메모리나 성능적인 이점이 매우 큽니다. 그리고 Redis에는 Case insensitive하게 처리해주는 내장 로직이 없어서 직접 대문자 혹은 소문자화 해가면서 Case insensitive하게 만드는 로직을 거치도록 구현해줘야 합니다. 하지만 MySQL은 그럴 필요가 전혀 없습니다. 왜냐하면 MySQL 쿼리 내 like 절은 애초에 Case insensitive하게 동작하기 때문입니다.
그래서 상황을 다시 한번 요약해보면 다음과 같습니다.
Case insensitive하게 특정 문자열이 포함되는 값들을 Full scan(전수 조사)해야 되는 상황이 있었고 이때, Redis에서는 이러한 기능을 내장하여 제공하고 있지 않기 때문에 별도의 추가 로직 필요하다는 점 그리고 Redis의 경우 데이터를 Filtering해서 사이즈를 대폭 줄여줄 수 있는 limit 로직을 Service 단보다 더 이른 시점인 Repository 단에서 걸어줄 수 없다는 점들이 성능 저하의 요인이 되었습니다.
특히 대용량 데이터의 Filtering 시점이 늦어짐에 따라 대량의 Repository 조회 결과를 고스란히 인스턴스화시켜서 Service 단까지 가져오고 이를 전체 조회해야 된다는 점이 성능 저하의 주된 요소로 보여집니다.
그래서 결론은, “인메모리 DB이므로 Redis는 항상 빠를 것이다.”라는 생각은 굉장히 잘못된 일반화의 오류이며 Redis의 특징인 Key-Value 자료 구조의 장점이 잘 부각될 수 있도록 캐싱이나 O(1) 조회 등이 필요한 상황에만 적용해서 사용하는 것이 바람직하다는 점입니다.
다음은 제가 성능 비교를 위해 직접 캡처한 Swagger UI 사진입니다.
위 사진이 Redis 적용 시 Response time을 나타내는 화면입니다. 약 2.5초가 걸리는 것을 알 수 있습니다.
그리고 위 사진은 MySQL 적용 시 Response time입니다. 약 0.05초가 걸리는 것을 확인할 수 있습니다. 위처럼 실제로 성능이 약 50배 가량 차이나게 됩니다. 그래서 저는 결국 수 일에 걸쳐 환경 구축하고 Refactoring 했던 Redis를 모두 포기하고 MySQL로 로직을 Migration하여 구현하였습니다.
📋 To Do