具体化されたビュー パターン

必要なクエリ操作用にデータが理想的に書式設定されていない場合は、1 つ以上のデータ ストア内のデータに対して事前設定されたビューを生成します。 このアプローチでは、効率的なクエリとデータ抽出をサポートし、アプリケーションのパフォーマンスを向上させることができます。

コンテキストと問題

データを格納する場合、開発者とデータ管理者は、多くの場合、データの読み取り方法ではなく、データの格納方法に優先順位を付けます。 選択したストレージ形式には、通常、データの形式、データ サイズとデータの整合性を管理するための要件、および使用中のストアの種類が反映されます。 たとえば、NoSQLドキュメント ストアを使用する場合、多くの場合、データは一連の集計として表され、それぞれにそのエンティティのすべての情報が含まれます。

ただし、この方法はクエリに悪影響を及ぼす可能性があります。 クエリで一部のエンティティのデータのサブセットだけが必要な場合 (注文の詳細がまったく含まれていない、複数の顧客の注文の概要など)、必要な情報を取得するために、関連するエンティティのすべてのデータを抽出する必要があります。

読み取り時にインデックスを追加したり、クエリを整形したりしても、この非効率性が解決されるわけではありません。 書き込みパフォーマンスに影響を与えずに、任意の読み取りパターンに対して多くのストアのインデックスを再作成することはできません。 エンティティ間の集計は、クエリ時にコストが高いままです。 一部のストアでは、設計上、クエリ機能が制限されています。 これらの制約のため、ソース ストア内での読み取りパスの最適化だけでは不十分なことがよくあります。

ソリューション

効率的なクエリをサポートするために、一般的な解決策は、必要な結果セットに適した形式でデータを具体化するビューを事前に生成することです。 具体化されたビュー パターンでは、ソース データがクエリに適した形式ではない環境、適切なクエリの生成が難しい環境、またはデータやデータ ストアの性質に起因してクエリのパフォーマンスが低い環境で、データの事前設定されたビューを生成します。

このパターンでは、 具体化されたビュー は、1 つ以上のソース ストアから派生したデータを保持する読み取りモデルまたはプロジェクションです。 専用のアプリケーション コンポーネントまたはデータ パイプラインは、ストアの境界を越えてなど、プロジェクションを維持できます。 クエリコンシューマーは、プロジェクションを読み取り専用として扱います。 このアーキテクチャの概念は、データベース エンジンが独自の機能の制約に従って定義、格納、更新する、データベースネイティブの具体化されたビュー オブジェクトよりも広いです。

これらの具体化されたビューには、クエリに必要なデータのみが含まれており、アプリケーションは必要な情報をすばやく取得できます。 テーブルの結合やデータ エンティティの組み合わせに加え、具体化されたビューには、計算列またはデータ項目の現在の値、データ項目の値の組み合わせまたは変換の実行の結果、クエリの一部として指定された値を含めることができます。 具体化されたビューは、1 つのクエリ用に最適化することもできます。

重要なポイントは、具体化されたビューとそのビューに含まれるデータは、ソース データ ストアから完全に再構築できるため、完全に破棄可能であるということです。 クエリ コンシューマーはビューを直接更新しません。 代わりに、専用のコンポーネント、データ パイプライン、またはデータベース エンジンによって管理されるため、特殊なキャッシュになります。

ビューのソース データが変更されると、新しい情報を含むようにビューを更新する必要があります。 この更新は、自動的に行われるか、システムが元のデータへの変更を検出したときにスケジュールできます。 場合によっては、ビューを手動で再生成することが必要になる場合があります。 次の図は、具体化されたビュー パターンの使用方法の例を示しています。

具体化されたビュー パターンの使用方法の例を示す図。

問題と考慮事項

このパターンを実装する方法を決定するときは、次の点を考慮してください。

  • 更新戦略を表示します。 ソース データの変更を示すイベントに応じてビューが再生成されるのが理想的ですが、ソース データが急速に変化すると、このアプローチによって過剰なオーバーヘッドが発生する可能性があります。 代わりに、スケジュールされたタスク、外部トリガー、または手動アクションを使用してビューを再生成することを検討してください。

  • 更新動作。 実装が完全な再構築を実行するか、変更を増分的に適用するかを決定します。 また、リフレッシュ操作で読み取りをブロックするかどうかを判断する必要もあります。

    これらの決定は、クエリが古いマテリアライズド データを返す可能性があるかどうか、具体化されたデータと未処理のソース変更を組み合わせて現在の結果を返すか、更新が完了するまで最後の完全なバージョンを引き続き提供するかを決定します。

  • 信号の信頼性を更新します。 ビューの再生成をトリガーするアクティブ化シグナルが失われたり遅れたりした場合 (たとえば、変更フィード イベントが見落とされたり、スケジュールされたタスクが失敗したりした場合)、ビューは古い結果を自動的に処理します。 更新の新しさを監視し、ビューの経過時間が許容される陳腐化の範囲を超えた場合にアラートを発します。

  • コンピューティング コストを更新します。 ビューを再生成すると、ソース データの量と変換の複雑さに比例したコンピューティング リソースが消費されます。 急速に変化するソース データに対するイベント ドリブンの更新、または大規模な分析ビューの完全な再構築の場合、更新のコンピューティング コストは大きなコスト ドライバーになる可能性があります。 更新頻度とスコープのサイズを適切に設定し、データの鮮度とコンピューティングの支出のバランスを取る。

  • イベントソーシングの依存関係。 イベント ソーシング パターン を使用してデータを変更したイベントのみのストアを維持する場合など、一部のシステムでは、具体化されたビューが通常必要になります。 すべてのイベントを調べ、現在の状態を確認してビューを事前設定することが、イベント ストアから情報を取得する唯一の方法と考えられます。 イベント ソーシングを使用していない場合は、具体化されたビューが役立つかどうかを検討してください。 具体化されたビューは、1 つまたは少数のクエリに合わせて特別に調整される傾向があります。 多数のクエリを使用する場合、具体化されたビューによって、ストレージ容量の許容できない要件やストレージ コストが発生する可能性があります。

  • データの整合性。 ビューを生成するとき、およびビューを更新するときに、このプロセスがスケジュールに従って行われる場合は、データの整合性への影響を考慮してください。 ソース データがビューの生成と同時に変更された場合、ビュー内のデータのコピーは元のデータと完全には一致しません。 最大制約期間は、更新間隔またはイベント処理ラグの直接的な結果であるため、イベント ドリブン更新、スケジュール更新、手動更新のいずれかを選択する前に、許容される制約を定義します。

  • ストレージの場所を表示します。 ビューは、元のデータと同じストアまたはパーティションに配置する必要はありません。 いくつかの異なるパーティションのサブセットを組み合わせることができます。

  • 損失時にリビルドします。 ビューは、失われても再作成できます。 そのため、ビューが一時的であり、データの現在の状態を反映してクエリのパフォーマンスを向上させるためだけに使用される場合、またはスケーラビリティを向上させるためにのみ使用される場合は、キャッシュまたは信頼性の低い場所に格納できます。

    ただし、更新プロセス自体が途中で失敗した場合 (たとえば、スケジュールされた再生成タスクがクラッシュした場合)、ワークロードが以前の完全なビュー、部分的に更新されたビュー、または再生成が成功するまでビューをまったく処理しないかどうかを判断します。

    最も安全なアプローチは通常、アトミックな公開またはバージョン付きの置き換えです。これにより、新しいビューを構築して検証している間も、ワークロードは最後に完成したビューを引き続き提供し、それを使用できます。 検証が完了したら、新しいビューにスワップします。

  • 計算列。 具体化されたビューを定義する場合は、既存のデータ項目の計算または変換、クエリで渡された値、または必要に応じてこれらの値の組み合わせに基づいてデータ項目または列を追加することで、その値を最大化します。

  • インデックス作成を表示します。 ストレージ メカニズムでサポートされている場合は、パフォーマンスをさらに向上させるために、具体化されたビューのインデックス作成を検討します。 多くのリレーショナル データベースでは、ビューのインデックス作成がサポートされています。 ただし、ビューのインデックスメンテナンスでは、各更新サイクル中に書き込みパスのオーバーヘッドが増加するため、読み取りパフォーマンスの向上と追加の更新時間とコンピューティング コストのバランスを取ります。

  • ビューに対するアクセス制御。 具体化されたビューを使用して、セキュリティやプライバシー上の理由など、特定のコンシューマーに表示されるデータ サブセットを制限する場合、ビュー ストアはソース データと同じまたはより厳密なアクセス制御を適用する必要があります。 意図したスコープを超えるデータを誤って含むビューは保護されたデータを公開する可能性があるため、更新パイプラインでは意図しない列または行を除外する必要があります。

  • ビュー上のデータライフサイクル。 すべての具体化されたビューに、ソース データの保持と削除の要件を適用します。 必要な期間内にソースの削除とやり直しを伝達し、コンプライアンス監視に各ビューを含めます。 詳細については、「Microsoft Purviewを使用したデータ ガバナンスとセキュリティ ベースライン」を参照してください。

  • ライフサイクル管理を表示します。 ビュー定義は、ソース管理と CI/CD パイプラインによって管理される配置可能な成果物として扱います(特に、ビューが宣言によって定義されている場合)。 ライフサイクル管理がないと、ビュー定義が環境間で誤差を生じ、開発、ステージング、運用全体で一貫性のないクエリ動作が発生する可能性があります。

このパターンを使用する場合

このパターンは次の状況で使用します。

  • 直接クエリを実行するのが困難なデータに対するビューを作成する必要があります。また、正規化、半構造化、または非構造化の方法で格納されているデータを抽出するには、クエリが非常に複雑である必要があります。
  • クエリのパフォーマンスを向上させる、または UI、レポート、または表示用のデータ転送オブジェクトを構築するために使用されるデータを整形する、再構築可能または一時的なキャッシュプロジェクションを作成する必要があります。
  • データ ストアへの接続を常時利用できるとは限らないため、断続的な接続環境や切断状態のシナリオをサポートする必要があります。 この場合は、ビューをローカルにキャッシュできます。
  • ソース データ形式に関する知識を必要としない方法で、クエリを簡略化し、実験用のデータを公開したいと考えています。 たとえば、1 つ以上のデータベースのさまざまなテーブルや NoSQL ストアの 1 つ以上のドメインを結合して、最終的な用途に合わせてデータを書式設定する場合。
  • セキュリティまたはプライバシー上の理由から、一般にアクセスできない、変更を受け入れる、またはユーザーに完全に公開してはならないソース データの特定のサブセットへのアクセスを提供する必要がある。
  • 個々の機能を利用するために、さまざまなデータ ストアをブリッジしたいと考えています。 たとえば、参照データ ストアとして記述するのに効率的なクラウド ストアと、具体化されたビューを保持するための優れたクエリと読み取りパフォーマンスを提供するリレーショナル データベースを使用できます。
  • マイクロサービスを使用する場合は、データ ストレージを含め、疎結合のままにします。 具体化されたビューは、サービスからのデータを統合するのに役立ちます。 マイクロサービス アーキテクチャまたは特定のシナリオで具体化されたビューが適切でない場合は、 ドメイン駆動型設計 (DDD) に合わせて明確に定義された境界を持ち、必要に応じてデータを集計することを検討してください。

このパターンは、次の場合に適さない場合があります。

  • ソース データが単純で簡単に照会できる場合。
  • ソース データの変更のペースが非常に速い場合や、ビューを使用しなくてもアクセスできる場合。 このような場合は、ビューを作成する処理オーバーヘッドを回避してください。
  • 整合性は優先度が高い。 ビューが元のデータと常に完全に一致するとは限りません。

ワークロード設計

設計者は、Azure Well-Architected Framework の柱で説明されている目標と原則に対処するために、ワークロードの設計でどのように具体化されたビュー パターンを使用できるかを評価する必要があります。 例えば次が挙げられます。

柱 このパターンが柱の目標をサポートする方法
パフォーマンス効率 は、スケーリング、データ、およびコードの最適化を通じて、ワークロード の需要を効率的に満たすのに役立ちます。 具体化されたビューは、複雑な計算やクエリの結果を格納します。データベース エンジンまたはクライアントが要求ごとに再計算する必要はありません。 この設計により、全体的なリソース消費量が削減されます。

- PE:08 データパフォーマンス

このパターンによって柱内にトレードオフが生じる場合は、他の柱の目標に照らして検討してください。

Example

order、OrderItem、Customer エンティティをAzure Table Storageに格納する販売アプリケーションについて考えてみましょう。 注文は顧客 ID で、注文項目は注文 ID で、顧客はリージョンで、それぞれパーティション分割されます。 これらのキーはアプリケーションの運用アクセス パターンをサポートしますが、製品別にグループ化された売上レポートは、パーティション間でデータを読み取り、アプリケーション コードに組み合わせる必要があります。

次の図は、エレクトロニクス カテゴリの各製品の個別の購入顧客の総数と数を格納する具体化されたビューを示しています。 ソース行と集計値は例示であり、表示される合計の完全な入力データセットではありません。

Order、OrderItem、Customer テーブルを結合して作成された、製品カテゴリ別にパーティション分割されたマテリアライズド売上サマリーを示す図。

バックグラウンド プロセスでは、必要なソース エンティティを読み取り、注文項目を注文と顧客に関連付け、製品別に売上を集計します。 顧客が複数の注文または注文明細行を持っている場合でも、各顧客は製品ごとに 1 回カウントされます。 このプロセスでは、製品カテゴリが PartitionKey 、製品 ID が RowKeyとして、結果が個別のサマリー テーブルに書き込まれます。 この概要テーブルは、データベースネイティブの具体化されたビューではなく、アプリケーションが管理するプロジェクションです。

ダッシュボードでは、すべての要求に対してパーティション間の読み取りと集計を繰り返す代わりに、Electronics パーティションに対してクエリを実行できます。 1 つの製品の検索では、両方のキーが提供されます。 これらのキーのクエリ パフォーマンスへの影響については、「 クエリの設計」を参照してください。

レポートの許容される制約期間を満たすスケジュールで概要を更新します。 新しいバージョンを発行する前にビルドして検証し、再構築中に以前の完全なバージョンを引き続き使用できるようにします。 更新では引き続きパーティション間の読み取りと集計のコストが発生しますが、レポート クエリを繰り返すと結果が再利用されます。 ソースの変更は、その後の再読み込みで反映されるまで表示されません。

次のステップ

  • インデックス付きビューの作成 では、SQL データベースでビューにインデックスを作成することで、計算ビューの結果を保持する方法について説明します。
  • NoSQLのAzure Cosmos DBの変更フィード設計パターンでは、変更フィードコンシューマーが具体化されたビューを維持する方法について説明します。
  • 具体化されたビューの概要では、Azure Data Explorerの具体化されたビューについて説明します。
  • Azure Managed Redis では、事前計算済みのクエリ結果を、永続的なデータ ストアの前に読み取り最適化レイヤーとしてキャッシュできます。
  • Azure Table Storageは、アプリケーション ロジックまたはバックグラウンド プロセスによって生成された事前計算済みのビュー データを格納できます。

このパターンを実装する場合は、次のパターンも関連している可能性があります。

  • コマンド クエリ責務分離 (CQRS) パターン。 基になるデータ値が変更されたときに発生するイベントに応答して具体化されたビューの情報を更新するために使用します。
  • イベント ソーシング パターン。 CQRS パターンと共に使用して、具体化されたビューで情報を維持します。 具体化されたビューのデータ値が変更に基づいている場合、システムはこれらの変更を記述するイベントを発生させ、イベント ストアに保存できます。
  • Index Table Pattern (インデックス テーブル パターン)。 通常、具体化されたビューのデータは主キー別に整理されていますが、クエリで他のフィールドのデータを調べて、このビューから情報を取得することが必要な場合があります。 ネイティブ セカンダリ インデックスをサポートしていないデータ ストアのデータ セットに対してセカンダリ インデックスを作成するには、このパターンを使用します。