안녕하세요. 올리브영 스토어전시 스쿼드에서 백엔드 개발을 하고 있는 복스뮤직 입니다.
올리브영 전시에도 여러 스쿼드가 존재하지만, 스토어전시는 주로 메인, 홈, 오특 등 GNB(Global Navigation Bar) 영역을 담당하여 개발을 진행하고 있습니다.
그 중 스토어전시 백엔드는 어떤 일을 어떻게 하고 있는지 최근에 진행했던 올리브영 온라인몰 Home 신규 아키텍처 전환을 통해 간략히 소개하고자 합니다!
Step 1. Why Monolithic to MSA?
- 올리브영 전시는 최근 Monolithic 구조에서 벗어나 MSA 로 전환하기 위해 계속해서 노력하고 있습니다.
Monolithic 구조에서 MSA 로 전환한 이유는 크게 두 가지가 있습니다.
1️⃣ 서비스 중 특정 부분에 대한 Scale-out 이 어렵다.
이런 앱 리뷰를 보신적 있으신가요?
기존 올리브영의 서비스는 On-premise 환경에 상당 부분이 Monolithic Architecture 로 구성되어 있었습니다.
또한, On-premise 환경에서 가용할 수 있는 서버 증설은 거의 MAX 상태로 세일을 맞이했습니다.
가용할 수 있는 자원은 한정되어 있는데, 이런 리뷰들을 볼 때마다 굉장히 마음이 아팠습니다.
특히, 세일 마지막 날 주문&결제에 몰리는 트래픽에 죄 없는 홈 등딱지가 터지는 모습을 보며 속이 아려왔습니다.
2️⃣ 전체 시스템에 대한 구조 파악이 어렵다.
과거 메인페이지 성능 개선기를 비롯하여 Home 을 개선하기 위해 여러 시도했고, 개선 결과를 가시적으로 볼 수 있을 만큼 성과도 있었습니다.
하지만, 기능 개발 또는 리팩토링 진행 시 영향도 파악부터 통합테스트까지 N개월 이상의 기간이 소요 되기도 했습니다.
Monolithic 구조에서 디펜던시들이 얽혀 있는 홈의 구조를 변경한다는 것은 큰 부담으로 다가왔고, 작은 기능 변경에도 매번 불필요한 공수를 쏟아야 했습니다.
Monolithic to MSA 전환 비용에 대한 Trade off 가 반드시 존재하기도 하고, 전환 이후 Monolithic 으로의 회귀가 그리워 질 수 있습니다.
그러나, 올영 세일을 필두로 올리브영이 가지고 있는 비지니스는 MSA 전환이 반드시 필요했고,
더 나은 개발 품질을 위해서 MSA 전환은 더 이상 선택이 아니게 되었습니다.
Step 2. 어떤 데이터를 Serving 할까?
- '어떻게 해야 더 좋을까?' 를 항상 고민합니다.
기존 Home 은 좌측 그림처럼 여러 비지니스 로직이 조합되어 한 화면을 그려주고 있었습니다.
따라서, 특정 로직에서 지연이 발생하는 경우 Home 전체를 그려주지 못하게 되고, 이는 곧 좋지 않은 유저 경험으로 이어졌습니다.
신규 Home 은 우측 그림처럼 빠르게 보여줄 수 있는 정적 영역(Static Data), 그 외 여러 비지니스 로직이 조합되는 영역은 개인화 영역(Personal Data) 으로 두 가지 영역을 나누어 고민했습니다.
1️⃣ Static Data
Static Data 는 ‘모든’ 유저에게 동일하게 제공되는 데이터들의 집합입니다. 해당 데이터는 캐싱 데이터 조회 후 바로 서빙 할 수 있는 데이터로 구성했습니다.
어떠한 디펜던시도 갖지 않고, 모든 유저에게 동일하게 제공할 수 있는 데이터를 하나로 묶어둠으로써 최초 화면에 노출되는 영역은 최대한 빠르게 제공하기 위해 Static Area 를 구분하여 사용하고 있습니다.
참고) 올리브영 전시영역 MongoDB 도입하기 의 내용을 바탕으로 Data 조회 → Validation check → Versioning 등의 단계를 거쳐 최종 캐싱 데이터를 생성합니다.
2️⃣ Personal Data
Personal Data 는 ‘특정’ 유저에게 차별되어 제공되는 데이터들의 집합입니다. 해당 데이터는 각 유저 마다 다르게 서빙 되어야 하는 데이터로 구성되어 있습니다.
Static Data 와 다르게 각 유저에 맞는 데이터를 서빙 하기 위해 비지니스 로직이 들어가거나, 여러 시스템간의 디펜던시를 가지고 있기 때문에 API 응답속도를 항상 보장하지 못할 수 있습니다. 따라서 이러한 영역은 별도의 API 로 관리하고 있습니다.
참고) 신규 전시 프로젝트에서 WebClient 사용하기 와 더불어 multiple call을 위한 zip, parallel 사용과 여러 fallback 로직들로 구성되어 있습니다.
Personal Data & Static Data 의 평균 API 응답 속도입니다.
의도한 것처럼 올리브영 앱에서 Home 을 더 빠르게 고객에게 노출 할 수 있었습니다.
Step 3. Static Area 는 하나의 API 로 묶어야 할까?
- 모든 의사결정은 데이터로 시작하고, 데이터로 판단합니다.
‘Static Area API 를 분리해야 할 이유는 없을까?’
Personal Data 는 성격에 맞게 API 를 분리하여 서빙하고 있습니다. 동일하게 ‘Static Data 는 API 분리를 할 필요가 없을까?’ 라는 의문을 가지게 되었습니다.
Static Data 는 하나의 API 로 제공 하더라도 응답속도를 보장할 수 있었기 때문에, 명확한 근거가 없다면 분리하지 않아도 무관했습니다.
저희가 목표로 하는 올영세일과 유저 패턴을 보면 아래와 같습니다.
(평시를 고려하면 어떤 방법으로 처리해도 문제가 발생하지 않습니다.)
- 세일 첫날 00시에 홈으로 유입되는 유저는 대량 트래픽을 유발하지만, 홈 하단으로 이동하지 않고, 대부분 쿠폰 발급을 위한 기획전 페이지로 이동한다.
- 세일 마지막 날 발생하는 대량 트래픽은 홈이 아닌 ‘구매’ 를 위한 상품 상세 또는 주문페이지이다.
- GNB 홈을 4등분 했을 때 하단 스크롤을 내리는 유저 비율은 생각했던 것 보다 저조하다.
- depth 50% > 74%,
- depth 75% > 18%
- depth 100% > 9%
- Static Area 데이터는 약 300kb로 구성되어 있다. 최초 고객에게 노출하는 영역만 분리했을 경우 약 3% 의 비용만 사용하면 된다. 또한, 불필요한 데이터로 인한 네트워크, 파싱 비용 등을 감소시킬 수 있다.
이를 정리하면 다음과 같은 결론을 얻을 수 있습니다.
- 홈의 하단 영역을 보는 사용자는 충분히 적다.
- 대량의 트래픽이 들어오는 시간에 홈은 Navigation 역할을 수행한다.
즉, 홈 하단을 유저가 탐색하지 않는다면 하단 영역은 불필요한 데이터가 되고, Static Data API 를 분리해야할 명확한 근거가 되었습니다. 저희는 즉시 개선을 진행했고, 개선 결과는 부하테스트를 통해 유의미한 개선임을 확인할 수 있었습니다.
네트워크, 외부 저장소, API call count 뿐만 아니라 유저의 패턴에 따라 더 좋은 서비스 제공을 위한 노력이기 때문에 더 기억에 남습니다. 이후에도 Json gzip 압축 등 여러 기능이 보완되어 더 나은 서비스를 위해 나아가고 있습니다.
Step 4. 장애 전파 방지(CircuitBreaker)
- 언제라도 안정된 서비스를 제공하기 위해 노력합니다.
올리브영 홈은 Redis, Mongo DB, Oracle, 외부 서비스 등 여러가지 시스템이 조합되어 있습니다. 특정 시스템의 단순 지연은 Application 전체 Hang 을 유발 할 수 있습니다. 또한, 특정 저장소 또는 외부 서비스의 장애는 곧바로 고객에게 Home 을 노출하지 못하는 장애로 이어지게 됩니다.
‘어떠한 환경에서도 Home 은 노출되어야 한다’
(아직 Monolithic 이 곳곳에 존재하지만)
Home 은 어떠한 환경에서도 노출되어야 함을 원칙으로 가져가고 있습니다.
올영 세일은 항상 경험하지 못한 새로운 문제를 발생 시킵니다. 하나의 혈이 뚫리면 다른 혈이 막혀버리는 재밌는 모습을 볼 수 있습니다. 따라서, 어떤 데이터 레이어가 죽을지, 외부 시스템의 지연 혹은 장애에 대해서도 항상 고려해야만 합니다. Home 은 발생 가능한 모든 시나리오를 고려하여 어떠한 서비스가 다운 되더라도 유저 레벨에서 인지할 수 없도록 fallback 로직이 2~3차 까지 준비 되어 있습니다. 또한, 스트레스 테스트 결과를 통해 CircuitBreaker 임계치도 시스템 상태에 맞게 가져가고 실제 검증까지 진행하고 있습니다.
- CircuitBreaker 의 기본 플로우는 아래와 같습니다.
(자세한 설명은 Circuitbreaker를 사용한 장애 전파 방지 에서 확인할 수 있습니다!)
- 홈에서 사용하고 있는 CircuitBreaker 로직 중 하나 입니다.
각 저장소가 fallback으로 묶여있고, 임계치 중 하나인 지연시간과 그에 맞는 Scale-out 은 모두 부하테스트를 근거로 적용되어 있습니다.
- Circuit Breaker 에 대한 테스트 코드 중 하나 입니다.
주요 로직들에 대해서는 테스트 코드를 통해서 안정성을 지향하고 있습니다.
또한, Circuit Status 변경 시 Slack 알람으로 받고, 대응할 수 있도록 구성해 두었습니다.
Step 5. 세일을 대비 하기 위한 부하테스트
- 발생할 수 있는 모든 예외를 생각하고 움직입니다.
목표는 단 한 순간 입니다.
‘올영 세일 1일차 23시 55분 ~ 00시 05분’
이 순간을 버틴다면, 올영 세일 기간을 모두 버틸 수 있다고 할 수 있습니다.
동네방네 광고를 하고 9월 세일을 맞이 하기 위한 Scale-out을 해야했기에 부하테스트를 진행합니다.
Env 를 구성하고, Goal 을 설정한 뒤, 각 자원들에 대해 Home이 ‘올영 세일 1일차 23시 55분 ~ 00시 05분’ 에 어떻게 버틸 수 있을지 아래의 케이스를 모두 정리하였습니다.
- 공용으로 사용하는 Redis 에 대해 Home 영역 부하 시 발생 가능한 시나리오
- fallback 로직들이 처리 될 때 Oracle Connection 처리량
- Redis, Mongo, Oracle, 외부 시스템 관점에서의 Home 부하 및 대응 방법
- CircuitBreaker 임계치 설정
- fallback 로직들이 동작하기 위한 Scale-out 수치
- …..
최초 부하 테스트로 시작했지만, 이 모든 것을 정리하니 성능, 부하, 스트레스 테스트로 마무리 되었습니다.
아키텍처 전환 이후 처음으로 맞이하는 세일이기 때문에 최선을 다해서 준비했습니다.
생각보다 만족할만큼의 결과를 얻을 수 있었고, 예상할 수 있는 예외 상황들에 대한 모든 준비를 끝냈습니다.
올영 세일 1일차 -
모든 케이스에 대한 시나리오를 정리해 대응할 수 있는 환경을 만들고, 스쿼드 내 개발자들이 모두 사무실에 남아 올영 세일 첫 날을 준비했습니다.
그러나 세상은 호락호락 하지 않았습니다.
하나의 혈이 뚫리니 뚫려있던 혈이 막혀버렸습니다..
마치며,
사실 많은 부분이 개선이 됐고, 의미있는 과정과 좋은 결과들이 많았던 신규 아키텍처 전환 및 9월 세일 이었습니다.
그 중 준비했던 Step 들을 통해 얻은 가장 두드러진 결과물인 6월과 9월의 응답 시간 비교입니다.
- 6월 대비 평균 응답 속도 시간
- ((10380 + 10430) - (92.66 + 11.53)) / (10380 + 10430) = 99.5% 속도 향상
- 6월 대비 최대 응답 속도 시간
- ((33500 + 33600) - (105 + 15)) / (33500 + 33600) 로 = 99.8% 속도 향상
신규 아키텍처 전환기를 모두 다 쓰니 이 또한 legacy 가 되었습니다.
어제의 개발이 legacy가 되며 이 또한 작은 Monolithic이 되는 것을 방지하기 위해 항상 노력 중입니다.
올리브영 온라인몰은 고객에게 더 유익한 가치를 제공하기 위해 어제보다 더 나은 내일로 나아가고 있으니 많은 관심 부탁드립니다.