You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-`TransactionIsolationLevel` - isolation level used for schema provisioning. Defaults to `ReadCommitted`.
70
+
-`PollDelay` - delay used when no message is available or after a transient polling error.
71
+
-`PollBatchSize` - maximum number of messages locked by one polling operation.
72
+
-`LockDuration` - how long a message lock is held before another consumer may pick it up.
73
+
-`MaxDeliveryAttempts` - number of processing attempts before a message is marked aborted.
74
+
-`NotifyOnPublish` - when enabled, producers call `pg_notify` after inserting messages. Polling remains the correctness mechanism.
75
+
-`SchemaCreationRetry` and `OperationRetry` - retry settings for schema creation and regular database operations.
76
+
77
+
PostgreSQL identifiers configured through `DatabaseSchemaName`, `DatabaseTableName`, and `DatabaseMigrationsTableName` are validated and quoted. Use letters, numbers, and underscores, and do not start identifiers with a number.
78
+
79
+
### Queues, topics, and request/response
80
+
81
+
Use `DefaultQueue()` and `Queue()` for competing-consumer queues:
The transport stores messages with two identifiers:
106
+
107
+
-`sequence_id` - a `bigserial` physical key used for insert locality and ordered polling.
108
+
-`id` - a logical `uuid` message id used by the transport when completing or failing messages.
109
+
110
+
By default, PostgreSQL uses `PostgreSqlMessageIdGenerationMode.ClientGuidGenerator` with `PostgreSqlSequentialGuidGenerator`, which creates sequential-ish UUIDs client-side for better index locality than random UUIDs.
-`ClientGuidGenerator` - client-side UUID generation. Defaults to `PostgreSqlSequentialGuidGenerator`, but `GuidGenerator` or `GuidGeneratorType` can be replaced.
- Message rows use a `bigserial` physical key for insert locality and a logical `uuid` message id.
50
134
- The default client-side id generator is sequential-ish for index locality. Random database ids can be selected through `cfg.IdGeneration`.
51
135
- Producers optionally call `pg_notify` after inserting messages. Polling remains the correctness mechanism.
136
+
137
+
### Polling and locking
138
+
139
+
Consumers poll the shared message table in batches. PostgreSQL uses `FOR UPDATE SKIP LOCKED` so competing consumers can skip rows already locked by another instance.
140
+
141
+
When a consumer locks a row, the transport stores the consumer instance id and lock expiration. If the process stops before completing the message, the row becomes visible again after `LockDuration`.
142
+
143
+
`pg_notify` is used as a lightweight wake-up hint when `NotifyOnPublish` is enabled. It is not used as the durable delivery mechanism; message rows in the database remain the source of truth.
144
+
145
+
### Retries and failed messages
146
+
147
+
Successful processing marks the row as complete. Failed processing increments `delivery_attempt`, clears the lock, and makes the row available for another attempt. Once `MaxDeliveryAttempts` is reached, the row is marked aborted and will no longer be delivered.
148
+
149
+
The transport retries transient PostgreSQL errors around schema provisioning and operations according to `SchemaCreationRetry` and `OperationRetry`.
150
+
151
+
### Schema provisioning
152
+
153
+
The provider provisions the required message, subscription, and migration tables during bus startup. All cooperating services should use the same database, schema, and table names.
154
+
155
+
## Testing locally
156
+
157
+
The integration tests use Testcontainers and require Docker to be running:
158
+
159
+
```bash
160
+
dotnet test src/Tests/SlimMessageBus.Host.PostgreSql.Test/SlimMessageBus.Host.PostgreSql.Test.csproj --filter "Category=Integration"
161
+
```
162
+
163
+
The repository also contains `infrastructure.ps1` for standing up shared development infrastructure used by broader integration test runs.
Please read the [Introduction](intro.md) before reading this provider documentation.
4
4
5
-
-[About](#about)
6
-
-[SQL Compatibility](#sql-compatibility)
7
-
-[Configuration](#configuration)
8
-
-[How it works](#how-it-works)
9
-
10
-
## About
11
-
5
+
-[About](#about)
6
+
-[SQL Compatibility](#sql-compatibility)
7
+
-[Configuration](#configuration)
8
+
-[Provider settings](#provider-settings)
9
+
-[Queues, topics, and request/response](#queues-topics-and-requestresponse)
10
+
-[Message id generation](#message-id-generation)
11
+
-[How it works](#how-it-works)
12
+
-[Polling and locking](#polling-and-locking)
13
+
-[Retries and failed messages](#retries-and-failed-messages)
14
+
-[Schema provisioning](#schema-provisioning)
15
+
-[Testing locally](#testing-locally)
16
+
17
+
## About
18
+
12
19
The SQL transport provider allows to leverage a single shared SQL database instance as a messaging broker for all the collaborating producers and consumers.
13
20
14
21
This transport might be optimal for simpler applications that do not have a dedicated messaging infrastructure available, do not have high throughput needs, or want to target a simplistic deployment model.
@@ -18,12 +25,20 @@ When the application grows over time, and given that SMB is an abstraction, the
18
25
## SQL Compatibility
19
26
20
27
This transport targets SQL Server / Azure SQL (T-SQL).
21
-
22
-
## Configuration
23
-
28
+
29
+
## Configuration
30
+
31
+
Install the transport package:
32
+
33
+
```bash
34
+
dotnet add package SlimMessageBus.Host.Sql
35
+
```
36
+
24
37
The configuration is arranged via the `.WithProviderSql(cfg => {})` method on the message bus builder.
The same SQL database instance is required for all the producers and consumers to collaborate.
66
+
67
+
### Provider settings
68
+
69
+
The most commonly configured settings are:
70
+
71
+
-`ConnectionString` - required SQL Server or Azure SQL connection string.
72
+
-`DatabaseSchemaName` - schema containing the transport tables. Defaults to `dbo`.
73
+
-`DatabaseTableName` - base message table name, for example `Messages`. The durable subscription table uses the same base name with a `Subscriptions` suffix.
74
+
-`DatabaseMigrationsTableName` - table used to track transport schema migrations.
75
+
-`CommandTimeout` - optional SQL command timeout.
76
+
-`TransactionIsolationLevel` - isolation level used by transport transactions. Defaults to `ReadCommitted`.
77
+
-`PollDelay` - delay used when no message is available or after a transient polling error.
78
+
-`PollBatchSize` - maximum number of messages locked by one polling operation.
79
+
-`LockDuration` - how long a message lock is held before another consumer may pick it up.
80
+
-`MaxDeliveryAttempts` - number of processing attempts before a message is marked aborted.
81
+
-`SchemaCreationRetry` and `OperationRetry` - retry settings for schema creation and regular database operations.
82
+
83
+
### Queues, topics, and request/response
84
+
85
+
Use `DefaultQueue()` and `Queue()` for competing-consumer queues:
The transport stores messages with two identifiers:
110
+
111
+
-`SequenceId` - a clustered `bigint identity` physical key used for insert locality and ordered polling.
112
+
-`Id` - a logical `uniqueidentifier` message id used by the transport when completing or failing messages.
113
+
114
+
By default, SQL uses `SqlMessageIdGenerationMode.ClientGuidGenerator` with `SqlSequentialGuidGenerator`, which creates sequential-ish GUIDs client-side for better index locality than random GUIDs.
-`ClientGuidGenerator` - client-side GUID generation. Defaults to `SqlSequentialGuidGenerator`, but `GuidGenerator` or `GuidGeneratorType` can be replaced.
129
+
-`DatabaseGeneratedGuid` - uses `NEWID()`.
130
+
-`DatabaseGeneratedSequentialGuid` - uses the table default, which is intended for SQL Server sequential GUID generation.
131
+
132
+
## How it works
133
+
134
+
The same SQL database instance is required for all the producers and consumers to collaborate.
53
135
Therefore ensure all of the service instances point to the same database cluster.
54
136
55
-
- A messages table is used to store exchanged messages (by default table is called `Messages`).
137
+
- A messages table is used to store exchanged messages.
56
138
- A subscriptions table is used to store durable topic subscriptions configured by consumers.
57
139
- Producers send messages to the messages table.
58
140
- There are two types of entities (queues, and topics for pub/sub).
@@ -66,3 +148,29 @@ Therefore ensure all of the service instances point to the same database cluster
66
148
- In the future we might consider:
67
149
- Table per each entity, so that we can minimize table locking.
68
150
- Sessions to ensure order of processing within the same message session ID - similar to how Azure Service Bus feature or Apache Kafka topic-partition works.
151
+
152
+
### Polling and locking
153
+
154
+
Consumers poll the shared message table in batches. SQL Server locking hints (`ROWLOCK`, `UPDLOCK`, `READPAST`) are used so competing consumers can skip rows already locked by another instance.
155
+
156
+
When a consumer locks a row, the transport stores the consumer instance id and lock expiration. If the process stops before completing the message, the row becomes visible again after `LockDuration`.
157
+
158
+
### Retries and failed messages
159
+
160
+
Successful processing marks the row as complete. Failed processing increments `DeliveryAttempt`, clears the lock, and makes the row available for another attempt. Once `MaxDeliveryAttempts` is reached, the row is marked aborted and will no longer be delivered.
161
+
162
+
The transport retries transient SQL errors around schema provisioning and operations according to `SchemaCreationRetry` and `OperationRetry`.
163
+
164
+
### Schema provisioning
165
+
166
+
The provider provisions the required message, subscription, and migration tables during bus startup. All cooperating services should use the same database, schema, and table names.
167
+
168
+
## Testing locally
169
+
170
+
The integration tests use Testcontainers and require Docker to be running:
171
+
172
+
```bash
173
+
dotnet test src/Tests/SlimMessageBus.Host.Sql.Test/SlimMessageBus.Host.Sql.Test.csproj --filter "Category=Integration"
174
+
```
175
+
176
+
The repository also contains `infrastructure.ps1` for standing up shared development infrastructure used by broader integration test runs.
0 commit comments