Skip to main content

Database

The system uses a SQL database to store meta data and queue messages. At the time of writing we support SQLite, Postgres and SQL Server. Feel free to reach out to us if you need support for another database engine like MySQL or MariaDB.

Configuring which database to use​

The support for the different databases exists in their own NuGet packages:

CommerceMind.Nexus.Postgres
CommerceMind.Nexus.SqlServer
CommerceMind.Nexus.Sqlite

Install the package(s) you need before you initialize it below.

After you've installed the db package(s) you open your Program.cs and go to these lines:

builder.Services
.AddNexus()
.AddSingleServerInstanceEvents() // Use a shared broker when running multiple instances
.AddFunctions()
.AddJobs()
.AddQueues()
// Either:
.AddPostgresConnection()
// Or:
.AddSqlServerConnection()
// Or:
.AddSqliteConnection()

// This should be the last thing you do, after you've finished configuring Nexus
.Build();

By default the system will call IConfiguration.GetConnectionString("postgres"), IConfiguration.GetConnectionString("sqlserver") or IConfiguration.GetConnectionString("sqlite") to get the connection string to the database. If that doesn't work for you can register a lamda to provide your connection string like this:

builder.Services.AddNexus().AddPostgresConnection(options =>
{
options.ConnectionStringLoader = serviceProvider =>
{
// Find and return your connection string here
};
});

// Or:
builder.Services.AddNexus().AddSqlServerConnection(options =>
{
options.ConnectionStringLoader = serviceProvider =>
{
// Find and return your connection string here
};
});

// Or:
builder.Services.AddNexus().AddSqliteConnection(options =>
{
options.ConnectionStringLoader = serviceProvider =>
{
// Find and return your connection string here
};
});
Using a ConnectionStringLoader

When you use ConnectionStringLoader you need to make sure that it's a very fast operation as the loader will be called every time Nexus runs a database query. You need to make sure to cache the value if you fetch it from an external store such as Azure KeyVault.

Controlling the connection​

By default Nexus will open and close database connections for almost every query, and rely on the underlying connection pooling. In some cases you might want to have more control over the connection such as if you want to include the Nexus queries inside a transaction.

One option is to use a DbConnectionScope like this:

public async Task RunAsync(
IEnqueuer<SomeMessage> enqueuer, IQueueReadinessNotifier readinessNotifier)
{
// CreateDbConnection returns an application-owned DbConnection.
await using var connection = CreateDbConnection();
await connection.OpenAsync();
await using var transaction = await connection.BeginTransactionAsync();

using (new DbConnectionScope(connection, transaction))
{
await enqueuer.EnqueueAsync(new SomeMessage());
await transaction.CommitAsync();
}

// The caller owns this explicit commit, so notify after it succeeds.
await readinessNotifier.NotifyAsync(enqueuer.QueueName);
}

DbConnectionScope is in CommerceMind.Nexus.Abstractions.Core.Database. Compatible Nexus database operations inside the block reuse the supplied connection/transaction. It does not make remote APIs, external brokers, or in-memory updates transactional. The caller owns commit/rollback and should notify readiness after a successful explicit commit; the scope does not provide a general after-commit hook.

If the EF integration is enabled, use its scoped unit of work instead of this standalone-enqueue example: queue operations are deferred until EF saves/commits. EF already requests readiness after commit. Use ImmediateQueueOperationScope only for deliberately independent operations, not to bypass entity-plus-message atomicity.

Database initialization​

Nexus initializes its tables and queue schemas before background processing begins. SQL Server and PostgreSQL serialize initialization with session-owned database locks. SQLite is intended for simple/local use and does not coordinate concurrent initializers; initialize it from one process.

V3 stores completed migration manifests and per-queue schema definitions in reserved Nexus.Startup.* key-value records. Unchanged starts skip migration-history and queue table/index/column checks. A new queue or changed filterable properties still trigger their own initialization, even if regular migrations have already run. Changed filters are backfilled from persisted messages, so those messages must remain readable by the updated contract. Interrupted backfills retain a plan and can resume on the next start.

Keep Nexus-owned schema and initialization records intact. Nexus no longer inspects or automatically repairs an unchanged table after manual schema edits. Coordinate instances when deploying incompatible filter/schema changes. Application migrations belong in regular migrations; custom idempotent migrations still run at every startup.

When upgrading from v2, stop all v2 instances first. V3 renames the persisted job tables and rebuilds queue lease indexes. Allow initialization time and extra database capacity for large queues; see the upgrade guide. A package downgrade alone does not reverse these changes.

Transient errors​

Nexus has built-in retries of transient errors using Polly but if you want to you can add custom retry logic by creating your own implementation of the ITransientDatabaseErrorRetryer interface.

Database maintenance​

Nexus includes two jobs that are enabled by default. One job is called RebuildDatabaseIndexes and will rebuild database indexes for Postgres and SQL Server. It's scheduled to run daily at midnight in JobsOptions.ScheduleTimeZone (UTC by default). If you're using SQLite this job won't do anything as it's not needed to rebuild SQLite indexes.

By default this job will only rebuild indexes in tables created by Nexus. If you want it to rebuild indexes for all tables in the database you can can pass an option to AddNexus().AddPostgresConnection() or AddNexus().AddSqlServerConnection():

builder.Services.AddNexus().AddPostgresConnection(options =>
{
options.LimitDatabaseMaintenanceToNexusTables = false;
});

// Or:
builder.Services.AddNexus().AddSqlServerConnection(options =>
{
options.LimitDatabaseMaintenanceToNexusTables = false;
});

The other job is called RunDatabaseMaintenanceJob and is also enabled by default and but not scheduled. For Postgres that will run VACUUM and then ANALYZE. If you're using SQL Server this job won't do anything as SQL Server has automatic update of query statistics. If you're using SQLite this job will not do anything either.

Remove database maintenance jobs​

If you want to completely hide these jobs you can pass a filter lambda when you register the jobs system to exclude these jobs like this:

builder.Services.AddNexus().AddJobs(options =>
{
options.JobFilter = jobType => jobType.Name != "RebuildDatabaseIndexesJob" && jobType.Name != "RunDatabaseMaintenanceJob";
});

If you want to run the jobs but do something different than the defaults you can create your own implementation of the IDatabaseMaintenanceService interface and register it with the service provider like this:

builder.Services.AddScoped<IDatabaseMaintenanceService, MyDatabaseMaintenanceService>();

Using another database engine​

If you want to use another database engine such as MySQL or MariaDB you can always implement it and open a PR to the Github repository. If you don't have access yet to the repository just ping us! Another option is of course to hire us to implement it for you.