사이드킥은 루비 언어로 이루어진 비동기 프레임워크로서 많이 사용되고 있다. 특히 레일즈 어플리케이션에서 비동기 처리할 때 많이 쓰이며, 사용 방법도 간단하여 다루기 쉽다. 그러나 사이드킥을 온전히 믿을 수 있을까? 잡을 처리하다가 비정상적인 종료로 작업이 끝나면 어떻게 될까?
사이드킥은 redis 를 이용하여 큐를 생성한다. 그리고 client 에서 잡을 큐에 삽입하면 server 에서 큐에 쌓인 잡을 처리한다. 이때 서버쪽 워커들은 각각 자신이 맡은 큐에 쌓인 잡들만 처리한다.
만약 워커에서 잡이 처리되고 있을 때 사이드킥 프로세스가 종료 시그널을 받으면 어떻게 될까? 이에 앞서 사이드킥에서 제공하는 시그널 처리 문서를 확인해볼 필요가 있다.
ref: https://github.com/sidekiq/sidekiq/wiki/Signals
사이드킥은 TSTP 시그널을 받으면 quiet 상태가 된다. 이는 해당 워커에서 더이상 추가로 잡은 받지 않음을 의미한다. 예를 들어, A 워커에서 a, b, c, d 잡을 처리하고 있는데 quiet 상태가 된다면, 이후 e, f 잡이 추가로 들어와도 A 워커는 e, f 잡을 처리하지 않는다.
-t
옵션 (default 25s) 값에 할당된 타임아웃을 토대로 사이드킥 프로세스를 종료한다. 이때 TERM 시그널을 받은 사이드킥 워커는 quiet 상태가 되며, 처리하는 잡이 없을 경우 바로 종료한다. 만약 처리하는 잡이 오래 걸려 타임아웃을 초과하면 완료되지 않은 잡은 다시 큐로 삽입이 되고 사이드킥 워커는 종료한다.
TERM 시그널은 TSTP + EXIT(?) 라고 보면 된다.
대략 pseudo code 로 작성해본다면 다음과 같이 작성할 수 있다.
PID=... # sidekiq process ID
# quiet 상태로 만듬
kill -TSTP PID
while count < timeout
# 처리중인 잡이 없다면 나간다
if jobs_count(processed by sidekiq) == 0
exit
end
count += 5
sleep 5
end
exit
타임아웃 값은 -t
옵션으로 커스터마이징 할 수 있으니 사용하는 클라우드 플랫폼 또는 환경에 따라 조정해주는게 좋을 거 같다 (ex: AWS EC2 instance 타임아웃 값 등을 고려하여 맞춤)
이미 사이드킥에서 위와 같이 graceful terminate 를 제공해주고 있으니 나는 잡을 만들 때 더욱 집중을 해야될 거 같다. 만약 처리되고 있다가 사이드킥 프로세스가 종료되어 다시 큐로 들어간 잡은 이후 다른 사이드킥에서 돌아갈 수 있으니 항상 멱등성을 가지도록 해야될 것이다. (즉, 여러번 실행되더라도 같은 결과가 나와야된다)
루비에서 사용하는 웹앱 프레임워크인 Rails 에서는 AcitveJob 으로 비동기 처리를 할 수가 있다. 그리고 어댑터에 사이드킥을 연결하여 ActiveJob 을 사이드킥으로 처리할 수 있도록 구현할 수도 있다. 그러나 ActiveJob 을 사용할 시 사이드킥의 장점을 최대로 이끌어 낼 수 없다. 예를 들어, 사이드킥에서 제공하는 sidekiq_options
를 사용할 수 없다는 점. 그리고 대규모로 잡을 처리할 때 ActiveJob 보다 Sidekiq 에서 2배 가량 더 좋은 성능을 보여준다. 기회가 된다면 ActiveJob -> Sidekiq 으로 마이그레이션하여 서버의 성능을 올려보고 싶다.
https://github.com/sidekiq/sidekiq
https://github.com/sidekiq/sidekiq/wiki/Signals
https://github.com/chrismaximin/benchmark-sidekiq-and-activejob
https://guides.rubyonrails.org/active_job_basics.html#backends