Salesforce データエンジニアのための包括的アーカイブ戦略:パフォーマンスとコンプライアンスの最適化

概要とビジネスシーン

Salesforceにおけるデータアーカイブ戦略は、増え続けるデータの効果的な管理、システムパフォーマンスの維持、ストレージコストの削減、そして法規制やコンプライアンス要件への対応を可能にする重要な取り組みです。データのライフサイクル管理の一環として、アクセス頻度が低い、あるいは古くなったデータを適切にアーカイブすることで、アクティブなSalesforce環境の健全性を保ちます。

実際のビジネスシーン

シーンA:金融業界 - ある大手銀行では、顧客の取引履歴や連絡履歴が日々数百万件発生し、過去5年分のデータをSalesforceに保持していました。これにより、レポート生成に時間がかかり、検索パフォーマンスが著しく低下し、年間数万ドルのストレージコストが発生していました。

  • ビジネス課題:レポート遅延、検索性能低下、高額なストレージコスト。
  • ソリューション:古くなった取引履歴と連絡履歴を特定し、Heroku Postgres を利用した外部データベースにアーカイブ。Salesforce上では主要な参照情報のみを保持し、必要に応じて Salesforce Connect を介して外部データにアクセスできるように設定。
  • 定量的効果:レポート生成時間が50%短縮され、Salesforce検索性能が30%向上。年間ストレージコストも25%削減。

シーンB:医療業界 - ある総合病院では、患者の診療記録、検査結果、同意書などの機密性の高い医療データがSalesforce上に蓄積されていました。これらのデータは法律により10年間の保存義務がありましたが、頻繁にアクセスされることはありませんでした。監査対応時の一時的なデータ検索にも時間がかかり、データの機密性保持も課題でした。

  • ビジネス課題:長期保存義務、監査時のデータ検索効率の悪さ、データセキュリティとコンプライアンス。
  • ソリューション:過去3年以上の診療記録を Salesforce Big Objects にアーカイブ。Big Objects には非同期 SOQL(Async SOQL)でアクセス可能で、Salesforce内で直接長期データをクエリできる環境を構築。機密データは暗号化し、アクセス権限を厳格に管理。
  • 定量的効果:監査対応時間が20%短縮され、コンプライアンス違反のリスクを大幅に低減。データセキュリティ対策も強化。

シーンC:製造業 - 大手自動車部品メーカーでは、IoTデバイスから収集されるセンサーデータや製品の品質管理データがSalesforceケースオブジェクトに関連付けられていました。これらのデータは日々膨大に増加し、半年経過したデータはあまり参照されませんが、製品保証や品質分析のために長期保存が必要でした。

  • ビジネス課題:IoTデータの爆発的増加によるパフォーマンス低下、ストレージ超過、リアルタイム分析と長期保存の分離。
  • ソリューション:半年以上前のセンサーデータおよび品質管理データをAWS S3に定期的にアーカイブ。S3へのデータ転送はApex BatchとAWS SDKを利用したHTTP Calloutで自動化。Salesforce上からはカスタムコンポーネント経由でS3上のデータを参照できるように設定。
  • 定量的効果:Salesforceのシステム応答性が25%向上し、年間ストレージコストが15%削減。長期的な品質分析のためのデータ活用も容易に。

技術原理とアーキテクチャ

Salesforceにおけるデータアーカイブの基本的な動作メカニズムは、主に「データの特定」「データの移動またはコピー」「元データの削除」の3つのフェーズで構成されます。これにより、Salesforceのデータベースから不要なデータを取り除き、別の保管場所に移動させることで、アクティブな環境のリソースを解放します。

主要コンポーネントとしては、Salesforce標準のBig Objects、外部データベース(Heroku Postgres, AWS RDSなど)、クラウドストレージ(AWS S3, Azure Blob Storageなど)が挙げられます。これらをSalesforceのAPI(Apex Batch, HTTP Callout, Bulk APIなど)や統合プラットフォーム(MuleSoft, Informatica)と連携させてアーカイブプロセスを構築します。

データフローの概要

フェーズ 処理内容 使用技術例 説明
1. データの特定 アーカイブ対象レコードのクエリ SOQL, カスタムロジック 定義された基準(例:作成日が1年以上前、ステータスが「完了」)に基づいて、アーカイブすべきレコードを効率的に検索します。
2. データの移動/コピー Salesforceから外部ストレージへデータ転送 Apex Callout, Heroku Connect, MuleSoft, Informatica, Bulk API 特定されたデータを外部のデータベース、Big Objects、またはクラウドストレージサービスに安全かつ効率的に転送します。
3. データの検証 (オプション) 外部ストレージへの保存確認 データ比較, チェックサム 転送されたデータが外部ストレージに正しく保存されていることを確認します。
4. 元データの削除 Salesforceからのレコード削除 Apex DML, Data Loader, Bulk API 外部ストレージへの転送と検証が完了した後、Salesforceから元のレコードを削除し、ストレージを解放します。
5. 参照管理 (オプション) 外部データへのリンク/参照の保持 Salesforce Connect (外部オブジェクト), カスタムオブジェクトのリンクフィールド アーカイブされたデータへの参照をSalesforce内に保持し、必要に応じてユーザーがアクセスできるようにします。

ソリューション比較と選定

Salesforceのアーカイブ戦略を検討する際、ビジネス要件と技術的制約に基づいて最適なソリューションを選択することが重要です。ここでは、主要なアーカイブアプローチを比較します。

ソリューション 適用シーン パフォーマンス Governor Limits 複雑度
Salesforce Big Objects Salesforce内で大量の履歴データ(監査ログ、IoTデータ、古いトランザクション)を直接保持・クエリしたいが、標準オブジェクトの制限を超過する場合。 大規模データに最適化されたクエリ(Async SOQL)が可能。書き込みはBulk APIまたはAsync SOQLインサート。 標準SOQLとは異なる独自の制限。DML操作はAPI経由。 定義と利用は比較的シンプル。データロードとクエリに特有の考慮が必要。中程度。
外部データベース
(Heroku Postgres, AWS RDSなど)
Salesforceのガバナ制限を完全に回避したい、より柔軟なデータモデルが必要、既存のデータウェアハウスとの統合を強化したい場合。 外部DBの性能に依存。SalesforceからのアクセスはAPIコールやSalesforce Connect経由で、ネットワークレイテンシの影響を受ける。 Salesforce側での処理(Callout、Salesforce Connect)にGovernor Limitsが適用されるが、データストレージ自体は外部。 外部DBの設計・管理、連携ロジックの開発、セキュリティ、同期戦略が必要。中~高。
外部ストレージ
(AWS S3, Azure Blob Storageなど)
低コストで大量の非構造化データ(ファイル、ログ、BLOBデータ)や非常にアクセス頻度の低いデータを長期保存したい場合。 ストレージ自体は高速だが、Salesforceからの直接アクセスは通常バッチ処理またはカスタムUI経由。検索は外部で実施。 データ転送ロジック(Apex Calloutなど)にApexのガバナ制限が適用。 データロード、検索ロジック、セキュリティ、ライフサイクル管理が別途必要。外部システムとの連携が最も複雑になる場合が多い。中~高。

archiving strategies を使用すべき場合:

  • ✅ Salesforceのデータストレージコストを大幅に削減したい場合。
  • ✅ Salesforce組織のレポート、ダッシュボード、検索のパフォーマンスを向上させたい場合。
  • ✅ 業界規制や法的要件(例:データ保持期間)に対応する必要がある場合。
  • ✅ 大量の履歴データがアクティブなプロセスに不要だが、参照可能性を維持したい場合。
  • ❌ リアルタイムで頻繁に更新・参照される活動中のデータ(これはアーカイブではなく、アクティブなデータベースに保持すべきです)。

実装例

ここでは、Salesforceの古い取引先レコードを特定し、HTTP Callout を使用して外部システムにアーカイブデータを送信するApex Batchの例を示します。これは一般的なパターンであり、実際のアーカイブ処理では、データのバリデーションや削除処理も含まれます。

この例では、過去1年以上更新されていない取引先レコードを対象とし、そのIDと名前をJSON形式で外部エンドポイントに送信します。外部エンドポイントはダミーです。

public class AccountArchiverBatch implements Database.Batchable<SObject>, Database.AllowsCallouts, Database.Stateful {

    // 外部アーカイブサービスのエンドポイントURLを定義
    private static final String ARCHIVE_ENDPOINT = 'https://api.example.com/archive/accounts';
    // 処理に失敗したレコードのIDを保持するためのリスト (Statefulインターフェースで状態を保持)
    public List<Id> failedAccountIds = new List<Id>();

    /**
     * startメソッド: バッチジョブのクエリ範囲を定義します。
     * ここでは、最終更新日が1年以上前の取引先を対象とします。
     * @param bc Database.BatchableContextオブジェクト
     * @return Database.QueryLocatorオブジェクト
     */
    public Database.QueryLocator start(Database.BatchableContext bc) {
        System.debug('Batch Start: Querying accounts to archive.');
        // 過去1年以上更新されていない取引先を抽出するSOQLクエリ
        return Database.getQueryLocator(
            'SELECT Id, Name, LastModifiedDate ' +
            'FROM Account ' +
            'WHERE LastModifiedDate < LAST_N_DAYS:365' // 最終更新日が365日より前のレコード
        );
    }

    /**
     * executeメソッド: クエリ結果の各バッチを処理します。
     * 各取引先を外部アーカイブサービスにPOSTリクエストで送信します。
     * @param bc Database.BatchableContextオブジェクト
     * @param scope 現在のバッチで処理されるレコードのリスト
     */
    public void execute(Database.BatchableContext bc, List<Account> scope) {
        System.debug('Batch Execute: Processing ' + scope.size() + ' accounts.');
        List<Map<String, Object>> accountsToArchive = new List<Map<String, Object>>();
        List<Id> successfullyArchivedIds = new List<Id>();

        // 外部サービスに送信するデータ形式に変換
        for (Account acc : scope) {
            accountsToArchive.add(new Map<String, Object>{
                'Id' => acc.Id,
                'Name' => acc.Name,
                'LastModifiedDate' => acc.LastModifiedDate
            });
        }

        // HTTP Callout を使用して外部アーカイブサービスにデータを送信
        HttpRequest req = new HttpRequest();
        req.setEndpoint(ARCHIVE_ENDPOINT);
        req.setMethod('POST');
        req.setHeader('Content-Type', 'application/json');
        req.setBody(JSON.serialize(accountsToArchive)); // JSON形式でリクエストボディを設定
        req.setTimeout(60000); // タイムアウトを60秒に設定

        try {
            Http http = new Http();
            HttpResponse res = http.send(req); // 外部サービスにリクエストを送信

            // 応答ステータスコードを確認
            if (res.getStatusCode() == 200 || res.getStatusCode() == 201) {
                System.debug('Successfully archived ' + scope.size() + ' accounts.');
                // 成功したレコードを削除対象としてマーク
                for (Account acc : scope) {
                    successfullyArchivedIds.add(acc.Id);
                }
            } else {
                // エラー応答の場合
                System.error('Error archiving accounts. Status: ' + res.getStatusCode() + ', Body: ' + res.getBody());
                for (Account acc : scope) {
                    failedAccountIds.add(acc.Id); // 失敗したIDをリストに追加
                }
            }
        } catch (Exception e) {
            // 例外発生の場合
            System.error('Exception during archiving: ' + e.getMessage() + ' at line ' + e.getLineNumber());
            for (Account acc : scope) {
                failedAccountIds.add(acc.Id); // 失敗したIDをリストに追加
            }
        }

        // 外部アーカイブに成功したレコードをSalesforceから削除 (注意: 実際の運用では検証後に行うべき)
        if (!successfullyArchivedIds.isEmpty()) {
            try {
                // Database.delete を使用してDML操作
                Database.DeleteResult[] deleteResults = Database.delete(successfullyArchivedIds, false); // 部分的な成功を許容 (allOrNone=false)
                for (Database.DeleteResult dr : deleteResults) {
                    if (!dr.isSuccess()) {
                        System.error('Failed to delete Account ID ' + dr.getId() + ': ' + dr.getErrors()[0].getMessage());
                        failedAccountIds.add(dr.getId()); // 削除に失敗したIDも追跡
                    }
                }
            } catch (Exception e) {
                System.error('Exception during deletion: ' + e.getMessage());
            }
        }
    }

    /**
     * finishメソッド: バッチジョブが完了した際に呼び出されます。
     * ここでは、処理結果のサマリーやエラー通知を行います。
     * @param bc Database.BatchableContextオブジェクト
     */
    public void finish(Database.BatchableContext bc) {
        System.debug('Batch Finish: Archiving process completed.');
        if (!failedAccountIds.isEmpty()) {
            // エラー通知のロジック (例: メール通知、Platform Eventの発行)
            System.error('The following accounts failed to archive or delete: ' + String.join(failedAccountIds, ', '));
            // カスタム通知サービスを呼び出すことも可能
            // MyNotificationService.sendErrorNotification('Account Archiving Failed', failedAccountIds);
        }
    }
}

// 実行方法の例 (匿名実行ウィンドウまたはScheduled Apexから)
// Id batchJobId = Database.executeBatch(new AccountArchiverBatch(), 200); // scopeSizeを200に設定
// System.debug('Batch Job ID: ' + batchJobId);

注意事項とベストプラクティス

権限要件

  • Apex Batch実行ユーザー:
    • 「Apexの実行」権限。
    • アーカイブ対象オブジェクト(例: Account)の「参照」「編集」「削除」権限。
  • 外部サービス連携:
    • リモートサイト設定(Remote Site Settings)で、Callout先の外部アーカイブサービスのエンドポイントURLを登録する必要があります。
    • ユーザープロファイルまたは権限セットに「API 有効」権限が必要です。
  • Big Objectsを使用する場合:
    • Big Object定義のための「Big Objectの管理」権限。
    • Big Objectへのデータ挿入/更新/クエリのための適切なオブジェクト権限。

Governor Limits

アーカイブ処理では大量のデータを扱うため、SalesforceのGovernor Limits(ガバナ制限)を意識することが極めて重要です。

  • Batch Apex:
    • `start` メソッドで返される `QueryLocator` は最大5000万レコードまで処理可能。
    • 各 `execute` メソッドのトランザクションは、同期Apexと同じGovernor Limitsが適用されます。
      • SOQL クエリ数: 100回
      • DML ステートメント数: 150回
      • DML レコード数: 10,000レコード
      • ヒープサイズ: 6 MB
      • CPU 時間: 10,000 ms
    • 1日あたりの非同期 Apex メソッド実行回数: 各組織は最大 250,000 回または組織のユーザーライセンス数の合計の200倍(上限250,000)のいずれか大きい方。
  • HTTP Callouts:
    • 1つのトランザクションあたりのCallout回数: 100回。
    • 同期Calloutのタイムアウト: 10秒。
    • 非同期Calloutのタイムアウト: 120秒。

エラー処理

  • `try-catch` ブロックを適切に配置し、APIコールやDML操作の失敗を捕捉します。
  • `Database.Stateful` インターフェースを実装し、バッチ実行中にエラーが発生したレコードIDなどの状態を保持し、`finish` メソッドで集計・報告します。
  • エラー発生時には、`System.debug` だけでなく、カスタムログオブジェクトへの記録や、管理者にメール通知を行うメカニズムを実装します。
  • 部分的な成功を許容するために、DML操作には `Database.insert(records, false)` や `Database.delete(records, false)` を使用することを検討します。

パフォーマンス最適化

  1. クエリの最適化: `start` メソッドでアーカイブ対象を特定するSOQLクエリは、選択性の高いフィールドにインデックスを使用し、効率的なデータ取得を心がけてください。
  2. バッチサイズ(Scope Size)の調整: `Database.executeBatch(batchInstance, scopeSize)` の `scopeSize` を調整することで、各 `execute` メソッドで処理されるレコード数を制御します。通常200レコードが推奨されますが、Calloutや複雑なロジックを含む場合は、Governor Limitsに抵触しない範囲で小さく調整することを検討します。
  3. 非同期処理とバルクAPIの活用: 外部システムへのデータ転送は、HTTP Calloutを個々のレコードごとに行うのではなく、バッチでまとめて送信するか、外部システムのバルクAPIを利用することで、Callout回数と処理時間を削減します。
  4. 削除前のバックアップと検証: データをSalesforceから削除する前に、外部システムへの転送が完全に成功し、データが正しく保存されていることを検証するステップを設けることが非常に重要です。

よくある質問 FAQ

Q1:アーカイブしたデータにSalesforceからアクセスする方法は?

A1:アーカイブしたデータの保管場所によって異なります。Big Objectsに保存した場合、SalesforceのAsync SOQLで直接クエリできます。外部データベースに保存した場合、Salesforce Connectの外部オブジェクトを設定することで、Salesforceの標準UIから外部データを参照したり、SOQLでクエリしたりできます。外部ストレージ(S3など)に保存した場合は、カスタムApexやVisualforce/Lightningコンポーネントを開発し、外部API経由でデータを取得して表示するのが一般的です。

Q2:アーカイブプロセスが途中で失敗した場合、どうデバッグしますか?

A2:まず、設定 > 環境 > ジョブ > Apex ジョブ でバッチジョブのステータスとログを確認します。失敗したバッチジョブの詳細には、エラーメッセージやスタックトレースが含まれることがあります。デバッグログを有効にし、`System.debug` ステートメントで処理の進行状況や変数の中身を追跡します。また、`Database.Stateful` インターフェースを実装していれば、バッチの状態変数(例:失敗したレコードIDリスト)にアクセスしてエラーの発生源を特定できます。

Q3:アーカイブ戦略のパフォーマンスを監視するための主要な指標は何ですか?

A3:主要な指標としては、バッチジョブの実行時間、処理されたレコード数、失敗したレコード数、外部システムへのCallout成功率と応答時間、Salesforce組織のストレージ使用量の削減率、およびアーカイブ後のSalesforceレポートやクエリの実行時間などが挙げられます。これらの指標を定期的に監視し、アーカイブプロセスの効率性と効果を評価します。


まとめと参考資料

Salesforceにおけるデータアーカイブ戦略は、単なるストレージコスト削減に留まらず、組織全体のパフォーマンス向上、ガバナ制限への対応、そして厳格なコンプライアンス要件への適合を可能にする、データエンジニアリングの重要な柱です。適切なソリューションを選定し、綿密な設計とテストを経て実装することで、Salesforce環境を長期にわたり健全に保つことができます。データのライフサイクル全体を考慮した戦略的なアプローチが成功の鍵となります。

公式リソース:

コメント