Azure Cosmos DB 中的数据建模

适用范围: NoSQL

尽管使用无架构数据库(例如 Azure Cosmos DB)能够十分方便地存储和查询非结构化与半结构化数据,但你应该花些时间来思考数据模型,以充分发挥服务的性能和可伸缩性优势,并尽量降低成本。

将如何存储数据? 应用程序将如何检索和查询数据? 应用程序是读取密集型还是写入密集型?

阅读本文之后,能够回答以下问题:

  • 什么是数据建模,我为什么应该关注?
  • Azure Cosmos DB 与关系数据库中的数据建模有何不同?
  • 如何在非关系型数据库中表示数据关系?
  • 我应何时嵌入数据和何时链接数据?

JSON 格式的数字

Azure Cosmos DB 将文档保存为 JSON 格式。 这意味着,有必要在将数字存储为 json 格式之前仔细确定是否需要将它们转换为字符串。 如果数字有可能超出 IEEE 754 binary64 规定的双精度数字边界,则所有数字最好都转换为 StringJson 规范说明了使用此边界之外的数字通常是一种 JSON 的不良做法的原因在于可能存在互操作性问题。 分区键列尤其需要担心这些,因为它不可变,需要数据迁移才能在稍后进行更改。

嵌入数据

开始对 Azure Cosmos DB 中的数据建模时,请尝试将实体视为使用 JSON 文档表示的自包含项

为了进行比较,让我们先了解一下关系数据库中的数据建模方式。 下面的示例演示了如何在关系型数据库中存储一个人的信息。

Relational database model

使用关系数据库时,策略是将所有数据规范化。 规范化数据通常涉及到将一个实体(例如某人)的信息分解为多个离散的组成部分。 在以上示例中,一个人可以有多个联系人详细信息记录,以及多个地址记录。 通过进一步提取通用字段(例如“类型”),可以进一步分解联系人详细信息。 这同样适用于地址,每条记录的类型可以是“家庭”或“工作”。

规范化数据时的指导前提是避免存储每个记录的冗余数据,并且引用数据。 在本示例中,若要读取某个人的所有联系人详细信息和地址信息,在运行时需要使用 JOINS 有效地重新撰写(或反规范化)数据。

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

更新一个人的联系方式和地址需要跨多个单独的表进行写入操作。

现在让我们了解如何将相同的数据建模为 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 文档,非规范化了其相关记录。 此外,因为我们不受固定架构的限制,所以我们可以灵活地执行一些操作,例如可以具有完全不同类型的联系人详细信息。

从数据库检索一条完整的人员记录现在成为针对单个容器和单个项的单个读取操作。 更新人员记录的联系人详细信息和地址也是针对单个项的单个写入操作

通过非规范化数据,应用程序需要发出的查询更少,并且更新为完成常见的操作。

何时嵌入

通常在下列情况下使用嵌入式数据模型:

  • 实体之间存在“包含” 关系。
  • 实体之间存在一对多关系。
  • 有些嵌入式数据不经常更改。
  • 有些嵌入式数据在未绑定的情况下不会增长。
  • 有些嵌入式数据会频繁地统一查询。

注意

通常非规范化数据模型具有更好的读取性能。

何时不嵌入

虽然 Azure Cosmos DB 的经验法则是将所有事物非规范化,并将所有数据嵌入到单个项中,但是这可能导致一些情况的发生,而这些情况是应该避免的。

以下面的 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 系统建模,那么具有嵌入式评论的发布实体可能就如上面的代码所示。 此示例中的问题是评论数组没有限制,这意味着任何单个发布的评论数都没有(实际)限制。 随着项大小的无限增大,这可能会成为一个问题,因此应避免这种设计。

随着项大小的不断增长,通过网络传输数据和大规模读取和更新项的能力会受到影响。

在此情况下,最好是考虑以下数据模型。

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?"}   
]

此模型的每个评论都有一个文档,其中的一个属性包含 post 标识符。 这允许帖子包含任意数量的评论,并且可以有效地增长。 如果用户想要查看的内容不止是最近的评论,则需通过传递 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 }
        }
    ]
}

这可以表示某人的股票投资组合。 我们已选择在每个投资组合文档中嵌入股票信息。 在一个相关数据频繁更改的环境中,例如股票交易应用程序,嵌入经常更改的数据将意味着每当进行一次股票交易,就需要更新每个投资组合文档,需要不停地更新。

在一天时间里股票 zbzb 可能交易成百上千次,并且数以千计的用户可能在其投资组合中具有股票 zbzb。 使用类似上面的数据模型,我们需要每天更新成千上万的投资组合文档许多次,导致系统无法很好地扩展。

引用数据

嵌入式数据在很多情况下都可以很好运作,但在一些情况下,非规范化数据会导致更多问题而得不偿失。 因此我们现在该怎么办?

关系数据库不是可以在实体之间创建关系的唯一数据库。 在文档数据库中,一个文档中的信息可能与其他文档中的数据相关。 我们不建议在 Azure Cosmos DB 或任何其他文档数据库中构建更适合于关系数据库的系统,但是简单关系是可以的,并且还可能非常有用。

在下面的 JSON 代码中我们选择使用前面的股票投资组合示例,但是这次我们引用了投资组合中的股票项目,而不是嵌入此项目。 在这种情况下,当一天当中股票项发生频繁更改时,仅有的需要更新的文档就是一个股票文档。

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
}

但是当前这种方法的缺点是当显示一个人的投资组合时,如果应用程序需要显示所持有的每个股票的信息,则需要多次访问数据库以加载每个股票文档的信息。 这里我们决定提高一天当中频繁发生的写操作的效率,但是这反过来会影响读取操作,读取操作对此特定系统的性能的潜在影响较小。

注意

规范化的数据模型可能需要更多的往返访问服务器

外键呢?

因为当前没有约束、外键或其他类似概念,所以文档中存在的任何文档间关系都是有效的“弱链接”,并且数据库不会验证此关系。 如果想要确保文档要引用的数据实际存在,那么需要在应用程序中进行此验证,或通过使用 Azure Cosmos DB 上的服务器端触发器或存储过程来验证。

何时引用

通常在下列情况下使用规范化的数据模型:

  • 表示一对多关系。
  • 表示多对多关系。
  • 相关数据频繁更改
  • 引用的数据可能没有限制

注意

通常规范化能够提供更好的写入性能。

将关系数据存储在何处?

关系的增长将有助于确定用于存储引用的文档。

让我们看看下面的对出版商和书籍进行建模的 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 China 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 China 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"}

在上面的示例中,我们删除了出版商文档中的无限制集合。 只在每个书籍文档中引用出版商。

如何对多对多关系建模?

在关系型数据库中,多对多关系通常使用联接表来建模,这种方法只是将其他表中的记录联接在一起。

Join tables

可能想要使用文档复制相同内容,并生成类似以下示例的数据模型。

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 China 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" }

此模型可行。 但是,加载一个作者及其书籍或加载一个书籍及其作者,将始终要求对数据库执行至少两次查询。 一次是对联接文档的查询,另一个查询用来获取联接的实际文档。

如果此联接只是将两个数据片段粘合在一起,那么为什么不将它完全删除? 请看下面的示例。

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 事务中插入书籍和更新作者信息等。 现在无需将所有数据嵌入一个文档,只需确保数据保持一致性 。

区分不同的文档类型

在某些情况下,你可能想要在同一个集合中混合使用不同的文档类型;当你想要将多个相关的文档放入同一个分区时,你往往会这样做。 例如,可将书籍和书籍评论放入同一个集合,并按 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 Cosmos DB 的 Azure Synapse Link 是一种云原生混合事务和分析处理 (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 事务存储被视为面向行的半结构化数据,但分析存储采用列和结构化格式。 系统会使用分析存储的架构推理规则为客户自动完成这种转换。 转换过程存在以下方面的限制:最大嵌套级别数目、最大属性数目、不支持的数据类型,等等。

注意

在分析存储的上下文中,我们将以下结构视为属性:

  • JSON“元素”或“由 : 分隔的字符串值对”。
  • JSON 对象,由 {} 分隔。
  • JSON 数组,由 [] 分隔。

使用以下技术可以最大程度地减轻架构推理转换造成的影响,并最大程度地提高分析能力。

标准化

规范化变得毫无意义,因为通过 Azure Synapse Link,可以使用 T-SQL 或 Spark SQL 在容器之间进行联接。 规范化的预期好处是:

  • 减少事务存储和分析存储中的数据占用空间。
  • 减小事务。
  • 减少每个文档的属性数。
  • 减少数据结构的嵌套级别数。

请注意,最后两个因素(减少属性数和减少级别数)不仅有助于提高分析查询的性能,而且还减少了部分数据不在分析存储中表示的可能性。 如有关自动架构推理规则的文章中所述,在分析存储中表示的级别和属性数目是有限制的。

规范化的另一个重要因素是 Azure Synapse 中的 SQL无服务器池支持最多包含 1000 列的结果集,并且公开嵌套列也计入该限制。 换言之,分析存储和 Synapse SQL无服务器池都限制为 1000 个属性。

但是,既然非规范化是 Azure Cosmos DB 的一项重要数据建模技术,我该怎么做呢? 答案是必须为事务和分析工作负载找到适当的平衡点。

分区键

分析存储中不使用 Azure Cosmos DB 分区键 (PK)。 现在可以使用所需的任何 PK 对分析存储的副本使用分析存储自定义分区。 由于这种隔离,可为事务数据选择一个 PK 并将重点放在数据引入和点读取上,而跨分区查询可以通过 Azure Synapse Link 来完成。 请看以下示例:

在假设的多区域 IoT 方案中,device id 是一个很好的 PK,因为所有设备都有类似的数据量,因此你不会有热分区问题。 但是若要分析多个设备的数据,例如“昨天的所有数据”或“每个城市的总计”,你可能会遇到问题,因为这是一些跨分区查询。 这些查询可能会损害事务性能,因为它们使用一部分吞吐量(以请求单位为单位)来运行。 但如果使用 Azure Synapse Link,可以运行这些分析查询而不产生请求单位成本。 分析存储列格式已针对分析查询进行优化,Azure Synapse Link 应用此特征来实现 Azure Synapse Analytics 运行时的优异性能。

数据类型和属性名称

有关自动架构推理规则的文章列出了支持的数据类型。 虽然不受支持的数据类型会阻碍分析存储中的表示,但 Azure Synapse 运行时能够以不同的方式处理受支持的数据类型。 一个示例是:在使用遵循 ISO 8601 UTC 标准的日期/时间字符串时,Azure Synapse 中的 Spark 池会将这些列表示为字符串,而 Azure Synapse 中的 SQL 无服务器池会将这些列表示为 varchar(8000)。

另一个挑战是 Azure Synapse Spark 并非接受所有字符。 虽然它接受空格,但不接受冒号、重音符和逗号等字符。 假设你的文档包含一个名为“First Name, Last Name”的属性。 此属性将在分析存储中表示,Synapse SQL 无服务器池也可以正常读取它。 但此属性位于分析存储中,而 Azure Synapse Spark 无法从分析存储中读取任何数据,包括所有其他属性。 总而言之,如果某个属性在其名称中使用了不受支持的字符,你就无法使用 Azure Synapse Spark。

数据平展

Azure Cosmos DB 数据根级别中的所有属性将在分析存储中表示为一列,文档数据模型的更深级别中的任何其他属性将同样在嵌套结构中表示为 JSON。 嵌套结构需要从 Azure Synapse 运行时进行额外的处理,以将数据平展为结构化格式,这在大数据方案中可能是一个挑战。

以下文档在分析存储中只包含两列:idcontactDetails。 所有其他数据(emailphone)需要通过 SQL 函数进行额外处理才可供单独读取。


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

以下文档在分析存储中包含三列:idemailphone。 可以列的形式直接访问所有数据。


{
    "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 用于存储在线订单,包括客户和商品详细信息:帐单邮寄地址、交付地址、交付方式、交付状态、商品价格等。只表示前 1000 个属性,关键信息不包含在分析存储中,这会阻止 Azure Synapse Link 的使用。 容器中包含 PB 量级的记录,无法更改应用程序以及为数据重新建模。

问题的另一个角度是大数据量。 分析部门经常使用数十亿行,导致他们无法使用 tttl 来删除旧数据。 由于分析需求,在事务数据库中维护整个数据历史记录迫使他们不断增加预配的请求单位,而这又会影响成本。 事务和分析工作负载同时争用相同的资源。

怎么办?

使用更改源的解决方案

  • 工程团队决定使用更改源来填充三个新容器:CustomersOrdersItems。 他们使用更改源来规范化和平展数据。 从数据模型中删除不必要的信息,使每个容器包含大约 100 个属性,这可以避免由于存在自动架构推理限制而导致的数据丢失。
  • 为这些新容器启用了分析存储。现在,分析部门正在使用 Synapse Analytics 来读取数据,从而减少了请求单位的使用,因为分析查询是在 Synapse Apache Spark 和无服务器 SQL 池中发生的。
  • 容器 CustomersOrdersAndItems 现在已将 tttl 设置为仅保留数据六个月,从而进一步减少了请求单位使用量,因为处理 Azure Cosmos DB 中每 GB 数据至少需要 10 个请求单位。 数据更少,单位更少请求。

要点

本文的最大的要点在于了解无架构环境下的数据建模的重要性一如既往。

就像有多种方法可在屏幕上表示一个数据片段一样,数据的建模方法也不会只有一种。 需要了解应用程序以及它如何生成、使用和处理数据。 然后,通过应用此处提供的一些准则,可以开始创建可满足应用程序当前需求的模型。 当应用程序需要进行更改时,可以使用无架构数据库的灵活性欣然接受更改,并轻松改进数据模型。

后续步骤