Solid Queue를 Puma 안에서 운영할 때 연결 풀 계산하기
worker thread 수만 세면 부족하다
소규모 Rails 8 애플리케이션은 Solid Queue를 별도 서버 없이 Puma 프로세스 안에서 실행할 수 있다. 배포 구성이 단순해지는 대신 웹 요청과 작업 실행이 같은 프로세스의 CPU, 메모리, DB 연결을 공유한다. 여기서 흔한 실수는 worker가 3 threads이므로 queue DB 연결 풀도 3이면 충분하다고 보는 것이다.
Solid Queue에는 실제 job을 수행하는 worker 외에 예약 작업을 전달하는 dispatcher와 전체 실행을 관리하는 supervisor가 있다. 구성과 실행 시점에 따라 이들도 queue DB 연결을 요구한다. 풀이 너무 작으면 작업 자체가 느리지 않아도 연결 대기나 ActiveRecord::ConnectionTimeoutError가 발생할 수 있다.
실행 구성부터 숫자로 적는다
먼저 queue 설정을 코드와 운영 문서에 같은 값으로 남긴다.
production:
dispatchers:
- polling_interval: 1
batch_size: 500
workers:
- queues: "*"
threads: 3
processes: 1
polling_interval: 0.1
이 예에서는 worker threads 3개에 dispatcher와 supervisor가 사용할 여유를 더해 queue DB의 max_connections를 최소 5로 둔다. 정확한 값은 프로세스 수와 queue 구성을 바꾸면 다시 계산해야 한다. JOB_CONCURRENCY=2로 processes를 늘리면서 기존 풀을 유지하면 연결 부족이 생길 수 있으므로 환경변수끼리의 관계를 배포 검증에 포함한다.
queue DB만 별도 풀을 사용한다
웹 요청의 동시성 값인 RAILS_MAX_THREADS를 모든 DB에 기계적으로 적용하지 않는다. primary와 queue는 연결을 소비하는 주체가 다르다.
queue:
adapter: sqlite3
database: storage/production_queue.sqlite3
max_connections: <%= ENV.fetch("SOLID_QUEUE_MAX_CONNECTIONS", 5) %>
migrations_paths: db/queue_migrate
SQLite는 동시에 여러 writer가 자유롭게 기록하는 서버형 DB가 아니다. 풀을 무작정 크게 늘리면 처리량이 비례해 증가하지 않고 writer 경합만 늘 수 있다. job이 CPU 또는 외부 API 대기 중심인지, queue DB에 짧은 쓰기를 반복하는지 관찰하며 threads와 polling interval을 조정한다.
작업은 재실행 가능하게 만든다
프로세스가 job 수행 도중 종료되면 같은 작업이 다시 실행될 수 있다. 따라서 “정확히 한 번 실행된다”는 가정보다 중복 실행돼도 결과가 깨지지 않는 구조가 현실적이다. 예를 들어 이미지 variant 생성 작업은 결과 파일이 이미 존재하는지 확인하고 종료할 수 있다. 외부 알림은 전송 식별자를 저장하거나 상태 전이를 조건부 갱신해 중복 발송을 막는다.
class RefreshPostPreviewJob < ApplicationJob
def perform(post_id)
post = Post.find_by(id: post_id)
return unless post
return if post.preview_fresh?
post.refresh_preview!
end
end
모델 객체 전체를 오래 보관하기보다 식별자를 인자로 넘기고 실행 시 최신 상태를 다시 조회한다. 삭제된 레코드는 정상적인 종료 조건으로 다룰지 실패로 기록할지 작업 성격에 따라 결정한다.
실패와 지연을 함께 관찰한다
운영 점검은 “worker 프로세스가 살아 있다”에서 끝나지 않는다. 실패 작업 수, 가장 오래 기다린 작업의 대기 시간, 처리 속도, queue DB 연결 timeout을 함께 본다. 실패 건수가 0이어도 대기 시간이 계속 늘면 용량이 부족하거나 특정 job이 worker를 오래 점유하고 있을 수 있다.
배포 전후에는 짧은 진단 job을 넣어 enqueue부터 완료까지 확인한다. 컨테이너를 재시작한 뒤 예약된 작업이 사라지지 않는지, queue DB가 영속 볼륨에 있는지도 검사한다. schema migration이 누락되면 worker 부팅이 늦게 실패할 수 있으므로 db:prepare와 migration status를 배포 gate에 넣는다.
변경은 한 변수씩 검증한다
처리 지연이 보인다고 threads, processes, pool, polling interval을 한꺼번에 바꾸면 무엇이 효과가 있었는지 알기 어렵다. 먼저 connection wait인지, 긴 job인지, SQLite writer 경합인지 로그와 측정값으로 구분한다. 그다음 가장 좁은 설정 하나를 바꾸고 같은 작업량으로 비교한다.
Puma 내부 실행은 트래픽과 작업량이 작은 서비스에 실용적인 선택이다. 웹 응답 시간이 job 부하에 영향을 받기 시작하거나 프로세스 재시작이 잦다면 Solid Queue worker를 별도 프로세스로 분리할 시점이다. 그때도 queue DB 연결 수와 작업의 멱등성 원칙은 그대로 유지된다.
댓글
아직 댓글이 없습니다.
댓글 남기기