Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ODataController performance is very slow #2644

Closed
andrew-vdb opened this issue May 9, 2022 · 12 comments
Closed

ODataController performance is very slow #2644

andrew-vdb opened this issue May 9, 2022 · 12 comments

Comments

@andrew-vdb
Copy link

When I use $top 100, within 1 minute there are only 213 requests served
It is so slow that its not something we can accept
Do we have any performance target?
Same query with ApiController + ADO.net and produce same result is much much faster

Assemblies affected

image

Reproduce steps

Create any OData endpoint, do k6 with --vus 250 --duration 3m
Compare it with Ado.net request

Expected result

Acceptable performance level

Actual result

Very slow

Additional detail

I did check the issues in github, is the performance with serialization still not fixed?

@gathogojr
Copy link
Contributor

Thanks @andrew-vdb for reporting the issue. To help with troubleshooting the issue,

  1. Could you clarify this? >> do k6 with --vus 250 --duration 3m
  2. One thing that we do in OData that vanilla ASP.NET Core doesn't typically do is return the total count as @odata.count, i.e. if you request for the top 10, we'll return the "out-of" total in the response. Could you please confirm what the size the results are without $top because that can have an impact if it's too large?
  3. Other things you could do to check what is happening under the hood is use a profiler (e.g. SQL Server Profiler) to inspect the queries being executed when the request is sent and compare with vanilla ASP.NET Core. You could share those if possible to help us with the investigation.
  4. You could also profile the application as a whole to determine where most of the time is being spent. It could easily be that the issue has nothing to do with the time spent executing the TOP N queries
  5. You could also share a minimal repro

Something to note is that Asp.Net Core OData does a little more that the vanilla Asp.Net Core does. The objects are not serialized arbitrarily. Quite a bit of validation logic is applied. However, that shouldn't result into a huge performance hit. We encourage you to share a much information as possible for your scenario to help identify what might be the root cause of the observed behaviour

@andrew-vdb
Copy link
Author

@gathogojr

k6 is stress test tool, see https://k6.io/
k6 --vus 250 --duration 3m means run stress test with 250 virtual users for 3 minutes
the k6 script simply http get to odata endpoint where the access token is hard coded
I think it will be nice if you can compare the benchmark running odataController vs ado.net, an official benchmark will give more clarity what is the expectation in regard of performance

  1. I'm aware of "count"
    The table only have 200 rows
    There is huge difference in performance between requesting $top 10 vs $top 100
    That's why I ask about serialization issue

3,4
I will try to get profiler running, problem is that visual studio 2022 is bloated at this moment, running k6 + visual studio + profiler will be wonky but will see

  1. There's nothing fancy in my repo, other than EnableQueryAsync attribute
    If you use EnableQuery, the execution of OData is not async

@habbes
Copy link
Contributor

habbes commented May 24, 2022

@andrew-vdb When you use EnableQuery (the non-async version), how does the performance compare?

@habbes
Copy link
Contributor

habbes commented May 24, 2022

@andrew-vdb are you using the 7.5.x version of WebAPI or 8.x?

@andrew-vdb
Copy link
Author

@habbes I used version 8.x

It is difficult to produce visual studio profiler report, it is barely working....

Just curious, why do we need to do fancy thing during serialization?

@habbes
Copy link
Contributor

habbes commented May 27, 2022

@andrew-vdb thanks for the additional context. And did you also try with EnableQuery and confirm whether you observe the same performance issues?

OData serialization does extra work to validate the payload, adding annotations that allow clients to consume the OData payload in predictable and unambiguous ways.

However, there are ongoing efforts to improve serialization performance.

@habbes
Copy link
Contributor

habbes commented May 27, 2022

@andrew-vdb Is EnableQueryAsync a custom attribute you created?

@andrew-vdb
Copy link
Author

andrew-vdb commented Jun 14, 2022

@habbes yes, the code is from this repo

public class EnableQueryAsyncAttribute : EnableQueryAttribute
    {
        private static readonly MethodInfo ReadInternalMethod = typeof(EnableQueryAsyncAttribute).GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
           .Where(method => method.Name == nameof(EnableQueryAsyncAttribute.ReadInternal))
           .Single();

        public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
        {
            if (context.Result is ObjectResult objectResult &&
                objectResult.Value is IQueryable queryable)
            {
                var cancellationToken = context.HttpContext.RequestAborted;

                var queryableType = queryable.GetType();
                if (queryableType.GetInterfaces().Any(x =>
                    x.IsGenericType &&
                    x.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>)))
                {
                    var readInternalMethod = ReadInternalMethod.MakeGenericMethod(queryableType.GenericTypeArguments[0]);

                    var invoked = readInternalMethod.Invoke(null, new object[] { queryable, cancellationToken })!;

                    var result = await (Task<ICollection>)invoked;

                    objectResult.Value = result;
                }
            }

            _ = await next().ConfigureAwait(false);
        }

        private static async Task<ICollection> ReadInternal<T>(object value, CancellationToken cancellationToken)
        {
            var asyncEnumerable = (IAsyncEnumerable<T>)value;
            var result = new List<T>();

            await foreach (var item in asyncEnumerable)
            {
                cancellationToken.ThrowIfCancellationRequested();

                result.Add(item);
            }

            return result;
        }
    }

@kerajel
Copy link

kerajel commented Nov 26, 2023

@andrew-vdb, could you please tell if this was resolved anyhow in newer versions of OData? Or how have you managed to overcome performance issues?

I am debating whether or not to use OData in my projects and it looks like neither async execution is supported out of the box nor the performance is great.

@andrew-vdb
Copy link
Author

@kerajel even Microsoft is not using OData and will go to GraphQl (check their latest project, its only support REST and GraphQl)

Stay away from this if you jump to something new

@rsgilbert
Copy link

Have same problem of slow performance with Odata. Any update?

@habbes
Copy link
Contributor

habbes commented Mar 5, 2024

@rsgilbert Could you create a new issue with details about the specific performance issues you are facing, with some profiler or benchmark results where possible that could help us narrow down the issue. We added support to IAsyncEnumerable which makes serializing async data sources more efficiently (no need to load the entire dataset in memory), we made some optimizations to the serializer. We'll share a guide soon for performance improvement tips to help you address common performance issues related to OData libraries.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants