Documentdb 简明教程

DocumentDB - Partitioning

当数据库开始增长到超过 10GB 时,只需创建新集合,然后在越来越多的集合中传播或分区您的数据,即可轻松扩展。

迟早,10GB容量的单个集合将不足以容纳你的数据库。现在10GB听起来可能不算是很大数字,但是请记住,我们正在存储JSON文档,其中只是纯文本,而且即便考虑到索引的存储开销,你也可以在10GB中放入许多纯文本文档。

对于可扩展性而言,存储并不是唯一的问题。在某个集合中可用的最大吞吐量为每秒两千五百个请求单元,你可以通过S3集合获得这个吞吐量。因此,如果你需要更高的吞吐量,那么你还需要通过使用多个集合进行分区来扩展。扩展分区也称为 horizontal partitioning

可以使用许多方法对Azure DocumentDB中的数据分区。以下是最常见策略:

  1. Spillover Partitioning

  2. Range Partitioning

  3. Lookup Partitioning

  4. Hash Partitioning

Spillover Partitioning

溢出分区是最简单的策略,因为它没有分区键。当你对很多事情不确定时,它往往是个不错的开端。你可能不知道你是否需要扩展到单个集合之外,或者你需要添加多少集合,又或者你可能需要多快添加它们。

  1. 溢出分区从单个集合开始,并且没有分区键。

  2. 该集合开始增长,然后增长更多,再增长更多,直到你接近10GB限制。

  3. 当你达到90%的容量时,你溢出到新集合,并开始将它用于新文档。

  4. 一旦数据库扩展到大量集合,你可能希望转向基于分区键的策略。

  5. 执行此操作时,你需要根据你迁移到的任何策略,通过将文档移动到不同集合重新平衡数据。

Range Partitioning

最常见的策略之一是范围分区。使用这种方法,你可以确定文档分区键可能落入的值范围,并将文档定向到与该范围相对应的集合中。

  1. 文档通常使用此策略,你可以创建一个集合来保存落在定义的日期范围内的文档。当你定义足够小的范围时,你可以确信没有集合会超过其10GB限制。例如,可能出现一个场景,其中单个集合可以合理地处理整个月的文档。

  2. 大多数用户查询当前数据的情况也可能发生,该数据可能是本月的或可能是上个月的,但用户很少搜索较旧的数据。因此,你从6月开始使用S3集合,它是你可以购买的最昂贵的集合,并且提供你所能获得的最佳吞吐量。

  3. 在7月,你购买另一个S3集合来存储7月的数据,并且你将6月的数据缩小到一个较便宜的S2集合。然后,在8月,你得到另一个S3集合并缩小7月到S2,并将6月一直缩小到S1。它一个接一个地进行,在这种情况下,你一直保持当前数据可用以获得高吞吐量,并且以较低的吞吐量保持旧数据可用。

  4. 只要查询提供分区键,那么只有需要查询的集合才会被查询,并且不会像溢出分区中那样对数据库中的所有集合进行查询。

Lookup Partitioning

使用查找分区,你可以定义一个分区映射,根据其分区键将文档路由到特定集合。例如,你可以按区域分区。

  1. 将所有美国文档存储在一个集合中,将所有欧洲文档存储在另一个集合中,并将所有其他区域的文档存储在第三个集合中。

  2. 使用此分区映射和查找分区解析器可以根据每个文档中包含的区域属性(即分区键)找出在哪个集合中创建文档以及查询哪些集合。

Hash Partitioning

在哈希分区中,分区根据哈希函数的值分配,从而使你可以在多个分区中均匀地分布请求和数据。

这通常用于对大量不同客户端产生或使用的数据进行分区,并且对于存储用户配置文件、目录项等非常有用。

让我们来看一下使用 .NET SDK 提供的 RangePartitionResolver 对范围分区进行简单分区的示例。

Step 1 − 创建一个新的 DocumentClient,我们将在 CreateCollections 任务中创建两个集合。一个集合将包含用户 ID 以 A 到 M 开头的用户的文档,另一个集合将包含用户 ID 为 N 到 Z 的用户的文档。

private static async Task CreateCollections(DocumentClient client) {
   await client.CreateDocumentCollectionAsync(“dbs/myfirstdb”, new DocumentCollection {
      Id = “CollectionAM” });

   await client.CreateDocumentCollectionAsync(“dbs/myfirstdb”, new DocumentCollection {
      Id = “CollectionNZ” });
}

Step 2 − 为数据库注册范围解析器。

Step 3 − 创建一个新的 RangePartitionResolver<string>,即分区键的数据类型。此构造函数接收两个参数,分区键的属性名称以及字典,该字典是分片映射或分区映射,它只是我们为解析器预定义的范围列表和对应集合。

private static void RegisterRangeResolver(DocumentClient client) {

   //Note: \uffff is the largest UTF8 value, so M\ufff includes all strings that start with M.

   var resolver = new RangePartitionResolver<string>(
      "userId", new Dictionary<Range<string>, string>() {
      { new Range<string>("A", "M\uffff"), "dbs/myfirstdb/colls/CollectionAM" },
      { new Range<string>("N", "Z\uffff"), "dbs/myfirstdb/colls/CollectionNZ" },
   });

   client.PartitionResolvers["dbs/myfirstdb"] = resolver;
 }

必须在此处编码尽可能大的 UTF-8 值。否则,第一个范围将不会匹配到任何 M,除了单个 M,同样,第二个范围中的 Z 也是如此。因此,您可以将此编码值视为匹配分区键的通配符。

Step 4 − 在创建解析器后,使用当前的 DocumentClient 为数据库注册它。为此,只需将其赋给 PartitionResolver 的字典属性即可。

我们将针对数据库而不是集合(像您通常所做的那样)创建和查询文档,解析器将使用此映射将请求路由到适当的集合。

现在,让我们创建一些文档。首先,我们将为 userId Kirk 创建一个文档,然后为 Spock 创建一个文档。

private static async Task CreateDocumentsAcrossPartitions(DocumentClient client) {
   Console.WriteLine();
   Console.WriteLine("**** Create Documents Across Partitions ****");

   var kirkDocument = await client.CreateDocumentAsync("dbs/myfirstdb", new { userId =
      "Kirk", title = "Captain" });
   Console.WriteLine("Document 1: {0}", kirkDocument.Resource.SelfLink);

   var spockDocument = await client.CreateDocumentAsync("dbs/myfirstdb", new { userId =
      "Spock", title = "Science Officer" });
   Console.WriteLine("Document 2: {0}", spockDocument.Resource.SelfLink);
}

此处的第一个参数是到数据库的自我链接,而不是特定集合。如果没有分区解析器,这是不可能的,但如果有一个,它只会无缝地工作。

如果 RangePartitionResolver 工作正常,两个文档都将保存到数据库 myfirstdb 中,但我们知道 Kirk 存储在 A 到 M 的集合中,而 Spock 存储在 N 到 Z 的集合中。

让我们在 CreateDocumentClient 任务中调用这些文档,如下面的代码所示。

private static async Task CreateDocumentClient() {
   // Create a new instance of the DocumentClient
   using (var client = new DocumentClient(new Uri(EndpointUrl), AuthorizationKey)) {
      await CreateCollections(client);
      RegisterRangeResolver(client);
      await CreateDocumentsAcrossPartitions(client);
   }
}

执行上述代码后,您将收到以下输出。

**** Create Documents Across Partitions ****
Document 1: dbs/Ic8LAA==/colls/Ic8LAO2DxAA=/docs/Ic8LAO2DxAABAAAAAAAAAA==/
Document 2: dbs/Ic8LAA==/colls/Ic8LAP12QAE=/docs/Ic8LAP12QAEBAAAAAAAAAA==/

正如所见,由于两个文档位于两个不同的集合中,因此这两个文档的自我链接具有不同的资源 ID。