「一度設定すればあとはお任せ」な同期システムの構築方法
商品価格はさまざまな場所で変更されます。管理パネル、一括インポート、あるいはAPIのWebhook経由などです。
これらの変更を外部のマーケットプレイスに同期したい場合、問題に直面します。すべてのコードパスに同期処理を追加するのは間違いです。必ずどれかを忘れたり、壊したりしてしまいます。メンテナンスが困難な悪夢へと変わります。
Django signalsを使えば、この問題は解決できます。モデルの保存(save)イベントにフックすることで、あらゆる変更を一箇所でキャッチできます。
しかし、signalsには欠点があります。一度に100個の価格を更新すると、signalが100回実行されます。これにより100回のAPIコールが発生し、レート制限に抵触したり、リソースを無駄にしたりすることになります。
私はこれを解決するために、3つの要素からなるパターンを使用しています。
• 即座に実行するのではなく、IDを収集するsignal handler。 • 重複を排除するためのスレッドごとのset。 • すべてを一度に処理するための、transaction.on_commitを使用したflushコールバック。
仕組みは以下の通りです。
threading.local()を使用する グローバル変数を使用してはいけません。グローバル変数はリクエスト間で状態を共有してしまうため、データの漏洩につながります。threading.local()を使えば、データを単一のスレッド内に隔離できます。実行するのではなく、記録する signal handlerは、単に製品IDをsetに追加するだけです。その後、データベースのトランザクションが成功した後にのみflush関数を実行するようDjangoに指示します。これにより、保存に失敗したデータの同期を防ぐことができます。
処理をバッチ化する トランザクションがコミットされると、flush関数が実行されます。この関数はsetをコピーして中身をクリアし、その後、IDのリスト全体をサービスレイヤーに送信します。
サービスレイヤーは、すべての製品を取得するために1回のバルククエリを実行します。それらを店舗ごとにグループ化し、最終的に店舗ごとに1つのタスクをCeleryに送信します。
メリットは明らかです。
- 重複排除が自動化される。setが自動的に処理してくれます。
- トランザクションの安全性が組み込まれている。ロールバックされたデータを同期することはありません。
- 効率が高い。N+1問題を回避できます。
- 信頼性が高い。APIが失敗した場合でも、Celeryがリトライを処理します。
システムは一度構築すれば完了です。後から追加する新しい機能も、自動的に同期システムと連携します。
あなたはDjangoで外部APIの同期をどのように扱っていますか?signalsを使っていますか、それとも別のパターンを使っていますか?
出典: https://dev.to/acel/how-i-built-a-set-it-and-forget-it-sync-system-with-django-signals-2ld7