Elfocrash / Cosmonaut

🌐 A supercharged Azure CosmosDB .NET SDK with ORM support
https://cosmonaut.readthedocs.io
MIT License
342 stars 44 forks source link

Speeding up initialisation #76

Closed kloudwerkz closed 5 years ago

kloudwerkz commented 5 years ago

Firstly, great work on the library, it's been so helpful in getting my app up and running on Cosmos without breaking the bank. I'm wondering if there is a way to speed up the initialisation of my API when I'm loading lots of CosmosStores. Mine is probably a bit of an edge case. I have about a dozen entity types and multiple regions so I've created a service for each region (which gets created as a singleton) and I'm creating the CosmosStores in each service. The ctor for the service does something like: foo = new CosmosStore(settings); bar = new CosmosStore(settings);

... and so on. It works great but the startup is slow and when profiling I can see that most of the time is burnt calling GetDatabaseAsync for every CosmosStore init. Depending on which region I'm calling it may burn 3 seconds per call. Is there any way I can get that once and share it for each CosmosStore I create in the same region? Hopefully that all makes sense :)

PS: If you're wondering why I have different regions and I'm not using Cosmos' awesome global replication features it's because I have to comply with data sovereignty issues.

Elfocrash commented 5 years ago

Hello and thank you for your kind worlds.

Cosmonaut will do some check in the beginning to make sure you database and collection exists and if not it will create them. This takes some time on initialisation but it can’t be shared across CosmosStores.

On top of that the DocumentClient internally will make a bunch of calls to get some stuff like a pkrange cache or a location cache. This is about 8 requests and 2-3 seconds.

There is not much I can do about the internal one and thats where the performance is noticeable. I am currently investigating the idea of sharing the DocumentClient across all CosmosStores instead of having one per CosmosStore. This would get rid of this issue but I don’t know what that would mean for performance.

However if you are registering as a singleton, you can just initialise your CosmosStores on the application startup and you shouldn’t notice this initial overhead because the app wouldn’t be running yet.

Does that make sense?

kloudwerkz commented 5 years ago

Thanks, yep that makes sense. I do warm it up at startup but with 3 regions/services it takes about a minute. That's fine for a deploy but not so good for a scale-out action on Azure App Service as it's not available for 60 seconds right when you need it. Sharing the DocumentClient sounds like it would solve my issue. If you made it possible to do that as an option I'd be happy to test it :)

Thanks for the quick reply and keep up the great work!

Elfocrash commented 5 years ago

There is actually a way to do that yeah. Well kind of. You would have to create a DocumentClient object with your settings, then call the OpenAsync method to do all the initial calls, then use that document client to create a CosmonautClient and then use that to initialise your CosmosStores.

This should work, give it a try and let me know.

kloudwerkz commented 5 years ago

Cheers! I will give that a try and let you know how it goes.

Elfocrash commented 5 years ago

Closing this. Feel free to reopen if you need further help.

kloudwerkz commented 5 years ago

Hi Nick, I just got around to trying this and it works really well. I did some reading up and MS indicate the DocumentClient should be used in the same way as an HttpClient. i.e. One instance of it per AppDomain. That was comforting so I implemented your suggestion and massively reduced the warmup time.

In case anyone has a similar issue to mine, I did this:

var docClient = new DocumentClient(new Uri(CosmosUri), CosmosKey, new ConnectionPolicy
            {
                ConnectionMode = ConnectionMode.Direct,
                ConnectionProtocol = Protocol.Tcp
            });
var client = new CosmonautClient(docClient);

and then called each CosmosStore as: fooCosmosStore = new CosmosStore(client, CosmosDbName);

I get the 2-3 second lag on the first init of a CosmosStore but once the client is connected the rest of them are created in < 1 second each. That's way quicker than before. I now have one DocumentClient per service/region so that seems like a good solution.

Thanks for your help!