当ブログではアフィリエイト広告を利用しています

Salesforce基礎

Salesforce ガバナ制限

はじめに

Salesforce開発の世界では、ガバナ制限を理解することは単なる利点ではなく、必須事項です。

ガバナ制限とは、Salesforceがマルチテナントアーキテクチャの安定性とパフォーマンスを確保するために強制する実行境界です。これらの制限により、単一の組織が共有リソースを独占し、他のユーザーのエクスペリエンスを低下させることを防いでいます。

Salesforce開発者として、開発中にこれらの制限に必ず遭遇することになります。制限に達すると、診断や修正が難しいランタイム例外が発生し、フラストレーションの原因となることがあります。この記事では、Salesforceのガバナ制限について包括的な概要を提供し、それらが何であるか、なぜ存在するのか、そして最も重要なこととして、どのように効果的に対処するかを説明します。

なぜSalesforceにガバナ制限があるのか

Salesforceはマルチテナントアーキテクチャで運用されており、複数の組織が同じインフラストラクチャ、データベース、計算リソースを共有しています。このアーキテクチャには以下のような多くの利点があります:

  1. コスト効率: リソースが顧客間で共有されるため、全体のコストが削減されます。
  2. 自動アップグレード: すべての顧客が中断なく同時に更新を受け取ります。
  3. スケーラビリティ: Salesforceは必要に応じてリソース間で負荷を分散できます。

単一の組織が過剰なリソースを消費することを防ぐため、Salesforceはガバナ制限を実装しました。これらの制限により、リソースの公平な分配とシステムの安定性およびパフォーマンスが維持されます。

ガバナ制限のカテゴリ

ガバナ制限はいくつかのタイプに大きく分類できます:

1. Apex実行制限

これらの制限は、API要求、Visualforceページ、Lightningコンポーネント、またはその他のソースによってトリガーされるかどうかにかかわらず、Apexコードの実行に適用されます。

※ガバナ制限の閾値についてはSalesforceの組織やライセンス数により変わるものがあります。またバージョンアップなどにより変更される場合があるため、最新の公式HPよりご確認ください。

トランザクションごとの実行制限

制限タイプ 同期制限 非同期制限
合計ヒープサイズ 6MB 12MB
CPU時間 10,000ms 60,000ms
SOQLクエリ 100 200
SOQLで取得されるレコード 50,000 50,000
SOSLクエリ 20 20
DMLステートメント 150 150
DMLで処理されるレコード 10,000 10,000
コールアウト 100 100

2. API制限

APIリクエストには独自の制限セットがあります:

  • APIリクエスト制限: Salesforceエディションとライセンス数に基づく
  • 同時APIリクエスト制限: 長時間実行APIリクエストの最大同時数
  • APIバッチサイズ: API操作あたり最大2,000レコード

3. ストレージ制限

  • データストレージ: エディションとユーザーライセンスによって異なる(通常、ユーザーあたり10MBから始まる)
  • ファイルストレージ: エディションとユーザーライセンスによって異なる(通常、2GBから始まる)
  • ビッグオブジェクトストレージ: エディションによって制限され、EnterpriseおよびUnlimitedエディションでは制限が高い

4. VisualforceとLightning制限

  • ビューステートサイズ: Visualforceページの最大135KB
  • Lightningコンポーネントイベントサイズ: 最大1MB
  • Lightningデータサービス制限: ページあたりメモリ内で100,000レコード

トランザクション境界の理解

把握すべき重要な概念は、Salesforceにおける「トランザクション」です。トランザクションは、操作(ボタンのクリックなど)が開始されると始まり、その操作に関連するすべての処理が完了すると終了します。このトランザクション中、すべてのガバナ制限が追跡され、強制されます。

トランザクションには以下のタイプがあります:

  1. ユーザーインターフェーストランザクション: UI(ボタンクリック、フォーム送信)を通じて開始
  2. APIトランザクション: API呼び出しを通じて開始
  3. データベーストリガー: DML操作によって発火
  4. 非同期プロセス: バッチApex、スケジュールされたジョブ、またはQueueable Apexなど

各トランザクションタイプには異なるガバナ制限がある場合があります。たとえば、非同期プロセスは通常、同期プロセスよりもCPU時間とヒープサイズの制限が高くなっています。

一般的なガバナ制限例外

コードがガバナ制限を超えると、Salesforceは特定の例外をスローします。遭遇する可能性のある一般的な例外は次のとおりです:

  1. System.LimitException: さまざまなガバナ制限違反に対する一般的な例外
  2. System.QueryException: SOQLクエリ制限を超えた場合にスロー
  3. System.DmlException: DML操作制限を超えた場合にスロー
  4. System.CalloutException: コールアウト制限を超えた場合にスロー

ガバナ制限内で作業するための戦略

効率的なSOQLクエリ

SOQL(Salesforce Object Query Language)クエリは、開発者が制限に達する最も一般的な領域の1つです。以下にいくつかのベストプラクティスを紹介します:

1. ループ内でのSOQLを避ける

// 悪い例
for (Account acc : accountList) {
    List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
    // コンタクトを処理
}

// 良い例
Map<Id, List<Contact>> contactsByAccountId = new Map<Id, List<Contact>>();
for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
    if (!contactsByAccountId.containsKey(c.AccountId)) {
        contactsByAccountId.put(c.AccountId, new List<Contact>());
    }
    contactsByAccountId.get(c.AccountId).add(c);
}

2. 選択的クエリを使用する

WHERE句には常に選択的なフィルターを含めてください。選択的でないクエリは、過剰なCPU使用量とタイムアウトにつながる可能性があります。

// 選択的でない(Email__cがインデックス化されていないと仮定)
List<Contact> contacts = [SELECT Id FROM Contact WHERE Email__c = 'test@example.com'];

// 選択的(IdとNameがインデックス化されていると仮定)
List<Contact> contacts = [SELECT Id FROM Contact WHERE Id = '0031t00000ABCDE' OR Name = 'John Doe'];

3. 必要なフィールドのみを取得する

ヒープサイズの使用を最小限に抑えるために、実際に必要なフィールドのみをクエリしてください。

// 不要なフィールドを取得
List<Account> accounts = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry, ShippingStreet, ShippingCity, ShippingState, ShippingPostalCode, ShippingCountry FROM Account];

// より良いアプローチ
List<Account> accounts = [SELECT Id, Name FROM Account];

DML操作の最適化

データベース操作にも厳格な制限があります。最適化方法は以下の通りです:

1. 一括処理

個別にではなく、常にレコードを一括で処理します。

// 非効率 - レコードごとに1つのDMLステートメント
for (Account acc : accountList) {
    update acc;
}

// 効率的 - すべてのレコードに対して1つのDMLステートメント
update accountList;

2. クロスオブジェクトトランザクションにプラットフォームイベントを使用する

複数のオブジェクトにまたがってレコードを更新する必要がある場合は、プラットフォームイベントを使用してトランザクションを分割することを検討してください。

// 1つのトランザクションで複数のオブジェクトを更新する代わりに
public void processAccounts(List<Account> accounts) {
    // アカウントを処理
    update accounts;
    
    List<Contact> contactsToUpdate = new List<Contact>();
    // 関連するコンタクトを取得して更新
    update contactsToUpdate;
    
    List<Opportunity> oppsToUpdate = new List<Opportunity>();
    // 関連する商談を取得して更新
    update oppsToUpdate;
}

// プラットフォームイベントを使用して複数のトランザクションに分割
public void processAccounts(List<Account> accounts) {
    // アカウントを処理
    update accounts;
    
    // コンタクト処理のためのイベントを発行
    AccountProcessedEvent__e event = new AccountProcessedEvent__e(AccountIds__c = JSON.serialize(accountIds));
    EventBus.publish(event);
}

// イベントトリガーハンドラー
public void handleAccountProcessedEvent(List<AccountProcessedEvent__e> events) {
    // コンタクトを処理
    // 商談を処理
}

コールアウトの管理

外部コールアウトは、制限に簡単に達する可能性のある別の領域です:

1. コールアウトを最小限に抑える

可能な場合は、複数のコールアウトを単一のリクエストにまとめます。

2. 非同期コールアウトを使用する

緊急ではないコールアウトには、非同期アプローチを使用します:

@future(callout=true)
public static void makeCalloutAsync(String payload) {
    // HTTPコールアウトを行う
    Http http = new Http();
    HttpRequest request = new HttpRequest();
    // リクエストプロパティを設定
    HttpResponse response = http.send(request);
    // レスポンスを処理
}

3. 複雑なコールアウトシナリオにはQueueable Apexを実装する

public class CalloutQueueable implements Queueable, Database.AllowsCallouts {
    private List<Id> recordIds;
    
    public CalloutQueueable(List<Id> recordIds) {
        this.recordIds = recordIds;
    }
    
    public void execute(QueueableContext context) {
        // レコードのコールアウトを行う
        // バッチサイズまで処理
        
        // 処理するレコードが残っている場合、別のジョブをチェーン
        if (hasMoreRecords) {
            System.enqueueJob(new CalloutQueueable(remainingIds));
        }
    }
}

フローチャート:大量データ処理の意思決定プロセス


ガバナ制限のモニタリングとデバッグ

Limitsメソッドの使用

Apexは、現在の使用状況を制限と照らし合わせて確認できるLimitsクラスを提供しています:

public void monitorLimits() {
    System.debug('SOQLクエリ: ' + Limits.getQueries() + '/' + Limits.getLimitQueries());
    System.debug('DMLステートメント: ' + Limits.getDmlStatements() + '/' + Limits.getLimitDmlStatements());
    System.debug('CPU時間: ' + Limits.getCpuTime() + '/' + Limits.getLimitCpuTime());
    System.debug('ヒープサイズ: ' + Limits.getHeapSize() + '/' + Limits.getLimitHeapSize());
}

デバッグログ

デバッグログを使用して、コードのどの部分が最もリソースを消費しているかを特定します:

  1. ユーザーのデバッグログを設定する
  2. 問題のある操作を実行する
  3. ログを確認して、実行時間やリソース使用量が多い領域を見つける

プロファイリングツール

Salesforceはコードのプロファイリングのためのいくつかのツールを提供しています:

  1. Developer Console: 「実行概要」と「タイムライン」を使用してボトルネックを特定
  2. Apex Flex Query: 非効率的なSOQLクエリを特定
  3. ISV Customer Debugger: ISVが顧客組織でデバッグするためのツール

高度なテクニック

遅延ロード

必要になるまでリソース消費を遅らせるための遅延ロードパターンを実装します:

public class ContactService {
    private Map<Id, List<Contact>> contactsByAccountId;
    
    public List<Contact> getContactsForAccount(Id accountId) {
        if (contactsByAccountId == null) {
            loadContacts();
        }
        return contactsByAccountId.containsKey(accountId) ? contactsByAccountId.get(accountId) : new List<Contact>();
    }
    
    private void loadContacts() {
        contactsByAccountId = new Map<Id, List<Contact>>();
        for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
            if (!contactsByAccountId.containsKey(c.AccountId)) {
                contactsByAccountId.put(c.AccountId, new List<Contact>());
            }
            contactsByAccountId.get(c.AccountId).add(c);
        }
    }
}

ステート保持設計パターン

バッチ処理のためのステート保持パターンを実装します:

public class StatefulBatchExample implements Database.Batchable<SObject>, Database.Stateful {
    private Integer recordsProcessed = 0;
    private Set<Id> processedIds = new Set<Id>();
    
    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator('SELECT Id FROM Account');
    }
    
    public void execute(Database.BatchableContext bc, List<SObject> scope) {
        // レコードを処理
        recordsProcessed += scope.size();
        
        for (SObject obj : scope) {
            processedIds.add(obj.Id);
        }
    }
    
    public void finish(Database.BatchableContext bc) {
        System.debug('処理された合計レコード数: ' + recordsProcessed);
        System.debug('処理されたユニークID数: ' + processedIds.size());
    }
}

大規模データセットのチャンキング

大規模データセットの操作では、チャンキングを実装します:

public void processLargeDataSet(List<Account> accounts) {
    Integer chunkSize = 100; // 操作の複雑さに基づいて調整
    
    for (Integer i = 0; i < accounts.size(); i += chunkSize) {
        Integer endIndex = Math.min(i + chunkSize, accounts.size());
        List<Account> chunk = accounts.subList(i, endIndex);
        
        // このチャンクを処理
        processAccountChunk(chunk);
    }
}

ガバナ制限の最近の強化

Salesforceは定期的にガバナ制限を更新し、時には緩和します。最近の強化には以下が含まれます:

  1. ヒープサイズの増加: 非同期操作の場合、6MBから12MBへ
  2. SOQLループの強化: クエリカーソルの処理の改善
  3. CPU制限の緩和: 特定のLightning操作向け
  4. トランザクションファイナライザ: Queueableジョブ後の後処理を扱うため

Salesforceのリリースノートで、ガバナ制限の最新の変更に注目してください。

結論

ガバナ制限は、マルチテナント環境での公平なリソース分配を確保するSalesforceプラットフォームの不可欠な部分です。これらは課題を提示することがありますが、これらの制限を理解し、ベストプラクティスを実装することで、プラットフォーム上で効率的でスケーラブルなアプリケーションを構築できます。

Salesforce開発者のための重要なポイント:

  1. 制限を理解する: コードに適用される制限を知る。
  2. 一括処理を設計する: コードが複数のレコードを処理することを常に想定する。
  3. 使用状況を監視する: Limitsクラスを使用して消費量を追跡する。
  4. 非同期パターンを実装する: バッチApex、Queueable Apex、プラットフォームイベントを使用して処理を分散する。
  5. クエリを最適化する: 選択的なSOQLクエリを作成し、不要なフィールドのクエリを避ける。
  6. 最新情報を入手する: リリースノートでガバナ制限の変更を追跡する。

これらのガイドラインに従うことで、Salesforceのガバナ制限内で効果的に作業し、ユーザーに堅牢でパフォーマンスの高いアプリケーションを提供できます。

参考URL

-Salesforce基礎
-,