동일한 비용으로 리포트 요청 10배 처리하기

동일한 비용으로 리포트 요청 10배 처리하기

적절한 공수로 Throughput 을 많이 끌어올릴 방법은 없을까?

Juho Choi — 최주호 @juho

들어가며

Airbridge 에는 마케팅 데이터를 다양하게 분석할 수 있는 여러 종류의 Report 가 있습니다. 이 중 Actuals Report 는 가장 기본적이면서 가장 많은 데이터를 직관적이고 유연하게 확인할 수 있는 Report 입니다.
최근에 마케팅 데이터를 더 보기쉬운 형태로 확인할 수 있는 Overview Dashboard 를 새로 공개했습니다. 주요 지표들의 기간 대비 증감률을 쉽게 확인할 수 있고, 차트를 통해 시각화된 데이터를 빠르게 확인할 수 있습니다. 또한, 여러 앱의 지표를 동시에 선택하여 앱 간 마케팅 성과를 한 눈에 비교할 수 있습니다.

문제 인식

Overview Dashboard 의 데이터는 Actuals Report 에서 제공하고 있는 데이터를 활용하여 제공하고 있습니다. 각각의 위젯은 독립적으로 데이터를 조회하고 있고, 하나의 위젯에는 최소 1개 이상의 Report API 를 호출하게 되어있습니다. 예를 들어 위 사진에는 총 8개의 위젯이 있고, 최소 8개의 Report API 를 호출합니다. 따라서 위젯이 많아질수록 동시에 요청하는 Report 의 수가 증가하게 됩니다.
실제로 Overview Dashboard 출시 이후 Actuals Report API 사용량이 크게 증가했습니다. 이에 따라 늘어난 요청을 지연 없이 빠르게 처리하기 위해 Worker 를 기존 대비 5배 많은 수준으로 늘리게 되었습니다. 그 결과, 요청을 처리하는데 필요한 비용도 5배로 증가하게 되었습니다.
Report 요청이 늘어날수록 비용이 늘어나는 구조가 많이 아쉽다고 생각이 들었고, 개선을 하고자 마음먹었습니다. 데이터 처리 과정을 살펴보던 중 병목이 발생하고 있는 지점을 확인했습니다. 이를 개선하기 위해 먼저 데이터를 처리하는 과정을 확인해보겠습니다.
대시보드에서 Report 를 조회하면 Airbridge 서버에서는 위와 같은 과정으로 요청을 처리하게 됩니다.
Report 요청은 기본적으로 비동기로 처리하고 있습니다. 사용자가 설정한 Config 정보를 Report Post API 로 요청하여 Task Id 를 발급받습니다. 발급 받은 Task Id 는 비동기 처리를 위해 SQS 에 메시지로 전송됩니다. 이 Task Id 를 이용하여 Report Get API 를 요청하고 처리 상태를 조회합니다. Task 가 아직 처리중이라면 처리가 완료될때 까지 Report Get API 를 Polling 형태로 반복하여 조회합니다. 성공적으로 처리가 완료되었다면 Report Get API 에서 Report 데이터를 확인할 수 있습니다.
Worker 에서는 SQS 로 부터 메시지를 받아와 데이터 처리를 시작합니다. 먼저 메시지의 Task ID 를 통해 Task 정보를 받아오고, Task 의 상태를 처리중으로 변경합니다. 요청에 따라 적절한 Data Source 에 쿼리하여 쿼리 결과를 받아와 리포트의 요구에 맞게 가공합니다. 완성된 Report 데이터는 외부 저장소에 저장합니다. 마지막으로 Task 의 상태를 성공으로 변경하고 처리를 완료합니다.

문제 원인

데이터를 처리하는 데 소요되는 시간을 과정별로 살펴보겠습니다. 데이터 가공은 대부분 10ms 이내에 처리되는 반면 Data Source 에 쿼리하는 시간과 완성된 데이터를 저장하는 시간은 상당히 많은 시간이 소요되는 것을 확인할 수 있습니다. 전체 처리 시간 중 약 98%의 시간을 IO Bound 에 사용하고 있고, 이 부분이 병목의 원인임을 확인할 수 있었습니다. 따라서 Data Source 에 쿼리하는 부분과 완성된 데이터를 저장하는 부분에 동시성을 높이면 비용을 개선할 수 있습니다.
각 처리 단계에서는 다음과 같은 특징을 가지고 있습니다. 이러한 특징을 고려하여 처리 과정을 개선해보겠습니다.
Data Source 에 쿼리하거나 완성된 Report 데이터를 저장하는 과정에서는 메모리 사용량이 높지 않음
Task 당 최대 메모리 사용량 예측 가능
쿼리 결과를 가공하는 과정에서 메모리 사용량이 매우 높아질 수 있음
Task 당 최대 메모리 사용량 예측 불가

문제 해결

먼저 기존에 하나의 Queue로 구성되어 있던 것을 각 처리 단계에 따라 Requester Queue, Processor Queue, Uploader Queue 로 분리했습니다.
Requester Queue 는 적절한 Data Source 에 쿼리하여 쿼리 결과를 받아오는 역할을 합니다.
Processor Queue 는 쿼리 결과를 받아와 리포트의 요구에 맞게 가공하는 역할을 합니다.
Uploader Queue 는 완성된 Report 데이터는 외부 저장소에 저장하는 역할을 합니다.
위에서 언급했던 것 처럼 Requester Queue 와 Uploader Queue 를 처리하는 Worker 는 메모리 사용량이 높지 않고, 하나의 Task 를 처리하기 위해 필요한 최대 메모리를 대략 예상할 수 있기 때문에 Multi Thread 를 사용하여 Task 를 처리할 수 있도록 개선했습니다. Processor Queue 는 아쉽게도 최대 메모리 사용량을 예측하기 어려워 Single Thread 로 처리하도록 유지했습니다. 개선된 Worker 의 형태는 아래와 같습니다.
위 캡쳐에서 보신 것 처럼, 각 처리 단계의 결과물은 Redis 에 저장하기로 의사결정 했습니다. 고민했던 옵션들과 내용은 다음과 같습니다.
Local Disk: Local Disk 를 사용하게 되면 Requester, Processor, Uploader 가 모두 같은 Node 에 띄워져야 합니다. 인프라 리소스를 유연하게 관리하기 어렵게 되고, HA 를 고려하여 이러한 제약은 좋지 않다고 판단했습니다.
NFS (EFS): Read / Write Performance 가 정말 좋지 않았습니다. NFS 를 사용하게 된다면 요청 처리에 필요한 시간이 최소 수백 ms 이상이 증가하게 되어 선택하지 못했습니다.
S3: NFS 와 마찬가지로 Read / Write Performance 가 좋지 않았습니다. S3 를 사용하게 된다면 요청 처리에 필요한 시간이 최소 수십 ms 이상이 증가하게 되어 선택하지 못했습니다. (S3 Express One Zone 을 사용해보는 방법도 있었겠지만, 시도해보지 않았습니다.)
File Upload & Download API: NFS 보다 더 Read / Write Performance 가 좋지 않아 선택하지 못했습니다.
In Memory DB (Redis): Read, Write Performance 가 좋았습니다. 대부분 수 ms ~ 수십 ms 정도의 시간에 처리되었고, 용인할 수 있는 수준의 Latency 라고 판단했습니다. 또한 큰 공수를 들이지 않고 빠르게 적용할 수 있어 가장 적합하다고 생각하여 선택했습니다.

성과

이렇게 개선하고 나서 Overview Dashboard 릴리즈 이후 늘어났던 Worker 를 릴리즈 이전보다도 더 적게 줄일 수 있게 되었고, 5배로 증가했던 비용도 릴리즈 이전보다 0.8배 정도로 줄어들게 되었습니다. 그럼에도 불구하고 훨씬 더 많은 Report 요청을 무리없이 처리할 수 있게 되었습니다. 또한 Worker 수가 늘어나면서 배포 시간이 증가했었는데, 배포 시간도 다시 릴리즈 이전 수준으로 줄어들게 되었습니다.
기존 1개로 사용하던 Queue 가 3개로 분리되면서 Queue 를 받아오는 Latency 가 최대 20ms 정도 늘어나게 되었습니다. Report Get API 에서는 외부 저장소에 저장된 완성된 Report 데이터를 가져오는 형태에서 Redis 에 저장되어 있는 데이터를 먼저 사용할 수 있도록 개선하여 결과적으로 전체적인 Report 요청 처리 속도가 100ms 정도 더 빨라졌습니다.

마치며

지금까지 늘어난 Reoprt 요청을 처리하는 과정에서 증가했던 Worker 비용을 줄이기 위한 개선 과정을 살펴보았습니다. 결과적으로 전체적인 비용이 줄어들었고, 처리 속도까지 챙길 수 있었던 작업이 될 수 있었습니다.
더 적은 비용으로 더 많은 Report 요청을 처리하기 위해서는 여전히 개선해볼만 한 부분이 많이 남아 있습니다. 앞으로 개선할 기회가 더 생긴다면 다시 한 번 개선기를 공유해보도록 하겠습니다. 긴 글 읽어주셔서 감사합니다.
ᴡʀɪᴛᴇʀ
Juho Choi @juho Report Team Lead @AB180
유니콘부터 대기업까지 쓰는 제품. 같이 만들어볼래요? 에이비일팔공에서 함께 성장할 다양한 직군의 동료들을 찾고 있어요! → 더 알아보기