Azure Cosmos DB のデータ モデリング

適用対象: NoSQL

Azure Cosmos DB のようなスキーマのないデータベースでは、非構造化データと半構造化データの格納とクエリを非常に簡単に行うことができますが、パフォーマンスとスケーラビリティ、そしてコストを最小限に抑えるという観点からサービスを最大限に活用するには、時間をとってデータ モデルについて検討する必要があります。

データをどのように格納するか。 アプリケーションがどのようにデータを取得してクエリを実行するか。 ご使用のアプリケーションの負荷は読み取りと書き込みのどちらが高いか。

この記事を読むと、次の質問に回答できるようになります。

  • データのモデル化とは何か、なぜ考慮する必要があるか。
  • データのモデル化は、Azure Cosmos DB とリレーショナル データベースでどのように異なるか。
  • 非リレーショナル データベース内のデータのリレーションシップをどのように表現するか。
  • いつデータを埋め込み、いつデータにリンクするか。

JSON の数値

Azure Cosmos DB では JSON でドキュメントが保存されます。 つまり、JSON で格納する前に数値を文字列に変換する必要があるか慎重に判断する必要があります。 IEEE 754 binary64 に従って倍精度数値の境界外にある可能性がある場合は、数値を String に変換するのが理想的です。 JSON 仕様 では、一般的にこの境界の外で数値を使用することが相互運用性の問題が原因らしい不適切な方法である理由が示されています。 これらの問題は、パーティション キー列に特に関連します。これは不変であり、後で変更するにはデータ移行が必要であるためです。

データの埋め込み

Azure Cosmos DB のデータのモデル化を開始する際、JSON ドキュメントとして表現された自己完結型項目としてエンティティを扱ってみてください。

比較のために、まずはリレーショナル データベースのデータをモデル化する方法を見てみましょう。 次の例は、ある個人がどのようにリレーショナル データベースに格納されるかを示しています。

リレーショナル データベース モデル

リレーショナル データベースを使用する場合の方法は、データをすべて正規化することです。 データの正規化には通常、個人などのエンティティを取得し、個別のコンポーネントに分解する操作が含まれます。 その例では、1 人に対して複数の連絡先詳細レコードと複数の住所レコードが存在する可能性があります。 連絡先詳細は、種類などの共通フィールドを抽出することでさらに細分化できます。 同じことが住所にも当てはまり、各レコードの種類として自宅会社が考えられます。

データを正規化する際に指標となる前提は、各レコードについて 冗長なデータを格納するのではなく 、データを参照することです。 この例で、すべての連絡先詳細と住所を含む個人のデータを読み取るには、JOIN を使用して実行時にデータを効率的に作成し直す (非正規化する) 必要があります。

SELECT p.FirstName, p.LastName, a.City, cd.Detail
FROM Person p
JOIN ContactDetail cd ON cd.PersonId = p.Id
JOIN ContactDetailType cdt ON cdt.Id = cd.TypeId
JOIN Address a ON a.PersonId = p.Id

1 人の連絡先詳細や住所で更新すると、多数の個別のテーブルへの書き込み操作が必要になります。

では、同じデータを Azure Cosmos DB 内の自己完結型エンティティとしてモデル化する方法を見ていきましょう。

{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "addresses": [
        {
            "line1": "100 Some Street",
            "line2": "Unit 1",
            "city": "Seattle",
            "state": "WA",
            "zip": 98012
        }
    ],
    "contactDetails": [
        {"email": "thomas@andersen.com"},
        {"phone": "+1 555 555-5555", "extension": 5555}
    ]
}

この方法を使用して、この個人に関するすべての情報 (連絡先詳細や住所など) を埋め込んで個人レコードを非正規化し、"シングルな JSON" ドキュメントにしました。 また、固定スキーマに限定されているわけではないので、まったく別の形で連絡先詳細を含めるような柔軟性もあります。

データベースから個人レコード全体を取得するのは、1 つのコンテナーと 1 つの項目に対する 1 回の読み取り操作です。 個人レコードの連絡先詳細や住所を更新することも、1 つの項目に対する 1 回の書き込み操作です。

データを非正規化することにより、アプリケーションが一般的な操作を完了するために発行する必要があるクエリや更新の数は少なくなります。

埋め込みをする場合

一般に、埋め込みデータ モデルを使用するのは次のような場合です。

  • エンティティ間に 包含された リレーションシップがある場合。
  • エンティティ間に one-to-few リレーションシップがある場合。
  • 頻繁には変更されない埋め込みデータがある場合。
  • 際限なく増大することはない埋め込みデータがある場合。
  • 頻繁にまとめてクエリが実行される埋め込みデータがある場合。

Note

通常、非正規化データ モデルでは 読み取り パフォーマンスが向上します。

埋め込みをしない場合

Azure Cosmos DB でよく使われる方法は、すべてを非正規化してすべてのデータを 1 つの項目に埋め込むことですが、この方法でうまくいかない場合もあります。

この JSON スニペットを見てください。

{
    "id": "1",
    "name": "What's new in the coolest Cloud",
    "summary": "A blog post by someone real famous",
    "comments": [
        {"id": 1, "author": "anon", "comment": "something useful, I'm sure"},
        {"id": 2, "author": "bob", "comment": "wisdom from the interwebs"},
        …
        {"id": 100001, "author": "jane", "comment": "and on we go ..."},
        …
        {"id": 1000000001, "author": "angry", "comment": "blah angry blah angry"},
        …
        {"id": ∞ + 1, "author": "bored", "comment": "oh man, will this ever end?"},
    ]
}

これは、典型的なブログや CMS システムをモデル化しようとしたときに、コメントが埋め込まれた投稿エンティティがどのようになるかを示したものです。 この例の問題点は、コメントの配列が無制限であること、つまり、1 つの投稿に付けることができるコメントの数に実質的な制限がないことです。 これは、項目のサイズが際限なく大きくなる可能性があるため、問題となる場合があり、そのような設計は避ける必要があります。

項目のサイズが増大すると、ネットワーク経由でデータを送信し、大規模に、項目の読み取りや更新を行う機能が影響を受けます。

このような場合は、次のデータ モデルを検討することをお勧めします。

Post item:
{
    "id": "1",
    "name": "What's new in the coolest Cloud",
    "summary": "A blog post by someone real famous",
    "recentComments": [
        {"id": 1, "author": "anon", "comment": "something useful, I'm sure"},
        {"id": 2, "author": "bob", "comment": "wisdom from the interwebs"},
        {"id": 3, "author": "jane", "comment": "....."}
    ]
}

Comment items:
[
    {"id": 4, "postId": "1", "author": "anon", "comment": "more goodness"},
    {"id": 5, "postId": "1", "author": "bob", "comment": "tails from the field"},
    ...
    {"id": 99, "postId": "1", "author": "angry", "comment": "blah angry blah angry"},
    {"id": 100, "postId": "2", "author": "anon", "comment": "yet more"},
    ...
    {"id": 199, "postId": "2", "author": "bored", "comment": "will this ever end?"}   
]

このモデルには、投稿識別子を含むプロパティを持つ各コメントのドキュメントがあります。 これにより、投稿に任意の数のコメントを含めて、効率的に成長させることができます。 最新のコメント以外も見たいというユーザーは、このコンテナーに対して、コメント コンテナーのパーティション キーとなる postId を渡してクエリを実行することになります。

また、データの埋め込みが推奨されない事例として、埋め込みデータが複数の項目にわたって頻繁に使用され、頻繁に変更される場合があります。

この JSON スニペットを見てください。

{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "holdings": [
        {
            "numberHeld": 100,
            "stock": { "symbol": "zbzb", "open": 1, "high": 2, "low": 0.5 }
        },
        {
            "numberHeld": 50,
            "stock": { "symbol": "xcxc", "open": 89, "high": 93.24, "low": 88.87 }
        }
    ]
}

これは、ある個人の株式ポートフォリオを表したものです。 各ポートフォリオ ドキュメントに株式情報が埋め込まれています。 株式取引アプリケーションのように関連データが頻繁に変化する環境では、埋め込みデータが頻繁に変更されるので、1 つの株式が取引されるたびに各ポートフォリオ ドキュメントを絶えず更新することになります。

株式 zbzb は 1 日に数百回取引され、数千人ものユーザーのポートフォリオに zbzb が含まれている可能性があります。 例のようなデータ モデルでは、毎日、数千ものポートフォリオ ドキュメントを何回も更新する必要があり、システムの拡張が不十分になる可能性もあります。

参照データ

データの埋め込みは多くの場合うまく機能しますが、データを非正規化すると、その効果よりも問題の方が多くなるシナリオがあります。 その場合は、どうすればよいでしょうか。

エンティティ間にリレーションシップを作成できる場所はリレーショナル データベースだけではありません。 ドキュメント データベース内では、あるドキュメント内に、他のドキュメント内のデータに関連する情報が含まれる場合があります。 リレーショナル データベースにより適しているであろうシステムを、Azure Cosmos DB やその他のドキュメント データベースで構築することはお勧めしませんが、シンプルなリレーションシップが十分に役割を果たし、有用な場合があります。

JSON では、前の株式ポートフォリオの例を使用していますが、ここでは株式に関する項目を埋め込むのではなく、ポートフォリオ上で参照しています。 これにより、株式の項目が終日頻繁に変更された場合でも、更新する必要があるのは 1 つの株式ドキュメントのみです。

Person document:
{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "holdings": [
        { "numberHeld":  100, "stockId": 1},
        { "numberHeld":  50, "stockId": 2}
    ]
}

Stock documents:
{
    "id": "1",
    "symbol": "zbzb",
    "open": 1,
    "high": 2,
    "low": 0.5,
    "vol": 11970000,
    "mkt-cap": 42000000,
    "pe": 5.89
},
{
    "id": "2",
    "symbol": "xcxc",
    "open": 89,
    "high": 93.24,
    "low": 88.87,
    "vol": 2970200,
    "mkt-cap": 1005000,
    "pe": 75.82
}

ただし、この方法にも欠点があります。アプリケーションで個人のポートフォリオを表示する際に、保有する各株式の情報を表示する必要がある場合、何度もデータベースにアクセスして、各株式ドキュメントの情報を読み込むことが必要になります。 ここで、終日頻繁に発生する書き込み操作の効率を向上させ、この特定のシステムにはあまり影響がないと考えられる読み取り操作の効率を下げるという意思決定を行いました。

Note

正規化されたデータ モデルは、サーバーに より多くのラウンド トリップを要求する可能性があります

外部キー

現在は制約の概念がないため、外部キーかどうかは別として、ドキュメント内にドキュメント間のリレーションシップがあったとしても、実際には "弱いリンク" であり、データベース自体によって検証されることはありません。 ドキュメントが参照しているデータが実際に存在していることを確認したい場合は、アプリケーション内で確認するか、または Azure Cosmos DB 上でサーバー側トリガーかストアド プロシージャを使用する必要があります。

参照を使用する場合

一般に、次のような場合に正規化されたデータ モデルを使用します。

  • 一対多 リレーションシップを表している。
  • 多対多 リレーションシップを表している。
  • 関連するデータが 頻繁に変更される
  • 参照されるデータに 制限がない可能性がある。

Note

通常は、正規化により 書き込み パフォーマンスが向上します。

リレーションシップの場所

リレーションシップの拡大により、どのドキュメントに参照を格納するかを決定しやすくなります。

発行元と書籍をモデル化する、JSON を見てみましょう。

Publisher document:
{
    "id": "mspress",
    "name": "Microsoft Press",
    "books": [ 1, 2, 3, ..., 100, ..., 1000]
}

Book documents:
{"id": "1", "name": "Azure Cosmos DB 101" }
{"id": "2", "name": "Azure Cosmos DB for RDBMS Users" }
{"id": "3", "name": "Taking over the world one JSON doc at a time" }
...
{"id": "100", "name": "Learn about Azure Cosmos DB" }
...
{"id": "1000", "name": "Deep Dive into Azure Cosmos DB" }

発行元あたりの書籍数が少なく、増加率が限定的な場合は、書籍の参照を発行元ドキュメント内に格納することが有効な場合もあります。 ただし、発行元あたりの書籍数に制限がない場合は、このデータ モデルは発行元ドキュメントに示すような、拡大し続ける可変配列になります。

位置を少し入れ替えることにより、モデルは、同じデータを表しながらも、このように大きい可変コレクションを回避できるようになります。

Publisher document:
{
    "id": "mspress",
    "name": "Microsoft Press"
}

Book documents:
{"id": "1","name": "Azure Cosmos DB 101", "pub-id": "mspress"}
{"id": "2","name": "Azure Cosmos DB for RDBMS Users", "pub-id": "mspress"}
{"id": "3","name": "Taking over the world one JSON doc at a time", "pub-id": "mspress"}
...
{"id": "100","name": "Learn about Azure Cosmos DB", "pub-id": "mspress"}
...
{"id": "1000","name": "Deep Dive into Azure Cosmos DB", "pub-id": "mspress"}

この例では、発行元ドキュメントで無制限のコレクションを切断しました。 代わりに、各書籍ドキュメントに発行元への参照が含まれているだけです。

多対多のリレーションシップはどのようにモデル化しますか?

リレーショナル データベースでは、"多対多" のリレーションシップは多くの場合、単に他のテーブルからのレコードを結合する結合テーブルを使用してモデル化されます。

テーブルの結合

ドキュメントを使用して同じ内容を複製し、次の例のようなデータ モデルを作成したくなるかもしれません。

Author documents:
{"id": "a1", "name": "Thomas Andersen" }
{"id": "a2", "name": "William Wakefield" }

Book documents:
{"id": "b1", "name": "Azure Cosmos DB 101" }
{"id": "b2", "name": "Azure Cosmos DB for RDBMS Users" }
{"id": "b3", "name": "Taking over the world one JSON doc at a time" }
{"id": "b4", "name": "Learn about Azure Cosmos DB" }
{"id": "b5", "name": "Deep Dive into Azure Cosmos DB" }

Joining documents:
{"authorId": "a1", "bookId": "b1" }
{"authorId": "a2", "bookId": "b1" }
{"authorId": "a1", "bookId": "b2" }
{"authorId": "a1", "bookId": "b3" }

これは、機能はします。 ただし、作者とその著作、または書籍とその作者の読み込みでは、常に、データベースに対して少なくとも 2 つの追加のクエリが必要になります。 1 つは結合するドキュメントに対するクエリ、もう 1 つは結合される実際のドキュメントを取得するクエリです。

この結合による処理が 2 つのデータ要素を結合することだけであれば、これを完全になくしたらどうでしょうか。 下記の例を検討してください。

Author documents:
{"id": "a1", "name": "Thomas Andersen", "books": ["b1", "b2", "b3"]}
{"id": "a2", "name": "William Wakefield", "books": ["b1", "b4"]}

Book documents:
{"id": "b1", "name": "Azure Cosmos DB 101", "authors": ["a1", "a2"]}
{"id": "b2", "name": "Azure Cosmos DB for RDBMS Users", "authors": ["a1"]}
{"id": "b3", "name": "Learn about Azure Cosmos DB", "authors": ["a1"]}
{"id": "b4", "name": "Deep Dive into Azure Cosmos DB", "authors": ["a2"]}

ここで、作者がわかっていれば、その作者の著作はすぐにわかります。そして逆に、書籍ドキュメントを読み込んでいれば、作者の ID がわかります。 これにより、結合テーブルに対する中間クエリは省略され、アプリケーションで行う必要があるサーバー ラウンド トリップの数が減少します。

ハイブリッド データ モデル

ここまで、データの埋め込み (または非正規化) と参照 (または正規化) について見てきました。 どのアプローチにも、アップサイドと侵害があります。

必ずしもどちらか一方にする必要はなく、両方を多少併用してもかまいません。

アプリケーション固有の使用パターンと負荷に基づき、埋め込みデータと参照データの併用が理にかなっていて、結果として、適切なパフォーマンス レベルを維持しながらアプリケーション ロジックがシンプルになり、サーバー ラウンド トリップが少なくなる場合もあります。

以下の JSON を検討してみてください。

Author documents:
{
    "id": "a1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "countOfBooks": 3,
    "books": ["b1", "b2", "b3"],
    "images": [
        {"thumbnail": "https://....png"}
        {"profile": "https://....png"}
        {"large": "https://....png"}
    ]
},
{
    "id": "a2",
    "firstName": "William",
    "lastName": "Wakefield",
    "countOfBooks": 1,
    "books": ["b1"],
    "images": [
        {"thumbnail": "https://....png"}
    ]
}

Book documents:
{
    "id": "b1",
    "name": "Azure Cosmos DB 101",
    "authors": [
        {"id": "a1", "name": "Thomas Andersen", "thumbnailUrl": "https://....png"},
        {"id": "a2", "name": "William Wakefield", "thumbnailUrl": "https://....png"}
    ]
},
{
    "id": "b2",
    "name": "Azure Cosmos DB for RDBMS Users",
    "authors": [
        {"id": "a1", "name": "Thomas Andersen", "thumbnailUrl": "https://....png"},
    ]
}

ここでは、(主に) 埋め込みモデルに従います。この埋め込みモデルでは、他のエンティティのデータが最上位のドキュメントに埋め込まれていますが、他のデータは参照されています。

書籍ドキュメントを見ると、作者の配列にいくつか興味深いフィールドがあります。 正規化されるモデルでの標準的な手法として、戻って作者ドキュメントを参照するために使用するフィールドである id フィールドがありますが、namethumbnailUrl もあります。 id の使用を続け、アプリケーションに、 "リンク" を使用して必要な追加情報をそれぞれの作者ドキュメントから取得させることはできますが、アプリケーションは、表示される書籍ごとに作者の名前とサムネイル写真を表示するので、作者の一部のデータを非正規化することにより、一覧表にある書籍ごとにサーバーとやりとりしないで済みます。

確かに、作者名が変更された場合や、作者が写真を更新したい場合は、これまでに発行されたすべての書籍に対して更新を行う必要がありますが、このアプリケーションでは、作者名は頻繁には変更されないという前提に基づいており、これは許容できる設計上の判断です。

この例には事前に計算された集計の値があり、読み取り操作の処理時間を削減しています。 この例の作者ドキュメントに埋め込まれたデータの一部は、実行時に計算されるデータです。 新しい書籍が発行されるたびに、書籍ドキュメントが作成され、 さらに その作者に対応する書籍ドキュメントの数に基づいて計算された値が countOfBooks フィールドに設定されます。 この最適化は、読み取りの負荷が高く、読み取りを最適化するために書き込み時に計算を実行する余裕のあるシステムに適しています。

モデルに事前計算フィールドを含める機能は、Azure Cosmos DB がマルチドキュメント トランザクションをサポートしていることにより実現されています。 多くの NoSQL ストアでは、ドキュメント間のトランザクションを実行することができません。したがってこの制限のために "常にすべてを埋め込む"、といった設計上の決定が擁護されています。 Azure Cosmos DB では、ACID トランザクション内で書籍の挿入や作者の更新をすべて実行する、サーバー側トリガー、またはストアド プロシージャを使用できます。 これで、データの整合性を維持するためだけに、1 つのドキュメントにすべてを埋め込む必要はなくなります

さまざまなドキュメントの種類を区別する

一部のシナリオで、さまざまなドキュメントの種類を同じコレクション内に混在させたい場合があります。複数の関連ドキュメントを同じ パーティション に含めたい場合などがこれに当たります。 たとえば、書籍とブック レビューを同じコレクションに入れて、bookId でパーティション分割することができます。 そのような場合は、通常、それらを差別化するために種類を識別するフィールドをドキュメントに追加することができます。

Book documents:
{
    "id": "b1",
    "name": "Azure Cosmos DB 101",
    "bookId": "b1",
    "type": "book"
}

Review documents:
{
    "id": "r1",
    "content": "This book is awesome",
    "bookId": "b1",
    "type": "review"
},
{
    "id": "r2",
    "content": "Best book ever!",
    "bookId": "b1",
    "type": "review"
}

Azure Synapse Link for Azure Cosmos DB は、クラウド ネイティブのハイブリッド トランザクションと分析処理 (HTAP) の機能です。これを使用すると、Azure Cosmos DB のオペレーショナル データに対してリアルタイムに近い分析を実行できます。 Azure Synapse Link によって、Azure Cosmos DB と Azure Synapse Analytics の間に緊密でシームレスな統合が作成されます。

この統合は、トランザクション ワークロードに影響を与えることなく大規模な分析を可能にする、トランザクション データの列形式の表現である Azure Cosmos DB 分析ストアを通じて行われます。 この分析ストアは、大規模なオペレーショナル データ セットに対する高速でコスト効率の高いクエリに適しており、データのコピー処理が生じたり、トランザクション ワークロードのパフォーマンスに影響したりすることがありません。 分析ストアを有効にしてコンテナーを作成する場合や、既存のコンテナーで分析ストアを有効にする場合は、トランザクションのすべての挿入、更新、削除がほぼリアルタイムに分析ストアと同期され、変更フィードや ETL ジョブは必要ありません。

Azure Synapse Link の使用時には、Azure Synapse Analytics からお使いの Azure Cosmos DB コンテナーに直接接続し、要求ユニット コストなしで分析ストアにアクセスできるようになりました。 Azure Synapse Analytics では、現在、Synapse Apache Spark およびサーバーレス SQL プールとの Azure Synapse Link がサポートされています。 グローバルに分散された Azure Cosmos DB アカウントがある場合、コンテナーの分析ストアを有効にした後、そのアカウントのすべてのリージョンでそれを使用できるようになります。

分析ストアの自動スキーマ推論

Azure Cosmos DB トランザクション ストアは、行指向の半構造化データと考えられていますが、分析ストアには列形の構造化された形式があります。 この変換は、分析ストアのスキーマ推論規則を使用して、ユーザーのために自動的に行われます。 この変換プロセスには、入れ子になったレベルの最大数、プロパティの最大数、サポートされないデータ型などの制限があります。

Note

分析ストアのコンテキストでは、次の構造がプロパティと見なされます。

  • JSON の "要素" または ": で区切られた文字列/値のペア"。
  • {} で区切られた JSON オブジェクト。
  • [] で区切られた JSON 配列。

以下の手法を使用して、スキーマ推論変換の影響を最小限に抑え、分析機能を最大限に活用することができます。

正規化

Azure Synapse Link の利用時には、T-SQL または Spark SQL を使用してコンテナー間で結合を実行できるため、正規化は意味を失います。 正規化の期待される利点は以下のとおりです。

  • トランザクション ストアと分析ストアの両方でデータ フットプリントがより小さくなる。
  • トランザクションがより小さくなる。
  • ドキュメントあたりのプロパティがより少なくなる。
  • データ構造で、入れ子になったレベルがより少なくなる。

これらの要因のうちの最後の 2 つ、プロパティとレベルの数が少なくなることは、分析クエリのパフォーマンスに貢献しますが、データの一部が分析ストアで表現されない可能性も少なくなります。 自動スキーマ推論規則に関する記事で説明したように、分析ストアで表現されるレベルとプロパティの数には制限があります。

正規化についての別の重要な要因は、Azure Synapse の SQL サーバーレス プールでは最大 1,000 列の結果セットがサポートされており、入れ子になった列の公開時にも、その制限に向けてカウントが進むという点です。 言い換えると、分析ストアと Synapse SQL サーバーレス プールの両方に、プロパティ数は 1,000 個という制限があります。

しかし、非正規化は Azure Cosmos DB の重要なデータ モデリング手法であるのに、どうすべきなのでしょうか。 実際のトランザクション ワークロードと分析ワークロードに適したバランスを見つける必要がある、というのがその答えです。

Partition Key

Azure Cosmos DB パーティション キー (PK) は、分析ストアでは使用されません。 そして、分析ストアのカスタム パーティション分割を利用し、必要な任意の PK を使用して分析ストアのコピーを作成できるようになっています。 この分離により、データ インジェストとポイント読み取りに重点を置いてトランザクション データの PK を選択できます。その一方、クロス パーティション クエリは、Azure Synapse Link を使用して実行できます。 例を見てみましょう。

グローバルな仮定の IoT シナリオにおいて、すべてのデバイスに類似したデータ ボリュームがあり、それを使用すればホット パーティションの問題は発生しないため、device id は適切な PK です。 しかし、"昨日のデータすべて" や "都市ごとの合計" など、複数のデバイスのデータを分析する場合、それらはクロス パーティション クエリであるため問題が生じる可能性があります。 それらのクエリは、実行のために要求ユニットのスループットの一部を使用するので、トランザクションのパフォーマンスを低下させるおそれがあります。 しかし Azure Synapse Link の利用時には、これらの分析クエリを要求ユニット コストなしで実行できます。 分析ストアの列形式は、分析クエリ用に最適化されており、Azure Synapse Link ではこの特性を適用して、Azure Synapse Analytics ランタイムで優れたパフォーマンスを実現します。

データ型とプロパティ名

自動スキーマ推論規則に関する記事に、サポートされているデータ型の一覧が記載されています。 サポートされていないデータ型は分析ストアでの表現を妨げますが、サポートされているデータ型は、Azure Synapse ランタイムでは異なる方法で処理される場合があります。 1 つ例を示します。ISO 8601 UTC 標準に従う DateTime 文字列を使用する場合、これらの列は、Azure Synapse の Spark プールでは文字列として表され、Azure Synapse の SQL サーバーレス プールでは varchar(8000) として表されます。

もう 1 つの課題は、すべての文字が Azure Synapse Spark で受け付けられるわけではないという点です。 空白は受け付けられますが、コロン、グレーブ アクセント、コンマのような文字は受け付けられません。 お使いの文書に "First Name, Last Name" という名前のプロパティがあるとします。 このプロパティは分析ストアでは表現されて、Synapse SQL サーバーレス プールでは問題なく読み取ることができます。 しかし、それは分析ストア内にあるため、Azure Synapse Spark では、他のすべてのプロパティを含め、分析ストアからどのデータも読み取ることができません。 1 日の終わりに、サポートされていない文字が名前で使用されているプロパティが 1 つあると、Azure Synapse Spark を使用することができません。

データのフラット化

Azure Cosmos DB データのルート レベルにあるすべてのプロパティは、分析ストアでは列として表現され、ドキュメント データ モデルの、より深いレベルにあるその他のプロパティはすべて、この場合も入れ子になった構造の JSON として表現されます。 入れ子になった構造では、構造化されていない形式でデータをフラット化するために、Azure Synapse ランタイムによる追加の処理が必要になります。これは、ビッグ データ のシナリオで問題になる可能性があります。

このドキュメントは、分析ストアに idcontactDetails という 2 つの列だけを含むものになります。 その他のすべてのデータである emailphone は、個別に読み取るために、SQL 関数による追加の処理を必要とします。


{
    "id": "1",
    "contactDetails": [
        {"email": "thomas@andersen.com"},
        {"phone": "+1 555 555-5555"}
    ]
}

このドキュメントは、分析ストアに idemailphone という 3 つの列を含むものになります。 すべてのデータは、列として直接アクセスできます。


{
    "id": "1",
    "email": "thomas@andersen.com",
    "phone": "+1 555 555-5555"
}

データの階層化

Azure Synapse Link を使用すると、以下の観点からコストを削減できます。

  • トランザクション データベースで実行されるクエリの数が少なくなります。
  • データ インジェストとポイント読み取りに最適化された PK により、データ フットプリント、ホット パーティション シナリオ、パーティション分割が減少します。
  • 分析有効期限 (attl) がトランザクション有効期限 (tttl) とは独立しているためにデータが階層化されます。 トランザクション データはトランザクション ストアに数日、数週間、数か月保管し、データは分析ストアに何年もの間、または長い間保管することができます。 分析ストアの列形式では、自然なデータ圧縮が 50% から 90% までに達します。 また、GB あたりのそのコストは、トランザクション ストアの実際の価格の約 10% です。 現在のバックアップの制限事項の詳細については、分析ストアの概要に関するページを参照してください。
  • ETL ジョブが環境内で実行されていない場合は、要求ユニットをプロビジョニングする必要がないことを意味します。

制御された冗長性

これは、データ モデルが既に存在していて変更できない状況での最適な代替手段です。 そして、入れ子になったレベルの制限や、プロパティの最大数のような自動スキーマ推論規則が原因で、既存のデータ モデルが分析ストアにうまく適合しません。 これに該当する場合は、Azure Cosmos DB 変更フィードを利用してデータを別のコンテナーにレプリケートし、Azure Synapse Link の扱いやすいデータ モデルのために必要な変換を適用できます。 例を見てみましょう。

シナリオ

コンテナー CustomersOrdersAndItems は、顧客や商品の詳細 (請求先住所、配送先住所、配送方法、配送状況、商品価格など) を含むオンライン注文を格納するために使用されます。最初の 1,000 個のプロパティだけが表現されて、重要な情報が分析ストアに含まれていないので、Azure Synapse Link の使用が妨げられます。 コンテナーには、用途を変更してデータを再モデル化することができない PB のレコードがあります。

問題のもう 1 つの観点は、ビッグ データの量です。 何十億行もの行が絶えず分析部門によって使用され、それによって、古いデータの削除のために tttl を使用することができなくなっています。 分析ニーズのためにトランザクション データベースでデータ履歴全体を維持すると、絶えず要求ユニットのプロビジョニングを増やさなければならず、コストに影響します。 トランザクションと分析のワークロードが、同時に同じリソースを求めて競合しています。

どうすればよいでしょうか。

変更フィードを使用したソリューション

  • エンジニアリング チームは、変更フィードを使用して、3 つの新しいコンテナー CustomersOrdersItems を作成することを決定しました。 変更フィードを使用して、データの正規化とフラット化を行う予定です。 不要な情報はデータ モデルから削除され、各コンテナーのプロパティ数は 100 近くとなり、自動スキーマ推論の制限によるデータ損失が回避されます。
  • これらの新しいコンテナーでは分析ストアが有効になっていて、分析部門は Synapse Analytics を使用してデータを読み取っています。分析クエリは Synapse Apache Spark とサーバーレス SQL プールで実行されるので、要求ユニットの使用量が減少しています。
  • コンテナー CustomersOrdersAndItems では、現在、6 か月間だけデータを保管するように tttl が設定されています。Azure Cosmos DB では GB あたり少なくとも 1 個の要求ユニットが存在するため、これでさらに要求ユニットの使用量を削減できます。 データが少なくなるほど、要求ユニットが少なくなります。

重要なポイント

この記事における最大の収穫は、スキーマのない状況でのデータのモデル化は依然として重要であると理解できたことです。

データの要素を画面で表現する方法が 1 つではないように、データをモデル化する方法も 1 つではありません。 使用するアプリケーションを理解し、アプリケーションがどのようにデータを作成、使用、処理するかを理解することが必要です。 次に、ここで示したガイドラインのいくつかを適用することにより、アプリケーションの当面のニーズに対応するモデルの作成を開始することができます。 アプリケーションの変更が必要な場合にも、スキーマのないデータベースの柔軟性を活用して、その変更を受け入れ、データ モデルを容易に進化させることができます。