json - 将 Azure Cosmos DB 中存储的实体上的“扩展”元数据存储为 JSON 文档
问题描述
我们正在构建一个 .NET 中的 REST API,部署到 Azure App Service / Azure API App。通过此 API,客户端可以创建“产品”并查询“产品”。产品实体有一组通用字段,所有客户在创建产品时都必须提供这些字段,如下面的字段(示例)
{
"id": "cbf3f7aa-4743-4198-b307-260f703c42c1"
"name": "Product One"
"description": "The number one product"
}
我们目前将这些产品作为自包含文档存储在 Azure Cosmos DB 中。
问题1:分区。该集合不会存储大量文档,我们谈论最多大约 2 500 000 个文档,每个文档大小在 1 - 5 kb 之间(估计)。我们目前选择了 id 字段(这是我们系统生成的 id,而不是内部 Cosmos DB 文档 id)作为分区键,这意味着 2 500 000 个逻辑分区,每个分区一个文档。这些文档将用于一些低延迟的工作负载,但这些工作负载将通过 id(分区键)进行查询。客户端也将通过例如名称进行查询,然后我们有一个扇出查询,但这些查询不会是延迟关键的。在门户中,您无法再创建单个分区集合,但您可以从 SDK 中创建或使用固定的分区键值。如果我们将所有这些文档放在一个分区中(我们在这里讨论远低于 10 GB 的数据),我们永远不会得到任何扇出查询,而是更多地依赖一个逻辑分区内的索引。那么问题来了:即使我们没有大量数据,像我们目前所做的那样进行分区是否仍然明智?
问题 2:扩展元数据。我们将面对想要编写超出基本公共字段的客户端/应用程序/客户特定元数据的客户。做这个的最好方式是什么?
下面是我的一些头脑风暴。
1:只需将所有内容转储到一个独立的文档中。
一种选择是允许 API 中的客户端在创建产品时添加一种带有键值对的嵌套“扩展元数据”字段。Cosmos DB 与模式无关,因此理论上这应该可以正常工作。一些产品可以有零扩展元数据,而其他产品可以有很多扩展元数据。对于客户,我们可以承诺基本的公共字段,但对于扩展的元数据字段,我们不能承诺任何关于字段数量、命名等的内容。文档大小会有所不同。如前所述,这些产品仍将用于将通过“id”(分区键“)查询的延迟关键型工作负载。扩展的元数据将永远不会用于任何延迟关键型工作负载。一般会影响文档的程度和方式调整性能/吞吐量?对于延迟关键的读取场景,查询优化器会直接进入正确的分区,然后使用索引快速检索感兴趣的文档字段。或者整个文档是否总是独立于您要查询的字段而被加载和处理?
{
"id": "cbf3f7aa-4743-4198-b307-260f703c42c1"
"name": "Product One"
"description": "The number one product"
"extendedMetadta" : {
"prop1": "prop1",
"prop2": "prop2",
"propN": "propN"
}
}
扩展元数据仅在某些情况下对从同一 API 检索有用。然后我们可以做类似的事情:
- api.org.com/products/{id} - 将始终返回具有基本公共字段的产品
- api.org.com/products/{id}/extended -- 将返回完整文档(基本 + 扩展元数据)
2:拆分文档
一种选择可能是进行某种拆分。如果来自 API 的客户端创建了包含扩展元数据的产品,我们可以实现一些逻辑,如果扩展元数据包含数据,则拆分文档。我想拆分可以通过多种方式完成,下面是头脑风暴。我想拆分文档的主要目标(这需要更多的写入操作工作)是为了获得更好的吞吐量,以防文档大小在这里发挥重要作用(在大多数情况下,客户端可以使用基本的公共字段)。
- 一个只包含基本公共字段的基本文档,一个扩展文档(具有相同的id)包含基本公共字段+扩展元数据(基本公共字段的重复)我们可以添加一个“类型”字段来区分基本文件和扩展文件。如果客户要求扩展,我们只会查询“扩展”类型的文档。
- 一个仅包含基本公共字段的基本文档 + 对仅包含扩展元数据的扩展文档的引用。这意味着客户端请求具有扩展元数据的产品的读取操作需要读取两个文档。
- 考虑将其拆分为不同的集合,一个集合包含具有专用于低延迟读取场景的吞吐量的基本文档,一个集合用于扩展元数据。
对不起,很长的帖子。希望这是可以理解的,期待您的反馈!
解决方案
答案1:
如果您可以保证文档总大小永远不会超过 10GB,那么创建一个固定集合是可行的方法,原因有两个。首先,不需要跨分区查询。我并不是说没有分区会很快,而是因为您只与一个简单的物理分区进行交互,所以它比在每个物理分区中查找数据要快。
(但请记住,每当人们认为他们可以保证某物的最大尺寸之类的东西时,它通常都行不通。)
/id 分区策略只有在您始终可以提供 id 时才有效。这称为读取。如果您需要按任何其他属性进行搜索,这意味着您正在执行查询。这是系统做得不好的地方。
理想情况下,您应该以一种永远不会将跨分区查询作为日常工作负载的一部分的方式来设计 Cosmos DB 集合。出于报告的原因,也许有一次千载难逢。
答案 2:
Cosmos DB 是一个 NoSQL 无模式数据库是有原因的。头脑风暴中的第二种方法适用于传统的 RDBMS 数据库,但我们这里没有。您可以简单地采用第一种方法,将所有内容都放在一个属性下,或者将它们放在顶层。
请记住,您可以将响应映射到您想要的任何对象,因此您可以简单地拥有 2 个 DTO。精简版和扩展版,仅根据端点映射到不同的版本。
希望这可以帮助。
推荐阅读
- java - javassist_16 休眠中实体的转换错误
- php - Codeigniter 日志中出现奇怪的 404 错误
- gtk - 为什么 gtk css 小部件样式不起作用?
- java - JPA Hibernate 表不存在
- angular - 动态表单生成Angular 5
- ruby-on-rails - 美国邮政编码之间的距离计算
- user-interface - How to create a transparent UI in flutter?
- project-reactor - 为什么 .share() 对冷源没有影响(自动连接与 refCount)?
- mysql - 阻止 CakePHP 3 列出它使用 find() 生成的 SELECT 查询中的所有字段
- html - 从选定的画布图像想要获取选定图像大小的高度、宽度