Skip to content

Performance Regression from 7.5.12 -> 7.6.3 Microsoft.AspNetCore.OData  #2745

Description

@stack111

Short summary (3-5 sentences) describing the issue.

We have a AspNetCore service running OData

image

Assemblies affected

*Which assemblies and versions are known to be affected e.g. OData WebApi lib 6.1.0
7.6.3

Reproduce steps

The simplest set of steps to reproduce the issue. If possible, reference a commit that demonstrates the issue.

            app.UseEndpoints(endpoints =>
            {
                endpoints.EnableDependencyInjection();
                endpoints.Select().Filter().OrderBy().Count().MaxTop(ModelConstants.MaxTopCount).SkipToken();
                endpoints.MapODataRoute(odata, odata, container =>
                {
                    container.AddService(OData.ServiceLifetime.Singleton, sp => GetEdmModel());
                    container.AddService<ODataPayloadValueConverter, ExtendedODataConverter>(OData.ServiceLifetime.Singleton);
                    container.AddService(OData.ServiceLifetime.Singleton, _ => app.ApplicationServices.GetRequiredService<ODataUriResolver>());
                    container.AddService<IEnumerable<IODataRoutingConvention>>(OData.ServiceLifetime.Singleton,
                        sp => ODataRoutingConventions.CreateDefaultWithAttributeRouting(odata, endpoints.ServiceProvider));
                    container.AddService<SkipTokenHandler, CosmosDbSkipTokenHandler>(OData.ServiceLifetime.Singleton);
                });
                endpoints.MapControllers();
            });
        private static ActionConfiguration EdmModelHelper<T>(ODataConventionModelBuilder odataBuilder, string baseKey, string[] keys)
             where T : class
        {
            odataBuilder.EntitySet<T>(baseKey);
            var accountScopedQuery = odataBuilder.Action(baseKey + "ScopeQuery");
            foreach (var key in keys)
            {
                accountScopedQuery.Parameter<string>(key).Optional();
            }

            accountScopedQuery.ReturnsCollectionFromEntitySet<T>(baseKey);
            return accountScopedQuery;
        }

        private static IEdmModel GetEdmModel()
        {
            const string partitionKey = "partitionKey";

            var odataBuilder = new ODataConventionModelBuilder();
            odataBuilder.EnableLowerCamelCase();
            EdmModelHelper<DocumentAccount>(odataBuilder, "Accounts", new string[] { partitionKey, ModelConstants.ResourceGroup, ModelConstants.ResourceName, ModelConstants.ClientId, ModelConstants.ParentId });
            var accountModel = odataBuilder.EntitySet<DocumentAccount>(partitionKey);
            accountModel.EntityType.CollectionProperty(l => l.LinkedResources).IsOptional();

            var repeatedSet = new string[] { partitionKey, ModelConstants.ResourceName, ModelConstants.Id, ModelConstants.ParentId };
            EdmModelHelper<DocumentCreatorResource>(odataBuilder, "Creators", repeatedSet);
            EdmModelHelper<DocumentEventGridFilter>(odataBuilder, "EventGridFilters", new string[] { partitionKey, ModelConstants.ResourceName, ModelConstants.Id, ModelConstants.ParentId });
            EdmModelHelper<DocumentSubscription>(odataBuilder, "Subscriptions", new string[] { partitionKey, ModelConstants.Id });
            odataBuilder.EntitySet<ManagedIdentityDocument>("msi");
            var model = odataBuilder.GetEdmModel();
            return model;
        }

Perf affected controller

namespace Microsoft.Azure.LocationServices.IdentityService.Controllers
{
    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Microsoft.AspNet.OData;
    using Microsoft.AspNet.OData.Query;
    using Microsoft.AspNet.OData.Routing;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Azure.LocationServices.IdentityService.CosmosDb;

    [Route("odata/accounts")]
    [ODataRoutePrefix("accounts")]
    [ApiController]
    [Authorize]
    public class AccountsODataController : ODataController
    {
        private readonly IResourceRepository accountRepository;

        public AccountsODataController(IResourceRepository accountRepository)
        {
            this.accountRepository = accountRepository;
        }

        [HttpGet("")]
        [EnableQuery(
            AllowedQueryOptions =
            AllowedQueryOptions.Select |
            AllowedQueryOptions.Count |
            AllowedQueryOptions.Filter |
            AllowedQueryOptions.OrderBy |
            AllowedQueryOptions.Top |
            AllowedQueryOptions.Skip |
            AllowedQueryOptions.SkipToken)]
        [ApiVersion(ApiVersions.V1)]
        public async Task<IEnumerable<DocumentAccount>> QueryAccounts(ODataQueryOptions queryOptions)
        {
            if (!queryOptions.TryExtractFilter(nameof(ModelConstants.PartitionKey), out string partitionKey))
            {
                queryOptions.TryExtractFilter(ModelConstants.SubscriptionId, out partitionKey);
            }

            ScopeQuery query = new ScopeQuery()
            {
                PartitionKey = partitionKey,
                ResourceType = ModelConstants.AccountResourceType
            };

            var result = await accountRepository.QueryPagedResourceTypeAsync<DocumentAccount>(query, queryOptions, HttpContext.RequestAborted);
            return result.FormattedResponse(Request);
        }
    }
}

Expected result

In our test environment we would not have seen a 100ms 99th percentile increase in latency from the nuget package upgrade from 7.5.12 -> 7.6.3.

Actual result

With a very vacuum controlled steady flow of requests (test environment) we observed requests increase significantly (seconds) after isolating the only change to the NuGet upgrade. We reverted the NuGet upgrade and the latency recovered back to expected behavior.

Additional detail

Example OData queries which are affected follow

/odata/accounts?$filter=partitionKey eq 'e58ec759-cdd4-42d9-af7b-807214f4a456' and (state eq 'Activated' or state eq 'Locked')

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions