CosmosDB 查询性能
CosmosDB Query Performance
我写了最新的更新,然后从 Stack Overflow 收到以下错误:"Body is limited to 30000 characters; you entered 38676."
公平地说,我在记录我的冒险经历时非常冗长,所以我重写了这里的内容以更加简洁。
我已经存储了我的(长)原始 post 和 pastebin 上的更新。我不认为很多人会读它们,但我为它们付出了很多努力,所以最好不要让它们丢失。
我有一个 collection,其中包含 100,000 个文档,用于学习如何使用 CosmosDB 以及性能测试等内容。
这些文档中的每一个都有一个 Location
属性,这是一个 GeoJSON Point
.
根据 documentation,一个 GeoJSON 点应该被自动索引。
Azure Cosmos DB supports automatic indexing of Points, Polygons, and LineStrings
我已经检查了我的 collection 的索引政策,它有自动点索引的条目:
{
"automatic":true,
"indexingMode":"Consistent",
"includedPaths":[
{
"path":"/*",
"indexes":[
...
{
"kind":"Spatial",
"dataType":"Point"
},
...
]
}
],
"excludedPaths":[ ]
}
我一直在寻找一种方法来列出或查询已创建的索引,但我还没有找到这样的东西,所以我无法确认这个 属性 肯定正在编入索引。
我创建了一个 GeoJSON Polygon
,然后用它来查询我的文档。
这是我的查询:
var query = client
.CreateDocumentQuery<TestDocument>(documentCollectionUri)
.Where(document => document.Type == this.documentType && document.Location.Intersects(target.Area));
然后我将该查询 object 传递给以下方法,这样我就可以在跟踪使用的请求单位的同时获得结果:
protected async Task<IEnumerable<T>> QueryTrackingUsedRUsAsync(IQueryable<T> query)
{
var documentQuery = query.AsDocumentQuery();
var documents = new List<T>();
while (documentQuery.HasMoreResults)
{
var response = await documentQuery.ExecuteNextAsync<T>();
this.AddUsedRUs(response.RequestCharge);
documents.AddRange(response);
}
return documents;
}
点位置是从数以百万计的英国地址中随机选择的,因此它们的分布应该相当真实。
多边形由16个点组成(首末点相同),所以不是很复杂。它覆盖了英国最南部的大部分地区,从伦敦向下。
此查询的一个示例 运行 在 170717.151 毫秒(不到 171 秒或不到 3 分钟)内使用 3917.92 RU 返回了 8728 个文档。
3918 卢/171 秒 = 22.91 RU/s
我目前将吞吐量 (RU/s) 设置为最低值,为 400 RU/s。
据我了解,这是您可以保证获得的保留级别。有时您可以 "burst" 高于该级别,但如果这样做过于频繁,您将被限制回保留的级别。
23 RU/s 的 "query speed" 显然远低于 400 RU/s 的吞吐量设置。
我运行正在连接客户端"locally",即在我的办公室,而不是在 Azure 数据中心。
每个文档的大小大约为 500 字节 (0.5 kb)。
所以发生了什么?
我是不是做错了什么?
我是否误解了关于 RU/s 我的查询是如何被限制的?
这是 GeoSpatial 索引运行的速度,所以我将获得最佳性能吗?
没有使用 GeoSpatial 索引吗?
有什么方法可以查看创建的索引吗?
有什么方法可以检查索引是否被使用?
有没有一种方法可以分析查询并获取有关时间花费的指标?例如s 用于按类型查找文档,s 用于地理空间过滤,s 用于传输数据。
更新 1
这是我在查询中使用的多边形:
Area = new Polygon(new List<LinearRing>()
{
new LinearRing(new List<Position>()
{
new Position(1.8567 ,51.3814),
new Position(0.5329 ,51.4618),
new Position(0.2477 ,51.2588),
new Position(-0.5329 ,51.2579),
new Position(-1.17 ,51.2173),
new Position(-1.9062 ,51.1958),
new Position(-2.5434 ,51.1614),
new Position(-3.8672 ,51.139 ),
new Position(-4.1578 ,50.9137),
new Position(-4.5373 ,50.694 ),
new Position(-5.1496 ,50.3282),
new Position(-5.2212 ,49.9586),
new Position(-3.7049 ,50.142 ),
new Position(-2.1698 ,50.314 ),
new Position(0.4669 ,50.6976),
new Position(1.8567 ,51.3814)
})
})
我也尝试过将其反转(因为圆环方向很重要),但是使用反转多边形的查询花费的时间明显更长(我没有时间处理)并返回了 91272 个项目。
此外,坐标被指定为 Longitude/Latitude、this is how GeoJSON expects them(即 X/Y),而不是传统的 Latitude/Longitude 顺序。
The GeoJSON specification specifies longitude first and latitude second.
更新 2
这是我的一份文件的 JSON:
{
"GeoTrigger": null,
"SeverityTrigger": -1,
"TypeTrigger": -1,
"Name": "13, LONSDALE SQUARE, LONDON, N1 1EN",
"IsEnabled": true,
"Type": 2,
"Location": {
"$type": "Microsoft.Azure.Documents.Spatial.Point, Microsoft.Azure.Documents.Client",
"type": "Point",
"coordinates": [
-0.1076407397346815,
51.53970315059827
]
},
"id": "0dc2c03e-082b-4aea-93a8-79d89546c12b",
"_rid": "EQttAMGhSQDWPwAAAAAAAA==",
"_self": "dbs/EQttAA==/colls/EQttAMGhSQA=/docs/EQttAMGhSQDWPwAAAAAAAA==/",
"_etag": "\"42001028-0000-0000-0000-594943fe0000\"",
"_attachments": "attachments/",
"_ts": 1497973747
}
更新 3
我创建了该问题的最小复现,我发现该问题不再发生。
说明确实是我自己的代码出了问题
我着手检查原始代码和复制代码之间的所有差异,最终发现对我来说似乎相当无辜的东西实际上产生了很大的影响。值得庆幸的是,根本不需要该代码,因此只需不使用该代码即可轻松修复。
有一次我使用的是自定义的 ContractResolver
,但当它不再需要时我没有删除它。
这是有问题的复制代码:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Spatial;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Repro.Cli
{
public class Program
{
static void Main(string[] args)
{
JsonConvert.DefaultSettings = () =>
{
return new JsonSerializerSettings
{
ContractResolver = new PropertyNameMapContractResolver(new Dictionary<string, string>()
{
{ "ID", "id" }
})
};
};
//AJ: Init logging
Trace.AutoFlush = true;
Trace.Listeners.Add(new ConsoleTraceListener());
Trace.Listeners.Add(new TextWriterTraceListener("trace.log"));
//AJ: Increase availible threads
//AJ: https://docs.microsoft.com/en-us/azure/storage/storage-performance-checklist#subheading10
//AJ: https://github.com/Azure/azure-documentdb-dotnet/blob/master/samples/documentdb-benchmark/Program.cs
var minThreadPoolSize = 100;
ThreadPool.SetMinThreads(minThreadPoolSize, minThreadPoolSize);
//AJ: https://docs.microsoft.com/en-us/azure/cosmos-db/performance-tips
//AJ: gcServer enabled in app.config
//AJ: Prefer 32-bit disabled in project properties
//AJ: DO IT
var program = new Program();
Trace.TraceInformation($"Starting @ {DateTime.UtcNow}");
program.RunAsync().Wait();
Trace.TraceInformation($"Finished @ {DateTime.UtcNow}");
//AJ: Wait for user to exit
Console.WriteLine();
Console.WriteLine("Hit enter to exit...");
Console.ReadLine();
}
public async Task RunAsync()
{
using (new CodeTimer())
{
var client = await this.GetDocumentClientAsync();
var documentCollectionUri = UriFactory.CreateDocumentCollectionUri(ConfigurationManager.AppSettings["databaseID"], ConfigurationManager.AppSettings["collectionID"]);
//AJ: Prepare Test Documents
var documentCount = 10000; //AJ: 10,000
var documentsForUpsert = this.GetDocuments(documentCount);
await this.UpsertDocumentsAsync(client, documentCollectionUri, documentsForUpsert);
var allDocuments = this.GetAllDocuments(client, documentCollectionUri);
var area = this.GetArea();
var documentsInArea = this.GetDocumentsInArea(client, documentCollectionUri, area);
}
}
private async Task<DocumentClient> GetDocumentClientAsync()
{
using (new CodeTimer())
{
var serviceEndpointUri = new Uri(ConfigurationManager.AppSettings["serviceEndpoint"]);
var authKey = ConfigurationManager.AppSettings["authKey"];
var connectionPolicy = new ConnectionPolicy
{
ConnectionMode = ConnectionMode.Direct,
ConnectionProtocol = Protocol.Tcp,
RequestTimeout = new TimeSpan(1, 0, 0),
RetryOptions = new RetryOptions
{
MaxRetryAttemptsOnThrottledRequests = 10,
MaxRetryWaitTimeInSeconds = 60
}
};
var client = new DocumentClient(serviceEndpointUri, authKey, connectionPolicy);
await client.OpenAsync();
return client;
}
}
private List<TestDocument> GetDocuments(int count)
{
using (new CodeTimer())
{
return External.CreateDocuments(count);
}
}
private async Task UpsertDocumentsAsync(DocumentClient client, Uri documentCollectionUri, List<TestDocument> documents)
{
using (new CodeTimer())
{
//TODO: AJ: Parallelise
foreach (var document in documents)
{
await client.UpsertDocumentAsync(documentCollectionUri, document);
}
}
}
private List<TestDocument> GetAllDocuments(DocumentClient client, Uri documentCollectionUri)
{
using (new CodeTimer())
{
var query = client
.CreateDocumentQuery<TestDocument>(documentCollectionUri, new FeedOptions()
{
MaxItemCount = 1000
});
var documents = query.ToList();
return documents;
}
}
private Polygon GetArea()
{
//AJ: Longitude,Latitude i.e. X/Y
//AJ: Ring orientation matters
return new Polygon(new List<LinearRing>()
{
new LinearRing(new List<Position>()
{
new Position(1.8567 ,51.3814),
new Position(0.5329 ,51.4618),
new Position(0.2477 ,51.2588),
new Position(-0.5329 ,51.2579),
new Position(-1.17 ,51.2173),
new Position(-1.9062 ,51.1958),
new Position(-2.5434 ,51.1614),
new Position(-3.8672 ,51.139 ),
new Position(-4.1578 ,50.9137),
new Position(-4.5373 ,50.694 ),
new Position(-5.1496 ,50.3282),
new Position(-5.2212 ,49.9586),
new Position(-3.7049 ,50.142 ),
new Position(-2.1698 ,50.314 ),
new Position(0.4669 ,50.6976),
//AJ: Last point must be the same as first point
new Position(1.8567 ,51.3814)
})
});
}
private List<TestDocument> GetDocumentsInArea(DocumentClient client, Uri documentCollectionUri, Polygon area)
{
using (new CodeTimer())
{
var query = client
.CreateDocumentQuery<TestDocument>(documentCollectionUri, new FeedOptions()
{
MaxItemCount = 1000
})
.Where(document => document.Location.Intersects(area));
var documents = query.ToList();
return documents;
}
}
}
public class TestDocument : Resource
{
public string Name { get; set; }
public Point Location { get; set; } //AJ: Longitude,Latitude i.e. X/Y
public TestDocument()
{
this.Id = Guid.NewGuid().ToString("N");
}
}
//AJ: This should be "good enough". The times being recorded are seconds or minutes.
public class CodeTimer : IDisposable
{
private Action<TimeSpan> reportFunction;
private Stopwatch stopwatch = new Stopwatch();
public CodeTimer([CallerMemberName]string name = "")
: this((ellapsed) =>
{
Trace.TraceInformation($"{name} took {ellapsed}, or {ellapsed.TotalMilliseconds} ms.");
})
{ }
public CodeTimer(Action<TimeSpan> report)
{
this.reportFunction = report;
this.stopwatch.Start();
}
public void Dispose()
{
this.stopwatch.Stop();
this.reportFunction(this.stopwatch.Elapsed);
}
}
public class PropertyNameMapContractResolver : DefaultContractResolver
{
private Dictionary<string, string> propertyNameMap;
public PropertyNameMapContractResolver(Dictionary<string, string> propertyNameMap)
{
this.propertyNameMap = propertyNameMap;
}
protected override string ResolvePropertyName(string propertyName)
{
if (this.propertyNameMap.TryGetValue(propertyName, out string resolvedName))
return resolvedName;
return base.ResolvePropertyName(propertyName);
}
}
}
我使用的是自定义 ContractResolver
,这显然对 .Net SDK 中的 DocumentDB 类 的性能有很大影响。
我就是这样设置 ContractResolver
:
JsonConvert.DefaultSettings = () =>
{
return new JsonSerializerSettings
{
ContractResolver = new PropertyNameMapContractResolver(new Dictionary<string, string>()
{
{ "ID", "id" }
})
};
};
它是这样实现的:
public class PropertyNameMapContractResolver : DefaultContractResolver
{
private Dictionary<string, string> propertyNameMap;
public PropertyNameMapContractResolver(Dictionary<string, string> propertyNameMap)
{
this.propertyNameMap = propertyNameMap;
}
protected override string ResolvePropertyName(string propertyName)
{
if (this.propertyNameMap.TryGetValue(propertyName, out string resolvedName))
return resolvedName;
return base.ResolvePropertyName(propertyName);
}
}
解决方法很简单,不要设置 JsonConvert.DefaultSettings
,这样就不会使用 ContractResolver
。
结果:
我能够在 21799.0221 毫秒内执行我的空间查询,即 22 秒。
之前用了 170717.151 毫秒,也就是 2 分 50 秒。
大约快 8 倍!
我写了最新的更新,然后从 Stack Overflow 收到以下错误:"Body is limited to 30000 characters; you entered 38676."
公平地说,我在记录我的冒险经历时非常冗长,所以我重写了这里的内容以更加简洁。
我已经存储了我的(长)原始 post 和 pastebin 上的更新。我不认为很多人会读它们,但我为它们付出了很多努力,所以最好不要让它们丢失。
我有一个 collection,其中包含 100,000 个文档,用于学习如何使用 CosmosDB 以及性能测试等内容。
这些文档中的每一个都有一个 Location
属性,这是一个 GeoJSON Point
.
根据 documentation,一个 GeoJSON 点应该被自动索引。
Azure Cosmos DB supports automatic indexing of Points, Polygons, and LineStrings
我已经检查了我的 collection 的索引政策,它有自动点索引的条目:
{
"automatic":true,
"indexingMode":"Consistent",
"includedPaths":[
{
"path":"/*",
"indexes":[
...
{
"kind":"Spatial",
"dataType":"Point"
},
...
]
}
],
"excludedPaths":[ ]
}
我一直在寻找一种方法来列出或查询已创建的索引,但我还没有找到这样的东西,所以我无法确认这个 属性 肯定正在编入索引。
我创建了一个 GeoJSON Polygon
,然后用它来查询我的文档。
这是我的查询:
var query = client
.CreateDocumentQuery<TestDocument>(documentCollectionUri)
.Where(document => document.Type == this.documentType && document.Location.Intersects(target.Area));
然后我将该查询 object 传递给以下方法,这样我就可以在跟踪使用的请求单位的同时获得结果:
protected async Task<IEnumerable<T>> QueryTrackingUsedRUsAsync(IQueryable<T> query)
{
var documentQuery = query.AsDocumentQuery();
var documents = new List<T>();
while (documentQuery.HasMoreResults)
{
var response = await documentQuery.ExecuteNextAsync<T>();
this.AddUsedRUs(response.RequestCharge);
documents.AddRange(response);
}
return documents;
}
点位置是从数以百万计的英国地址中随机选择的,因此它们的分布应该相当真实。
多边形由16个点组成(首末点相同),所以不是很复杂。它覆盖了英国最南部的大部分地区,从伦敦向下。
此查询的一个示例 运行 在 170717.151 毫秒(不到 171 秒或不到 3 分钟)内使用 3917.92 RU 返回了 8728 个文档。
3918 卢/171 秒 = 22.91 RU/s
我目前将吞吐量 (RU/s) 设置为最低值,为 400 RU/s。
据我了解,这是您可以保证获得的保留级别。有时您可以 "burst" 高于该级别,但如果这样做过于频繁,您将被限制回保留的级别。
23 RU/s 的 "query speed" 显然远低于 400 RU/s 的吞吐量设置。
我运行正在连接客户端"locally",即在我的办公室,而不是在 Azure 数据中心。
每个文档的大小大约为 500 字节 (0.5 kb)。
所以发生了什么?
我是不是做错了什么?
我是否误解了关于 RU/s 我的查询是如何被限制的?
这是 GeoSpatial 索引运行的速度,所以我将获得最佳性能吗?
没有使用 GeoSpatial 索引吗?
有什么方法可以查看创建的索引吗?
有什么方法可以检查索引是否被使用?
有没有一种方法可以分析查询并获取有关时间花费的指标?例如s 用于按类型查找文档,s 用于地理空间过滤,s 用于传输数据。
更新 1
这是我在查询中使用的多边形:
Area = new Polygon(new List<LinearRing>()
{
new LinearRing(new List<Position>()
{
new Position(1.8567 ,51.3814),
new Position(0.5329 ,51.4618),
new Position(0.2477 ,51.2588),
new Position(-0.5329 ,51.2579),
new Position(-1.17 ,51.2173),
new Position(-1.9062 ,51.1958),
new Position(-2.5434 ,51.1614),
new Position(-3.8672 ,51.139 ),
new Position(-4.1578 ,50.9137),
new Position(-4.5373 ,50.694 ),
new Position(-5.1496 ,50.3282),
new Position(-5.2212 ,49.9586),
new Position(-3.7049 ,50.142 ),
new Position(-2.1698 ,50.314 ),
new Position(0.4669 ,50.6976),
new Position(1.8567 ,51.3814)
})
})
我也尝试过将其反转(因为圆环方向很重要),但是使用反转多边形的查询花费的时间明显更长(我没有时间处理)并返回了 91272 个项目。
此外,坐标被指定为 Longitude/Latitude、this is how GeoJSON expects them(即 X/Y),而不是传统的 Latitude/Longitude 顺序。
The GeoJSON specification specifies longitude first and latitude second.
更新 2
这是我的一份文件的 JSON:
{
"GeoTrigger": null,
"SeverityTrigger": -1,
"TypeTrigger": -1,
"Name": "13, LONSDALE SQUARE, LONDON, N1 1EN",
"IsEnabled": true,
"Type": 2,
"Location": {
"$type": "Microsoft.Azure.Documents.Spatial.Point, Microsoft.Azure.Documents.Client",
"type": "Point",
"coordinates": [
-0.1076407397346815,
51.53970315059827
]
},
"id": "0dc2c03e-082b-4aea-93a8-79d89546c12b",
"_rid": "EQttAMGhSQDWPwAAAAAAAA==",
"_self": "dbs/EQttAA==/colls/EQttAMGhSQA=/docs/EQttAMGhSQDWPwAAAAAAAA==/",
"_etag": "\"42001028-0000-0000-0000-594943fe0000\"",
"_attachments": "attachments/",
"_ts": 1497973747
}
更新 3
我创建了该问题的最小复现,我发现该问题不再发生。
说明确实是我自己的代码出了问题
我着手检查原始代码和复制代码之间的所有差异,最终发现对我来说似乎相当无辜的东西实际上产生了很大的影响。值得庆幸的是,根本不需要该代码,因此只需不使用该代码即可轻松修复。
有一次我使用的是自定义的 ContractResolver
,但当它不再需要时我没有删除它。
这是有问题的复制代码:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Spatial;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Repro.Cli
{
public class Program
{
static void Main(string[] args)
{
JsonConvert.DefaultSettings = () =>
{
return new JsonSerializerSettings
{
ContractResolver = new PropertyNameMapContractResolver(new Dictionary<string, string>()
{
{ "ID", "id" }
})
};
};
//AJ: Init logging
Trace.AutoFlush = true;
Trace.Listeners.Add(new ConsoleTraceListener());
Trace.Listeners.Add(new TextWriterTraceListener("trace.log"));
//AJ: Increase availible threads
//AJ: https://docs.microsoft.com/en-us/azure/storage/storage-performance-checklist#subheading10
//AJ: https://github.com/Azure/azure-documentdb-dotnet/blob/master/samples/documentdb-benchmark/Program.cs
var minThreadPoolSize = 100;
ThreadPool.SetMinThreads(minThreadPoolSize, minThreadPoolSize);
//AJ: https://docs.microsoft.com/en-us/azure/cosmos-db/performance-tips
//AJ: gcServer enabled in app.config
//AJ: Prefer 32-bit disabled in project properties
//AJ: DO IT
var program = new Program();
Trace.TraceInformation($"Starting @ {DateTime.UtcNow}");
program.RunAsync().Wait();
Trace.TraceInformation($"Finished @ {DateTime.UtcNow}");
//AJ: Wait for user to exit
Console.WriteLine();
Console.WriteLine("Hit enter to exit...");
Console.ReadLine();
}
public async Task RunAsync()
{
using (new CodeTimer())
{
var client = await this.GetDocumentClientAsync();
var documentCollectionUri = UriFactory.CreateDocumentCollectionUri(ConfigurationManager.AppSettings["databaseID"], ConfigurationManager.AppSettings["collectionID"]);
//AJ: Prepare Test Documents
var documentCount = 10000; //AJ: 10,000
var documentsForUpsert = this.GetDocuments(documentCount);
await this.UpsertDocumentsAsync(client, documentCollectionUri, documentsForUpsert);
var allDocuments = this.GetAllDocuments(client, documentCollectionUri);
var area = this.GetArea();
var documentsInArea = this.GetDocumentsInArea(client, documentCollectionUri, area);
}
}
private async Task<DocumentClient> GetDocumentClientAsync()
{
using (new CodeTimer())
{
var serviceEndpointUri = new Uri(ConfigurationManager.AppSettings["serviceEndpoint"]);
var authKey = ConfigurationManager.AppSettings["authKey"];
var connectionPolicy = new ConnectionPolicy
{
ConnectionMode = ConnectionMode.Direct,
ConnectionProtocol = Protocol.Tcp,
RequestTimeout = new TimeSpan(1, 0, 0),
RetryOptions = new RetryOptions
{
MaxRetryAttemptsOnThrottledRequests = 10,
MaxRetryWaitTimeInSeconds = 60
}
};
var client = new DocumentClient(serviceEndpointUri, authKey, connectionPolicy);
await client.OpenAsync();
return client;
}
}
private List<TestDocument> GetDocuments(int count)
{
using (new CodeTimer())
{
return External.CreateDocuments(count);
}
}
private async Task UpsertDocumentsAsync(DocumentClient client, Uri documentCollectionUri, List<TestDocument> documents)
{
using (new CodeTimer())
{
//TODO: AJ: Parallelise
foreach (var document in documents)
{
await client.UpsertDocumentAsync(documentCollectionUri, document);
}
}
}
private List<TestDocument> GetAllDocuments(DocumentClient client, Uri documentCollectionUri)
{
using (new CodeTimer())
{
var query = client
.CreateDocumentQuery<TestDocument>(documentCollectionUri, new FeedOptions()
{
MaxItemCount = 1000
});
var documents = query.ToList();
return documents;
}
}
private Polygon GetArea()
{
//AJ: Longitude,Latitude i.e. X/Y
//AJ: Ring orientation matters
return new Polygon(new List<LinearRing>()
{
new LinearRing(new List<Position>()
{
new Position(1.8567 ,51.3814),
new Position(0.5329 ,51.4618),
new Position(0.2477 ,51.2588),
new Position(-0.5329 ,51.2579),
new Position(-1.17 ,51.2173),
new Position(-1.9062 ,51.1958),
new Position(-2.5434 ,51.1614),
new Position(-3.8672 ,51.139 ),
new Position(-4.1578 ,50.9137),
new Position(-4.5373 ,50.694 ),
new Position(-5.1496 ,50.3282),
new Position(-5.2212 ,49.9586),
new Position(-3.7049 ,50.142 ),
new Position(-2.1698 ,50.314 ),
new Position(0.4669 ,50.6976),
//AJ: Last point must be the same as first point
new Position(1.8567 ,51.3814)
})
});
}
private List<TestDocument> GetDocumentsInArea(DocumentClient client, Uri documentCollectionUri, Polygon area)
{
using (new CodeTimer())
{
var query = client
.CreateDocumentQuery<TestDocument>(documentCollectionUri, new FeedOptions()
{
MaxItemCount = 1000
})
.Where(document => document.Location.Intersects(area));
var documents = query.ToList();
return documents;
}
}
}
public class TestDocument : Resource
{
public string Name { get; set; }
public Point Location { get; set; } //AJ: Longitude,Latitude i.e. X/Y
public TestDocument()
{
this.Id = Guid.NewGuid().ToString("N");
}
}
//AJ: This should be "good enough". The times being recorded are seconds or minutes.
public class CodeTimer : IDisposable
{
private Action<TimeSpan> reportFunction;
private Stopwatch stopwatch = new Stopwatch();
public CodeTimer([CallerMemberName]string name = "")
: this((ellapsed) =>
{
Trace.TraceInformation($"{name} took {ellapsed}, or {ellapsed.TotalMilliseconds} ms.");
})
{ }
public CodeTimer(Action<TimeSpan> report)
{
this.reportFunction = report;
this.stopwatch.Start();
}
public void Dispose()
{
this.stopwatch.Stop();
this.reportFunction(this.stopwatch.Elapsed);
}
}
public class PropertyNameMapContractResolver : DefaultContractResolver
{
private Dictionary<string, string> propertyNameMap;
public PropertyNameMapContractResolver(Dictionary<string, string> propertyNameMap)
{
this.propertyNameMap = propertyNameMap;
}
protected override string ResolvePropertyName(string propertyName)
{
if (this.propertyNameMap.TryGetValue(propertyName, out string resolvedName))
return resolvedName;
return base.ResolvePropertyName(propertyName);
}
}
}
我使用的是自定义 ContractResolver
,这显然对 .Net SDK 中的 DocumentDB 类 的性能有很大影响。
我就是这样设置 ContractResolver
:
JsonConvert.DefaultSettings = () =>
{
return new JsonSerializerSettings
{
ContractResolver = new PropertyNameMapContractResolver(new Dictionary<string, string>()
{
{ "ID", "id" }
})
};
};
它是这样实现的:
public class PropertyNameMapContractResolver : DefaultContractResolver
{
private Dictionary<string, string> propertyNameMap;
public PropertyNameMapContractResolver(Dictionary<string, string> propertyNameMap)
{
this.propertyNameMap = propertyNameMap;
}
protected override string ResolvePropertyName(string propertyName)
{
if (this.propertyNameMap.TryGetValue(propertyName, out string resolvedName))
return resolvedName;
return base.ResolvePropertyName(propertyName);
}
}
解决方法很简单,不要设置 JsonConvert.DefaultSettings
,这样就不会使用 ContractResolver
。
结果:
我能够在 21799.0221 毫秒内执行我的空间查询,即 22 秒。
之前用了 170717.151 毫秒,也就是 2 分 50 秒。
大约快 8 倍!