WebApi
WebApi copied to clipboard
ODataController performance is very slow
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
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?
Thanks @andrew-vdb for reporting the issue. To help with troubleshooting the issue,
- Could you clarify this? >> do k6 with --vus 250 --duration 3m
- 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? - 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.
- 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
- 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
@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
- 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
- There's nothing fancy in my repo, other than EnableQueryAsync attribute If you use EnableQuery, the execution of OData is not async
@andrew-vdb When you use EnableQuery
(the non-async version), how does the performance compare?
@andrew-vdb are you using the 7.5.x version of WebAPI or 8.x?
@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?
@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.
@andrew-vdb Is EnableQueryAsync
a custom attribute you created?
@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;
}
}
@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.
@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
Have same problem of slow performance with Odata. Any update?
@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.