草庐IT

c# - CosmosDB 查询性能

coder 2024-06-03 原文

我写了我的最新更新,然后从 Stack Overflow 收到以下错误:“正文限制为 30000 个字符;您输入了 38676。”

公平地说,我在记录我的冒险经历时一直非常冗长,所以我重写了这里的内容,使其更加简洁。

我已将我的(长)原始帖子和更新存储在 pastebin 上.我认为不会有很多人会阅读它们,但我为它们付出了很多努力,所以最好不要让它们丢失。

我有一个包含 100,000 个文档的集合,用于学习如何使用 CosmosDB 和性能测试等。

这些文档中的每一个都有一个 Location属性是 GeoJSON Point .

根据documentation ,GeoJSON 点应自动编入索引。

Azure Cosmos DB supports automatic indexing of Points, Polygons, and LineStrings



我已经检查了我收藏的索引策略,它有自动点索引的条目:
{
   "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));

然后我将该查询对象传递给以下方法,以便在跟踪所使用的请求单位时获得结果:
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 个点组成(第一个和最后一个点相同),所以它不是很复杂。它覆盖了英国最南部的大部分地区,从伦敦往下。

此查询的示例运行返回 8728 个文档,使用 3917.92 RU,在 170717.151 毫秒内,即不到 171 秒,或不到 3 分钟。

3918 RU/171 秒 = 22.91 RU/秒

我目前将吞吐量 (RU/s) 设置为最低值,即 400 RU/s。

我的理解是,这是您保证获得的保留级别。您有时可以“爆发”到该级别以上,但这样做过于频繁,您将被限制回您的保留级别。

23 RU/s 的“查询速度”显然远低于 400 RU/s 的吞吐量设置。

我在“本地”运行客户端,即在我的办公室,而不是在 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 个项目。

此外,坐标指定为经度/纬度,如 this is how GeoJSON expects them (即作为 X/Y),而不是在谈到纬度/经度时使用的传统顺序。

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 倍!

关于c# - CosmosDB 查询性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44673702/

有关c# - CosmosDB 查询性能的更多相关文章

  1. ruby - ECONNRESET (Whois::ConnectionError) - 尝试在 Ruby 中查询 Whois 时出错 - 2

    我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.

  2. ruby-on-rails - 在 Rails 和 ActiveRecord 中查询时忽略某些字段 - 2

    我知道我可以指定某些字段来使用pluck查询数据库。ids=Item.where('due_at但是我想知道,是否有一种方法可以指定我想避免从数据库查询的某些字段。某种反拔?posts=Post.where(published:true).do_not_lookup(:enormous_field) 最佳答案 Model#attribute_names应该返回列/属性数组。您可以排除其中一些并传递给pluck或select方法。像这样:posts=Post.where(published:true).select(Post.attr

  3. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  4. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  5. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  6. sql - 查询忽略时间戳日期的时间范围 - 2

    我正在尝试查询我的Rails数据库(Postgres)中的购买表,我想查询时间范围。例如,我想知道在所有日期的下午2点到3点之间进行了多少次购买。此表中有一个created_at列,但我不知道如何在不搜索特定日期的情况下完成此操作。我试过:Purchases.where("created_atBETWEEN?and?",Time.now-1.hour,Time.now)但这最终只会搜索今天与那些时间的日期。 最佳答案 您需要使用PostgreSQL'sdate_part/extractfunction从created_at中提取小时

  7. Ruby 的数字方法性能 - 2

    我正在使用Ruby解决一些ProjectEuler问题,特别是这里我要讨论的问题25(Fibonacci数列中包含1000位数字的第一项的索引是多少?)。起初,我使用的是Ruby2.2.3,我将问题编码为:number=3a=1b=2whileb.to_s.length但后来我发现2.4.2版本有一个名为digits的方法,这正是我需要的。我转换为代码:whileb.digits.length当我比较这两种方法时,digits慢得多。时间./025/problem025.rb0.13s用户0.02s系统80%cpu0.190总计./025/problem025.rb2.19s用户0.0

  8. ruby-on-rails - solr 清理查询 - 2

    我在Rails上使用带有ruby​​的solr。一切正常,我只需要知道是否有任何现有代码来清理用户输入,比如以?开头的查询。或* 最佳答案 我不知道执行此操作的任何代码,但理论上可以通过查看parsingcodeinLucene来完成并搜索thrownewParseException(只有16个匹配!)。在实践中,我认为您最好只捕获代码中的任何solr异常并显示“无效查询”消息或类似信息。编辑:这里有几个“sanitizer”:http://pivotallabs.com/users/zach/blog/articles/937-s

  9. ruby - Ruby 性能中的计时器 - 2

    我正在寻找一个用ruby​​演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent

  10. ruby-on-rails - Rails 3 在一个查询中包含多个表 - 2

    我正在为锦标赛开发一个Rails应用程序。我在这个查询中使用了三个模型:classPlayertruehas_and_belongs_to_many:tournamentsclassTournament:destroyclassPlayerMatch"Player",:foreign_key=>"player_one"belongs_to:player_two,:class_name=>"Player",:foreign_key=>"player_two"在tournaments_controller的显示操作中,我调用以下查询:Tournament.where(:id=>params

随机推荐