Zum Inhalt springen
Projekt besprechen

16. Oktober 2026

Rails find_each sortiert nach ID – nicht nach deinem order

RailsActiveRecordRubyEngineering

Was find_each tatsächlich macht

find_each ist für große Mengen gedacht. Statt alle Datensätze auf einmal zu laden, ruft ActiveRecord sie in Batches ab und übergibt sie einzeln an den Block. Die Reihenfolge ist dabei standardmäßig aufsteigend nach dem Primärschlüssel.

User.find_each do |user|
  process(user)
end

Das ist nicht nur ein Detail der Implementierung. Es bestimmt, welcher Datensatz zuerst verarbeitet wird und wie ActiveRecord den nächsten Batch findet.

Dasselbe Prinzip gilt für in_batches. Der Unterschied liegt vor allem in der Übergabe: find_each liefert einzelne Modelle, in_batches liefert Relationen beziehungsweise Batch-Mengen.

User.in_batches do |batch|
  batch.update_all(active: true)
end

Beide gehören zu ActiveRecord Batches und folgen einer stabilen Cursor-Logik über den Primärschlüssel.

Warum find_each order nicht bedeutet, was es nahelegt

Dieser Scope sieht auf den ersten Blick plausibel aus:

User.order(created_at: :desc).find_each do |user|
  process(user)
end

Die Erwartung wäre: zuerst die neuesten Nutzer verarbeiten. Das passiert so nicht. Das order aus dem Scope wird von find_each nicht als fachliche Sortierung verwendet. Die Verarbeitung läuft weiterhin über die Primärschlüssel-Reihenfolge.

Aktuelle Rails-Versionen machen auf diese Konfliktsituation aufmerksam. Je nach Konfiguration erscheint eine Warnung oder ActiveRecord löst einen Fehler aus. Beides soll verhindern, dass ein vorhandenes order fälschlich als wirksam verstanden wird.

Das betrifft nicht nur created_at. Auch diese Varianten liefern mit find_each nicht die erwartete fachliche Reihenfolge:

Invoice.order(due_date: :asc).find_each
Article.order(published_at: :desc).in_batches
Task.order(priority: :desc).find_each

Der technische Grund: Batches brauchen einen stabilen Cursor

Batch-Verarbeitung muss nach einem abgearbeiteten Bereich zuverlässig fortsetzen können. Vereinfacht läuft das Muster so:

WHERE id > letzte_verarbeitete_id
ORDER BY id ASC
LIMIT batch_groesse

Nach einem Batch kennt ActiveRecord die letzte ID und kann den nächsten Bereich laden. Eine Sortierung nach einem veränderlichen oder nicht eindeutigen Fachattribut wäre dafür ungeeignet oder zumindest deutlich komplizierter.

Angenommen, es würde ausschließlich nach created_at sortiert: Mehrere Datensätze können denselben Zeitstempel haben. Außerdem kann sich ein Sortierwert während der Verarbeitung ändern. Ohne zusätzliche Regeln drohen Lücken oder doppelte Verarbeitung.

Darum priorisiert Rails bei find_each und in_batches die stabile Batch-Reihenfolge gegenüber einem order im Scope.

Die praktische Folge: Neueste zuerst funktioniert nicht automatisch

Besonders kritisch ist das bei Hintergrundjobs. Ein Job kann fachlich verlangen, dass aktuelle Datensätze zuerst behandelt werden, etwa bei einer Warteschlange, einer Priorisierung oder einer begrenzten Nachverarbeitung.

Wer dafür Folgendes schreibt, erhält nicht automatisch eine Verarbeitung vom neuesten zum ältesten Datensatz:

Event.order(created_at: :desc).find_each do |event|
  handle(event)
end

Die Verarbeitung kann technisch korrekt sein und trotzdem fachlich die falsche Reihenfolge haben. Das ist schwer zu bemerken, wenn der Job nur Nebenwirkungen erzeugt und keine explizite Reihenfolge protokolliert.

Welche Alternative passt

Die einfachste Lösung ist oft: Die Reihenfolge tatsächlich egal machen. Viele Datenmigrationen, Reindexierungen oder idempotente Aktualisierungen brauchen keine fachliche Sortierung. Dann ist find_each passend und effizient.

Falls nur die Richtung der Primärschlüssel-Reihenfolge relevant ist, kann sie explizit gesetzt werden:

User.find_each(order: :desc) do |user|
  process(user)
end

Auch dann gilt: Sortiert wird nach dem Primärschlüssel, nur absteigend statt aufsteigend. Das ist keine Sortierung nach created_at, Status oder Priorität.

Wenn eine fachliche Reihenfolge zwingend ist, müssen die Datensätze anders geladen werden. Für eine begrenzte Menge kann ein normales order mit limit ausreichen:

Event.order(created_at: :desc).limit(100).each do |event|
  handle(event)
end

Für größere Mengen ist Keyset-Pagination eine typische Option. Dabei basiert der Cursor auf den Sortierspalten, etwa Zeitstempel und ID als eindeutiger Tie-Breaker. Die Abfragen und Randfälle müssen dann bewusst zur gewählten Sortierung passen.

Rails find_each ist damit kein allgemeines Werkzeug für beliebige Sortierungen, sondern ein Werkzeug für robuste Primärschlüssel-Batches. Genau diese Unterscheidung sollte bei find_each order und bei in_batches sichtbar sein.

Prüft, wo in eurer Codebase find_each mit order kombiniert ist, und ob die Reihenfolge wirklich egal ist.