후기 서비스 AWS Opensearch 도입기
위기에서 기회를 만들어 낸 후기 서비스 이야기
들어가며
안녕하세요! 현재 후기 서비스를 개선하고 보살피고 있는 백엔드 개발자입니다.
우리 컬리에서는 좋은 품질의 고급 식재료와 밀키트, 와인 그리고 전통주와 더불어 다양한 뷰티 제품들을 고객들에게 선보이고 있습니다. 하지만, 좋은 상품들에 대한 단순한 전시가 고객들의 구매 경험으로 이어질까요? 아닙니다. 우리 컬리는 좋은 상품들을 선보이는 것만으로 끝내는 것이 아닌, 컬리 상품을 이용하는 다양한 유저들의 경험을 후기를 통해 보여주기 위하여 매우 많은 노력을 하고 있습니다. (여담이지만, 저는 그중에서 레시피가 있는 후기들을 좋아합니다.)
이러한 후기 서비스는 여러 번 개편 끝에 현재의 모습을 가지게 되었는데요. 지난번에는 기획자의 관점으로 이야기를 했다면, 이번 포스팅에서는 개발자의 관점에서 후기 개편 과정 중 가장 기억에 남던 일을 하나 여러분에게 들려주려고 합니다.
후기 서비스에 찾아온 위기
후기 서비스의 개편이 끝나갈 때쯤, 후기 서비스에 위기가 찾아옵니다.
그동안 중앙 집중형 데이터베이스(Mysql 5.6버전)에서 원천 데이터를 보관하던 후기 서비스는 컬리 서비스의 규모가 커짐에 따라서 분산 데이터베이스(Mysql 5.7버전)로 분리하는 목표를 잡았고, 분리 작업은 백엔드 개발의 꽃! 무중단 마이그레이션으로 진행하기로 했습니다.
듀얼 라이팅을 통하여 한 달간 데이터를 무사히 마이그레이션을 하였고, 조회에 대한 데이터베이스를 변경하려던 그때 튜닝에 튜닝을 걸쳐 콜라병 입구처럼 만들어 놓은 조회에 대한 병목 구간이 바늘구멍처럼 좁아져 레이턴시가 평소에 비해 2~3배 늘어나 Mysql 5.7버전에서는 후기 서비스의 조회를 맡겨놓을 수가 없었습니다.
그 원인은 쿼리문에 포함된 과도한 in절에 의한 filesort의 오버헤드였습니다. 하지만, in절을 사용하는 쿼리는 후기 서비스의 도메인 기능이기 때문에, 포기할 수 없는 상태였습니다. 이러한 문제를 해결하기 위하여 Mysql 5.6버전 대비 Mysql 5.7버전에서의 쿼리 옵티마이저 변경에 대한 문서를 하나씩 살펴보았고, 5.7버전의 Mysql에 대한 변경 지점을 모두 5.6버전의 쿼리 옵티마이저의 설정과 같이 변경을 해도 병목 구간은 해소되지 않았습니다.
아래의 두 가지 생각을 할 수가 있었습니다만,
- 집중형 데이터베이스에서 문제에 대한 원인을 찾기 위하여 시간을 더 가지는 방법.
- 분산 데이터베이스를 Mysql 5.7에서 5.6으로 다운그레이드 하는 방법.
2월 28일 Amazon Aurora MySQL 호환 버전 1 수명 종료 준비가 임박하고 있기 때문에 두 가지 방법 모두 힘든 상태였습니다.
결국 후기 서비스는 한 달 뒤에 하려고 했던 AWS Opensearch 도입을 5일 만에 하자는 결정을 하게 되었습니다.
위기를 기회로 극복하기 - AWS Opensearch 도입
남은 시간은 약 5일 밖에 남아있지 않았습니다.
5일의 스케줄
- SRE팀과 AWS Opensearch 운영 환경 구축
- 성능 테스트를 통하여 AWS Opensearch 인스턴스 사양 결정
- 후기 원천 데이터를 모두 역정규화하여 운영 AWS Opensearch 인덱싱
- 운영 배포
다행히 이미 컬리에서는 후기 서비스를 위한 Opensearch 도입은 지속적으로 검토 중이었고, 개발 또한 진행이 되고 있었기 때문에 도입 과정을 빠르게 진행할 수 있었습니다.
AWS Opensearch 도입을 위한 검토 사항
AWS Opensearch VS ElasticCloud
AWS Opensearch와 ElasticCloud에 대한 기술 조사 및 Library의 호환성 등을 확인을 해보았으나, 컬리는 AWS를 중심으로 인프라가 구성되어 있기 때문에 후기 서비스에 ElasticCloud가 아닌 AWS Opensearch를 도입하기로 결정했습니다.
Opensearch VS NO-SQL
후기 서비스는 앞에서 언급했듯이 과도한 in절에 의한 filesort가 일어났고, 이에 대해서 Opensearch가 아닌 다른 솔루션으로, MongoDB와 같은 NoSQL을 선택을 할 수 있었습니다. 하지만, 후기 서비스는 복잡한 쿼리 요청뿐 아니라, 후기 내용에 대한 검색 혹은, 상품에 대한 검색이 필요했고, 이러한 검색 요구사항에 대하여 Opensearch의 역색인을 이용한 빠른 검색이 더 좋은 퍼포먼스를 나타낼 수 있다고 생각하여 Opensearch 선택하게 되었습니다.
AWS Opensearch 도입을 위한 후기 아키텍처의 변화
AWS Opensearch를 도입을 위해 후기 서비스는 CQRS 패턴을 선택하게 되었습니다. CQRS 패턴을 사용하면 Presentation 영역을 보여 주기 위하여 EventSourcing Pattern과 Materialized View Pattern이 언급됩니다. 후기 서비스는 이미 1억 건 가까운 데이터가 쌓인 상태여서 EventSourcing Pattern으로 변경하기는 많은 시간이 들기 때문에 Materialized View Pattern을 통한 역정규화 된 데이터를 Opensearch에 저장하기로 결정되었습니다.
Opensearch에 역정규화 된 데이터를 넣어야 하기 때문에 후기 서비스는 자연스럽게 EventDriven Architecture를 선택하게 되었습니다.
후기 등록, 수정, 삭제가 이루어졌을 경우 원천 데이터에 대한 작업과 Opensearch에 대한 작업이 한 플로우에 섞이게 된다면 사용자에게 좋지 않은 경험을 줄뿐더러, 시스템으로도 악영향을 줄 수가 있기 때문이었습니다.
- 후기 원천 데이터는 저장되었지만, Opensearch에서 실패하는 경우 사용자에게 실패 응답을 보여줄 수 있어야 합니다.
- Opensearch 서버 문제로 사용자에게 응답이 지연되는 경우가 있기 때문에 분산하여 실행시켜야 합니다.
- 역정규화 된 데이터까지 저장을 하게 된다면 등록, 수정, 삭제에 대한 행위에 대해 SRP에 위반됩니다.
- 유저용 서버와 관리자 서버 모두 Opensearch를 사용해야 하기 때문에 시스템 통합이 필요하였습니다.
이러한 이유로 후기 서비스에서 이벤트 발행을 하기 시작하였으며, 이벤트를 통하여 Opensearch 데이터 추가 / 수정 / 삭제하도록 EventDrivenArchitecture를 선택하게 되었습니다.
AWS Opensearch 도입을 위한 성능 테스트
AWS Opensearch 성능 테스트의 목적은 AWS Opensearch Instance 사양 결정 및 AWS Opensearch 성능 체크였습니다.
성능 테스트는 Ngrinder를 이용하였으며, Datadog APM, AWS Opensearch Monitoring를 통하여 운영 환경에 맞는 지표를 찾아냈습니다.
AWS Opensearch 성능 테스트의 결과는 만족스러웠습니다.
- AWS Opensearch Monitoring의 CPU 사용률을 통하여 부하가 일어나는 경우의 후기 서비스에 맞는 최적의 인스턴스 사양을 찾아낼 수 있었습니다.
- 성능 테스트 결과에 대한 Datadog의 RPS 지표 및 Latency 지표와 현재 운영 환경 지표를 함께 비교하며 최적의 인스턴스 사양을 찾아낼 수 있었습니다.
오직 Ngrinder를 이용한 성능 테스트는 정확한 테스트가 이루어지고 있는지 의문이 들 때가 많았는데, AWS Opensearch Monitoring과 Datadog 지표를 함께 이용하여 실제 운영 환경과 비교한 테스트는 더 정확하고 AWS Opensearch 적용 전과의 차이점을 더 확실히 알 수가 있었습니다.
결과적으로 후기 서비스는 AWS Opensearch 4xlarge 기준 1200RPS / CPU 70% ~ 80% 사이면 현재 상용에서도 충분할 것 같다는 결정과 함께 후기 서비스에 맞는 Opensearch 인스턴스를 찾을 수 있었습니다.
AWS Opensearch 운영 배포 결과
무중단 마이그레이션부터 시작하여 AWS Opensearch 검토, AWS Opensearch를 도입을 위한 아키텍처 변화, AWS Opensearch 성능 테스트까지 후기 서비스는 위기를 기회로 만들었고 약속한 5일 뒤 운영 배포를 하게 되었습니다.
Canary 배포를 통하여 오전 10시에 일부 서버를 배포하였고, 배포 후에 모니터링을 통하여 모든 인스턴스에 적용할 수 있는 파악 후 15시 45분에 모든 운영 인스턴스에 AWS Opensearch가 적용 된 후기 서비스를 배포하였습니다.
배포 후 후기 서비스의 Latency 지표는 매우 아름다웠습니다.
그동안 DataDog Max Latency을 기준으로 최대 17s를 기록했던 Latency가 200ms까지 내려오는 것을 확인할 수가 있었습니다. 그리고 Mysql 지표가 안정화되는 것을 볼 수가 있었고, 어느 때보다 빠르게 후기 목록을 노출되는 것을 볼 수 있었습니다.
이때 팀장님, 모듈장님, 후기 서비스를 같이 진행 중이던 개발자님, 그리고 저는 이러한 다이나믹한 변화를 보고 매우 감격하였고, 만세를 외칠 수밖에 없었습니다. 이 순간은 그동안 고생했던 나날에 대한 보상이었다고 저는 생각했습니다.
위기 극복 후 안정화
위기를 극복하더라도 사내에서 테스트를 충분히 한 시스템이라도 무수히 많은 사용자들에 대한 트래픽을 받으면 결국에는 미처 발견하지 못한 이슈가 생길 수가 있습니다. 이러한 이슈들을 하나하나 찾아내 우리의 시스템을 강건하게 만드는 것은 또 다른 시작이라고 생각합니다.
AWS Opensearch 모니터링 대시보드 추가
AWS Opensearch를 도입하고 난 후에 바로 모니터링 대시보드를 추가하였습니다.
모니터링 대시보드를 추가한 이유는
- AWS Opensearch는 완전 관리형 서비스여서 특정 노드에 프라이머리 샤드나 레플리카 샤드가 편중되었을 경우 relocation 기능을 사용할 수 없어 리밸런싱을 해야 하는 경우가 있기 때문에 샤드에 대한 상태를 확인할 필요가 있었습니다.
- 노드별 용량에 대해서 확인할 필요가 있었습니다.
- 간혹 AWS Opensearch의 데이터노드가 하드웨어 오류로 인하여 shutdown 될 때도 있어 그 사이에 발생 된 인덱스 추가, 수정 건에 대해서 대응해줘야할 필요가 있었습니다.
- AWS Opensearch의 CPU, Memory, JVM, GC, Disk I/O, Latency, Threads 등 다양한 지표를 확인할 필요가 있었습니다.
후기팀에서는 모니터링 대시보드를 DataDog으로 만들었습니다. 이렇게 만들어진 모니터링 대시보드를 통하여 우리에게 필요한 지표들을 직관적으로 볼 수 있었고, 특정 지표에 문제가 생겼을 때 바로 확인할 수 있도록 알림 기능 또한 추가하였습니다.
현재는 모니터링 대시보드뿐만아니라, 노드에 대한 샤드 분포율, CPU, Heap, Disk에 대한 사용률을 전체적으로 볼 수 있으며 Alias 할당, IndexTemplate 설정, cluster setting 상태 확인 등을 확인하기 위하여 cerebro 도입을 검토하고 있습니다.
AWS Opensearch Update 시 Version 충돌 이슈
Opensearch나 ElasticSearch에서 Document를 동시에 업데이트했을
경우에 version conflict, current version [3] is different than the one provided [2]
라는 예외가 발생하여 업데이트가 되지 않을 때가 있습니다.
이러한 버전 충돌에 대한 부분은 후기 서비스에서는 update인 경우에는 partial update로 모두 적용한 상태여서 retry_on_conflict 즉, version conflict가 일어났을 경우 몇 번 재시도를 할 것인지에 대한 옵션을 설정 해놓았습니다.
은전한닢 형태소 분석기 오프셋 역전 현상
Opensearch에서는 한글 형태소 분석을 위하여 은전한닢 형태소 분석기를 제공하고 있습니다.
검색을 위하여 은전한닢 형태소 분석기를
사용하다 보면 startOffset must be non-negative, and endOffset must be >= startOffset, and offsets must not go backwards startOffset=x,endOffset=x,lastStartOffset=x for field 'contents'
라는 예외가 발생할 수가 있습니다. 원인은 형태소 분석 후에 start_offset이 점점 커지면서 어느 순간 start_offset이 작아지는 오프셋 역전 현상이 발생하였기 때문입니다.
이러한 문제를 해결하기 위해서 후기 서비스는 은전한닢 형태소 분석기 옵션을 decompound : true, deinflect : false, pos_tagging: false로 수정하였습니다.
복합명사(decompound)에 대해서는 true로 변경한 이유는 “잠실역“을 검색 했을 때 “잠실“, “역”, “잠실역” 으로 토큰나이저가 되어야 “잠실“이나 “역”을 검색 했을 때도 “잠실역“이 검색 되어야하기 때문이였습니다.
활용어(deinflect)가 true인 경우에는 “맛있“, “맛있어요“로 토크나이저가 되지만, "해얄거"로 토크나이저 할 때 오프셋 역전현상이 일어날 수 있습니다. 이를 방지하기 위하여 false로 변경했습니다. 활용어에 대해서 “맛있어요” 라는 부분에 대해 토크나이저가 되면 “맛”만 되기는 하지만, 검색 시 “맛”, “맛있“, “맛있어요” 모두 검색되는 것으로 확인이 되었습니다.
품사태깅(pos_tagging)을 false로 추가를 했는데, 몇몇 명사에 대해서 은전한닢은 토크나이저 할 때 품사태그표를 토크나이저에 붙여주고 있습니다. “느끼/V”가 대표적입니다. /V를 붙여줌으로써 “느끼“만 검색했을 경우에 /V에 대한 filter 처리가 되어있지 않기 때문에 검색이 되지 않는 부분이 있어 pos_tagging 기능을 끄고, 토크나이저 시에 “느끼“로 검색이 되었습니다.
하지만, 2023년 10월 17일 Amazon OpenSearch Service, 4개의 새로운 언어 분석기에 대한 지원 추가를 통하여 AWS Opensearch는 Nori 형태소 분석기를 사용할 수 있기 때문에 이제 해당 문제는 발생하지 않을 것 같아 보입니다.
또 다른 관점을 찾아서
AWS Opensearch를 접하기 전의 저는 어떻게 해야 코드를 잘 짤 수 있을까? 혹은 어떻게 해야 더 좋은 아키텍처를 설계할 수 있을까? 같은 개발 위주로만 생각을 하였습니다. 하지만 AWS Opensearch 도입 과정을 통해서, 수많은 후기 데이터를 마주치게 되면서 스스로 담당하고 있는 서비스에서 관리하는 데이터가 궁금해지기 시작하였습니다.
Opensearch 대시보드 적용
후기 데이터 특성이 궁금하여 Opensearch 대시보드를 만들어 상품별 후기 분포도, 이미지가 있는 후기에 대한 분포도, 회원 등급 별 후기 분포도를 시각화할 수 있었습니다.
컬리에서는 후기 적립금 또한 자동화를 하여 지급을 해주고 있습니다. 후기 서비스에서는 후기 적립금 지급 여부를 정상인 후기와 불량인 후기로 판단하여 선별적으로 적립금을 지급하고 있습니다. 이러한 과정에서 우리는 시각화를 통해서 긍/부정 후기에 대한 분포도, 적립금을 얼마나 사용자에게 주었는지, 불량 후기를 검토하여 적립금을 절약했는지에 대한 부분을 확인할 수가 있었습니다.
이러한 시각화 대시보드를 통하여 기획자와 개발자 간의 데이터 기반의 소통을 할 수 있었고 데이터 중심으로 후기 서비스의 방향성을 잡을 수 있었습니다.
이러한 경험을 통하여 데이터 직군이 아닌 개발자라 하더라도 운영하고 있는 서비스에 대한 데이터에 관심을 가져야 하며, 데이터를 가지고 더 좋은 방향성을 만들어 낼 수 있도록 해야 한다는 교훈을 얻게 되었습니다.
마치며..
후기 서비스에 AWS Opensearch를 도입하며 3가지 느낀 점이 있습니다.
- 기술에 선택에 대해서는 많은 검토가 필요하며, 검토를 통하여 가장 방향성에 맞는 최선의 선택을 해야 합니다.
- 아키텍처를 설계하더라도 언제든지 환경 변화에 적응이 가능한 아키텍처를 설계해야 하며, 개발자는 환경의 변화를 늘 포착해야 합니다.
- 데이터 직군이 아닌 개발자라도 운영하고 있는 서비스에 대한 데이터에 대한 관심을 가져야 하며, 데이터를 가지고 더 좋은 방향성을 만들어 낼 수 있도록 해야 합니다.
그 밖에 후기 서비스에 AWS Opnesearch를 도입하는 과정은 다른 서비스의 AWS Opensearch 도입에도 또한 기여할 수 있었습니다. 이렇듯 하나의 기술 도입은 많은 교훈을 줍니다. 그만큼 고민의 시간이 많았고, 서비스 전반적으로 검토해야 할 부분도 많았기 때문이라고 생각됩니다.
컬리에 합류하고 나서 약 1년 동안 후기 서비스 개편을 담당한 것 같습니다. 후기 서비스 개편을 하며 다양한 경험을 할 수 있었습니다. 아마 저 혼자라면 못했을 것입니다. 함께해서 가능했었던 것 같습니다. 마지막으로 후기 서비스 개편을 함께해 주신 컬리 동료분들에게 감사의 인사드립니다.
긴 글 읽어주셔서 감사합니다.