Skip to content

Log stats details for long running SQL queries#5376

Merged
apurvabhaleMS merged 21 commits intomainfrom
personal/abhale/log-info-long-running-queries
Feb 19, 2026
Merged

Log stats details for long running SQL queries#5376
apurvabhaleMS merged 21 commits intomainfrom
personal/abhale/log-info-long-running-queries

Conversation

@apurvabhaleMS
Copy link
Contributor

@apurvabhaleMS apurvabhaleMS commented Feb 6, 2026

Description

This PR adds diagnostic logging for long-running SQL Server search queries by measuring execution time and (optionally) querying SQL Server Query Store for recent runtime stats matching the executed query text. The logging is runtime-configurable via dbo.Parameters, runs asynchronously (fire-and-forget), and is best-effort (never impacts the search request path).

What’s included

  1. Runtime feature flag + threshold (dbo.Parameters)

    • Added Search.LongRunningQueryDetails.IsEnabled to enable/disable Query Store stats logging at runtime (default: enabled).
    • Added Search.LongRunningQueryDetails.Threshold to configure the “long-running” threshold in milliseconds (default: 5000ms).
  2. Cached dbo.Parameters reader (generalized)

    • Replaced ProcessingFlag<T> with CachedParameter<T>:
      • Reads numeric values from dbo.Parameters and caches them (10-minute TTL).
      • Supports feature flags via IsEnabled() (1 = enabled, 0 = disabled).
    • Updated existing SQL datastore flags to use CachedParameter<T>.
  3. Execution time measurement

    • Added Stopwatch timing around ExecuteReaderAsync for:
      • the main SQL search execution path
      • the SQL include execution path
    • If elapsed time exceeds the threshold and the feature is enabled, a Query Store lookup/log is triggered.
  4. Query Store lookup + logging (non-blocking, best-effort)

    • Added query text normalization to improve matching against Query Store stored texts:
      • strips preamble lines like SET STATISTICS ..., DECLARE ..., OPTION (RECOMPILE), and timeout comments
      • normalizes leading ;WITH to WITH
    • Handles multi-statement include batches by splitting into fragments at INSERT INTO @FilteredData.
    • Runs Query Store lookup on a new connection and logs top recent matches (avg/max duration, CPU, IO, rows, last execution time, query_id, plan_id).
    • Lookup is fire-and-forget (Task.Run) with a short timeout (2 seconds).
    • Any Query Store lookup failures are caught and logged (warning/debug) and do not affect the original request.

Related issues

Addresses AB180196

Testing

Describe how this change was tested.

FHIR Team Checklist

  • Update the title of the PR to be succinct and less than 65 characters
  • Add a milestone to the PR for the sprint that it is merged (i.e. add S47)
  • Tag the PR with the type of update: Bug, Build, Dependencies, Enhancement, New-Feature or Documentation
  • Tag the PR with Open source, Azure API for FHIR (CosmosDB or common code) or Azure Healthcare APIs (SQL or common code) to specify where this change is intended to be released.
  • Tag the PR with Schema Version backward compatible or Schema Version backward incompatible or Schema Version unchanged if this adds or updates Sql script which is/is not backward compatible with the code.
  • When changing or adding behavior, if your code modifies the system design or changes design assumptions, please create and include an ADR.
  • CI is green before merge Build Status
  • Review squash-merge requirements

Semver Change (docs)

Patch|Skip|Feature|Breaking (reason)

@apurvabhaleMS apurvabhaleMS added the Azure Healthcare APIs Label denotes that the issue or PR is relevant to the FHIR service in the Azure Healthcare APIs label Feb 6, 2026
@apurvabhaleMS apurvabhaleMS added this to the FY26\Q3\2Wk\2Wk16 milestone Feb 6, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds best-effort logging of Query Store runtime stats for long-running SQL search queries in the SQL Server search layer, controlled via dbo.Parameters feature flag + threshold.

Changes:

  • Adds a runtime feature flag (Search.LongRunningQueryDetails.IsEnabled) and numeric threshold (Search.LongRunningQueryDetails.Threshold, default 60s).
  • Measures query execution time around ExecuteReaderAsync in both search and include-search paths.
  • Introduces query-text normalization and a Query Store lookup query to log recent runtime stats for matching statements.

apurvabhaleMS and others added 3 commits February 6, 2026 10:40
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
@apurvabhaleMS apurvabhaleMS added Enhancement Enhancement on existing functionality. No-PaaS-breaking-change labels Feb 9, 2026
@apurvabhaleMS apurvabhaleMS requested a review from Copilot February 9, 2026 18:44
@apurvabhaleMS apurvabhaleMS added the No-ADR ADR not needed label Feb 9, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.

Comments suppressed due to low confidence (1)

src/Microsoft.Health.Fhir.SqlServer/Features/Search/SqlServerSearchService.cs:570

  • The stopwatch starts before ExecuteReaderAsync but stops after the entire reader processing completes, so the measured "execution" time includes result materialization/decompression in addition to SQL execution. If the intent is to detect long-running SQL, consider measuring only the DB call (e.g., around ExecuteReaderAsync / DB read loop) or clearly renaming this to reflect end-to-end processing time.
                        var st = DateTime.UtcNow;
                        var executionStopwatch = Stopwatch.StartNew();

                        try
                        {
                            using (var reader = await sqlCommand.ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken))
                            {

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.

@apurvabhaleMS apurvabhaleMS marked this pull request as ready for review February 11, 2026 19:14
@apurvabhaleMS apurvabhaleMS requested a review from a team as a code owner February 11, 2026 19:14
@apurvabhaleMS
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@SergeyGaluzo
Copy link
Contributor

I do not see "fire and forget" mentioned in description, Is it implemented?

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

src/Microsoft.Health.Fhir.SqlServer/Features/Storage/CachedParameter.cs:20

  • CachedParameter<TLogger> is declared public even though it appears to be an internal SQL Server implementation detail (replacing ProcessingFlag). Making it public expands the surface area of Microsoft.Health.Fhir.SqlServer and can become a supported API inadvertently. Consider making the class internal (and only exposing what’s needed via internal helpers) unless there’s a concrete cross-assembly requirement.
    src/Microsoft.Health.Fhir.SqlServer/Features/Storage/CachedParameter.cs:85
  • ReadFromDatabase blocks on an async call via .Result. This can cause thread-pool starvation under load and wraps exceptions in AggregateException, making diagnostics harder. Prefer awaiting asynchronously (make the call path async) or, if it must be synchronous, use GetAwaiter().GetResult() and ensure the async operation doesn’t capture a context.

@apurvabhaleMS
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@apurvabhaleMS apurvabhaleMS enabled auto-merge (squash) February 19, 2026 21:42
@apurvabhaleMS apurvabhaleMS merged commit ef667cf into main Feb 19, 2026
61 checks passed
@apurvabhaleMS apurvabhaleMS deleted the personal/abhale/log-info-long-running-queries branch February 19, 2026 21:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Azure Healthcare APIs Label denotes that the issue or PR is relevant to the FHIR service in the Azure Healthcare APIs Enhancement Enhancement on existing functionality. No-ADR ADR not needed No-PaaS-breaking-change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants