Zum Inhalt springen
Projekt besprechen

9. Oktober 2026

Deine sidekiq_options stehen noch da. Sie tun nur nichts mehr.

RailsSidekiqBackground Jobs

Das Problem ist nicht sichtbar im Code

Der Wechsel von direkter Sidekiq-Nutzung zu ActiveJob ist oft bewusst klein gehalten: Ein Job erbt künftig von ApplicationJob, die vorhandenen sidekiq_options bleiben zunächst stehen. Das wirkt plausibel, denn Sidekiq verarbeitet den Job weiterhin.

Genau darin liegt die Falle. Unter dem Sidekiq-Adapter von ActiveJob haben nicht alle Optionen dieselbe Wirkung wie bei einem direkten Sidekiq-Worker. Insbesondere kann sidekiq_options queue: stehen bleiben und dennoch ignoriert werden. Der Job läuft dann auf default.

Im beschriebenen Fall wären dadurch alle fünf Wartungs-Jobs auf der default-Queue gelaufen. Die Test-Suite blieb grün, weil sie zwar prüfte, dass Jobs eingeplant wurden, nicht aber auf welcher Queue sie tatsächlich ankamen.

Vorher: Direkter Sidekiq-Worker

Bei einem klassischen Worker ist die Queue-Konfiguration direkt Teil der Sidekiq-Deklaration:

class CleanupWorker
  include Sidekiq::Worker
 
  sidekiq_options queue: :maintenance, retry: 5
 
  def perform(account_id)
    # Aufräumlogik
  end
end

Hier beschreibt sidekiq_options queue: :maintenance die Ziel-Queue des Workers. Beim Umbau zu Sidekiq ActiveJob ändert sich aber die Zuständigkeit: ActiveJob definiert Queue und Serialisierung, der Sidekiq-Adapter verpackt den Auftrag anschließend für Sidekiq.

Nachher: Queue mit Rails queue_as festlegen

Für ActiveJob gehört die Queue daher in die ActiveJob-API:

class CleanupJob < ApplicationJob
  queue_as :maintenance
 
  sidekiq_options retry: 5
 
  def perform(account_id)
    # Aufräumlogik
  end
end

Das ist keine Empfehlung gegen ActiveJob. Im Gegenteil: Wer Sidekiq ActiveJob verwendet, sollte die jeweilige Verantwortung klar zuordnen. queue_as ist die passende Deklaration für die Queue. Sidekiq-spezifische Optionen müssen dagegen einzeln darauf geprüft werden, ob sie über den Adapter weiterhin greifen.

Tückisch ist, dass retry: weiter wirken kann. Eine Mischung aus funktionierenden und wirkungslosen sidekiq_options erzeugt ein falsches Sicherheitsgefühl: Der Job wird verarbeitet, Wiederholungen funktionieren, aber das Routing ist falsch.

Tests müssen die gepushte Payload prüfen

Ein Test wie expect { CleanupJob.perform_later(42) }.to have_enqueued_job reicht nicht aus. Er beweist nur, dass ActiveJob einen Auftrag angenommen hat. Er beweist weder den Queue-Namen noch die Form der Argumente nach dem Sidekiq-Adapter.

Ich würde einen Test ergänzen, der die tatsächlich gepushte Sidekiq-Payload auswertet. Das genaue Hilfswerkzeug hängt vom Test-Setup ab; relevant sind aber immer mindestens Queue und Argumente.

CleanupJob.perform_later(42)
 
payload = pushed_sidekiq_payloads.last
 
expect(payload["queue"]).to eq("maintenance")
expect(payload["args"].first["arguments"]).to eq([42])

Das Beispiel zeigt bewusst die Ebene, die zählt: nicht die Ruby-Klasse und nicht nur die ActiveJob-Test-Queue, sondern die Nachricht nach der Übersetzung für Sidekiq. Je nach Rails- und Test-Setup kann die Hilfsmethode anders heißen. Auch die Payload enthält zusätzliche Metadaten. Entscheidend ist die Prüfung des tatsächlichen Queue-Namens und der serialisierten Argumente.

retries_exhausted erhält andere Argumente

Besondere Aufmerksamkeit verdient sidekiq_retries_exhausted. Bei direkten Sidekiq-Workern enthält msg['args'] die ursprünglichen Argumente des Workers. Bei Sidekiq ActiveJob enthält msg['args'] stattdessen die ActiveJob-Payload als Hash.

Code, der etwa eine ID direkt aus msg['args'] liest, muss deshalb angepasst werden. Die ursprünglichen Argumente liegen innerhalb der ActiveJob-Struktur:

sidekiq_retries_exhausted do |msg, exception|
  job_payload = msg["args"].first
  account_id = job_payload["arguments"].first
 
  # Fehlerbehandlung mit account_id
end

Auch das sollte ein Test abdecken. Sonst fällt die Abweichung erst auf, wenn ein Retry endgültig erschöpft ist.

Sidekiq Capsules: Queue-Kapazität bewusst planen

Seit Sidekiq 7 können Sidekiq Capsules einer Queue eigene Threads geben. Das ist nützlich, wenn etwa Wartungs-Jobs getrennt von anderen Hintergrundaufgaben verarbeitet werden sollen. Das Thread-Limit gilt dabei pro Prozess und wird beim Boot gelesen.

Die Konsequenz ist praktisch: Eine Queue-Zuordnung ist nicht nur ein Label. Sie kann bestimmen, welche Verarbeitungskapazität ein Job erhält. Wenn ein Job wegen einer ignorierten sidekiq_options queue auf default landet, greifen auch die Überlegungen zu Sidekiq Capsules und getrennten Threads nicht wie geplant.

Mein Rat ist daher schlicht: Nach einer Migration nicht nur den Code vergleichen. Prüft die Redis-nahe Payload, den Queue-Namen, die Argumentstruktur und die Retry-Callbacks. Schaut nach, auf welcher Queue eure Jobs wirklich laufen.