Terminate message queries that exceed a configurable time limit - #5848
Draft
ramonsmits wants to merge 2 commits into
Draft
Terminate message queries that exceed a configurable time limit#5848ramonsmits wants to merge 2 commits into
ramonsmits wants to merge 2 commits into
Conversation
mauroservienti
requested changes
Sep 3, 2026
This was referenced Sep 3, 2026
mauroservienti
approved these changes
Sep 4, 2026
ramonsmits
marked this pull request as draft
September 4, 2026 14:47
Customer debug packages showed audit message queries against
MessagesViewIndexWithFullTextSearch running for up to ~65 minutes.
RavenDB's own Databases.QueryTimeoutInSec (default 300) does not stop
them because the server renews the operation deadline while a query
keeps making progress, so large scans/sorts run for hours and their
sorted results spill into the server's Temp folder, filling the disk.
The message view and search queries (all messages, per endpoint,
search, conversation) now run under a linked CancellationTokenSource
that cancels after a configurable time limit, default 1 minute, maximum
1 hour. Cancelling the client request aborts the call to the database
server, which does terminate the query server-side. On expiry a
TimeoutException names the setting to adjust; invalid values fall back
to the default.
One QueryTimeLimit helper in ServiceControl.Infrastructure serves every
persister, so they share one setting with one behavior, a hard
wall-clock deadline per data store call. Its rule is that whatever
exception surfaces after the deadline fired is the timeout: RavenDB and
Npgsql raise OperationCanceledException, Microsoft.Data.SqlClient raises
SqlException("Operation cancelled by user"), which a plain
OperationCanceledException catch never sees. A test against the real
SQL Server and PostgreSQL containers slows the command down server-side
to cover that.
- ServiceControl.Audit/QueryTimeoutInSeconds bounds the audit
RavenAuditDataStore message view queries. The licensing audit counts
and the saga history lookup are not under the limit.
- ServiceControl/QueryTimeoutInSeconds bounds the primary instance
IMessagesViewDataStore queries on RavenDB, SQL Server and PostgreSQL,
which use the same unbounded sorted index query shape. On SQL Server
and PostgreSQL these queries also use the limit as their per-command
timeout, so Database/CommandTimeout cannot undercut it.
Co-authored-by: Mauro Servienti <mauro.servienti@gmail.com>
…data A data store's TimeoutException now becomes a 504 Gateway Timeout with a problem body naming the setting, on both the primary and the audit host, so a caller can tell a timeout from a crash and from an empty result. The primary's scatter-gather no longer hides what went wrong. A local timeout is absorbed the way a remote failure already was, a remote 504 or error is a missing instance rather than an instance with no data, and the response names every missing instance in the X-Particular-Incomplete-Results header (instanceId:timeout|unavailable| error) and carries no ETag. Only when no instance that was asked answered and one of them timed out does the composite itself fail with the timeout. The signal is a header rather than a body field because the composite endpoints return a bare array; an envelope would change the response schema and need a new API for every existing client. The in-process audit counts used for licensing throughput fail instead of recording a partial sum as the day's throughput. The remote instance HttpClient timeout follows ServiceControl/QueryTimeoutInSeconds plus a 30 second margin instead of the 100 second HttpClient default, so raising the limit on both instances does not make the primary give up on the audit instance first.
ramonsmits
force-pushed
the
ramon/audit-query-timeout
branch
from
September 4, 2026 15:24
b7e02a4 to
ec9cf12
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Customer debug packages showed audit message queries against
MessagesViewIndexWithFullTextSearchrunning for up to ~65 minutes. RavenDB's ownDatabases.QueryTimeoutInSec(default 300) does not stop them because the server renews the operation deadline while a query keeps making progress, so large scans/sorts run for hours and their sorted results spill into the server's Temp folder, filling the disk.Query time limit
The message view and search queries now run under a linked
CancellationTokenSourcethat cancels after a configurable time limit, default 1 minute, maximum 1 hour. Cancelling the client request aborts the call to the database server, which does terminate the query server-side. Invalid values fall back to the default.One
QueryTimeLimithelper inServiceControl.Infrastructureserves all persisters. Its rule is that whatever exception surfaces after the deadline fired is the timeout: RavenDB and Npgsql raiseOperationCanceledException, Microsoft.Data.SqlClient raisesSqlException("Operation cancelled by user"). The SQL Server path is covered by a test against the real container that slows the command down server-side.The limit applies to exactly these queries:
RavenAuditDataStore:GetMessages,QueryMessages,QueryMessagesByReceivingEndpoint,QueryMessagesByReceivingEndpointAndKeyword,QueryMessagesByConversationId, read fromServiceControl.Audit/QueryTimeoutInSeconds.QueryAuditCounts(licensing throughput) andQuerySagaHistoryByIdare not under the limit.IMessagesViewDataStoreon RavenDB, SQL Server and PostgreSQL:GetAllMessages,GetAllMessagesForEndpoint,GetAllMessagesByConversation,GetAllMessagesForSearch,SearchEndpointMessages, read fromServiceControl/QueryTimeoutInSeconds. On SQL Server and PostgreSQL these queries also use the limit as their per-command timeout, soDatabase/CommandTimeoutcannot undercut it.Other paged index queries (
GetFailedMessages, groups, event log, ...) are unchanged.What a caller sees
504 Gateway Timeoutand aapplication/problem+jsonbody whose detail names the setting, on both hosts.504or error is a missing instance rather than an instance with no data. The response lists every missing instance in theX-Particular-Incomplete-Resultsheader (instanceId:timeout|unavailable|error, exposed through CORS) and carries noETag. Only when no queried instance answered and one of them timed out does the composite itself answer504.HttpClienttimeout isServiceControl/QueryTimeoutInSecondsplus 30 seconds instead of the 100 second default, so raising the limit on both instances does not make the primary give up on the audit instance first.ServicePulse follow-up: the audit list already treats a non-2xx answer (the new
504) as a failed query; readingX-Particular-Incomplete-Resultson a200is still to be added.Docs: Particular/docs.particular.net#8486