STORES Product Blog

こだわりを持ったお商売を支える「STORES」のテクノロジー部門のメンバーによるブログです。

SFA環境を1つのSalesforceに統合する際のデータ移行時に考慮したこと

はじめに

こんにちは、データ本部でデータエンジニアをやっている@takaHALです。

STORES では2025年8月、これまで2つ存在していたSalesforce組織と7個のkintoneアプリを、1つのSalesforce組織に統合しました。

今回は、この組織統合プロジェクトにおいて発生した データ移行 に関する技術的な考慮事項やハマりポイントを紹介します。

移行方法について

今回の移行対象となるオブジェクトは以下の通りです。

  • Account
  • Campaign
  • CampaignMember
  • Case
  • Contact
  • EmailMessage
  • Event
  • Lead
  • Opportunity
  • OpportunityContactRole
  • OpportunityLineItem
  • Pricebook2
  • PriceBookEntry
  • Product2
  • QuickText
  • Task
  • カスタムオブジェクト 1つ

移行対象のオブジェクトが多く、かつ kintoneからSalesforceへ という異なるテーブル構造間のマッピングも混在していました。 そこで、一度BigQueryに全データを集約し、SQLでデータ加工を行った上で新環境のSalesforceへ移行する方針を採用しました。

データの転送には、データ基盤チームで普段運用しているArgo Workflowsではなく、あえてTROCCOを採用しました。 理由は以下の2点です。

  1. 移行用のデータインポート定義やスケジュール設定の変更にスピーディに対応するため

  2. 既存のデータ基盤ワークフローの運用に影響を与えないため

BigQueryにデータを集約したことで、SQLを用いてリレーションを維持したまま、柔軟にインポート用データを作成する準備が整いました。

インポート手法の選定

インポート用データの作成まではBigQueryで行いますが、Salesforceへの投入には以下の要件がありました。

  • UPSERT / INSERT / DELETEが可能であること

    • DELETEの対応については監査項目を再設定する場合、1度データをDELETEしてINSERTし直すため必要
  • Bulk APIに対応していること

    • 大量データを扱うため、通常のAPIでは時間がかかりすぎるため必須

これらの前提に対応しつつ、柔軟な転送を行うために、今回はPythonスクリプトを自作しました。 ライブラリには simple_salesforce を採用し、Bulk API v2を使用しています。

最終的な構成はこちらです。

自作スクリプトの構成概要

作成したPythonスクリプトの詳細については、別途GitHubに公開した上で別記事にて紹介しようと思います。 ここでは簡単に、どのような仕組みで実装したかを紹介します。

構成としては、queries ディレクトリ配下に データ加工用のSQLをオブジェクト単位で作成し、設定ファイルである target_tables.yml の内容に応じてインポート手法を制御する形にしました。

target_tables.yml では以下のように定義を行い、実行順序やaction (INSERT/UPSERT/DELETE)を管理しています。

  • priority: 数字が小さい順に直列実行(同じ数字のものは並列実行)

  • name: queries 配下のSQLファイルを指定(例: Account/parent_company → Account/parent_company.sql)

  • upsert_key: Upsert時のキーとなる外部ID項目を指定

target_tables:
  - name: Account/parent_company
    priority: 0
    sync:
      enabled: false
    salesforce:
      sobject: Account
      action: upsert
      upsert_key: ForeignKeyForDataMigration__c
  - name: Account/company
    priority: 1
    sync:
      enabled: false
    salesforce:
      sobject: Account
      action: upsert
      upsert_key: ForeignKeyForDataMigration__c

また、SQLファイル内には独自のテンプレートタグを埋め込み、環境変数に応じてクエリを動的に書き換える工夫をしました。

S_PARTITION_BLOCK_STARTS_PARTITION_BLOCK_END に囲まれた範囲は、S_PARTITIONE_PARTITION の環境変数が設定されている場合にのみ値を差し込み、設定されていない場合は実行時にブロックごと削除する仕様にしています。

これにより、同じSQLファイルを使いながら組織切り替え当日までの全量データ挿入当日の差分取り込みを容易に切り替えられるようにしました。

SELECT
  *
FROM
  `xxxx.xxxx.parent_company`
WHERE 1=1
 
  -- {S_PARTITION_BLOCK_START}
 AND DATETIME(
    PARSE_TIMESTAMP('%Y-%m-%dT%H:%M:%E3S', CreatedDate),
    'Asia/Tokyo'
 ) >= '{S_PARTITION}'
 AND DATETIME(
    PARSE_TIMESTAMP('%Y-%m-%dT%H:%M:%E3S', CreatedDate),
    'Asia/Tokyo'
  ) < '{E_PARTITION}'
  -- {S_PARTITION_BLOCK_END}

移行作業で考慮すべきポイント

ここからは、実際の移行作業で考慮すべき具体的なポイントを紹介します。

1. 監査項目(作成日など)をインポート可能にする

Salesforceには監査項目と呼ばれる、通常はAPIやUIから編集不可能なシステム項目(作成日、作成者など)が存在します。 旧環境のリードがいつ作成されたか?という時系列情報を維持するためには、データインポートを実行するアカウントに「レコードの作成時に監査項目を設定」および「すべてのデータの編集」権限を付与する必要があります。

※ 権限を付与していても、監査項目はレコード作成時にのみ設定可能です。インポート後の更新では変更できません。

インポート時にセット可能な監査項目のリストは、以下のヘルプページを参照してください。

help.salesforce.com

2. TODOの完了日は移行できないため合意を取る

2025年12月時点の仕様では、TODOの 完了日 は監査項目を操作する権限をもっていてもAPI経由で操作不可能な項目となっています。 そのため、完了日のデータ移行が必要かどうかについて、事前に組織内で合意を取る必要があります。

もし移行が必須である場合は、カスタム項目を作成し、そこに旧環境の完了日をセットするなどの代替案を検討しましょう。

ideas.salesforce.com

3. リードの取引開始状態を維持してデータインポートする

Salesforceには、リードを取引先・取引先責任者・商談に変換する取引の開始という機能があります。 キャンペーンを用いた成果集計を行っている場合、リードが 取引開始済みかどうか の状態を維持して移行することは非常に重要です。

取引開始状態を表現するには、以下の項目に値をセットする必要があります。

  • リードが変換された日付 (ConvertedDate)
  • 変換先の取引先ID (ConvertedAccountId)
  • 変換先の責任者ID (ConvertedContactId)
  • 変換先の商談ID (ConvertedOpportunityId)

これらはすべて監査項目のため、Insert時にしか設定できません。また、紐づく親データが先に存在している必要があります。 そのため、以下の順序でインポートを行いました。

  1. 組織切り替え当日まで:継続して取引先 / 取引先責任者 / 商談をインポート
  2. 上記1と紐づく取引開始済みのリードを親IDをセットした状態でインポート
  3. 組織切り替え当日:残りの取引開始済みのリードその他のリードをインポート

4. APIでUpsert不可のオブジェクトを確認する

Salesforceには一部、Upsertができないオブジェクトが存在します。今回移行対象となったオブジェクトの中では、以下が該当しました。

  • EmailMessage(メールメッセージ)

    • Statusが下書き状態のレコード以外はUpdateが不可
    • 対応策:移行時に下書き状態で移行するか、Updateが必要になった場合は Delete & Insert を行う方針にしました。
  • OpportunityContactRole(取引先責任者の役割)

    • 外部ID項目の作成ができないため、Upsertが不可
    • 対応策:組織切り替え当日までにUpdateされる可能性があるため、切り替え当日に全量Insertを行う方針にしました。

5. 旧環境のレコードIDを外部ID項目として設定する

環境切り替えの際、既存のモニタリング環境との差分チェックや、データの再Updateが発生する可能性があります。 そこで、Salesforce側に外部ID項目を作成し、Upsert時のキーとして利用できるようにしました。

今回は ForeignKeyForDataMigration__c というテキスト項目(外部ID)を作成し、以下のような命名規則で値を格納しました。

  • kintoneからの移行: kintone{アプリID}_{レコードID}
  • Salesforceからの移行: 組織名_{レコードID}

6. アーカイブ済みの活動レコードの移行計画を立てる

Salesforceには完了から365日以上経過した活動レコードを自動的にアーカイブする仕様があります。 アーカイブされると、通常のレポートで取得できなくなりますが、取引先画面のタイムラインには表示されるため、移行対象として考慮する必要があります。

アーカイブ済みのレコードを移行する上では、以下の2点に注意が必要です。

  1. 取得方法
  2. 移行スケジュール

取得方法

APIであれば QueryAll 、データローダーであれば Export All 等を使用する必要があります。 今回はTROCCOを使用してデータ移行用のデータをBigQueryに集めていたので、TROCCOのオプションからアーカイブ済みのレコードを含めて取得する設定を行いました。

移行スケジュールと isArchived 項目

Salesforce上でレコードがアーカイブ済みであることを示す項目 isArchived は、データ挿入時には操作不可能な読み取り専用項目です。 このフラグは、Salesforceのバックグラウンド処理によって週末に更新されます。

Salesforce Help

この値を縮小した場合の処理と通常のアーカイブ処理は週末に行われます。詳細については、「Lightning Experience で活動を開くと「問題が発生しているようです」エラー」を参照してください。

実際に検証として金曜日にアーカイブ対象となる過去データを挿入したところ、日曜時点で正常にアーカイブされていました。 公式ヘルプにある通常のアーカイブ処理は週末に行われますという仕様を信頼して計画を立てて良いと思います。

しかし、組織切り替え当日に本来はアーカイブされるはずの大量の過去データがアーカイブされていない状態で存在すると、 新環境のパフォーマンス低下を招く恐れがあります。 そのため、アーカイブ処理が実行されるタイミングを見越したスケジュールを組む必要があります。

スケジュール案

  • 案A: 組織切り替え当日の前の週末までに移行を行い、アーカイブ処理を待つ
  • 案B: 組織切り替え後にアーカイブ済みデータを移管することを合意した上で、切り替え後の週末に行う

今回は、旧環境のSalesforceにおいて切り替え直前まで一部データの更新が発生する可能性があること、および旧環境が切り替え後も一定期間閲覧可能であることから、案B(切り替え後にアーカイブ済みデータを移管) を採用しました

7. kintoneのSUBTABLE型の移行も忘れずに

kintoneには、1つのアプリ内で子レコードのような構造を持てる便利な型 SUBTABLE があります。

内部的には別テーブルではなく、SUBTABLE型を持つアプリのデータ内にJSON形式として1カラムに格納される仕様になっています。 アプリ単位で単純なマッピングを行っていると漏れてしまいがちですし、SUBTABLE型の仕様を知らないとデータがどこにあるのかを探すことになります。移行用データの作成漏れがないよう注意しましょう。

外部IDについては、JSON内に各行のIDが含まれているため、それを利用して kintone{アプリID}_{レコードID}_{SUBTABLEのID} という形式にしました。

JSONデータの例:

フィールド形式 - cybozu developer network から引用

"フィールドコード": {
  "type": "SUBTABLE",
  "value": [
    {
      "id": "48290",
      "value": {
        "文字列__1行__0": {
          "type": "SINGLE_LINE_TEXT",
          "value": "サンプル1"
        },
        "数値_0": {
          "type": "NUMBER",
          "value": "1"
        },
        "チェックボックス_0": {
          "type": "CHECK_BOX",
          "value": ["選択肢1"]
        }
      }
    },
    {
      "id": "48291",
      "value": {
        "文字列__1行__0": {
          "type": "SINGLE_LINE_TEXT",
          "value": "サンプル2"
        },
        "数値_0": {
          "type": "NUMBER",
          "value": "2"
        },
        "チェックボックス_0": {
          "type": "CHECK_BOX",
          "value": ["選択肢2"]
        }
      }
    }
  ]
}

さいごに

SFAが1つのSalesforce組織に統合されたことで、データ基盤や各種システムからの連携が容易になり、STORES からオーナー様へのサポートもより体験の良いものになる土台が整いました。 今後も全社のデータ利活用を推進するため、データ基盤チームとしてSalesforce環境をより良いものにしていきます。

STORES では積極的にデータエンジニアの採用活動を行っております。 データ分析 AI Agentの改善、新BIツールの導入など、今後も面白い取り組みがたくさん待っています。 少しでも気になる方は、ぜひカジュアル面談で未来のデータ基盤についてお話ししましょう!

hrmos.co