Heroku Connect で短時間に複数の更新を行った場合の問題
最終更新日 2022年11月29日(火)
Table of Contents
Heroku Connect のシステム設計では、Salesforce との最終的な整合性は保証されていますが、即時の整合性は保証されていません。読み取り/書き込みマッピングでは、同じフィールドに複数の更新を行うと、Postgres で一時的に同期が失われる場合があります。この動作は想定されており、さまざまな形で表出します。この記事では、このような動作とそれを防ぐ方法について説明します。
この記事のいくつかの概念は、「Heroku Connect トリガーログ」の記事で理解を深めることができます。
“フリップフロップ” 値
フィールドが “フリップフロップ” のように見えるのは、Postgres で値が更新され、前の値に設定され、数秒または数分後にまた新しい値に更新されるからです。
- Postgres がテーブル内のフィールドを更新します。Heroku Connect のトリガーによって
_trigger_log
にエントリが作成されます。 - Postgres が再度フィールドを更新します。新しいエントリが
_trigger_log
に挿入されます。 - 最初の
_trigger_log
エントリが Connect によって処理され、手順 1 の後の Postgres 更新の値に Salesforce が更新されます。 - Salesforce からの値が Postgres に同期され、フィールドが手順 1 の値に設定されます。
- 2 番目の
_trigger_log
エントリが処理され、Salesforce のフィールドが手順 2 の値に更新されます。 - 更新された Salesforce の値が Postgres に再同期されます。
削除されたレコードの一時的な復活
このシナリオは次のとおりです。
- Postgres でレコードが削除されます。
- Postgres プロセスからの削除の前に、同じレコードが Salesforce で更新されます。
- Salesforce の更新によって、Postgres のレコードが一時的に復活します。
- 削除の同期が発生し、SFDC と Postgres の両方でレコードが削除されます。
再挿入されたレコード
マージ書き込みアルゴリズムを使用した場合
一意識別子を使用している場合でも、Postgres で挿入した直後にレコードを削除した場合は、削除したレコードを保持することができます。この問題は、次の場合に起きることがあります。
- Postgres でレコードが作成されます。
- レコードが Salesforce に同期される前に Postgres から削除されます。
- ステップ 1 による挿入が、マージされた書き込みの最初のバッチで処理され、Salesforce に挿入されます。
- ステップ 2 による削除は、削除時にレコードに
sfid
がなかったため、Heroku Connect によって無視されます。 - Salesforce からのポーリングが発生し、レコードが Salesforce から元の Postgres に挿入されます。
この状況はまれにしか発生しませんが、レコードを Postgres に挿入した直後に削除することはお勧めできません。Postgres のレコードに sfid
値が確実に入力されるようにするために、レコードを削除する前にレコードが Salesforce で作成されるまで待ちます。順序付き書き込みを使用するとこの問題を防ぐことができますが、まれに発生することがあります。
順序付き書き込みアルゴリズムを使用した場合
このシナリオは次のとおりです。
- Postgres でレコードが作成されます。
- Heroku Connect が、そのレコードを Salesforce と同期します。
- Heroku Connect サービスは、正常に挿入されたことを示す受信確認を Salesforce から受信する前に、再開します。その挿入のトリガーログエントリは
PENDING
のままになります。 - Heroku Connect が再開する前に、同じレコードが Salesforce から削除されます。
- Heroku Connect は再開し、
PENDING
の挿入トリガーログエントリを再試行します。Salesforce にレコードが存在しないことを検出し、そのレコードを再挿入します。
この状況はまれにしか発生しませんが、Salesforce のレコードを Heroku Connect 経由で同期した直後に削除することはお勧めできません。削除する前に少なくとも 10 分間は待ってください。
カウンターの誤ったインクリメントまたはデクリメント
カウンターとして使用されるフィールドは、その値が誤ってインクリメントまたはデクリメントされる場合があります。最終的な値が挿入される前にフィールドが更新され、値が変更されると、新しい最終的な値は期待値と異なったものになります。
次に例を示します。
- Heroku Postgres で
view_count = 1
を設定すると、Heroku Connect で処理する変更を捕捉するためのエントリが_trigger_log
に挿入されます。 - Heroku Postgres で
view_count
をview_count + 1
に更新すると、2 番目の_trigger_log
エントリが挿入されます。view_count
は 2 になります。 - Connect は最初の
_trigger_log
を処理し、view_count = 1
の更新を Salesforce に書き込みます。 - Salesforce 側の値が Postgres に再同期されます。Postgres 側の
view_count
は 1 です。 - Heroku Postgres で再度
view_count
をview_count + 1
に更新すると、3 番目の_trigger_log
エントリがに挿入されます。この場合、インクリメントの動作はべき等ではないため、view_count
が 3 にインクリメントされることはありません。最終的な値は 2 です。
失われる更新
開始時点のフィールドと値が {A: "name", B: true}
であるレコードがあるシナリオを考えます。
- Heroku Postgres で A を
"new name"
に更新します。_trigger_log
エントリは{A: "new name"}
を捕捉します。この時点で、Heroku Postgres のレコードは{A: "new name", B: true}
です。 - Heroku Postgres で B を
false
に更新します。2 番目の_trigger_log
エントリは{B: false}
を捕捉します。Postgres のレコードは{A: "new name", B: false}
です。 - Connect が最初の
_trigger_log
エントリを処理します。更新が Salesforce から Postgres に再同期され、Postgres のレコードは{A: "new name", B: true}
になります。B は正しくなく、"フリップフロップ" されています。他の要素によるレコードの更新がなければ、最終的に正しい値に更新されます。 - Heroku Postgres で B を
true
に更新します。B は現在、手順 3 により誤ってtrue
になっているため、これはノーオペレーションの変更です。ノーオペレーションの変更は_trigger_log
エントリに反映されません。 - Connect は 2 番目の
_trigger_log
を処理し、Salesforce で B をfalse
に更新します。 - Salesforce の値が Postgres に同期します。最終的な Heroku Postgres のレコードは
{A: "new name", B: false}
になります。B をtrue
に設定する更新は失われます。
短時間の複数の更新による問題の回避または善後策
- 加速ポーリングを使用すると、一時的な状態が Postgres にプッシュされやすくなります。マッピングで加速ポーリングを無効化すると、そのような現象が起きにくくなります。
- ポーリング間隔を長くした場合も、これらの問題が起きにくくなります。
- 短時間にレコードを何回も更新する必要があるとわかっている場合は、それらの更新をステージングテーブルで行ってください。頻繁に変更する必要がなくなったら、マップされたテーブルにレコードを移動してください。