1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Salesforce Apexヒープ超過を防ぐ実装パターン|Winter '27の10MB/25MB後も必要な対策

1
Posted at

はじめに

Apexの単体テストは通るのに、本番の大量データで「Apex heap size too large」が出た、という経験はないでしょうか。

Winter '27で同期10MB・非同期25MBに上限が広がりましたが、ヒープ超過はコードの持ち方次第で今も起こります。
しかもこのエラーはtry-catchで拾えません。

この記事では、SOQL forループとBatch Apexによる実装の直し方と、サンドボックスで旧上限を再現して検証する手順をまとめます。

まず確認:ヒープ上限はいくつに変わった?

ヒープとは、Apexが処理の途中でメモリ上に持っておけるデータの置き場のことです。

Winter '27のリリースノートでは、次のように変わると説明されています。

  • 同期トランザクション:6MBから10MBへ
  • 非同期トランザクション:12MBから25MBへ

Winter '27のリリーススケジュールに合わせて、すべてのorgで自動的に有効になります。自分のorgの日程は、Trust Statusでインスタンスを検索して、メンテナンスのタブから確認できます。

いま自分のorgでどの値が効いているかは、次のメソッドで確認できます。

System.debug(Limits.getLimitHeapSize());

なお、Batch Apexやfuture、Queueableは非同期側の上限です。ちなみにスケジュールApexは非同期の仲間に見えますが、同期の上限が適用されると公式の制限一覧に書かれています。

つまり、「上限は増えたけれど、無限になったわけではない」という理解でOKです!

出典:Salesforceヘルプ(Winter '27リリースノート)

上限が上がってもエラーが出る、よくある原因

ヒープ超過の原因は、ほとんどが「メモリに乗せすぎ」です。公式ドキュメントの記述をもとに、代表的なパターンを整理します。

  1. 全件を一気にリストへ入れている

    Account[] accts = [SELECT Id FROM Account]; のように、大量の結果を1つの変数に受けると、結果が大きいときに実行時エラーになります。

  2. 使わない項目まで取得している

    必要な項目だけを SELECT に書くのが基本です。項目が多いほど、1件あたりのメモリが増えます。

  3. 処理が終わったコレクションを持ち続けている

    SOQL forループのようにバッチ単位で処理すると、リスト変数は200件分だけになり、バッチごとに作り直されます。公式のコード例でも、この点が説明されています。

  4. 大きなHTTPレスポンスを受け取っている

    コールアウトのリクエストとレスポンスのサイズは、ヒープにも数えられます。

もうひとつ大事なのが、ヒープ超過は try-catch で処理できないという点です。上限を超えると、拾えない System.LimitException が投げられます。「エラーを握りつぶして続行」という逃げ道はありません。

実務では、開発中のテストデータが数十件だったので気づかず、本番で数万件を流した瞬間に落ちるパターンが本当に多いです。

つまり、原因の大半は「一度に持つデータ量が多すぎる」ことだ、という理解でOKです!

出典:Apex開発者ガイド(実行時ガバナ制限)、Apex開発者ガイド(Running Apex within Governor Execution Limits)

直し方:SOQL forループとBatch Apexに切り替える

一番効くのは、SOQLの結果を「200件ずつ」処理する書き方への変更です。洗濯物を1枚ずつ洗うのではなく、数回に分けてまとめて洗うイメージです。

  1. エラーメッセージが指しているクラスとメソッドを特定します。
  2. 全件を受けているSOQLを、SOQL forループに書き換えます。
  3. SELECT の項目を、処理に必要なものだけに絞ります。
  4. ループの中で更新まで行う場合は、リスト形式のforループを使います。
  5. 更新対象が1万件を超える処理(DMLで処理できるレコード数の上限)や、取得が5万件を超える処理(SOQLの取得行数の上限)は、QueryLocatorを使ったBatch Apexに切り替えます。

まず、修正前と修正後の書き方です。

// 修正前:結果が大きいとヒープを使い切る
List<Account> accts = [SELECT Id, Name FROM Account WHERE Name LIKE 'Acme%'];

// 修正後:200件ずつ処理する(DMLをループ内で行う場合はこの形)
for (List<Account> batch : [SELECT Id, Name FROM Account WHERE Name LIKE 'Acme%']) {
    for (Account a : batch) {
        // ここで処理
    }
    update batch;
}

SOQL forループは、内部でqueryとqueryMoreを呼び出して、複数のバッチに分けて処理します。ただし、守れるのはヒープだけです。SOQLで取得できるレコードは合計5万件まで、DMLで処理できるレコードは合計1万件まで、DML文は150回までと、他の制限は別に数えられます。
リスト形式のループ内でupdateすると、200件ごとにDML文が1回使われます。大量レコードの一括更新には、Batch Apexが推奨されています。Batch Apexでは、これらの制限がexecuteメソッドの実行ごとにリセットされます。

つまり、「一度に持つ量を減らす」のが基本で、規模が大きければBatch Apexに任せる、という理解でOKです!

出典:Apex開発者ガイド(大量のSOQLクエリの扱い)、Apex開発者ガイド(SOQL For Loops)、Apex開発者ガイド(実行時ガバナ制限)

サンドボックスで旧上限を再現して確認する

もうひとつ、地味に大事な手順があります。Winter '27のサンドボックスは新しい上限で動きますが、本番がまだSummer '26のままだと、本番だけ旧上限(6MB/12MB)で動きます。サンドボックスで通ったコードが本番で落ちる、という事故が起こり得ます。

これを防ぐために、非本番orgには旧上限に戻すための設定が用意されています。

  1. 設定を開きます。
  2. クイック検索に「Apex 設定」と入力して、表示された項目を選びます。
  3. 「Summer '26 の Apex ヒープ制限を適用」をオンにします。
  4. 大量データのテストを流して、旧上限でも成功するかを確認します。

この設定が使えるのは、サンドボックス、Developer Edition、スクラッチ組織といった本番以外のorgです。リリースノートには、旧上限へ「一時的に」制限できる設定と書かれています。恒常的な切り替えではないので、移行期間の検証用と考えてください。

つまり、「本番のバージョンに合わせて、サンドボックス側で上限を戻して試す」という理解でOKです!

ここは注意!つまずきポイント

  • 本番が未アップグレードのタイミングに注意です。 サンドボックスだけ先にWinter '27になっている期間は、新上限で通ったコードが本番の旧上限で落ちることがあります。デプロイ前に、上の設定で旧上限を試しておくと安心です。
  • ヒープ超過は拾えません。 try-catch では処理できないので、そもそも超えない設計にするしかありません。
  • ヒープ確認用のデバッグ文は、調査が終わったら消してください。 Limits.getHeapSize() をデバッグ文に残したままにすると、不要な処理が本番に残ります。コメントアウトしておきましょう。
  • ヒープとCPUは関連しています。 公式の制限一覧の注記に、ヒープとCPUの両方が上限に近いコードは、ヒープサイズの計算コストでCPU制限に当たりやすくなる可能性がある、と書かれています。「上限いっぱいまで使う設計」は避けた方が良さそうです。(出典:Apex開発者ガイド(実行時ガバナ制限)
  • SOQL forループにも制約があります。 集計関数を含むクエリはqueryMoreに対応していません。また、200件以上の子レコードをループ内で扱うと、例外になる場合があります。(出典:Apex開発者ガイド(SOQL For Loops))
  • コールアウトのサイズは別の行に書かれています。 同じ制限一覧のページには、コールアウトのリクエスト・レスポンスサイズの上限が6MB(同期)と12MB(非同期)と記載されています。そのうえで、これらのサイズはヒープにも数えられます。
  • 追加費用について。 リリースノート上では、Apexが動くすべてのエディションが対象と書かれています。追加のアドオン購入が必要という記載はありませんでした。

実務で使うなら、ヒープの上限は「目標」ではなく「保険」だと考えておいた方が、あとで泣かずに済みます。

ヒープの次に確認:CPU時間とガバナ制限

ヒープ超過を直したあとに、CPU時間やDML・SOQLの上限で別のエラーが出ることがあります。主な上限は次のとおりです(Winter '27時点の公式の制限一覧より、同期/非同期の順)。

  • ヒープ:10MB / 25MB
  • CPU時間:10,000ミリ秒 / 60,000ミリ秒
  • SOQLで取得できる合計レコード数:5万件(どちらも同じ)
  • DMLで処理できる合計レコード数:1万件(どちらも同じ)
  • DML文の回数:150回(どちらも同じ)
  • SOQLクエリの回数:100回 / 200回

CPU時間は、DB内での待ち時間やコールアウトの待ち時間は数えられません。一方、DMLで使うアプリケーションサーバ側のCPU時間は数えられます。Batch Apexでは、これらの制限がexecuteメソッドの実行ごとにリセットされます。

症状別の切り分けは、次のとおりです。

  1. ヒープ超過なら、SOQL forループで一度に持つ量を減らします。
  2. CPU時間の超過なら、Limits.getCpuTime() で消費量を測り、どの処理が重いかを切り分けます。
  3. 取得5万件・DML1万件の超過なら、Batch Apexに切り替えます。QueryLocatorなら、最大5,000万件のレコードを扱えます。
  4. Batch Apex以外で Database.getQueryLocator を使うと、取得できるのは合計1万件までです。

測定用のデバッグ文は、調査が終わったら消しておきましょう。

まとめ

  • Winter '27で、Apexのヒープ上限は同期10MB・非同期25MBに拡大されました。
  • 上限が増えても、全件をリストに受ける書き方はエラーの原因になり続けます。
  • 直し方は、SOQL forループで200件ずつ処理することと、更新が1万件超・取得が5万件超のような大量処理は Batch Apex に任せることです。
  • サンドボックスでは「Enforce the Summer '26 Apex heap limit」で旧上限を再現して、本番との差を確認しておきましょう。

ヒープ制限は地味ですが、一度ハマると原因探しが大変です。これを知っているといないとでは検証スピードも変わってくると思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?