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

Register service called over multiple grpc services which are part of same dot net service, these calls cause startup delay #324

Open
sghatbale opened this issue Feb 7, 2024 · 24 comments

Comments

@sghatbale
Copy link

sghatbale commented Feb 7, 2024

Hello Marc,

Below is the extracted code of startup of c# dot net service.
As you can see MapGrpcService() is called for each of services file.

There are many grpc services files. MapGrpcService() for all of these services sometimes combinedly takes 20 seconds.

We are starting grpc service as windows service. And windows service timeout is 30 seconds. This slowness of MapGrpcService() is really causing the service startup timeout.

How to speed up MapGrpcService() OR is there any other alternative??

.....
builder.Services.AddControllers();
var app = builder.Build();

app.MapGrpcService<Service1>();
app.MapGrpcService<Service2>();
app.MapGrpcService<Service3>();
app.MapGrpcService<Service4>();
app.MapGrpcService<Service5>();
app.MapGrpcService<Service6>();
app.MapGrpcService<Service7>();
app.MapGrpcService<Service8>();
......
app.MapGrpcService<Service35>();

app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");

app.Run(Url);

@mgravell
Copy link
Member

mgravell commented Feb 7, 2024

Question: are you using protobuf-net.Grpc here? Meaning: are you using the code-first feature here? It isn't clear from the example, and I want to be 100% sure I understand before I dig.

But what I am hearing: your web app has a lot of gRPC services (can we get a number on the number of services and methods, so we can understand?), and the setup is taking too long. I agree 20+ seconds is way too long here.

@sghatbale
Copy link
Author

sghatbale commented Feb 7, 2024

@mgravell One of the service file looks like below

public class Service1 : IService1 { private readonly Logger logger; public Service1(Logger logger) { this.logger = logger; } public ValueTask getDataAccessNodes(getDataRequest request, CallContext context = default) { try { UInt32 outputSize = request.outputSize; Int32 retVal = request.retVal; return new ValueTask( new getDataResponse { grpcRetVal = foo(request.ccid,request.clid) ,outputSize = outputSize,retVal = retVal }); } catch (Exception ex) { logger.LogError("Exception message - " + ex.Message); logger.LogException(ex);
		return new ValueTask<getDataResponse>(
				new getDataResponse
				{
					responseErrorCode = -1,
					responseErrorMessage = ex.Message
				});
	}
}

}

Interface class has below code

[ProtoContract(SkipConstructor = true)] public class getDataRequest { [ProtoMember(1)] public UInt32 ccid { get; set; } [ProtoMember(2)] public UInt32 clid { get; set; } } [ProtoContract(SkipConstructor = true)] public class getDataResponse { [ProtoMember(1)] public String grpcRetVal { get; set; } [ProtoMember(2)] public UInt32 outputSize { get; set; } } [ServiceContract] public interface IService1 { [OperationContract] public ValueTask getDataAccessNodes (getDataRequest request, CallContext context = default); }

@sghatbale
Copy link
Author

sghatbale commented Feb 7, 2024

Yes @mgravell ,

using protobuf-net.Grpc , protobuf-net.Grpc.AspNetCore

image

Builder has following code

builder.Services.AddCodeFirstGrpc(options => { options.EnableDetailedErrors = true; options.MaxReceiveMessageSize = int.MaxValue; options.MaxSendMessageSize = int.MaxValue; });

There are 35 services files. On Average each service has 6 to 10 methods.
Max method a service has 20 methods.
Usually the service having high number of methods take higher time for MapGrpcService()

@sghatbale
Copy link
Author

Hi Marc,

Let me know if you need any other info.

@mgravell
Copy link
Member

mgravell commented Feb 7, 2024

That's interesting, thanks. I'll see if I can setup a repro with some dummy services - then we can see what needs optimizing. This is way, way too slow.

mgravell added a commit that referenced this issue Feb 8, 2024
@mgravell
Copy link
Member

mgravell commented Feb 8, 2024

OK, so: I set up a test rig with 100 services, 6 methods per service, each using different data contracts (albeit dummy generated ones); entire test takes < 450ms; from the logging output, I can see that this does touch the models:

Init: 26ms
ProtoBuf.Grpc.Server.ServicesExtensions.CodeFirstServiceMethodProvider:RPC services being provided by protobuf_net.Grpc.Test.Issues.Issue324+TestService0: 6
...
ProtoBuf.Grpc.Server.ServicesExtensions.CodeFirstServiceMethodProvider:RPC services being provided by protobuf_net.Grpc.Test.Issues.Issue324+TestService99: 6
UseEndpoints: 0ms
Build: 0ms

so: whatever is happening isn't being trivial to discover; I'll need to see if I can set up a real test app and push the same through there.

@sghatbale
Copy link
Author

Sure @mgravell , please let me know your findings with real test app.

@menaheme
Copy link

menaheme commented Feb 8, 2024 via email

@mgravell
Copy link
Member

mgravell commented Feb 8, 2024

Hacked in, with ILogger args - worked just fine, no delays. Do you have a minimal runnable repro of what is required to see this being slow?

@sghatbale
Copy link
Author

sghatbale commented Feb 9, 2024

@mgravell
Did you get a chance to check EvSecurityCheckManagedCppSharpService from above attachment.
Essentially for this service, MapGrpcService() takes 450+ ms every time.

I'm looking for either optimization of MapGrpcService() to reduce delay OR is there a way which allows calling MapGrpcService() to be done post startup of the service.

@mgravell
Copy link
Member

mgravell commented Feb 9, 2024

No, I haven't had chance to look yet - this isn't my full time job, and I'd already spent a bunch of time looking yesterday. I'll try to take another look, but I have 47 other things need attention :)

@sghatbale
Copy link
Author

I appreciate your efforts @mgravell .
We had interacted in the past as well.

Please let me know your findings.

@sghatbale
Copy link
Author

@mgravell FYI, one more thing I've observed is that looks like MapGrpcService() is compute intensive i.e. app.MapGrpcService() takes upto 1.5 seconds if ran on machine with CPU already occupied 40% vs it takes 450 ms on idle systems.

@mgravell
Copy link
Member

I have a sample using the 4 files above (I had to delete the internals and re-implement the interfaces to all throw new NotImplementedException();, but that doesn't matter since we're not actually invoking anything!)

Based on my local measurement, I get very different numbers to you:

dbug: Grpc.AspNetCore.Server.Model.Internal.BinderServiceMethodProvider[1]
      Could not find bind method for CVGrpcCppSharpService.EvSecurityCheckManagedCppSharpService.
info: ProtoBuf.Grpc.Server.ServicesExtensions.CodeFirstServiceMethodProvider[0]
      RPC services being provided by CVGrpcCppSharpService.EvSecurityCheckManagedCppSharpService: 60
1: 98ms
dbug: Grpc.AspNetCore.Server.Model.Internal.BinderServiceMethodProvider[1]
      Could not find bind method for CVGrpcCppSharpService.EvSecurityNewManagedCppSharpService.
info: ProtoBuf.Grpc.Server.ServicesExtensions.CodeFirstServiceMethodProvider[0]
      RPC services being provided by CVGrpcCppSharpService.EvSecurityNewManagedCppSharpService: 12
2: 18ms

So: <100ms for the first service, and <20ms for the second. I'm still of the opinion that this is too slow, and I'm happy to try to make it faster, but: so that I understand, can I get more information on your setup? In particular: what framework / version, operating system and CPU are you using? Obviously if you're on a low power machine, it will be slower, but I'm not trying to argue "this is OK".

One of the options I'm particularly interested in: is it time that we used AOT to generate the binding code here? if we did that, there might be nothing huge for the service code to do. I'll see if I can generate that manually for now, to see what happens.

@mgravell
Copy link
Member

AOT results:

dbug: Grpc.AspNetCore.Server.Model.Internal.BinderServiceMethodProvider[1]
      Could not find bind method for CVGrpcCppSharpService.EvSecurityCheckManagedCppSharpService.
info: ProtoBuf.Grpc.Server.ServicesExtensions.CodeFirstServiceMethodProvider[0]
      RPC services being provided by CVGrpcCppSharpService.EvSecurityCheckManagedCppSharpService: 999
1: 61ms
dbug: Grpc.AspNetCore.Server.Model.Internal.BinderServiceMethodProvider[1]
      Could not find bind method for CVGrpcCppSharpService.EvSecurityNewManagedCppSharpService.
info: ProtoBuf.Grpc.Server.ServicesExtensions.CodeFirstServiceMethodProvider[0]
      RPC services being provided by CVGrpcCppSharpService.EvSecurityNewManagedCppSharpService: 999
2: 11ms

(ignore the 999 - that's placeholder; this is manually implemented "what we would do with AOT" - the actual AOT work needs many hours of effort)

So: that doubles the performance, and that removes all of the protobuf-net reflection code - everything else is most likely not protobuf-net.Grpc overheads. To say more, I'd need to get a repro of it actually running horribly slowly.

@sghatbale
Copy link
Author

@mgravell
"what framework / version, operating system and CPU are you using"
We have dot net core 8.0
Windows 2022 Server with 16 GB RAM & 4 cores.

Are you looking for any additional info from me? I did not understand when you say "To say more, I'd need to get a repro of it actually running horribly slowly."

Can we get the update which is reducing from 98 ms to 61 ms?

@mgravell
Copy link
Member

mgravell commented Feb 13, 2024 via email

@sghatbale
Copy link
Author

Thanks @mgravell !!!
Really appreciate your efforts.

Can you please update once the experimental code is official. I guess that will help us.

@mgravell
Copy link
Member

mgravell commented Feb 14, 2024

It would very much help to know what patterns your APIs use. In the code I've seen, everything was (IIRC) "takes TRequest and CancellationToken, returns ValueTask<TResponse>" - does that cover all of your usage? Basically, it changes how quickly I can get a preview that covers your specific usage, even if it doesn't handle every other possible allowed usage.

@sghatbale
Copy link
Author

Hi @mgravell

Is your experimental code is official (which improves the performance by reducing time taken by MapGrpcService() ) now?

@mgravell
Copy link
Member

mgravell commented Mar 5, 2024

No, I would have said.

I have lots of demands on my time, and ultimately this is stuff I do that comes out of my own/family time. It is on my list of things.

@sghatbale
Copy link
Author

Hi @mgravell ,

Just checking if you were able to submit the changes.

@shivkumarghatbale
Copy link

hi @mgravell ,

It would be really helpful if you can put your experimental code is official (which improves the performance by reducing time taken by MapGrpcService() ) .

Please update this thread.

@shivkumarghatbale
Copy link

Hi @mgravell ,

Just checking if you were able to submit the changes.

@sghatbale sghatbale changed the title MapGrpcService() called over multiple grpc services which are part of same dot net service, these calls cause startup delay Register service called over multiple grpc services which are part of same dot net service, these calls cause startup delay Oct 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants