NServiceBus inefficient SQL with persistence

There is an issue within the codebase when persistence is being used in that Inserts and Updates generated do not match the column description within SQL Server. In these cases SQL Server will add CONVERT functions to the plan which is driving up cpu usage and restricts the actual plans available to SQL Server to use.

DECLARE @MessageId NVarChar(36) SET @MessageId = '1a3161d8-6d2f-429e-a761-ae3c01370460'
DECLARE @Operations NVarChar(2) SET @Operations = '[]'
DECLARE @PersistenceVersion NVarChar(7) SET @PersistenceVersion = '6.1.1.0'

-- Executed query

insert into [dbo].[Services_Response_OutboxData]
(
    MessageId,
    Operations,
    PersistenceVersion
)
values
(
    @MessageId,
    @Operations,
    @PersistenceVersion
)

Plan
[Expr1003] = CONVERT_IMPLICIT(nvarchar(200),[@MessageId],0)
[Expr1004] = CONVERT_IMPLICIT(nvarchar(max),[@Operations],0)
[Expr1005] = CONVERT_IMPLICIT(varchar(23),[@PersistenceVersion],0)

The actual columns in the table have different descriptors on the sizes leading to SQL placing CONVERT functions into the generated plan.

Can this be fixed please as it is restricting the maximum performance that persistence can be used at for us?

Brian

Hi Brian!

You are correct, I have raised an issue to track a fix for this

can you describe the impact this has on your system? Is this somehow blocking you?

Cheers,

Andreas

1 Like

Thanks Andreas.
We have only started using it and this got spotted as an issue. Glad it has been raised already!

Brian

I noticed this issue in my system recently as well and found this thread and the linked GitHub issue. This still occurs with persistence version 6.5.1. I know that is a bit out-of-date, but I don’t see any release notes indicating it would be fixed in a later version, although open to trying it.

The outbox operations consistently are in the top most duplicated queries in my SQL Server plan cache. Compiling plans for each unique Operations value length requires more CPU usage compared to reusing plans, and caching more plans takes memory away from caching data.

Here’s a sample of duplicate plans for nearly identical statements, except where the Operations parameter length varies:

(@MessageId nvarchar(36),@Operations nvarchar(3690),@PersistenceVersion nvarchar(7))  insert into [persistence].[Endpoint_OutboxData]  (      MessageId,      Operations,      PersistenceVersion  )  values  (      @MessageId,      @Operations,      @PersistenceVersion  )
(@MessageId nvarchar(36),@Operations nvarchar(3803),@PersistenceVersion nvarchar(7))  insert into [persistence].[Endpoint_OutboxData]  (      MessageId,      Operations,      PersistenceVersion  )  values  (      @MessageId,      @Operations,      @PersistenceVersion  )
(@MessageId nvarchar(36),@Operations nvarchar(3804),@PersistenceVersion nvarchar(7))  insert into [persistence].[Endpoint_OutboxData]  (      MessageId,      Operations,      PersistenceVersion  )  values  (      @MessageId,      @Operations,      @PersistenceVersion  )
(@MessageId nvarchar(36),@Operations nvarchar(2012),@PersistenceVersion nvarchar(7))  insert into [persistence].[Endpoint_OutboxData]  (      MessageId,      Operations,      PersistenceVersion  )  values  (      @MessageId,      @Operations,      @PersistenceVersion  )
(@MessageId nvarchar(36),@Operations nvarchar(3800),@PersistenceVersion nvarchar(7))  insert into [persistence].[Endpoint_OutboxData]  (      MessageId,      Operations,      PersistenceVersion  )  values  (      @MessageId,      @Operations,      @PersistenceVersion  )
(@MessageId nvarchar(36),@Operations nvarchar(3805),@PersistenceVersion nvarchar(7))  insert into [persistence].[Endpoint_OutboxData]  (      MessageId,      Operations,      PersistenceVersion  )  values  (      @MessageId,      @Operations,      @PersistenceVersion  )
(@MessageId nvarchar(36),@Operations nvarchar(2054),@PersistenceVersion nvarchar(7))  insert into [persistence].[Endpoint_OutboxData]  (      MessageId,      Operations,      PersistenceVersion  )  values  (      @MessageId,      @Operations,      @PersistenceVersion  )
(@MessageId nvarchar(36),@Operations nvarchar(2013),@PersistenceVersion nvarchar(7))  insert into [persistence].[Endpoint_OutboxData]  (      MessageId,      Operations,      PersistenceVersion  )  values  (      @MessageId,      @Operations,      @PersistenceVersion  )
(@MessageId nvarchar(36),@Operations nvarchar(3948),@PersistenceVersion nvarchar(7))  insert into [persistence].[Endpoint_OutboxData]  (      MessageId,      Operations,      PersistenceVersion  )  values  (      @MessageId,      @Operations,      @PersistenceVersion  )
(@MessageId nvarchar(36),@Operations nvarchar(2055),@PersistenceVersion nvarchar(7))  insert into [persistence].[Endpoint_OutboxData]  (      MessageId,      Operations,      PersistenceVersion  )  values  (      @MessageId,      @Operations,      @PersistenceVersion  )

I believe this statement from Microsoft Docs site is what is happening:

For bidirectional and output parameters, and return values, you must set the value of Size. This is not required for input parameters, and if not explicitly set, the value is inferred from the actual size of the specified parameter when a parameterized statement is executed.
(DbParameter.Size Property (System.Data.Common) | Microsoft Learn)

@shoffman11 This optimization has not yet been applied. I advise to add most details on that issue. This is not considered a bug. Enhancements/features are only added to the latest version.

Do you have more insights on the performance penalty due to this in your environment? If you have can you please add that info to the GitHub issue?

https://github.com/Particular/NServiceBus.Persistence.Sql/issues/873

FYI: Based on your details I created the following improvement by initializing size for the @Operations parameter:

FYI: The earlier fix has been tested. Check the github issue for more details.

While testing I discovered that the SQL Transport is affected too and we will fix this too to ensure the queries are optimally making use of the query cache:

We released NServiceBus.Persistence.Sql 7.0.3 which contains a patch to address this problem: