Deployment checklist¶
A practical guide to getting an OpStream-powered app into production.
Pre-flight¶
- Storage replaced. No
MemoryDocumentStorein production —UseSqlServer()/UseRedisStorage()/UseEfCoreStorage<T>()etc. - Authorizer replaced. No
AllowAllAuthorizerin production —UseAuthorization<MyAuthorizer>()wired against your identity model. - Backplane chosen. Single node →
LocalBackplaneis fine. Multi-node →UseRedisBackplane(). - Snapshot policy tuned. Default 100 ops / 5 min is safe; tune for your document size and write rate.
- CORS / Auth configured. Standard ASP.NET Core checklist — OpStream doesn't add anything custom here.
Single-node deployment¶
A single ASP.NET Core process running OpStream + your app:
builder.Services
.AddOpStream()
.UseSqlServer(connStr)
.UseAuthorization<MyAuthorizer>()
.AddSignalRTransport();
builder.Services.AddSignalR();
app.MapOpStreamSignalR();
Storage durability is your only worry — back up the database normally. No backplane needed.
Multi-node deployment¶
Two or more nodes behind a load balancer + Redis:
builder.Services
.AddOpStream()
.UseSqlServer(connStr)
.UseRedisBackplane(redisConnStr)
.UseAuthorization<MyAuthorizer>()
.AddSignalRTransport();
Load balancer¶
- Sticky sessions are NOT required. OpStream's router transparently proxies requests to the document's owner node via the backplane.
- A peer that lands on the "wrong" node still gets correct ordering and fan-out; the proxy adds one Redis round-trip per request.
- If you can enable sticky-by-cookie cheaply, it's a small latency win (avoids the proxy). Not required.
Health checks¶
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false // process up?
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = h => h.Tags.Contains("opstream")
});
Use /health/live for the load balancer's liveness probe and
/health/ready for readiness — readiness goes false when storage or
the backplane can't be reached.
Aspire deployment¶
If you're using .NET Aspire to orchestrate your topology:
// AppHost
var redis = builder.AddRedis("redis");
var sql = builder.AddSqlServer("sql").AddDatabase("opstream");
builder.AddProject<Projects.MyApp>("api")
.WithReference(redis)
.WithReference(sql);
Then in MyApp/Program.cs:
builder.AddRedisClient("redis");
builder.AddSqlServerClient("opstream");
builder.Services
.AddOpStream()
.UseSqlServer(builder.Configuration.GetConnectionString("opstream")!)
.UseRedisBackplane(builder.Configuration.GetConnectionString("redis")!);
See OpStream.Hosting.Aspire for the matching AppHost-side resource helpers.
Logging and tracing¶
See Observability. At minimum:
builder.Services.AddOpenTelemetry()
.ConfigureResource(r => r.AddService("my-app"))
.WithTracing(t => t.AddSource("OpStream").AddOtlpExporter())
.WithMetrics(m => m.AddMeter("OpStream").AddOtlpExporter());
Backup strategy¶
- Op log: append-only; incremental backups are cheap. Frequency matches your RPO.
- Snapshots: upserted; ship with the rest of the database.
- Awareness: ephemeral; never backed up.
Common pitfalls¶
| Symptom | Likely cause | Fix |
|---|---|---|
| Edits don't reach the other client | Two server processes without backplane | UseRedisBackplane() |
| 403 on every op | Default AllowAllAuthorizer replaced incorrectly |
Check DI registration order |
| Slow document load | Op log grew large without snapshots | Tune UseSnapshotPolicy |
MemoryStorage warning in prod logs |
Storage Use* call missing |
Add the storage provider |
| Sticky-session client gets stale snapshot after failover | Ownership lease still held by failed node | Lower OwnershipLeaseTtl |