Sidekiq의 성능을 최적화하거나 모니터링하는 방법에 대한 문의를 자주 받습니다. 모니터링은 저희의 전문 분야이지만, Sidekiq 성능 최적화는 완전히 다른 차원의 이야기입니다. 사실 최적화는 결코 간단하지 않지만, 겉보기에 단순해 보이는 답부터 하나씩 살펴보겠습니다.
속도를 높이는 두 가지 방법
처리 속도를 높이는 방법은 크게 두 가지입니다. 하나는 더 큰 작업(job)을 수행하는 것이고(메모리나 IO를 더 많이 사용하거나 로직이 복잡해질 수 있음), 다른 하나는 동시성(concurrency) 설정이나 워커(worker) 수를 조정하는 것입니다(이 경우 CPU와 IO 사용량이 늘어납니다). 현실은 이미 벽을 드러내고 있습니다. 속도에는 대가가 따른다는 것이죠. 여유 메모리, IO, CPU가 있다면 추가 비용 없이 진행할 수 있지만, 어떤 트레이드오프가 필요한지 인식하는 것이 중요합니다.
동시성 설정으로 속도 높이기
Sidekiq는 프로세스 내에서 여러 스레드를 사용해 동시성을 처리합니다. 이를 통해 한 번에 여러 작업을 처리할 수 있으며, 각 스레드는 한 번에 하나의 작업을 담당합니다. 기본적으로 Sidekiq는 프로세스당 10개의 스레드를 사용하며, 이 값을 늘려 동시성을 높일 수 있습니다.
단, 새로운 동시성 모델을 적용하려면 애플리케이션이 이를 지원해야 합니다. 예를 들어 데이터베이스 커넥션 풀이 충분히 커야 하고, 호스트 역시 증가한 I/O 작업을 감당할 수 있어야 합니다. 자세한 내용은 Sidekiq의 concurrency 관련 공식 문서를 참고하세요.
여러 워커를 실행해 속도 높이기
프로세스를 여러 개 띄우는 것 외에도 여러 워커를 실행하는 방법이 있습니다. 이렇게 하면 CPU 집약적인 작업들을 병렬로 실행할 수 있어, 단일 프로세스에서 서로 CPU를 두고 경합하며 실행될 때보다 훨씬 효율적입니다. Ruby의 동시성과 GIL(Global Interpreter Lock)에 대해 더 알고 싶다면 이전에 작성한 글을 확인해 보세요.
제대로 작동하는가, 아니면 무언가 깨지는가? 모니터링 대상과 매직 대시보드
기본 설정값을 변경하면 애플리케이션 성능에 영향을 줄 수 있습니다. 따라서 설정을 변경하기 전에 먼저 모니터링 체계를 갖춰, 변경 사항이 긍정적인 효과를 가져오는지 아니면 오히려 성능을 저하시키는지 확인하는 것이 좋습니다.
먼저 호스트 수준에서 문제가 발생하지 않는지 확인해 보겠습니다. 애플리케이션이 실행 중인 하드웨어에서 얼마나 많은 리소스를 소비하는지 파악해야 합니다. 메모리와 CPU에 여유가 있다면 동시성 설정을 높여 처리 속도를 좀 더 끌어올릴 수 있습니다.
사용하는 APM(Application Performance Monitoring) 도구가 무엇이든 호스트 지표를 추적하는 기능은 대부분 제공합니다. AppSignal을 사용한다면 호스트별 대시보드가 자동으로 생성되며, Anomaly Detection(이상 탐지)을 활용해 특정 호스트의 CPU 사용량에 대한 알림을 손쉽게 설정할 수 있습니다. 이렇게 하면 상황이 심각해지기 전에 미리 대응할 수 있습니다.
이제 Sidekiq 수준에서 문제가 없는지 살펴보겠습니다. 사용 중인 APM이라면 백그라운드 작업에 대한 모니터링과 알림을 수동으로 구성하는 기능을 어느 정도 제공할 것입니다. AppSignal에서는 이 과정이 아주 간단합니다. 최신 버전의 젬(gem)을 실행하기만 하면 별도 설정 없이 Sidekiq 매직 대시보드가 자동 생성되어 이를 추적해 주기 때문입니다.
이 환경에서는 큐별 작업 상태(job status)를 모니터링하고, 실패율(failure rate)에 트리거를 설정해 Slack, PagerDuty, 이메일 등 평소 사용하는 채널로 알림을 받도록 하는 것이 좋습니다.
더 적고, 더 큰 작업?
지금까지 CPU를 희생하면서 속도를 높이는 방법을 간단히 살펴봤습니다. 이번에는 메모리나 IO, 혹은 코드 복잡도를 희생하며 속도를 높이는 방법을 살펴보겠습니다. 더 큰 작업을 만들면 된다고 생각할 수 있습니다. 작업 하나하나마다 발생하는 Sidekiq 오버헤드가 줄어들기 때문에 큰 작업이 더 빠를 수 있다는 논리입니다.
하지만 저희 경험상 Sidekiq 오버헤드가 성능을 좌우하는 경우는 드뭅니다. 오히려 성능을 잡아먹는 주범은 대개 작업이 수행하는 데이터베이스 쿼리입니다. 여러 작업을 묶어 쿼리 횟수를 줄이는 것이 실제로 성능 개선의 중요한 열쇠입니다. 단순화해서 말하면, "레코드 1건씩 1000번 업데이트하는 대신 1000건을 한 번에 업데이트하라"는 것입니다.
작동 여부를 확인하려면 무엇을 모니터링해야 할까?
여기서도 마찬가지로 큐별 작업 상태를 모니터링하고 실패율에 대한 트리거를 설정해야 합니다. 작업이 더 오래 걸리고 연산량도 많아지므로, Sidekiq의 메모리 사용량도 함께 모니터링해야 합니다. 또한 AppSignal의 Anomaly Detection을 활용해 Sidekiq 메모리 사용량, 특히 호스트의 메모리 부족 상황에 대한 알림을 설정하는 것이 좋습니다.
아울러 큐별 대기열 길이(queue length)도 모니터링해야 합니다. 워커가 처리할 수 있는 양보다 대기 작업이 많아져 적체(backlog)가 형성되기 시작하면 알림을 받을 수 있습니다. '중요(critical)' 작업 큐에 대해서는 반드시 Anomaly Detection 트리거를 설정하길 권장합니다.
APM에서는 보이지 않는 트레이드오프
더 큰 작업의 가장 큰 문제는 코딩 난이도가 올라간다는 점입니다. 각 작업이 명시적으로 멱등성(idempotency)을 갖도록 만들고, 작업의 진행 상태까지 추적해야 합니다. 간단한 예를 들어보겠습니다. 한 사람에게 메일을 보내는 작업이 실패하면 별다른 피해 없이 재시도할 수 있습니다. 하지만 1000명에게 메일을 보내는 작업이 실패했다면, 실패 전에 이미 메일을 보낸 240명에게 메일이 다시 발송되지 않도록 해야 합니다. 이를 보장하려면 코드가 훨씬 복잡해집니다.
현실의 진짜 복잡성
한 번 더 현실을 직시할 시간입니다. 종종 백그라운드 작업이 느린 원인은 Sidekiq 내부에 있지 않을 수 있습니다. 쿼리하는 데이터베이스가 느린 원인이 그 DB가 돌아가는 호스트의 메모리 부족일 수도 있습니다. 이 글을 위해 좋은 사례를 찾던 중 실제로 그런 시나리오를 만났는데, 알고 보니 응답 시간이 치솟은 원인은 외부 API의 긴 응답 지연이었습니다. 이런 문제는 Sidekiq 안에서는 아무리 노력해도 해결할 수 없습니다.
문제가 한 곳에서 보이지만 실제 원인은 전혀 다른 곳에 있을 수 있기 때문에, AppSignal은 에러, 성능, 호스트 지표를 하나로 통합한 제품을 만들었습니다. 이러한 지표들이 한눈에 함께 보이면 문제의 근본 원인을 찾기가 훨씬 수월합니다.
쉬운 답은 없는 법이죠
이 글을 Sidekiq 모니터링과 최적화의 기본기로 시작했지만, 순식간에 현실과 마주했습니다. 속도는 CPU와 IO, 혹은 메모리를 요구하고, 때로는 더 큰 복잡성을 안겨준다는 사실 말입니다. 아키텍처 성능의 일부 복잡성을 짚었지만, 솔직히 말해 이 글에서 우리는 정말 어려운 문제의 99%쯤을 살짝 비껴갔습니다.
그래서 간단한 답을 기대했던 분들의 기대를 배신하게 되었을지 모르지만, 그래도 이 글이 여러분의 Sidekiq 환경을 최적화하고 모니터링하는 데 몇 가지 흥미로운 통찰을 주었다면 좋겠습니다!