CosmosDB 令人困惑的分区键

Confusing partitioning key of CosmosDB

我正在研究 CosmosDB 的 Python 个示例(请参阅 CosmosDB for Python),我看到容器定义如下:

    partition_key = PartitionKey(path='/id', kind='Hash')
    db.create_container(id=id, partition_key=partition_key)

阅读项目的代码:

response = container.read_item(item=doc_id, partition_key=doc_id)

现在我的困惑是为什么选择与唯一文档 ID 相同的分区键。那么,这里分区有什么用呢?

在我看来,分区是应用于共享一些公共组的键的东西,例如食物组的分区。

In my opinion, partition is something which applies over keys sharing some common group, for example partition over food groups.

这不完全正确。如果您查看 documentation,它表示您应该选择具有高基数的分区键。换句话说,属性 应该具有广泛的可能值。它应该是一个不会改变的值。您还需要注意,如果要更新或删除文档,则需要传递分区键。

后台发生的事情是,Cosmos 可以有多个服务器,从 1 到无穷大。它使用您的分区键对您的数据进行逻辑分区。但它仍然在一台服务器上。如果您的吞吐量超过 10K RU 或者如果您的存储超过 50GB,Cosmos 将自动拆分为 2 个物理服务器。这意味着您的数据被拆分到 2 个服务器中。拆分可以继续进行,直到每台服务器的最大吞吐量 < 10K RU 并且每台服务器的存储量 < 50GB。这就是 Cosmos 管理无限规模的方式。您可能会问如何预测文档可能进入哪个分区。答案是你不能。 Cosmos 使用您的分区键生成一个散列,其值介于 1 和服务器数量之间。

所以 doc id 是一个很好的分区键,因为它是唯一的并且可以有很大范围的值。

请注意,一旦 Cosmos 分区到多个服务器,即使您减少存储或减少吞吐量,目前也没有自动减少服务器数量的方法。

在我看来CosmosDB 的文档使用 id 作为分区键的示例是错误的。很可能是因为他们没有其他任何东西可以用作分区键。

对 CosmosDB 数据库进行分区时,您需要考虑几个因素。

首先,您需要了解域和流量模型。没有领域知识,几乎不可能想出优化的分区键。这(我认为)正是文档使用 id 作为分区键的原因,因为它是 unidentified 域中逻辑分区的最大可能数量。

这 100% 是一个错误的决定,除非您希望 id 作为您的分区键,因为您域中的某些要求表明了这一点。

Do not use id as partition key!

请记住,您可以通过选择一个好的分区键来管理 logical 个分区。但是,您不负责 physical 个分区,这使得扩展成为可能。 Azure 会随机将 x 个逻辑分区放在一个物理分区中。因此潜在的“热”逻辑分区可以位于一个物理分区中并形成一个“热”分片。

Defining the best partition key is almost impossible without the business domain knowledge.

选择最大可能的分区数(例如 id)并不是最优化的决定,并且会导致性能和扩展性能不佳。请记住,有些活动只能在单个分区内进行,不能在多个分区或容器之间进行。

例如:

  • 如果要在几个文档之间进行原子操作,只有当这些文档位于单个分区中时才有可能
  • 唯一字段仅在一个分区的上下文中是唯一的
  • 最大分区数将使您的大部分查询 multi-partition-queries 通常更昂贵。

Choose a partition key that at any given time distributes your traffic as well as storage in the most balanced way

了解业务后,您需要选择一个分区键,在任何给定时间以最平衡的方式分配您的流量和存储。

Be prepared to repartition your data.

开发软件并投入使用后,您可能会了解有关负载和用户的新信息,这可能会导致选择更优化的分区键。如果发生这种情况,请做好重新分区数据的准备。

在文档上定义一个 属性 只是为了分区的目的,即使它是另一个 属性 的值的精确副本。这将使您能够根据为该字段设置的任何值灵活地重新分区数据。

在我们的例子中,我们在每个文档上都有一个名为 section 的 属性,无论它在哪里以及它是什么类型。然后根据我们围绕每个容器中的文档类型的逻辑,我们有不同的方法来生成该值。

最后一个提示是,使用单一类型的容器将使分区更容易。例如,对仅包含用户的容器进行分区会更容易。但是,如果您将用户和订单放在一个容器中,那么您希望属于某个用户的订单与该用户位于同一分区。如果单个容器中有更多数据类型,这将变得更加复杂。