Part 12: ガバナ制限の超過を防ぐエラーハンドリング

## **Part 12: ガバナ制限 (Governor Limits) の超過を防ぐエラーハンドリング**

前回は **プラットフォームイベントとスケジュールバッチのエラーハンドリング** を解説しました。  
今回は **Salesforce のガバナ制限 (Governor Limits) に関するエラーを防ぐ方法** について詳しく解説します。

---

## **1. ガバナ制限とは？**
Salesforce は **マルチテナント環境** で動作するため、1 つの組織がシステムリソースを独占しないように **「ガバナ制限 (Governor Limits)」** を設定しています。

例えば：
- **SOQL クエリ制限** → 1 トランザクションで 100 件まで
- **DML ステートメント制限** → 1 トランザクションで 150 件まで
- **CPU タイム制限** → 同期処理は最大 10 秒、非同期処理は最大 60 秒まで
- **ヒープサイズ制限** → 6MB (標準)、非同期処理は 12MB
- **コールアウト制限** → 1 回のトランザクションで最大 120 秒

これらの制限を超えると **LimitException** が発生し、処理が中断されます。

---

## **2. LimitException の発生を防ぐ方法**

### **2.1. SOQL クエリ制限 (System.QueryException)**
SOQL クエリの実行回数が **1 トランザクションあたり 100 件** を超えると **QueryException** が発生します。

#### ❌ **NG 例: ループ内で SOQL クエリを実行**
```apex
for (Account acc : Trigger.new) {
    Contact c = [SELECT Id FROM Contact WHERE AccountId = :acc.Id LIMIT 1];
}
```
**→ ガバナ制限エラー (System.LimitException: Too many SOQL queries: 101) が発生！**

✅ **OK 例: SOQL を 1 回だけ実行**
```apex
Map<Id, Contact> contactMap = new Map<Id, Contact>();
for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :Trigger.newMap.keySet()]) {
    contactMap.put(c.AccountId, c);
}

// ループ内で SOQL を実行しない
for (Account acc : Trigger.new) {
    Contact c = contactMap.get(acc.Id);
}
```
**→ SOQL クエリを 1 回だけ実行し、マップを活用することで制限を回避！**

> **適用ケース:**  
> **トリガー (Trigger) やバッチ処理で SOQL を実行する際、クエリ回数を削減したい場合**

---

### **2.2. DML 制限 (System.DmlException)**
DML (insert, update, delete, upsert) の実行回数が **1 トランザクションあたり 150 件** を超えると **DmlException** が発生します。

#### ❌ **NG 例: ループ内で DML を実行**
```apex
for (Account acc : accounts) {
    update acc;
}
```
**→ ガバナ制限エラー (System.LimitException: Too many DML statements: 151) が発生！**

✅ **OK 例: DML を 1 回だけ実行**
```apex
update accounts;
```
**→ DML を 1 回でまとめて実行 (バルク処理) することで制限を回避！**

> **適用ケース:**  
> **多数のレコードを更新する際に DML 制限を回避したい場合**

---

### **2.3. CPU タイム制限 (System.LimitException)**
同期処理では **最大 10 秒**、非同期処理では **最大 60 秒** を超えると **LimitException** が発生します。

#### ❌ **NG 例: 非効率な処理**
```apex
for (Integer i = 0; i < 1000000; i++) {
    System.debug('処理中: ' + i);
}
```
**→ 不要なループが多すぎると、CPU タイム制限に引っかかる！**

✅ **OK 例: 必要な処理だけ実行**
```apex
for (Integer i = 0; i < 1000000; i+=100000) { // 10万ごとにログ出力
    System.debug('処理中: ' + i);
}
```
**→ 不要な処理を削減し、CPU タイムを節約！**

> **適用ケース:**  
> **大量データ処理を行う場合に CPU タイム制限を回避したい場合**

---

### **2.4. ヒープサイズ制限 (System.LimitException)**
ヒープサイズの制限は **同期処理で 6MB、非同期処理で 12MB**。  
大量データを扱う場合、ヒープサイズを超えると **LimitException** が発生します。

#### ❌ **NG 例: 大量データをすべてメモリに保持**
```apex
List<Account> allAccounts = [SELECT Id, Name FROM Account];
```
**→ 全レコードを取得すると、ヒープサイズ制限を超える可能性あり！**

✅ **OK 例: バッチ処理を活用**
```apex
Database.QueryLocator query = Database.getQueryLocator(
    'SELECT Id, Name FROM Account'
);
```
**→ `Database.QueryLocator` を使うと、レコードを一括取得せずに処理できる！**

> **適用ケース:**  
> **大量のレコードを扱う際にヒープサイズ制限を回避したい場合**

---

### **2.5. コールアウト制限 (System.CalloutException)**
コールアウト (外部 API 呼び出し) の制限は **1 トランザクションあたり 120 秒**。

#### ❌ **NG 例: ループ内で API を呼び出す**
```apex
for (Account acc : accounts) {
    HttpRequest req = new HttpRequest();
    req.setEndpoint('https://api.example.com/data');
    req.setMethod('GET');
    HttpResponse res = new Http().send(req);
}
```
**→ ガバナ制限エラー (System.CalloutException: Callout loop detected) が発生！**

✅ **OK 例: コールアウトをバッチ処理に分割**
```apex
// コールアウトをバッチ処理で実行
public class CalloutBatch implements Database.Batchable<SObject>, Database.AllowsCallouts {
    public Iterable<SObject> start(Database.BatchableContext bc) {
        return [SELECT Id FROM Account LIMIT 100];
    }

    public void execute(Database.BatchableContext bc, List<SObject> scope) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://api.example.com/data');
        req.setMethod('GET');
        HttpResponse res = new Http().send(req);
    }

    public void finish(Database.BatchableContext bc) {}
}
```
**→ `Database.AllowsCallouts` を使ってバッチ処理でコールアウト！**

> **適用ケース:**  
> **API コールアウトを効率的に処理し、制限を回避したい場合**

---

## **まとめ**
今回は **ガバナ制限 (Governor Limits) を超えないためのエラーハンドリング** を解説しました。

✅ **SOQL クエリ制限**
- **ループ内で SOQL を実行せず、マップを活用する**

✅ **DML 制限**
- **DML を 1 回だけ実行 (バルク処理)**

✅ **CPU タイム制限**
- **不要なループや処理を減らす**

✅ **ヒープサイズ制限**
- **バッチ処理 (`Database.QueryLocator`) を活用**

✅ **コールアウト制限**
- **バッチ処理 (`Database.AllowsCallouts`) を使う**

> **次の Part 13** では、**例外をトラブルシューティングする方法 (デバッグ手法)** について解説します！