Can Cosmos SDK3 Container be a singleton?
Asked Answered
P

3

9

I would like to reduce load on Azure Cosmos DB SQL-API, which is called from a .NET Core Web API with dependency injection.

In App Insights, I have noticed that every call to the Web API results in GetDatabase and GetCollection calls to Cosmos which can take 5s to run when Cosmos is under heavy load.

I have made CosmosClient a singleton (e.g advice here - https://learn.microsoft.com/en-us/azure/cosmos-db/performance-tips-dotnet-sdk-v3-sql)

However I could not find any advice for whether the Database or Container objects could also be singletons so these are created for each request to the Web API.

I check for the existence of the database and collection (e.g. following advice here - https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.cosmosclient.getdatabase?view=azure-dotnet#remarks and https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.cosmosclient.getcontainer?view=azure-dotnet#remarks)

This means that for every request to the Web API, the following code is run

var databaseResponse = await this.cosmosClient.CreateDatabaseIfNotExistsAsync(
    this.databaseConfiguration.DatabaseName,
    throughput: this.databaseConfiguration.DatabaseLevelThroughput);
var database = databaseResponse.Database;
var containerResponse = await database.CreateContainerIfNotExistsAsync(containerId, partitionKey);
var container = containerResponse.Container;

Can I make Database and Container singletons and add them to the DI to be injected like CosmosClient in order to reduce the number of calls to GetDatabase and GetCollection seen in App Insights?

Pneumodynamics answered 15/6, 2020 at 7:19 Comment(0)
I
0

You don't need to call CreateIfNotExistsAsync every time, if you know that they are available, you can use CosmosClient.GetContainer(dbName, containerName) which is a lightweight proxy class.

Unless you are expecting the database and containers to be deleted dynamically at some point?

Idol answered 15/6, 2020 at 21:39 Comment(4)
Can the Container instance returned by CosmosClient.GetContainer(dbName, containerName be kept for the lifetime of the application like CosmosClient? e.g. I wrap the Cosmos Container in a "repository" class. In this repository class the Container is lazily instantiated on first use. If the repository class is a singleton then, the Container instance would exist for the lifetime of the application and be accessed by multiple threads. Would this cause problems.Pneumodynamics
e.g. are Container instances threadsafe like CosmosClientPneumodynamics
Yes, Container instance is just a proxy, nothing more. There is no overhead or network calls when calling GetContainer, so you can keep a single instance and it will be thread-safeIdol
Hi, there is also a discussion on github regarding this topic and seems that Container could be hold as singleton - linkLowder
Z
4

According to the latest Microsoft documentation, you create a CosmosClient Service singleton, which owns the Containers you will be working with. In affect making the Containers singletons as well.

First, make your interface contract:

public interface ICosmosDbService
{
    // identify the database CRUD operations you need
}

Second, define your Service based on the contract:

public class CosmosDbService : ICosmosDbService
{
    private Container _container;

    public CosmosDbService(CosmosClient dbClient, string databaseName, string containerName)
    {
        this._container = dbClient.GetContainer(databaseName, containerName);
    }
    
    // your database CRUD operations go here using the Container field(s)
}

Third, create a method in your Startup class to return a CosmosClient:

private static async Task<CosmosDbService> InitializeCosmosClientAsync(IConfigurationSection cosmosConfig)
{
    var databaseName = cosmosConfig.GetSection("DatabaseName").Value;
    var containerName = cosmosConfig.GetSection("ContainerName").Value;
    var account = cosmosConfig.GetSection("Account").Value;
    var key = cosmosConfig.GetSection("Key").Value;
    var client = new Microsoft.Azure.Cosmos.CosmosClient(account, key);
    var database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
    await database.Database.CreateContainerIfNotExistsAsync(containerName, "/id");
    return new CosmosDbService(client, databaseName, containerName);
}

Finally, add your CosmosClient to the ServiceCollection:

public void ConfigureServices(IServiceCollection services)
{
    var cosmosConfig = this.Configuration.GetSection("CosmosDb");
    var cosmosClient = InitializeCosmosClientAsync(cosmosConfig).GetAwaiter().GetResult();
    services.AddSingleton<ICosmosDbService>(cosmosClient);
}

Now your CosmosClient has only been created once (with all the Containers), and it will be reused each time you get it through Dependency Injection.

Zsazsa answered 15/1, 2022 at 18:47 Comment(2)
How do we inject a generic service which can query different containers?Bedpost
CreateContainerIfNotExistsAsync returns a ContainerResponse, that already has a property with the Container reference. You might as well provide that to the constructor no?Polky
E
0

CreateDatabaseIfNotExistsAsync should only be called once as it is just a setup step for DB configuration.

You'd better create a DbService to persist the container object. And inject the DbService into each services instead of the DB client

Eberly answered 15/6, 2020 at 9:44 Comment(3)
Thanks - I'd like to do this. But I can't find any documentation about whether it's safe for the Container to be kept for the lifetime of the application (unlike CosmosClient where the docs say it's ok). Do you know if the Container instance is safe to keep for the duration of the application and threadsafe?Pneumodynamics
found a doc for repository pattern with cosmos DB: Repository Pattern with Azure Cosmos DB SQL API. In the sample repo: PartitionedRepository, the author init a new client in each query...I don't know whether it's a good practiceEberly
sorry, I misunderstand the sample repo. They persist the IDocumentClient object and wrap it with a DB client. maybe you can read the code as a referenceEberly
I
0

You don't need to call CreateIfNotExistsAsync every time, if you know that they are available, you can use CosmosClient.GetContainer(dbName, containerName) which is a lightweight proxy class.

Unless you are expecting the database and containers to be deleted dynamically at some point?

Idol answered 15/6, 2020 at 21:39 Comment(4)
Can the Container instance returned by CosmosClient.GetContainer(dbName, containerName be kept for the lifetime of the application like CosmosClient? e.g. I wrap the Cosmos Container in a "repository" class. In this repository class the Container is lazily instantiated on first use. If the repository class is a singleton then, the Container instance would exist for the lifetime of the application and be accessed by multiple threads. Would this cause problems.Pneumodynamics
e.g. are Container instances threadsafe like CosmosClientPneumodynamics
Yes, Container instance is just a proxy, nothing more. There is no overhead or network calls when calling GetContainer, so you can keep a single instance and it will be thread-safeIdol
Hi, there is also a discussion on github regarding this topic and seems that Container could be hold as singleton - linkLowder

© 2022 - 2024 — McMap. All rights reserved.