べき等を解明:信頼なシステムデザインへのガイド
Wenhao Wang
Dev Intern · Leapcell

抽象的な概念
べき等性は、数学およびコンピュータサイエンスの概念であり、抽象代数でよく見られます。
プログラミングにおいて、べき等な操作は、それを複数回実行しても一度実行した場合と同じ効果があるという特徴があります。べき等な関数またはメソッドは、同じパラメータで繰り返し実行でき、同じ結果が得られるものです。これらの関数はシステムの状態を変更せず、繰り返し実行しても意図しない変更は発生しません。たとえば、getUsername()
やsetTrue()
のような関数はべき等です。
平たく言うと、操作が何回実行されても、その効果または戻り結果は同じままです。
例:
- フロントエンドフォームが複数回送信された場合、バックエンドは1つの結果のみを生成する必要があります。
- 支払い要求を開始する場合、ユーザーのアカウントからの引き落としは1回のみである必要があります。ネットワークの再試行やシステムバグにより複数回送信された場合でも、引き落としは1回のみ発生する必要があります。
- メッセージを送信する場合、1回のみ送信される必要があります。同じSMSが複数回送信されると、ユーザーをイライラさせる可能性があります。
- ビジネスオーダーを作成する場合、各リクエストは1つのオーダーの作成のみをもたらし、重複レコードを回避する必要があります。
べき等性が不可欠なシナリオは多数存在します。
べき等性を実装するためのテクニック
読み取り(クエリ)操作
クエリ操作は、1回実行されても複数回実行されても、基礎となるデータが変更されない限り、同じ結果を返します。これにより、SELECT
クエリは本質的にべき等になります。
削除操作
削除操作もべき等です。削除が1回実行されても複数回実行されても、データは削除されます。(ただし、戻り値は異なる場合があります。データが存在しない場合、削除は0を返し、複数のレコードが存在する場合、複数の行が影響を受けます。)
ダーティデータを防ぐためのユニークインデックス
たとえば、各ユーザーが1つの金融口座のみを持つべき金融システムでは、単一のユーザーに対して複数の口座が作成されるのをどのように防ぐことができますか?
金融口座テーブルのユーザーIDフィールドにユニークインデックスを追加することで、口座を作成しようとすると、1つのリクエストのみが成功します。後続のリクエストは、org.springframework.dao.DuplicateKeyException
などのユニーク制約違反エラーをスローします。その後、システムはデータベースを再度クエリして、データがすでに存在するかどうかを確認し、適切な結果を返すことができます。
重複するフォーム送信を防ぐためのトークンメカニズム
**要件:**ページ上のデータは1回のみ送信される必要があります。
**原因:**重複クリック、ネットワークの再試行、またはNginxの再試行によってトリガーされた再送信により、同じデータが複数回処理される可能性があります。
解決策:
- **クラスタ環境の場合:**Redisと組み合わせたトークンを使用します。
- **単一のJVM環境の場合:**Redisと組み合わせたトークン、またはJVMメモリに格納されたトークンを使用します。
プロセス:
- データ送信の前に、サービスからトークンを要求します。これは、有効期間付きでRedisまたはJVMメモリに格納されます。
- 送信時に、バックエンドはトークンを検証し、削除して、次のリクエストのために新しいトークンを生成します。
トークンの特性:
- 送信する前にリクエストする必要があります。
- 1回しか使用できません。
- レート制限メカニズムとして機能できます。
重要:Redisは、トークンを検証するために削除操作を使用する必要があります。削除が成功した場合、トークンは有効と見なされます。SELECT + DELETE
を使用すると、同時実行の問題が発生する可能性があり、推奨されません。
ペシミスティックロック
データを取得する際にロックを取得します:
SELECT * FROM table_xxx WHERE id='xxx' FOR UPDATE;
注意:id
フィールドはプライマリキーまたはユニークインデックスである必要があります。そうでない場合、クエリはテーブル全体をロックし、パフォーマンスの問題を引き起こす可能性があります。
ペシミスティックロックは通常トランザクションで使用され、データはトランザクションの期間中ロックされたままになります。ロックが保持される時間が長ければ長いほど、パフォーマンスへの潜在的な影響が大きくなるため、特定の要件に基づいて使用してください。
オプティミスティックロック
オプティミスティックロックは、更新時にのみデータをロックし、他の時点での不必要なロックを回避します。ペシミスティックロックと比較して、効率が向上します。
オプティミスティックロックを実装するには、バージョン番号または条件ベースの制約を使用するなど、複数の方法があります。
-
バージョン番号アプローチ:
UPDATE table_xxx SET name=#name#, version=version+1 WHERE version=#version#
-
条件ベースのアプローチ:
UPDATE table_xxx SET avai_amount=avai_amount-#subAmount# WHERE avai_amount-#subAmount# >= 0
- 条件
avai_amount - subAmount >= 0
は、バージョン番号を必要とせずに安全な更新を保証します。 - これは、在庫が差し引かれ、必要に応じてロールバックされる在庫モデルに最適です。
- より優れたパフォーマンスを提供します。
- 条件
ベストプラクティス:
-
テーブルロックを回避するために、
UPDATE
クエリでプライマリキーまたはユニークインデックスを使用します。上記のクエリは、次のように変更する必要があります。UPDATE table_xxx SET name=#name#, version=version+1 WHERE id=#id# AND version=#version# UPDATE table_xxx SET avai_amount=avai_amount-#subAmount# WHERE id=#id# AND avai_amount-#subAmount# >= 0
分散ロック
分散システムにデータを挿入する場合、グローバルにユニークなインデックスを強制することが難しい場合があります(たとえば、分散ノード全体でユニバーサルユニークキーがないため)。
このような場合、分散ロックは、RedisまたはZookeeperを使用して実装し、同時データ挿入または更新を管理できます。システムはロックを取得し、操作を実行し、ロックを解除します。このアプローチは同時書き込みを防ぐのに役立ち、分散システムにおける一般的なソリューションです。
SELECT + INSERT
パターン
同時実行性が低いバックエンドシステムまたはスケジュールされたジョブタスクの場合、操作がすでに実行されているかどうかを確認することで、べき等性を保証できます。
- まず、データベースでクリティカルデータをクエリして、操作がすでに実行されているかどうかを判断します。
- 実行されていない場合は、処理に進みます。
**注意:**高同時実行シナリオでは、このアプローチを使用しないでください。
ステートマシンべき等性
オーダー処理またはタスク実行を含むビジネスアプリケーションでは、状態遷移を慎重に管理する必要があります。
- ビジネスレコードには通常状態フィールドがあり、遷移は事前定義された有限状態マシンに基づいて発生します。
- リクエストが古い状態からすでに更新された状態に遷移しようとすると、拒否されるはずです。
- これにより、状態遷移におけるべき等性が保証されます。
重要な考慮事項:状態が時間とともに進化するオーダーベースのワークフローの場合、堅牢なビジネスシステムを設計するには、ステートマシンの強力な理解が不可欠です。
API呼び出しにおけるべき等性の確保
たとえば、支払いAPIを提供する銀行は、マーチャントにリクエストに2つのフィールドを含めるように要求しています。
source
:リクエストのオリジン。seq
:ユニークなシーケンス番号。
データベースでsource + seq
にユニークインデックスを強制することで、重複する支払いを防ぎ、同時実行シナリオで1つのリクエストのみが処理されることを保証できます。
キーポイント:
外部APIでべき等性をサポートするには:
- 2つのキーフィールドが必要:
source
とseq
。 - サービスプロバイダーのシステムでこれらのフィールドにユニーク制約を強制します。
- リクエストを処理する前に、すでに処理されているかどうかを確認します。
- 処理されている場合は、既存の結果を返します。
- 処理されていない場合は、実行に進みます。
**ベストプラクティス:**新しいレコードを挿入する前に、リクエストがすでに処理されているかどうかを常にクエリします。チェックせずにデータを直接挿入すると、操作がすでに成功している場合でも、エラーが発生する可能性があります。
最後に
べき等性は、優れたソフトウェア設計の基本原則です。これは、信頼性の高いシステムを設計する際に非常に重要な考慮事項であり、特にサードパーティの支払いプラットフォーム、銀行、およびFintechなどの業界では、精度が最も重要です。重複する引き落としや複数の支払いのような問題は、修正が非常に難しく、ユーザーエクスペリエンスに大きな影響を与える可能性があります。
べき等な操作を実装することで、開発者は一貫性、正確性、およびシームレスなユーザーエクスペリエンスを保証する堅牢でフォールトトレラントなシステムを構築できます。
Leapcell は、バックエンドプロジェクトをホストするための最適な選択肢です。
Leapcell は、Webホスティング、非同期タスク、およびRedisのための次世代サーバーレスプラットフォームです。
多言語サポート
- Node.js、Python、Go、Rustで開発。
無制限のプロジェクトを無料でデプロイ
- リクエストなし、料金なし - 使用量に対してのみお支払い。
比べるものがない費用効率
- アイドル料金なしの従量課金制。
- 例:25ドルで平均応答時間60msで694万リクエストをサポート。
合理化された開発者エクスペリエンス
- 簡単なセットアップのための直感的なUI。
- 完全に自動化されたCI/CDパイプラインとGitOpsの統合。
- 実用的な洞察のためのリアルタイムメトリックとロギング。
簡単なスケーラビリティと高性能
- 高い同時実行性を簡単に処理するための自動スケーリング。
- 運用上のオーバーヘッドなし - 構築に集中するだけ。
ドキュメントで詳細をご覧ください。
Xでフォローしてください:@LeapcellHQ