고로시롤은 리그 오브 레전드 전적을 직접 조회하는 번거로움을 해소하기 위해 만든 전적 조회 디스코드 봇입니다.
처음에는 개인적인 사용을 목적으로 개발한 서비스가 현재는 260개 이상 채널에서 사용되고 있다.
사용자가 확대되면서, 서비스의 핵심 기능인 자동 전적 조회의 속도 개선이 필요해졌다.
특히, Riot Games API Rate Limit을 준수하면서도 자동 전적 조회의 속도를 개선하는 것이 중요했다.
이 글은 속도 개선을 위한 병렬 처리를 도입하고, RateLimiter 를 구현해 병렬 처리 간 외부 API 규칙을 준수하는 과정에 대한 글이다.
고로시롤은 매 정각(0시부터 23시)에 이 봇이 초대된 채널에 등록된 소환사의 전적을 조회하여 전송하고 있다.
초기에는 사용자가 적어, 각 정시에 시작하면 30초 내로 모든 전적 조회를 완료하고 그 정보를 전송할 수 있었다.
하지만 사용자 수가 증가하면서 전적 조회 시간이 약 6분으로 크게 늘어나 서비스 성능 저하가 발생했다.
전적 조회 시간이 길어지면서 이탈하는 사용자도 생기는 것을 확인했다.
특히, 기본으로 설정되어 있는 시간인 12시(사진상 3시)에는 전적 조회에 약 7분 가량 걸리는 것을 확인할 수 있었다.
문제의 주요 원인은 이 전적 조회의 모든 구성이 순차 처리 방식이라는 것이다.
초기에는 사용자 수가 적어 이러한 문제가 눈에 띄지 않았지만, 사용자 수의 증가로 성능 저하를 느낄 수 있었다.
전적 조회 성능 저하 문제를 해결하기 위해 병렬 처리를 도입했다.
기존 전적 조회는 다음과 같은 순서로 처리되고 있었다.
정각에 작업 시작: 매 정각(0시부터 23시)에 전적 조회 작업이 이루어진다.
조회할 채널 목록 조회: 해당 시간에 조회해야할 채널 목록을 조회하고, 각 채널에 등록된 소환사들의 목록을 조회한다.
채널 목록 순회: 조회한 채널 목록을 하나씩 순차적으로 순회하며 각 채널에 대해 다음 작업을 수행한다.
3-1. 소환사 전적 조회: 채널의 소환사 목록을 하나씩 순차적으로 순회하며 각 소환사 전적을 조회한다. 이 과정에서 Riot API 에 요청이 발생한다.
3-2. 채널에 메시지 전송: 조회된 전적 정보를 채널에 전송한다.
사용자 수 증가에 따라 3번 과정에서 문제가 발생했다.
채널별 여러 소환사의 정보를 순차적으로 조회하던 중 전체 조회 시간이 급격히 늘어났다.
이를 개선하기 위해 병렬 처리를 도입해 동시에 여러 작업을 처리하고 전체 조회 시간을 줄여 성능을 개선하고자 했다.
그러나 새로운 문제가 있었다.
채널별 소환사 정보 조회와 채널별 조회 기능을 모두 병렬처리하며 외부 API Rate Limit 문제가 있었다.
Riot API 는 10초당 500개의 요청으로 제한되어 있어, 대량 요청이 동시에 발생할 경우 초과할 위험이 컸다.
현재를 기준으로 0시에 약 120명의 소환사를 조회하는 데 소환사당 최소 3회의 요청이 필요했고,
소환사가 한 판 플레이할 때마다 요청 횟수가 1회씩 증가한다.
즉, 평균 2판 이상 플레이할 경우 Rate Limit을 초과하는 것이었다.
이를 해결하기 위해 RateLimiter 을 만들어보고자 했다.
최초 설계는 10초당 500개의 요청을 순차처리하는 방식이었다.
모든 요청 사이에 일정한 딜레이를 두어 API 규칙을 준수하는 것이었다.
하지만, 이 방식은 위의 병렬 처리 도입 목적과 맞지 않았다.
병렬 처리를 통해 여러 요청을 동시에 처리하면서 성능을 개선하고자 했지만,
순차적으로 처리하는 방식은 결국 병렬 처리의 효율성을 저하시키는 것이었다.
따라서, 두 가지 모드의 RateLimiter 를 생각해봤다.
동작 흐름은 다음과 같다.
각 모드의 전환은 RateLimiter 인스턴스를 생성할 때 입력한 값을 기준으로, 그리고 임계치를 설정할 수 있도록 했다.
실제 코드는 여기에서 확인할 수 있습니다.
API Rate Limit 을 만족하기 위해 RateLimiter 를 만들어서 적용해도 요청 허용 수를 초과했다는 에러가 발생했다.
이는 각 엔드포인트마다 설정된 Rate Limit 이 달랐기 때문이다.
예를 들어, 내 API 키를 기준으로 각 엔드포인트의 Rate Limit 은 다음과 같다.
/riot/account
는 1분에 1000개/lol/summoner
는 1분에 1600개/lol/league
는 1분에 100개/lol/match
는 1분에 2000개따라서, 하나의 RateLimiter가 아닌 엔드포인트 별로 다른 인스턴스로 적용해야 했다.
위 과정을 거쳐 RateLimiter 를 도입했고, 다음과 같은 결과를 얻을 수 있었다.
외부 API 규칙을 준수하면서 7분이라는 시간을 1분으로 줄인 것은 충분히 좋은 성과다.
하지만, 아직 더 개선할 점들이 있다.
사용자가 많아지면 여전히 문제가 발생한다.
쓰로틀 모드는 결국 순차 처리가 되고 있기 때문이다.
가장 우려가 되는 부분은 /lol/league
에 대한 요청에 대한 것이다. 1분에 100개 이기 때문에.
저 엔드포인트는 랭크와 티어를 조회하는 요청이다.
리그 오브 레전드의 게임의 평균 플레이 타임을 고려한다면 20분 이내에는 같은 결과를 조회할 수 있다.
따라서, 크론 작업 사이에 저 부분을 분리해서 순차적으로 실행시킨다면, 쓰로틀 모드로 요청이 순차 처리되는 비율을 줄일 수 있을 것이다.
정시에 발송되지 않는다.
정시에 전적을 발송해주는 서비스를 만들고 있기 때문에 지금은 그 간격을 줄였을 뿐 아직 정시에 발송되지 않는다.
전적 조회를 미리 해두고 정시에 발송하는 방향으로 개선할 수 있을 것이다.