Skip to content

Commit 1e4f126

Browse files
andreasohlundpoornimanayarcquirosj
authored
Supporting transactional session for send only endpoints (#7089)
* Remove send only limitation * Add skeleton docs * Add link to persisters * Document that persisters will disable cleanup when a remote processor is used * Pull new section out to partial * Add v3.3 snippet * Clarify that not all pesisters have endpoint driven cleanup * Update to beta 1 * Typo * Add place holder for SQLP config * SQLP instructions for sharing outbox table * Add prerelease marker * Add sql p to outbox cleanup disable list * Fix cleanup * Add cosmos config requirements * Wording * Document ravendb * Cleanup * Spike docs for setting ravendb endpoint name * Improve ravendb outbox docs and link back to processor section * Add dynamo db * Add docs for dynamo * Add note * Remove docs for new api * Cleanup docs to match new design * Update snippets to new api * Correct label * Use stable component for snippets * Typos * Add inline upgrade guide * Update nservicebus/transactional-session/index_remote-processor_transactionalsession_[3.3,).partial.md Co-authored-by: Poornima Nayar <[email protected]> * Spelling * Document new failure scenario * Spelling * Apply suggestions from code review Co-authored-by: Poornima Nayar <[email protected]> * Spelling * Add note about tx session having to be enabled * Fix link * Fix link * Update index_remote-processor_transactionalsession_[3.3,).partial.md * Update nservicebus/transactional-session/index.md * minor improvement to existing text * adding link back --------- Co-authored-by: Poornima Nayar <[email protected]> Co-authored-by: Christian <[email protected]>
1 parent 1f9f2c0 commit 1e4f126

File tree

8 files changed

+152
-10
lines changed

8 files changed

+152
-10
lines changed

Snippets/TransactionalSession/TransactionalSession.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TransactionalSession_3", "T
1111
EndProject
1212
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TransactionalSession_3.1", "TransactionalSession_3.1\TransactionalSession_3.1.csproj", "{F914C826-E6E1-47F5-89AD-1BA1A5DA19BE}"
1313
EndProject
14+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TransactionalSession_3.3", "TransactionalSession_3.3\TransactionalSession_3.3.csproj", "{1D6EA06A-D31A-476E-833C-981C5A8DC946}"
15+
EndProject
1416
Global
1517
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1618
Debug|Any CPU = Debug|Any CPU
@@ -69,6 +71,18 @@ Global
6971
{F914C826-E6E1-47F5-89AD-1BA1A5DA19BE}.Release|x64.Build.0 = Release|Any CPU
7072
{F914C826-E6E1-47F5-89AD-1BA1A5DA19BE}.Release|x86.ActiveCfg = Release|Any CPU
7173
{F914C826-E6E1-47F5-89AD-1BA1A5DA19BE}.Release|x86.Build.0 = Release|Any CPU
74+
{1D6EA06A-D31A-476E-833C-981C5A8DC946}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
75+
{1D6EA06A-D31A-476E-833C-981C5A8DC946}.Debug|Any CPU.Build.0 = Debug|Any CPU
76+
{1D6EA06A-D31A-476E-833C-981C5A8DC946}.Debug|x64.ActiveCfg = Debug|Any CPU
77+
{1D6EA06A-D31A-476E-833C-981C5A8DC946}.Debug|x64.Build.0 = Debug|Any CPU
78+
{1D6EA06A-D31A-476E-833C-981C5A8DC946}.Debug|x86.ActiveCfg = Debug|Any CPU
79+
{1D6EA06A-D31A-476E-833C-981C5A8DC946}.Debug|x86.Build.0 = Debug|Any CPU
80+
{1D6EA06A-D31A-476E-833C-981C5A8DC946}.Release|Any CPU.ActiveCfg = Release|Any CPU
81+
{1D6EA06A-D31A-476E-833C-981C5A8DC946}.Release|Any CPU.Build.0 = Release|Any CPU
82+
{1D6EA06A-D31A-476E-833C-981C5A8DC946}.Release|x64.ActiveCfg = Release|Any CPU
83+
{1D6EA06A-D31A-476E-833C-981C5A8DC946}.Release|x64.Build.0 = Release|Any CPU
84+
{1D6EA06A-D31A-476E-833C-981C5A8DC946}.Release|x86.ActiveCfg = Release|Any CPU
85+
{1D6EA06A-D31A-476E-833C-981C5A8DC946}.Release|x86.Build.0 = Release|Any CPU
7286
EndGlobalSection
7387
GlobalSection(SolutionProperties) = preSolution
7488
HideSolutionNode = FALSE
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using NServiceBus;
6+
using NServiceBus.Persistence;
7+
using NServiceBus.TransactionalSession;
8+
9+
class Api
10+
{
11+
public async Task Open(IServiceScope scope, CancellationToken cancellationToken)
12+
{
13+
var session = scope.ServiceProvider.GetRequiredService<ITransactionalSession>();
14+
15+
#region configuring-commit-delay-transactional-session
16+
17+
await session.Open(new MyPersistenceOpenSessionOptions
18+
{
19+
CommitDelayIncrement = TimeSpan.FromSeconds(1)
20+
},
21+
cancellationToken: cancellationToken);
22+
23+
#endregion
24+
}
25+
26+
public void ConfigureRemoteProcessor(EndpointConfiguration endpointConfiguration)
27+
{
28+
#region configure-remote-processor
29+
30+
var transactionalSessionOptions = new TransactionalSessionOptions
31+
{
32+
ProcessorEndpoint = "MyProcessorEndpoint"
33+
};
34+
35+
endpointConfiguration.UsePersistence<MyPersistence>()
36+
.EnableTransactionalSession(transactionalSessionOptions);
37+
38+
#endregion
39+
}
40+
}
41+
42+
public static class TransactionalSessionConfigurationExtensions
43+
{
44+
public static void EnableTransactionalSession(this PersistenceExtensions<MyPersistence> persistence, TransactionalSessionOptions transactionalSessionOptions = null)
45+
{
46+
}
47+
}
48+
49+
public class MyPersistence : PersistenceDefinition
50+
{
51+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
using NServiceBus.TransactionalSession;
2+
3+
class MyPersistenceOpenSessionOptions : OpenSessionOptions
4+
{
5+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net8.0</TargetFramework>
4+
</PropertyGroup>
5+
<ItemGroup>
6+
<PackageReference Include="NServiceBus.TransactionalSession" Version="3.3.0" />
7+
</ItemGroup>
8+
</Project>

nservicebus/transactional-session/index.md

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ related:
88
- samples/transactional-session/cosmosdb
99
---
1010

11-
This article describes how to achieve consistency when modifying business data and sending messages, similar to the [outbox](/nservicebus/outbox), outside the context of an NServiceBus message handler.
11+
This article describes how to achieve consistency when modifying business data and sending messages, similar to the [outbox](/nservicebus/outbox), but outside the context of an NServiceBus message handler.
1212

1313
youtube: https://www.youtube.com/watch?v=-UOyxjnlYXs
1414

@@ -58,11 +58,13 @@ Once all the operations that are part of the atomic request have been executed,
5858

5959
snippet: committing-transactional-session
6060

61-
Disposing the transactional session without committing will roll back any changes that were made.
61+
Disposing of the transactional session without committing will roll back any changes that were made.
6262

6363
> [!NOTE]
6464
> The `Commit` operation may fail and throw an exception for reasons outlined in the [failure scenarios section](#failure-scenarios).
6565
66+
partial: remote-processor
67+
6668
## Requirements
6769

6870
The transactional session feature requires a supported persistence package to store outgoing messages. This feature is currently supported for the following persistence packages:
@@ -73,6 +75,7 @@ The transactional session feature requires a supported persistence package to st
7375
* [NHibernate](/persistence/nhibernate)
7476
* [RavenDB](/persistence/ravendb)
7577
* [MongoDB](/persistence/mongodb)
78+
* [DynamoDB](/persistence/dynamodb/)
7679

7780
## Transaction consistency
7881

@@ -87,7 +90,7 @@ With the outbox disabled, database and message operations are not applied until
8790

8891
The transactional session feature guarantees that all outgoing message operations are eventually consistent with the data operations.
8992

90-
Returning to the earlier example of a message handler which creates a `User` and then publishes a `UserCreated` event, the following process occurs. Details are described following the diagram.
93+
Returning to the earlier example of a message handler that creates a `User` and then publishes a `UserCreated` event, the following process occurs. Details are described following the diagram.
9194

9295
```mermaid
9396
sequenceDiagram
@@ -169,20 +172,35 @@ The endpoint receives the control message and processes it as follows:
169172

170173
## Failure scenarios
171174

172-
The transactional session provides atomic store-and-send guarantees, similar to the outbox feature (except for incoming message de-duplication). The control message is used to ensure that **exactly one** of the following outcomes occur:
175+
The transactional session provides atomic store-and-send guarantees, similar to the outbox feature (except for incoming message de-duplication). The control message is used to ensure that **exactly one** of the following outcomes occurs:
173176

174177
* Transaction finishes with data being stored, and outgoing messages eventually sent - when the `Commit` path successfully stores the `OutboxRecord`
175178
* Transaction finishes with no visible side effects - when the control message stores the `OutboxRecord`
176179

177-
Sending the control message first ensures that eventually, the transaction will have an atomic outcome. If the `Commit` of the `OutboxRecord` succeeds, the control message will ensure the outgoing operations are sent. If the `Commit` fails, the control message will (after the [maximum commit duration](#advanced-configuration-maximum-commit-duration) elapses) eventually be consumed, leaving no side effects.
180+
Sending the control message first ensures that eventually, the transaction will have an atomic outcome. If the `Commit` of the `OutboxRecord` succeeds, the control message will ensure the outgoing operations are sent.
181+
182+
### Failure to dispatch the control message
178183

179184
If dispatching the control message fails, the transactional session changes will roll back, and an error will be raised to the user committing the session.
180185

181-
If the transaction completes and the control message fails to process through all the retry attempts, the control message will be moved to the error queue, and the outgoing messages will not be dispatched. Once the error is resolved, the control message must be manually retried in ServicePulse to ensure the outgoing messages are dispatched. If this doesn't happen, the stored outgoing messages will never delivered. If that's undesirable, the system should be returned to a consistent state through another action.
186+
If the transaction completes and the control message fails to process through all the retry attempts, the control message will be moved to the error queue, and the outgoing messages will not be dispatched. Once the error is resolved, the control message must be manually retried in ServicePulse to ensure the outgoing messages are dispatched. If this doesn't happen, the stored outgoing messages will never be delivered. If that's undesirable, the system should be returned to a consistent state through another action.
187+
188+
### Failure to commit the outbox record
189+
190+
If the `Commit` fails, the control message will (after the [maximum commit duration](#advanced-configuration-maximum-commit-duration) elapses) eventually be consumed, leaving no side effects.
191+
192+
### Commit takes too long
193+
194+
When the commit takes longer than the [maximum commit duration](#advanced-configuration-maximum-commit-duration) the control message will result in a tombstone record in the outbox preventing the commit to succeed. The following exception is thrown:
195+
196+
`Failed to commit the transactional session. This might happen if the maximum commit duration is exceeded`
197+
198+
A variation of this is when using a remote processing endpoint that does not have the transactional session enabled. In this scenario, the tombstone record will be created immediately when the control message is processed, forcing a rollback of the commit. When this happens, the following exception is thrown:
199+
200+
`Failed to commit the transactional session. This might happen if the maximum commit duration is exceeded or if the transactional session has not been enabled on the configured processor endpoint - MyProcessorEndpoint`
182201

183202
## Limitations
184203

185-
* The transactional session cannot be used in send-only endpoints. A full endpoint is required to send a control message to the local queue.
186204
* The transport must have the same or higher availability guarantees as the database.
187205

188206
## Advanced configuration

nservicebus/transactional-session/index_config_transactionalsession_[3.1, ).partial.md renamed to nservicebus/transactional-session/index_config_transactionalsession_[3.1,).partial.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
#### CommitDelayIncrement
32

43
The time increment used to delay the commit of the transactional session when the outbox record is not yet in the storage (more details see [Phase 2](#how-it-works-phase-2)).
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
## Send-only
2+
3+
When used in a [send-only](/nservicebus/hosting/#self-hosting-send-only-hosting) endpoint, the transactional session must be configured with a remote endpoint(processor endpoint) that will manage the outbox on behalf of the send-only endpoint.
4+
5+
snippet: configure-remote-processor
6+
7+
> [!WARNING]
8+
> Both the send-only endpoint and the processor endpoint must be connected to the same database. See [documentation for the individual persisters](/persistence/) for more details.
9+
10+
> [!NOTE]
11+
The processor endpoint [must have both the outbox and the transactional session enabled](/nservicebus/transactional-session/#failure-scenarios-commit-takes-too-long).
12+
13+
> [!NOTE]
14+
> If migrating to this mode from an endpoint couldn't be made send-only due to the previous limitations of the transactional session, see the migration guidance below.
15+
16+
### Outbox cleanup
17+
18+
For persisters where [Outbox cleanup](/nservicebus/outbox/#outbox-expiration-duration) is performed by the endpoint instances, only the remote processing endpoint should have the cleanup enabled to prevent concurrent cleanup from happening.
19+
20+
Applies to:
21+
22+
- SQL Persistence
23+
- NHibernate
24+
25+
### Migration to send-only endpoint mode
26+
27+
For endpoints that previously couldn't be send-only because of the transactional session limitations, you can use the following procedure to migrate your endpoint.
28+
29+
#### Preparation
30+
31+
1. Ensure that no message handlers exist in the endpoint
32+
1. Deploy a new processor endpoint
33+
1. Stop the endpoint and ensure that all messages in the input queue are consumed
34+
35+
#### Configuration
36+
37+
1. Make the endpoint send only
38+
1. Configure the endpoint to use the new processor endpoint as shown above
39+
1. Start the endpoint
40+
41+
> [!NOTE]
42+
> The send-only endpoint will now use the outbox of the new processor endpoint. Since [message deduplication is not possible when using the transactional session](https://github.com/Particular/NServiceBus.TransactionalSession/issues/97), no data migration is needed
43+
44+
#### Cleanup
45+
46+
1. Remove the no longer used input queue of the send-only endpoint
47+
1. As needed, remove any database artefacts (tables, documents, etc) related to the send-only endpoint. See [documentation for the individual persisters](/persistence/) for more details.

persistence/ravendb/outbox.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ include: cluster-configuration-info
1515

1616
The [Outbox](/nservicebus/outbox) feature requires persistence in order to store messages and enable deduplication.
1717

18-
## Extra collections created by the RavenDB Outbox persistence
18+
## Storage format
1919

20-
To keep track of duplicate messages, the RavenDB implementation of Outbox creates a special collection of documents called `OutboxRecord`.
20+
The persister stores outbox data for all endpoints in a [collection](https://ravendb.net/docs/article-page/7.0/csharp/client-api/faq/what-is-a-collection) called `OutboxRecords`. To separate data for individual endpoints stored documents will have their endpoint name embedded in the document ID using the following format `Outbox/{Endpoint-name}/{Message-id}`.
2121

2222
## Deduplication record lifespan
2323

0 commit comments

Comments
 (0)