Skip to content

Commit a5c8279

Browse files
committed
Add performance notes, tweak the receive loop to not setup extra unnecessary SqlDependencies when it can be avoided.
1 parent 9f695d0 commit a5c8279

File tree

2 files changed

+28
-4
lines changed

2 files changed

+28
-4
lines changed

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,17 @@ services.Configure<SqlServerOptions>(Configuration.GetSection("SignalR:SqlServer
7777

7878
## Caveats
7979

80-
* As mentioned above, if SQL Server Service Broker is not available, messages will not always be transmitted immediately since a fallback of periodic querying must be used.
81-
* This is not the right solution for applications with a need for very high throughput, or very high degrees of scale-out. Consider Redis or Azure SignalR Service instead for such cases. You should always do an appropriate amount of testing to determine if a particular solution is suitable for your application.
82-
* While this uses the same table schema as the classic ASP.NET SignalR SQL Server backplane, the message payloads are completely different. If you are migrating, consider configuring a different `SchemaName` to prevent conflicts.
80+
As mentioned above, if SQL Server Service Broker is not available, messages will not always be transmitted immediately since a fallback of periodic querying must be used.
81+
82+
While this uses the same table schema as the classic ASP.NET SignalR SQL Server backplane, the message payloads are completely different. If you are migrating, consider configuring a different `SchemaName` to prevent conflicts.
83+
84+
### Performance
85+
86+
This is not the right solution for applications with a need for very high throughput, or very high degrees of scale-out. Consider Redis or Azure SignalR Service instead for such cases. You should always do an appropriate amount of testing to determine if a particular solution is suitable for your application.
87+
88+
The results of some ad-hoc performance testing yielded that you can expect about 1000 messages per second per table (setting `TableCount`). However, increasing table count does have diminishing returns; attempting to push 20,000 messages/sec with 20 tables had a throughput of only ~10,000 messages/sec. This was observed with a SQL Server instance and load generator running on the same machine, an i9 9900k with an SSD, with a message size of ~200 bytes. A Redis backplane on the same hardware sustained 20,000 messages per second without issue.
89+
90+
Do note that a broadcast message is considered a single message. Any call to `SendAsync` within a hub is a single message.
8391

8492
## License
8593

src/IntelliTect.AspNetCore.SignalR.SqlServer/Internal/SqlServer/SqlReceiver.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,13 @@ private async Task NotificationLoop(CancellationToken cancellationToken)
107107

108108
if (recordCount > 0)
109109
{
110+
// If we got one message, there might be more coming right after.
111+
// Keep consuming as fast as we can until we stop getting more messages.
112+
while (recordCount > 0)
113+
{
114+
recordCount = await ReadRows(null);
115+
}
116+
110117
_logger.LogDebug("{0}Records were returned by the command that sets up the SQL notification, restarting the receive loop", _tracePrefix);
111118
continue;
112119
}
@@ -122,7 +129,16 @@ private async Task NotificationLoop(CancellationToken cancellationToken)
122129
case SqlNotificationType.Change when depResult.Info is SqlNotificationInfo.Update:
123130
// Typically means new records are available. (TODO: verify this?).
124131
// Loop again to pick them up by performing another query.
125-
_logger.LogTrace("{0}SQL notification details: Type={1}, Source={2}, Info={3}", _tracePrefix, depResult.Type, depResult.Source, depResult.Info); continue;
132+
_logger.LogTrace("{0}SQL notification details: Type={1}, Source={2}, Info={3}", _tracePrefix, depResult.Type, depResult.Source, depResult.Info);
133+
134+
// Read rows immediately, since on the next loop we know for certain there will be rows.
135+
// There's no point doing a full loop that includes setting up a SqlDependency that
136+
// will go unused due to the fact that we pulled back rows.
137+
while (recordCount > 0)
138+
{
139+
recordCount = await ReadRows(null);
140+
}
141+
continue;
126142

127143
case SqlNotificationType.Change when depResult.Source is SqlNotificationSource.Timeout:
128144
// Expected while there is no activity. We put a timeout on our SqlDependency so they're not running infinitely.

0 commit comments

Comments
 (0)