Upgrade to 1.6.0
Two changes need attention. Both affect only the Cosmos DB store.
- Containers are created with an indexing policy — nothing to do unless you query the container yourself. Existing containers are untouched.
- The Cosmos DB client is now shared — only if you construct
CosmosDataStore,CosmosDomainServiceorCosmosSetupyourself.
Cosmos DB containers are created with an indexing policy
Only affects the Cosmos DB store, and only containers that CosmosSetup creates from now on.
CreateDatabaseAndContainerIfNotExist used to create the container with the Cosmos DB default
indexing policy, which indexes every path of every document — including the serialised data
payload, the largest property in the document, which no Memoria query can filter on. It now creates
the container with a policy covering only the paths the store filters or sorts on. Measured against
the emulator that is about 2.4% off writes and 3–6% off reads.
Existing containers are untouched. The policy applies only to a container this call creates, so upgrading changes nothing about a database you already have. To bring an existing container across, ask for it explicitly:
await cosmosSetup.ReplaceIndexingPolicy(CosmosIndexingPolicy.CreateRecommended());
That starts a background reindex: the container stays online and writes keep succeeding, but queries
can return incomplete results until it finishes. Do it during a quiet period on a container that
holds data. The equivalent scripts under scripts/install do the same thing through the Azure CLI.
If you query the container yourself, check your queries first. Filtering or sorting on a path the policy excludes still returns correct results, but scans the partition instead of using an index. Either add the path, or keep the previous behaviour by passing a policy of your own:
await cosmosSetup.CreateDatabaseAndContainerIfNotExist(new IndexingPolicy());
See Tune the Cosmos DB container for which paths are indexed and why, including why there are no composite indexes.
The Cosmos DB client is now shared
Previously CosmosDataStore and CosmosDomainService each built their own CosmosClient in their
constructor, and each disposed it. Both are registered with a scoped lifetime, so an ASP.NET Core
application created and destroyed two clients per request, and CosmosSetup created a third on every
call.
A CosmosClient is designed to live for the lifetime of the application: it performs account
discovery, builds a routing map, and — in Direct mode, the Memoria default — opens its own
connections to every replica it touches. None of that survives disposal, so every request paid the
warm-up again.
One CosmosClient is now created for the application and shared. It is owned by a new
CosmosClientProvider, which AddMemoriaCosmos registers as a singleton.
If you register through AddMemoriaCosmos
Nothing to do. The wiring changed underneath you.
If you construct the types yourself
Both constructors now take a CosmosClientProvider in place of IOptions<CosmosOptions>, and so
does CosmosSetup:
// Before
var dataStore = new CosmosDataStore(options, timeProvider, httpContextAccessor);
var domainService = new CosmosDomainService(options, timeProvider, httpContextAccessor, dataStore);
var setup = new CosmosSetup(options);
// After
var clientProvider = new CosmosClientProvider(options);
var dataStore = new CosmosDataStore(clientProvider, timeProvider, httpContextAccessor);
var domainService = new CosmosDomainService(clientProvider, timeProvider, httpContextAccessor, dataStore);
var setup = new CosmosSetup(options, clientProvider);
Create one CosmosClientProvider and share it, as the example does — one per call would reintroduce
exactly the cost this change removes. Dispose it when the application shuts down; the dependency
injection container does that for you.
Resolve CosmosClientProvider if you need the shared CosmosClient or Container directly.
Two consequences worth knowing
Dispose() on the store types no longer does anything. CosmosDataStore and
CosmosDomainService still implement IDisposable, because ICosmosDataStore and IDomainService
declare it, but the client they used to dispose is not theirs to close. Existing using blocks stay
correct and now stop tearing down connections other scopes are still using.
Options are read once. The client is built on first resolution, so changing CosmosOptions
afterwards no longer affects the connection it holds. Previously each scope picked up the current
options. If you relied on reconfiguring the endpoint, key or client options at runtime, that no
longer takes effect.