次の方法で共有


スケーラブルなカスタマイズ設計: トランザクション設計パターン

これは、スケーラブルなカスタマイズ設計に関する一連のトピックの 4 番目です。 最初に、「 Microsoft Dataverse のスケーラブルなカスタマイズ設計」を参照してください。

このセクションでは、回避または最小化する設計パターンとその影響について説明します。 各設計パターンは、解決されるビジネス上の問題のコンテキストで考慮する必要があり、調査するオプションとして役立ちます。

ロックを回避しない

ロックは SQL Server と Dataverse の重要なコンポーネントであり、システムの正常な操作と一貫性に不可欠です。 このため、特に大規模な設計への影響を理解することが重要です。

トランザクションの使用状況: Nolock ヒント

ビューで頻繁に使用される Dataverse プラットフォームの機能の 1 つは、nolock ヒントを使用してクエリを実行できるように指定し、このクエリにロックが必要ないことをデータベースに伝える機能です。

ビューを起動する操作と後続のアクションの間に直接リンクがないため、ビューではこの方法が使用されます。 他の多くのアクティビティは、そのユーザーまたはその間の他のユーザーによって発生する可能性があり、ユーザーが移動するまで待機しているビューに表示されるデータのテーブル全体をロックすることは実用的でも有益でもありません。

大規模なデータ セット全体のクエリは、そのデータを操作しようとしている他のユーザーに影響を与える可能性があることを意味するため、ロックが不要であることを指定できることは、システムのスケーラビリティに大きなメリットをもたらします。

SDK を使用してプラットフォームのクエリを実行する場合は、nolock を使用できることを指定することが重要です。 これは、このクエリがデータベースで読み取りロックを取る必要がないことを認識していることを示します。 これは、次のようなクエリに特に役立ちます。

  • データには幅広い範囲があります
  • 非常に競争の激しいリソースに対してクエリが実行される
  • シリアル化は重要ではありません

前の自動番号ロックの例のように、後のアクションが結果の変更に依存しない場合は、Nolock を使用しないでください。

便利なシナリオの例として、電子メールが既存のケースに関連しているかどうかを判断します。 他のユーザーが新しいケースを作成するのをブロックして、電子メールがリンクできるケースが生成される可能性がないことを確認することは、一貫性制御の有益なレベルではありません。

代わりに、関連するケースのクエリを実行し、既存のケースにメールを添付したり、新しいケースを作成したりしながら、他のケースの生成を許可する方が適切です。 特に、これら 2 つのアクション間のタイミングに固有のリンクがないため、メールは数秒前に簡単に送信でき、リンクは検出されません。

nolock ヒントが特定のシナリオに対して有効かどうかは、通常、競合が発生する可能性とその影響、それに加えて取得アクションとその後のアクション間の一貫性を確保しないことがビジネスに与える影響についての判断に基づいています。 ロックの回避によってビジネスへの影響が生じない場合は、nolock を使用することが重要な最適化の選択肢になります。 ビジネスに潜在的な影響がある場合、その結果は、ロックを回避するパフォーマンスとスケーラビリティの利点と比較できます。

ロックの順序を検討する

ブロックの影響を軽減し、特にデッドロックを回避する際に役立つもう 1 つのアプローチは、実装でのロックの順序付けの一貫したアプローチを持つことです。

単純で一般的な例は、ユーザーのグループを更新または操作する場合です。 関連するユーザーを更新する要求がある場合 (チームへのメンバーの追加やアクティビティのすべての参加者の更新など)、注文を指定しないと、2 つの同時アクティビティが同じユーザーを更新しようとすると、次の動作が発生し、デッドロックが発生する可能性があります。

  • トランザクション A がユーザー X とユーザー Y の更新を試みる
  • トランザクション B はユーザー Y を更新し、次にユーザー X を更新しようとします

両方の要求が一緒に開始されるため、トランザクション A はユーザー X に対してロックを取得でき、トランザクション B はユーザー Y に対してロックを取得できますが、各要求が他のユーザーのロックを取得しようとするとすぐにブロックされ、デッドロックが発生します。

問題の例: トランザクションのデッドロック。

アクセスするリソースを一貫した方法で並べ替えるだけで、多くのデッドロックの状況を防ぐことができます。 順序付けメカニズムは、一貫性があり、可能な限り効率的に実行できる限り重要ではありません。 たとえば、ユーザーを名前または GUID で並べ替えることで、少なくともデッドロックを回避する一貫性のレベルを確保できます。

この方法を使用するシナリオでは、トランザクション A はユーザー X を取得しますが、トランザクション B はユーザー Y ではなくユーザー X を最初に取得しようとします。 つまり、トランザクション B はトランザクション A が完了するまでブロックされますが、このシナリオではデッドロックが回避され、正常に完了します。

一貫した方法でリソースを並べ替えることでデッドロックを回避する。

より複雑で効率的なシナリオでは、最も一般的に参照されていないユーザーを最初にロックし、より頻繁に参照されるユーザーを最後にロックし、次の設計パターンにつながる可能性があります。

最短期間、競合するロックを保持する

自動番号付けアプローチなど、ロックが必要なリソースが頻繁に争われるという事実を回避する方法がないシナリオがあります。 その場合、ブロックの問題を回避することはできませんが、最小限に抑えることができます。

リソースの競争が激しい場合は、プロセス内の機能的に論理的なポイントでそのリソースとの対話を含めず、トランザクションの最後にできるだけ近くに移動することをお勧めします。

この方法では、このリソースに対して何らかのブロックが引き続き発生しますが、リソースがロックされる時間が減るため、リソースの待機中に他の要求がブロックされる可能性と時間が短縮されます。

競合するロックを可能な限り短い期間保持します。

トランザクションの長さを減らす

同様に、2 つのプロセスが同時に同じリソースにアクセスする必要がある場合にのみ、ロックがブロックの問題になります。 ロックを保持するトランザクションが短くなればなるほど、2つのプロセスが同じリソースにアクセスしたとしても、それらがまったく同時に同じリソースを必要とし、衝突を引き起こす可能性は低くなります。 トランザクションが保持される時間が短いほど、ブロックが問題になる可能性が低くなります。

次の例では、同じロックが適用されますが、トランザクション内の他の処理は、トランザクションの全体の長さが拡張され、同じリソースに対する要求が重複することを意味します。 つまり、ブロックが発生し、各要求が全体的に低速になります。

問題の例: トランザクションの長さが長すぎるため、ブロックしています。

トランザクションの全体の長さを短くすることで、最初のトランザクションが完了し、2 番目の要求が開始される前にそのロックが解放されます。つまり、ブロックがなく、両方のトランザクションが効率的に完了します。

トランザクションの有効期間を延長する要求内の他のアクティビティは、特に複数の重複する要求がある場合にブロックする可能性が高くなり、システムが遅くなる可能性があります。

トランザクションの長さが減るため、ブロックが少なくなります。

トランザクションの長さを短くする方法は多数あります。

要求を最適化する

各トランザクションは、一連のデータベース要求で構成されます。 各要求が可能な限り効率的に行われると、トランザクションの全体の長さが減少し、競合の可能性が減少します。

作成した各クエリを評価し、次のことを判断します。

  • クエリでは、必要なもの (列、レコード、エンティティ型など) のみを要求します。

    • これにより、インデックスを使用してクエリに効率的にサービスを提供できる可能性が最大になります。
    • これにより、アクセスする必要があるテーブルとリソースの数が減り、データベース サーバー内の他のリソースのオーバーヘッドが減り、クエリ時間が短縮されます
    • 不要なリソースに対するブロックの可能性を回避します。特に、別のテーブルへの結合が要求されるが、回避できる場合や不要な場合
  • クエリを支援するインデックスが配置され、効率的な方法でクエリを実行し、スキャンではなくインデックスシークが行われている

    インデックスを導入しても、基になるテーブル内のレコードの作成/更新に対するロックが回避されないことに注目してください。 インデックス自体が変更される可能性がある場合、関連レコードが更新されると、インデックス内のエントリもロックされます。 インデックスが存在しても、この問題は完全には回避されません。

次の例では、関連するケースの取得が最適化されておらず、トランザクションの全体の長さが増え、スレッド間でブロックが発生します。

最適化されていない関連ケースの取得。

クエリを最適化することで、クエリの実行に費やす時間が短縮され、競合の可能性が低くなり、ブロックが減少します。

最適化された関連ケースの取得。

データベース サーバーがクエリを可能な限り効率的に処理できることを確認すると、トランザクションの全体的な時間が大幅に短縮され、ブロックの可能性が低下する可能性があります。

イベントのチェーンを減らす

前の例で示したように、関連するイベントの長いチェーンの結果は、トランザクション全体の時間に重大な影響を与える可能性があるため、ブロックが発生する可能性があります。 これは特に、同期プラグインとワークフローをトリガーし、他のアクションをトリガーし、さらに同期プラグインとワークフローをトリガーする場合です。

同期的に発生するイベントの長いチェーンを回避するために実装を慎重に確認および設計することは、トランザクションの全体の長さを減らすのに役立ちます。 これにより、ロックをより迅速に解放し、ブロックの可能性を減らすことができます。

また、セカンダリ ロックが大きな懸念事項になる可能性も低下します。 アカウント作成時の自動番号付け例では、最初は自動番号付けテーブルへのアクセスが主な問題ですが、1 つのシーケンスで多数の異なるアクションが実行されると、関連するユーザー レコードの更新などのセカンダリブロックも発生する可能性があります。 複数の競合リソースが関与すると、ブロックの回避がさらに困難になります。

一部のアクティビティを同期または非同期のどちらにする必要があるかを考慮すると、同じアクティビティが達成されるが、初期の影響は少ないことを意味する可能性があります。 特に、実行時間の長いアクションや、頻繁に競争するリソースに依存するアクションの場合、非同期アクションで実行することでメイン トランザクションから分離すると、大きな利点が得られます。 このアプローチは、ポリシー犯罪レポートを次の自動番号値で更新して連続する連続番号スキームを維持するなど、より広範なプラットフォームステップでアクションを完了または失敗する必要がある場合には機能しません。 このようなシナリオでは、影響を最小限に抑えるための他のアプローチを取る必要があります。

次の例に示すように、一部のアクションを非同期プロセスに移動するだけで(つまり、アクションはプラットフォーム トランザクションの外部で実行されます)、トランザクションの長さが短くなり、同時処理の可能性が高くなる可能性があります。

一部のアクションを非同期プロセスに移動すると、トランザクションが短縮されます。

同じレコードに対して複数の更新を行わないようにする

機能アクティビティの複数のレイヤーを設計する場合は、必要なアクションを論理的かつ簡単にフォローできるアクティビティフローに分割することをお勧めしますが、多くの場合、これは同じレコードに対して複数の個別の更新につながります。

ケースを処理するシナリオでは、最初に提起された顧客に基づいて既定の所有者でケースを更新し、その後、その顧客に自動的に通信を送りケースに対する最終連絡日を更新する別のプロセスを待機することは、機能的には完全に論理的です。

ただし、この課題は、Dataverse に対して同じレコードを更新する要求が複数存在することを意味し、多くの影響を及ぼします。

  • 各要求は個別のプラットフォーム更新であり、Dataverse サーバーに全体的な負荷を追加し、トランザクション全体の長さに時間を追加して、ブロックする可能性を高めます。
  • また、ケース レコードは、そのケースに対して最初に実行されたアクションからロックされます。つまり、トランザクションの残りの部分でロックが保持されます。 ケースに複数の並列プロセスがアクセスすると、他のアクティビティがブロックされる可能性があります。

同じレコードに対する更新を 1 つの更新ステップに統合し、トランザクションの後半で、全体的なスケーラビリティに大きなメリットを与える可能性があります。特に、レコードが作成後に頻繁に争われたり、複数のユーザーによってアクセスされたりする場合などです。

同じレコードの更新を 1 つのプロセスに統合するかどうかは、実装の複雑さと、個別の更新プログラムによって生じる可能性のある競合の可能性とのバランスに基づいて決定されます。 しかし、大量のシステムでは、これは非常に競争の激しいリソースに役立つ可能性があります。

必要なものだけを更新する

有益なアクティビティを除外して Dataverse システムの利点を減らさないことが重要ですが、多くの場合、ビジネス価値をほとんど追加せず、技術的な複雑さを促進するカスタマイズを含める要求が行われます。

タスクを作成するたびに、ユーザー レコードを現在割り当てられているタスクの数で更新すると、ユーザー レコードも大きく競合するため、ブロックのセカンダリ レベルが発生する可能性があります。 各リクエストがブロックして待機する必要があるかもしれない別のリソースが追加されますが、これはアクションに必ずしも重要ではありません。 この例では、ユーザーに対してタスクの数を格納することが重要か、またはカウントをオンデマンドで計算できるか、または Dataverse の階層とロールアップ フィールド機能をネイティブで使用するなど、別の場所に格納できるかを慎重に検討してください。

不要な更新プログラムを示す問題の例。

後で示すように、システム ユーザー レコードを更新すると、スケーラビリティの観点から悪影響を受ける可能性があります。

同じイベントでトリガーされる複数のカスタマイズ

同じイベントに対して複数のアクションをトリガーすると、それらのアクションが同じ関連オブジェクトまたは親オブジェクトと対話する可能性が高い要求の性質上、競合の可能性が高くなる可能性があります。

同じイベントでトリガーされる複数のカスタマイズ。

これは、特に異なるユーザーが異なるプロセスを実装する場合に、競合を見落としやすいため、慎重に検討または回避する必要があるパターンです。

さまざまな種類のカスタマイズを使用する場合

カスタマイズの種類ごとに、使用に異なる影響があります。 次の表では、一般的なパターン、それぞれを考慮して使用する必要がある場合、および使用に適していない場合を示します。

多くの場合、異なる動作間の妥協点を考慮する必要があります。これにより、考慮すべき一般的な特性とシナリオの一部についてのガイダンスが提供されますが、各シナリオを評価し、関連するすべての要因に基づいて適切なアプローチを選択する必要があります。

事前フェーズ/事後フェーズ Sync/async (同期/非同期) カスタマイズの種類 いつ使用するか 使用を避けるべき場合
事前検証 同期 プラグイン 入力値の短期的な検証 実行時間の長いアクション。

後の手順で失敗した場合にロールバックする必要がある関連項目を作成する場合。
事前操作 同期 ワークフロー/プラグイン 入力値の短期的な検証。

プラットフォーム ステップの失敗時にロールバックする必要がある関連項目を作成する場合。
実行時間の長いアクション。

項目を作成し、結果の GUID を項目に対して格納する必要がある場合、プラットフォーム ステップは作成/更新します。
事後操作 同期 ワークフロー/プラグイン プラットフォームの手順に自然に従い、後の手順が失敗した場合にロールバックする必要がある短い実行アクション (たとえば、新しく作成されたアカウントの所有者のタスクの作成)。

作成された項目の GUID を必要とし、障害が発生した場合にプラットフォーム ステップをロールバックする必要がある関連項目の作成
実行時間の長いアクション。

障害がプラットフォーム パイプライン ステップの完了に影響しない場合。
イベント パイプラインに含まれていない 非同期 ワークフロー/プラグイン ユーザー エクスペリエンスに影響する中程度の長さのアクション。

障害が発生した場合にロールバックできないアクション。

障害が発生した場合にプラットフォーム ステップのロールバックを強制してはならないアクション。
実行時間が非常に長いアクション。

これらは Dataverse で管理しないでください。

低コストのアクション。 低コストのアクションに対して非同期動作を生成するオーバーヘッドは、非常に大きくなる可能性があります。可能であれば、これらを同期的に行い、非同期処理のオーバーヘッドを回避します。
N/A
呼び出し先のコンテキストを受け取ります
カスタム アクション Web リソースなど、外部ソースから起動されたアクションの組み合わせ プラットフォーム イベントに応答して常にトリガーされる場合は、そのような場合はプラグイン/ワークフローを使用します。

プラグイン/ワークフローはバッチ処理メカニズムではありません

実行時間の長いアクションやボリューム アクションは、プラグインやワークフローから実行することを意図していません。 Dataverse はコンピューティング プラットフォームを意図したものではなく、特に、無関係な更新プログラムの大規模なグループを推進するためのコントローラーとして意図されていません。

これを行う必要がある場合は、Azure worker ロールなどの別のサービスからオフロードして実行します。

セキュリティの設定

一般的なエスカレーション領域は、セキュリティの設定のスケーラビリティです。 これはコストのかかる操作であるため、ボリュームで実行すると、理解して慎重に検討しないと、常に課題が発生する可能性があります。

チームのセットアップ

  • 常に同じ順序でユーザーを追加する: デッドロックを回避する
  • 更新が必要な場合にのみユーザーを更新する: ユーザーのキャッシュを不必要に無効にしないようにする

所有者 v. アクセス チーム

  • ユーザーのチームが定期的に変更される場合は、所有者チームを頻繁に使用する場合に注意してください。変更するたびに、Web サーバー内のユーザーのキャッシュが無効になります
  • ユーザーが作業していないときに変更を加え、夜間などの影響を軽減するのが理想的です

たくさんのチーム メンバーシップ / BU

  • 多くのチームや事業部が計算の複雑さを増すシナリオを慎重に検討することが重要です。

カスケード動作

  • 割り当てなど、カスケード共有を検討する

ユーザー レコードの慎重な更新

  • ユーザー キャッシュが強制的に再読み込みされ、セキュリティ特権が再計算され、コストの高いアクティビティが発生するため、根本的な変更がない限り、システム ユーザー レコードを定期的に更新しないでください
  • システム ユーザーを使用して、ユーザーが持っている開いているアクティビティの数を記録しないでください (例:

予防措置として役立つアクティビティと、ブロックの問題を診断するためのツールは、Dataverse プラットフォームでトリガーされる関連アクションを図にすることです。 これを行うと、システム内の意図的な依存関係と意図しない依存関係とトリガーの両方が強調表示されます。 ソリューションに対してこれを実行できない場合は、実装が実際に何を行うかを明確に把握できない可能性があります。 このような図を作成すると、意図しない結果が発生する可能性があり、実装では常に適切な方法です。

次の例では、最初の 2 つのプロセスが完全に連携するしくみについて説明しますが、継続的なメンテナンスでは、タスクを作成するための新しいステップを追加すると、意図しないループが作成される可能性があります。 このドキュメント手法を使用すると、設計段階でこれを強調し、システムに影響を与えないようにすることができます。

関連するアクションを図で示します。

テレメトリとトレースを確認する

Application Insights 環境を設定して、Dataverse プラットホームで取得された診断とパフォーマンスに関するテレメトリを受信することができます。 Application Insights を使用して Dataverse テレメトリを分析する方法について説明します

Application Insights 環境を作成したら、プラグイン コードで Microsoft.Xrm.Sdk.PluginTelemetry.ILogger インターフェイス を使用して、Application Insights にテレメトリを書き込むことができます。 ILogger を使用して Application Insights リソースにテレメトリを書き込む方法について説明します

特定のエラーが発生している場合は、サーバー トレース ファイルを使用して、プラットフォームで関連する問題が発生している可能性がある場所を理解することもできます。 詳細情報: トレースを使用

概要

Dataverse のスケーラブルなカスタマイズ設計の内容と、以降の記事「データベース トランザクション」、コンカレンシーの問題、およびこの記事では、Dataverse のスケーラブルなカスタマイズを設計および実装する方法を理解するのに役立つ例と戦略を使用して、次の概念について説明しました。

覚えておく必要がある重要な点を次に示します。

ロック/トランザクション

  • ロックとトランザクションは、正常なシステムにとって不可欠です
  • しかし、間違って使用すると、問題につながる可能性があります

プラットフォームの制約

  • 多くの場合、プラットフォームの制約はエラーの形式で示されます
  • しかし、制約が問題の原因になることはほとんどありません
  • プラットフォームやその他のアクティビティが影響を受けるのを防ぎ、

トランザクション利用のための設計

  • 実装がトランザクション動作を念頭に置いて設計されている場合、スケーラビリティが向上し、ユーザー パフォーマンスが向上する可能性があります