`in_batches`で`use_ranges`とlimitの組み合わせ時に負のLIMITが発生する不具合を修正

rails/rails

Active Recordのin_batchesメソッドでuse_ranges: trueとlimitを組み合わせた際、limitがバッチサイズの倍数でない場合に「LIMIT must not be negative」エラーが発生する不具合が修正されました。この問題は、バッチ処理の残件数管理に起因していました。

背景

in_batchesは大量のレコードを一定のサイズごとに分割して処理するメソッドです。use_rangesオプションは、効率的なバッチ処理のために範囲ベースのクエリを使用する機能です。しかし、このオプションとlimitを組み合わせた際、特定の条件下でデータベースエラーが発生していました。

#56819で報告された問題では、以下のようなコードでエラーが発生します:

# データベースに11件のPostレコードが存在する場合
Post.limit(5).in_batches(of: 3, use_ranges: true) { |batch| batch.count }
# => PG::InvalidRowCountInLimitClause: ERROR: LIMIT must not be negative

このコードは、5件のレコードを3件ずつのバッチで処理しようとしています(1回目に3件、2回目に2件)。しかし、実際には2回目の処理前に負のLIMIT値が設定され、PostgreSQLなどのデータベースがエラーを返していました。

技術的な変更

activerecord/lib/active_record/relation/batches.rbbatch_on_unloaded_relationメソッド内で、values_sizeの計算ロジックが修正されました。

変更前:

values_size = batch_limit
values_last = batch_relation.offset(batch_limit - 1).pick(*cursor)

変更後:

values_size = remaining ? [batch_limit, remaining].min : batch_limit
values_last = batch_relation.offset(values_size - 1).pick(*cursor)

変更前のコードでは、values_sizeは常にbatch_limit(元のバッチサイズ)に設定されていました。しかし、残件数がbatch_limitより少ない場合でも、この値が更新されずに残っていました。そのため、次のイテレーションでremaining -= values_sizeを実行すると、実際に処理された件数より大きな値が減算され、remainingが負になっていました。

修正後は、values_sizeremainingbatch_limitの小さい方に設定することで、常に実際の処理件数を反映するようになりました。これにより、remainingの計算が正確になり、負のLIMIT値が設定されることがなくなりました。

問題の発生メカニズム

不具合の発生メカニズムを具体例で説明します。Post.limit(5).in_batches(of: 3, use_ranges: true)の場合:

1回目のイテレーション:
- remaining = 5
- batch_limit = 3
- relation.limit(5)でクエリ実行
- values_size = 3(修正前)
- remaining -= 3remaining = 2

2回目のイテレーション:
- remaining = 2batch_limit = 3より小さい)
- relation = relation.limit(2)に更新される
- しかしbatch_limitは3のまま
- values_size = 3(修正前、実際には2件しか取得されない)
- remaining -= 3remaining = -1

3回目のイテレーション:
- remaining = -1
- relation.limit(-1)が実行され、データベースエラー発生

修正後は、2回目のイテレーションでvalues_size = min(3, 2) = 2となるため、remaining -= 2remaining = 0となり、3回目のイテレーションは発生しません。

設計判断

values_sizeの計算タイミングでの修正という最小限の変更が採用されました。

この問題は、use_rangesコードパスにおいてvalues_sizeが実際の取得件数ではなく想定バッチサイズに固定されていたことが原因でした。修正では、remainingが存在する場合(limitが設定されている場合)に限り、batch_limitremainingの小さい方を使用するよう変更されています。

offsetの計算でも同じvalues_sizeが使用されるため、この修正は二重の効果を持ちます。正確なvalues_sizeにより、次のバッチの開始位置を示すvalues_lastの取得も正しく行われるようになりました。

limitが設定されていない場合(remainingnilの場合)は従来通りbatch_limitを使用する条件分岐により、既存の動作への影響を最小限に抑えています。

まとめ

本PRは、in_batchesuse_rangesオプション使用時に、limitとバッチサイズの組み合わせによって発生していた負のLIMIT値の問題を修正しました。values_sizeの計算ロジックに1行の変更を加えることで、残件数の管理を正確にし、limitが設定されたバッチ処理を安全に実行できるようになりました。use_rangesオプションを使用するアプリケーションにとって重要な修正です。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「総論(リード文)→各論(背景、技術的変更、設計判断)→結論(まとめ)」という3部構成が明確に適用されており、読者が理解しやすい構成になっています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きのシンタックスハイライト(```ruby:path/to/file.rb)や、PR・Issue番号のリンク記法([#123](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Active Recordの内部実装に関するトピックであり、専門知識を持つエンジニアという対象読者に適した技術レベルと表現で書かれています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが要旨から詳細へという流れで構成され、各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、可読性が高いです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードは、提供されたDiff情報と完全に一致しており、変更点が正確に反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`in_batches`, `use_ranges`, `batch_limit`などの技術用語が、文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

不具合の発生メカニズムと修正内容に関する説明は、PRのDescriptionとDiffコードによって裏付けられており、技術的に正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(問題の背景、再現コード、修正ロジック)は、提供されたPR情報に基づいており、ハルシネーション(捏造)は見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#56803)、Issue番号(#56819)、コード例内の数値(limit(5), of: 3)など、すべての数値・固有名詞が正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルは、PRのタイトル「Fix in_batches use_ranges with limit producing negative LIMIT」の内容を忠実に反映しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事の内容はすべてPR情報に基づいており、バージョンサポート状況やリリース日程など、PRに記載のない外部知識の追加はありません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

記事内には時間表現の歪曲はなく、事実関係が正確に記述されています。