쿠팡 로켓그로스의 ML 플랫폼: 20개 이상의 모델 서비스 및 트래픽 처리 비용 효율화

대규모 머신러닝 플랫폼의 아키텍처 단순화와 플랫폼 확장을 위한 오케스트레이션 레이어 도입

쿠팡 엔지니어링
Coupang Engineering Blog
12 min readNov 7, 2022

--

By Harsha Konda

본 포스트는 영문으로도 제공됩니다.

쿠팡의 풀필먼트 사업인 쿠팡 로켓그로스(Coupang Rocket Growth)를 통해 판매자는 고객에게 가장 빠른 제품 배송 서비스와 최상의 구매 후 경험을 제공할 수 있습니다. 하지만 판매자가 많아지고 로켓그로스의 로켓 배송 서비스 및 세일즈 분석 도구가 더 많이 활용되면서, 신제품 등록 시 품질 검사와 같은 수동 프로세스 일부가 빠르게 시스템의 병목이 되어버렸습니다.
이러한 병목을 제거하고 시스템 확장성을 개선하기 위해 저희는 머신러닝(ML) 기술을 도입했고, 수작업으로 이루어지던 의사 결정 과정을 자동화하고 의사 결정 속도를 높일 수 있었습니다. 이런 노력을 통해 저희는 지연시간, 내결함성 및 확장성과 같은 시스템 아키텍처의 성능 부분에 있어 많은 개선을 이룰 수 있었습니다.

이번 포스트에서는 저희가 마주했던 문제들 각각을 해결하기 위해 아키텍처를 어떻게 단계별로 발전시켰는지를 논의하면서 여러분들이 ML 서비스를 개발할 때 참조할 수 있을 만한 몇 가지 공통 패턴을 공유드리고자 합니다.

목차

· 초기 아키텍처
장점
단점
· 지연 시간이 긴 모델들 설정/관리/조정하기
· 모델의 추론 속도 높이기
벤치마크
· 결론

초기 아키텍처

로켓그로스의 ML 플랫폼은 판매자 제품의 특성들을 사람이 직접 감사하는 일반적인 과정 그대로를 본떠 만들어졌습니다. 예를 들어, 제품 품질 검사에는 브랜드의 진위 여부 식별, 인증 로고 감지, 제품 이미지에서 텍스트를 추출해 부적절한 텍스트 필터링하기 등이 포함되어 있습니다.

저희는 여러 ML 모델을 활용해 데이터를 수집하고 자동으로 품질 검사에 대한 판단을 내립니다. 클라이언트 또는 피처(feature)가 관련된 모델을 호출하고, 응답을 기다리며, 모델의 출력을 기반으로 결정합니다.

ML 플랫폼에 대해 다음의 세 가지 주요 요구사항을 설정했습니다:

  1. 대량의 트래픽 처리: ML 모듈들이 하루에 5백만 개의 요청을 처리해야할 것으로 예상됨.
  2. 짧은 지연 시간: 각 모델의 요청 처리 시간은 1초에서 2분 정도.
  3. 내결함성: 간헐적 오류 및 재요청을 처리할 수 있는 설정.

위 요구 사항의 충족을 위해, 각 ML 모듈 컴포넌트를 카프카 토픽(Kafka topic)을 통해 노출해 클라이언트 또는 피처가 자신의 요청을 해당 토픽에 메시지로 게시(publish)할 수 있도록 만들었습니다. 모듈 컴포넌트는 요청용 토픽의 메시지를 읽어와(consume), ML 모델을 호출해 추론을 출력 받은 다음, 최종적으로 추론 결과를 응답용 토픽에 게시합니다. 클라이언트는 응답용 토픽으로부터 추론 결과들을 읽어와, 그 중 관련 없는 결과들을 요청 ID를 통해 필터링하고, 필터링된 추론 결과들을 집계할 수 있습니다.

쿠팡 로켓그로스 ML 플랫폼의 초기 아키텍처
그림 1. 쿠팡 로켓그로스 ML 플랫폼의 초기 아키텍처

장점

이 초기 ML 플랫폼 아키텍처에는 두 가지 주요 장점이 있었습니다.

첫째, 비동기적(asynchronous) 작동 방식이었습니다. 각 피처는 평균 5–10개의 모델을 호출할 수 있었고, 그러다 보니 모델들로부터 응답을 모을 때 발생하는 전체적인 지연(overall latency) 시간 문제를 해결할 필요가 있었습니다. 모델들을 REST로 서비스했고, 클라이언트 쪽에 종속 모듈들 각각에 요청을 보내는 기능, 요청에 대한 응답을 기다리면서(일반적으로 5분 정도 소요될 수 있음) 실패 시 요청을 재시도하는 기능을 구현했습니다. 실패한 요청을 재시도 할 때 발생하는 이슈들의 해결을 위해, 모델들을 카프카 토픽으로 래핑(wrapping)하는 방법을 적용했고 컨슈머(consumer)가 모델 수준에서의 요청 실패를 처리하게 되었습니다. 또한 통신 방식이 동기식에서 비동기식으로 전환되면서, 클라이언트는 모든 요청이 완료될 때까지 기다릴 필요 없이 추론 결과들이 게시되는 응답용 토픽만 듣고(listen) 있으면 되었습니다.

둘째, 느슨한결합(loosely-coupled) 방식입니다. 클라이언트가 상호 작용할 수 있는 자체 카프카 토픽을 각 ML 모듈이 갖게 되면서 모듈들이 서로 분리되었습니다. 이 패턴을 사용해 팀은 컨테이너 오케스트레이션 시스템 상에서 실행되는 각 모델을 독립적으로 배포할 수 있었습니다. 게다가 프로덕션에서 발생하는 이슈들은 각 이슈에 해당하는 컴포넌트로 한정되어 다른 컴포넌트에 영향을 주지 않게 되었습니다.

단점

초기 아키텍처는 위에서 설명한 요구 사항 1. 대량의 트래픽 처리와 2. 짧은 지연 시간을 충족시켰지만 다음과 같은 몇 가지 주요 단점이 있었습니다.

  • 약한 추상화 계층: 모듈을 분리해 잡 오케스트레이션(job orchestration)을 클라이언트에게 위임(delegation)하였습니다. 그러다보니 새로운 피처 추가 시 클라이언트는 해당 피처와 관련된 모든 카프카 토픽에 메시지를 게시하는 로직과 응답들을 집계하는 방법을 만들고 구현해야했습니다.
  • 모델 추가 시 늘어나는 번거로운 작업들: ML 모델을 새로 생성할 때마다 새로운 토픽을 만들고 응답용 토픽에 메시지를 게시하는 데 필요한 작업들을 수행해야만 했습니다.
  • 지연 시간 증가: 요청들이 카프카 토픽들을 거쳐 처리되다 보니 메시지가 토픽에 게시되고 토픽에서 메시지를 받아오는 과정에서 추가적인 지연 시간이 발생했습니다.

이러한 단점들을 어떤 비용 효율적인 방법으로 해결했는지 다음 섹션을 통해 설명드리겠습니다.

지연 시간이 긴 ML 모델들 설정/관리/조정하기

클라이언트의 경험을 개선하기 위해 저희는 ML 모델과 클라이언트 간에 상호 작용을 관리하는 오케스트레이션 계층(orchestration layer)을 도입했습니다. 클라이언트는 오케스트레이션 계층에 요청을 제출하고 오케스트레이션 계층은 요청을 각각의 모델들로 라우팅(routing)합니다. 마지막으로 요청에 대한 응답이 집계되어 오케스트레이션 계층을 통해 클라이언트에 반환됩니다.

초기 아키텍처에서는 새로운 ML 모델을 구축할 때, 엔지니어가 직접 새로운 카프카 토픽생성하고, 해당 토픽을 구독해 메시지를 읽어오고(consume), 메시지를 응답용 토픽에 게시(publish)하는 작업들을 수행해야만 했습니다. 저희는 이 작업들을 단순화하고 싶었고 REST 서비스로 모델과 상호작용하는 방법을 선택했습니다. 하지만 REST 서비스의 지연 시간이 길어지면 발생하는 높은 I/O 비용을 피하기 위해, 저희는 NIO(Non-blocking I/O) 오케스트레이션 계층을 추가로 도입하게 되었습니다.

이전에는 카프카 토픽들이 요청을 라우팅 처리해왔는데, 새로 도입된 오케스트레이터(orchestrator)가 이 작업을 담당하게 되면서 카프카 토픽들 모두 더 이상 해당 작업을 처리할 필요가 없어졌습니다. 오케스트레이터는 클라이언트를 위해 어떤 피처들에 요청을 보낼지 파악하고, 요청에 대한 응답을 기다리고, 실패한 요청은 재시도하고, 응답들을 결합해 클라이언트에게 보냅니다.

클라이언트와 오케스트레이터의 작동 방식은 다음과 같습니다.

  • 클라이언트가 오케스트레이터에 요청을 보냄: 클라이언트는 요청 ID, 실행 유형, 탐지할 특성들 및 데이터로 구성된 요청을오케스트레이터에게 보냅니다(submit). 탐지할 특성들(detections) 속성은 클라이언트가 활용하려는 ML 모델을 나타냅니다. 데이터(data) 속성은 오케스트레이터에게 모델이 사용해야 하는 모델이 입력으로 받으로 수 있는 데이터 모두를 알려줍니다. 요청의 일반적인 형태는 다음과 같습니다.
{
“requestId”: “01a66e3c-c168–407d-bfbe-9729f5ebbbd1”,
“execution”: “sync”,
“detections”: [“keyword_detection”,”brand_detection”,
“logo_detection”],
“data”: [{“imageurl”: “http://url-link1"}]
}
  • 오케스트레이터가 클라이언트의 요청을 실행: 요청에 지정된 실행(execution) 속성값이 sync일 경우 오케스트레이터는 요청 연결을 계속 유지하고, async일 경우 오케스트레이터는 즉시 응답하고 비동기적으로 처리된 결과를 응답용 토픽에 게시합니다. 요청 재시도는 MonoFlux와 같은 리액터 코어 추상화를 사용해 처리할 수 있습니다.
쿠팡 로켓그로스 ML 모델 플랫폼과 오케스트레이터
그림 2. 지연 시간을 줄이고 클라이언트 경험을 개선하기 위해, 쿠팡그로스 ML 모듈 플랫폼에 오케스트레이터를 추가했습니다.

이 패턴을 사용해 클라이언트에 명확하고 직관적인 인터페이스를 제공하고 ML 모델의 응답을 기다릴 때 발생하는 I/O 비용 또한 크게 줄이고 있습니다. 다음 섹션에서는 모델들의 추론 속도를 어떻게 개선했는지를 설명드리겠습니다.

모델의 추론 속도 높이기

저희는 처음에 ML 모델 모두의 연산 플랫폼으로 컨테이너 오케스트레이션 시스템을 선택했습니다. 해당 시스템은 배포 용이성과 높은 완성도의 모니터링 도구와 같은 장점들을 갖고 있습니다. 하지만 컴퓨터 비전 기반(computer vision-based) 모델처럼 과다한 행렬 연산을 포함하는 모델 다수를 실행하기에는 적합하지 않습니다. 따라서 추가 비용을 고려하더라도 GPU 인스턴스를 사용해 이러한 모델을 실행하는 것이 저희에게 있어서는 최선의 선택이었습니다.

프로덕션 설정에 있어 비용 효율성을 고려해 ML 모듈들을, 분산된 GPU들 상에서 더 나은 성능을 보이는 GPU 기반 모듈과 범용 인스턴스들에서 운영하는 것이 더 저렴한 CPU 기반 모듈, 이 두 개의 버킷으로 분류했습니다. 오케스트레이터는 모듈들을 각각의 버킷으로 자동 매핑합니다.

이 시스템을 통해 저희는 강력한 GPU 인스턴스를 최대한 활용할 수 있게 되었음에도 불구하고 비용은 상대적으로 낮게 유지할 수 있었습니다. 예를 들어, 광학 문자 인식 및 로고 감지와 같은 컴퓨터 비전 모델은 GPU에서 실행하면 이점이 있는 반면 키워드 감지, 연령 감지와 같은 일부 NLP 모델은 CPU에서 더 효율적입니다.

ML 모듈들을 GPU, CPU 2개의 버킷으로 나누어 관리하며 적은 비용으로 높은 연산속도를 보장하는 쿠팡 로켓그로스 ML 플랫폼 내 서브 시스템
그림 3. 분리된 ML 모듈들을 시스템으로 관리하면서 비교적 적은 비용으로도 연산 속도를 높일 수 있었습니다.

2개의 버킷으로 ML 모듈들을 관리하는 시스템을 개선해 나가면서 저희는 다음과 같은 주요 사항들을 발견했습니다.

  • 차세대 Graviton 인스턴스. AWS Graviton을 사용함으로써 Intel 기반 P3와 비교해 인스턴스 비용을 크게 줄일 수 있었습니다.
  • 스팟 인스턴스. 스팟(spot) 인스턴스는 일반 온디맨드(on-demand) 인스턴스에 비해 훨씬 저렴한 것으로 알려져 있습니다. 그러나 예기치 않게 종료되는 경우들이 있습니다. 발생가능한 서비스 중단을 방지하려면 스팟 인스턴스들을 원하는 만큼 사용할 수 있어야 합니다. 이렇게 할 수 없는 경우에 대비해 저희는 다른 대체 규칙이 설정가능한 오토 스케일링(auto-scaling) 그룹을 여러 인스턴스 유형으로 구성했습니다. 자세한 내용은 공식 AWS 가이드를 참조하세요.
  • 단일 스레드 서버. ML 모델과의 인터랙션을 위해 GPU 인스턴스에서 웹 서버를 실행할 때 작업자 스레드 수를 1로 설정하면 최고의 성능을 보인다는 것을 알게 되었습니다. 분석에 따르면 둘 이상의 워커(worker)가 훨씬 더 많은 동시 작업들을 일으켜 결과적으로 텐서 가중치(tensor weights)에 대한 GPU의 메모리 할당 및 할당 해제가 발생했습니다. 이로 인해 더 많은 메모리 복사가 일어나 요청 시간이 크게 증가했습니다.

벤치마크

  • 지연 시간: 위에서 언급한 일부 최적화와 함께 모델들을 GPU들에서 실행하면 속도가 20–40배 향상되었습니다. 다음은 광학 문자 인식 모델을 CPU와 GPU에서 실행했을 때의 지연 시간에 대한 벤치마크 결과 중 하나입니다.
쿠팡 로켓그로스 ML 플랫폼에서 광학 문자 인식 모델 실행 시의 CPU vs. GPU 지연 시간 초단위 비교
그림 4. 광학 문자 인식 모델 실행 시의 CPU vs. GPU 지연 시간 초단위 비교
  • 비용: 아래 그래프는 일련의 GPU 인스턴스 설정들을 실험했을 때 각 설정의 지연 시간에 대한 비용을 초 단위로 보여줍니다. 저희는 테스트에서 G5 instances가 가장 저렴한 비용으로 최고의 성능을 제공한다는 것을 발견할 수 있었습니다. 이 포스트에 설명된 몇 가지 방법들을 잘 결합해 초기에 예상했던 비용보다 95%의 비용을 절약했습니다.
쿠팡 로켓그로스 ML 플랫폼에서 GPU 인스턴스 설정 별 지연 시간에 대한 비용 초단위 비교
그림 5. GPU 인스턴스 설정 별 지연 시간에 대한 비용 초단위 비교

결론

저희는 이 포스트에서 다음과 같은 몇 가지 패턴들을 다뤘습니다.

  • Non-blocking I/O로 지연 시간이 긴 모델들의 설정, 관리, 조정하기
  • 성능 대 비용을 분석해 적절한 인스턴스 선택, 단일 워커로 서버 실행 같은 방법을 통한 GPU에서의 서버 실행 최적화

이러한 방법들을 통해 쿠팡 로켓그로스의 ML 플랫폼은 2개월 동안 매일 수백만 건의 요청을 처리할 수 있는 수준으로 확장되었고 20개의 ML 모델들을 통합할 수 있었습니다. 하지만 메모리 용량 최적화를 위해 모델들을 인스턴스 전체에 배포하기, 모델들을 개선할 수 있는 피드백 루프 개발하기와 같은 과제들이 아직도 많이 남아있습니다.

이 포스트에서 혹시 관심 가는 이슈를 발견하셨나요? 쿠팡에 합류해 저희와 함께 해결해보세요!

Twitter logo

쿠팡 엔지니어링의 최신 소식을 이제 트위터로 업데이트 받으실 수 있습니다! 팔로우부탁드려요!

--

--

쿠팡 엔지니어링
Coupang Engineering Blog

쿠팡의 엔지니어들은 매일 쿠팡 이커머스, 이츠, 플레이 서비스를 만들고 발전시켜 나갑니다. 그 과정과 결과를 이곳에 기록하고 공유합니다.