Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SQL performance option #4785

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public override async Task ExecuteResultAsync(ActionContext context)
}
catch (ObjectDisposedException ode)
{
throw new ServiceUnavailableException(Resources.NotAbleToCreateTheFinalResultsOfAnOperation, ode);
throw new ServiceUnavailableException(Api.Resources.NotAbleToCreateTheFinalResultsOfAnOperation, ode);
}

HttpResponse response = context.HttpContext.Response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public TooManyRequestsActionResult()
new OperationOutcomeIssue(
OperationOutcomeConstants.IssueSeverity.Error,
OperationOutcomeConstants.IssueType.Throttled,
Resources.TooManyConcurrentRequests),
Api.Resources.TooManyConcurrentRequests),
HttpStatusCode.TooManyRequests)
{
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using EnsureThat;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Health.Core.Features.Context;
using Microsoft.Health.Fhir.Api.Features.Headers;
using Microsoft.Health.Fhir.Core.Features.Context;
using Microsoft.Health.Fhir.Core.Registration;

namespace Microsoft.Health.Fhir.Api.Features.Filters
{
/// <summary>
/// Latency over efficiency filter.
/// Adds to FHIR Request Context a flag to optimize query latency over efficiency.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class QueryCacheFilterAttribute : ActionFilterAttribute
{
private readonly RequestContextAccessor<IFhirRequestContext> _fhirRequestContextAccessor;
private readonly IFhirRuntimeConfiguration _runtimeConfiguration;

public QueryCacheFilterAttribute(RequestContextAccessor<IFhirRequestContext> fhirRequestContextAccessor, IFhirRuntimeConfiguration runtimeConfiguration)
{
EnsureArg.IsNotNull(fhirRequestContextAccessor, nameof(fhirRequestContextAccessor));
EnsureArg.IsNotNull(runtimeConfiguration, nameof(runtimeConfiguration));

_fhirRequestContextAccessor = fhirRequestContextAccessor;
_runtimeConfiguration = runtimeConfiguration;
}

public override void OnActionExecuting(ActionExecutingContext context)
{
EnsureArg.IsNotNull(context, nameof(context));

if (_runtimeConfiguration.IsQueryCacheSupported)
{
SetupConditionalRequestWithQueryCache(context.HttpContext, _fhirRequestContextAccessor.RequestContext);
}

base.OnActionExecuting(context);
}

private static void SetupConditionalRequestWithQueryCache(HttpContext context, IFhirRequestContext fhirRequestContext)
{
if (context?.Request?.Headers != null && fhirRequestContext != null)
{
string useQueryCache = context.GetQueryCache();

if (!string.IsNullOrEmpty(useQueryCache))
{
fhirRequestContext.DecorateRequestContextWithQueryCache(useQueryCache);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
if (!context.HttpContext.Request.Headers.TryGetValue(KnownHeaders.Prefer, out var preferHeaderValue) ||
!string.Equals(preferHeaderValue[0], PreferHeaderExpectedValue, StringComparison.OrdinalIgnoreCase))
{
throw new RequestNotValidException(string.Format(Resources.UnsupportedHeaderValue, preferHeaderValue.FirstOrDefault(), KnownHeaders.Prefer));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedHeaderValue, preferHeaderValue.FirstOrDefault(), KnownHeaders.Prefer));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
acceptHeaderValue.Count != 1 ||
!string.Equals(acceptHeaderValue[0], KnownContentTypes.JsonContentType, StringComparison.OrdinalIgnoreCase))
{
throw new RequestNotValidException(string.Format(Resources.UnsupportedHeaderValue, acceptHeaderValue.FirstOrDefault(), HeaderNames.Accept));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedHeaderValue, acceptHeaderValue.FirstOrDefault(), HeaderNames.Accept));
}

if (context.HttpContext.Request.Headers.TryGetValue(PreferHeaderName, out var preferHeaderValues))
Expand All @@ -79,7 +79,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
|| (v.Length == 1 && !(requiredHeaderValueFound = string.Equals(v[0], PreferHeaderValueRequired, StringComparison.OrdinalIgnoreCase)))
|| (v.Length == 2 && (!string.Equals(v[0], PreferHeaderValueOptional, StringComparison.OrdinalIgnoreCase) || !Enum.TryParse<SearchParameterHandling>(v[1], true, out _))))
{
throw new RequestNotValidException(string.Format(Resources.UnsupportedHeaderValue, value, PreferHeaderName));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedHeaderValue, value, PreferHeaderName));
}
}

Expand All @@ -102,14 +102,14 @@ public override void OnActionExecuting(ActionExecutingContext context)
continue;
}

throw new RequestNotValidException(string.Format(Resources.UnsupportedParameter, paramName));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedParameter, paramName));
}

if (queryCollection?.Keys != null &&
queryCollection.Keys.Contains(KnownQueryParameterNames.TypeFilter) &&
!queryCollection.Keys.Contains(KnownQueryParameterNames.Type))
{
throw new RequestNotValidException(Resources.TypeFilterWithoutTypeIsUnsupported);
throw new RequestNotValidException(Api.Resources.TypeFilterWithoutTypeIsUnsupported);
}

if (queryCollection.TryGetValue(KnownQueryParameterNames.OutputFormat, out var outputFormats))
Expand All @@ -118,7 +118,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
{
if (!(outputFormat == null || SupportedOutputFormats.Contains(outputFormat)))
{
throw new RequestNotValidException(string.Format(Resources.InvalidOutputFormat, outputFormat));
throw new RequestNotValidException(string.Format(Api.Resources.InvalidOutputFormat, outputFormat));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ public override async Task OnActionExecutionAsync(ActionExecutingContext context
{
if (!await _parametersValidator.IsFormatSupportedAsync(headerValue[0]))
{
throw new UnsupportedMediaTypeException(string.Format(Resources.UnsupportedHeaderValue, headerValue.FirstOrDefault(), HeaderNames.ContentType));
throw new UnsupportedMediaTypeException(string.Format(Api.Resources.UnsupportedHeaderValue, headerValue.FirstOrDefault(), HeaderNames.ContentType));
}
}
else
{
// If no content type is supplied, then the server should respond with an unsupported media type exception.
throw new UnsupportedMediaTypeException(Resources.ContentTypeHeaderRequired);
throw new UnsupportedMediaTypeException(Api.Resources.ContentTypeHeaderRequired);
}
}
else if (httpContext.Request.Method.Equals(HttpMethod.Patch.Method, StringComparison.OrdinalIgnoreCase))
Expand All @@ -66,7 +66,7 @@ public override async Task OnActionExecutionAsync(ActionExecutingContext context
{
if (!await _parametersValidator.IsPatchFormatSupportedAsync(headerValue[0]))
{
throw new UnsupportedMediaTypeException(string.Format(Resources.UnsupportedHeaderValue, headerValue.FirstOrDefault(), HeaderNames.ContentType));
throw new UnsupportedMediaTypeException(string.Format(Api.Resources.UnsupportedHeaderValue, headerValue.FirstOrDefault(), HeaderNames.ContentType));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
preferHeaderValue.Count != 1 ||
!string.Equals(preferHeaderValue[0], PreferHeaderExpectedValue, StringComparison.OrdinalIgnoreCase))
{
throw new RequestNotValidException(string.Format(Resources.UnsupportedHeaderValue, preferHeaderValue.FirstOrDefault(), PreferHeaderName));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedHeaderValue, preferHeaderValue.FirstOrDefault(), PreferHeaderName));
}

if (string.Equals(context.HttpContext.Request.Method, "POST", StringComparison.OrdinalIgnoreCase))
Expand All @@ -44,7 +44,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
contentTypeHeaderValue.Count != 1 ||
!contentTypeHeaderValue[0].Contains(ContentTypeHeaderExpectedValue, StringComparison.OrdinalIgnoreCase))
{
throw new RequestNotValidException(string.Format(Resources.UnsupportedHeaderValue, contentTypeHeaderValue.FirstOrDefault(), HeaderNames.ContentType));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedHeaderValue, contentTypeHeaderValue.FirstOrDefault(), HeaderNames.ContentType));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
context.ActionArguments?.TryGetValue("inputParams", out inputResource);
if (inputResource == null)
{
throw new RequestNotValidException(Resources.MissingInputParams);
throw new RequestNotValidException(Api.Resources.MissingInputParams);
}

if (inputResource is not Parameters)
{
throw new RequestNotValidException(string.Format(Resources.UnsupportedResourceType, inputResource.GetType().ToString()));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedResourceType, inputResource.GetType().ToString()));

Check notice

Code scanning / CodeQL

Redundant ToString() call Note

Redundant call to 'ToString' on a String object.
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public override void OnActionExecuting(ActionExecutingContext context)
if (context.HttpContext.Request.Headers.TryGetValue(PreferHeaderName, out var preferHeaderValue) &&
!string.Equals(preferHeaderValue[0], PreferHeaderExpectedValue, StringComparison.OrdinalIgnoreCase))
{
throw new RequestNotValidException(string.Format(Resources.UnsupportedHeaderValue, preferHeaderValue.FirstOrDefault(), PreferHeaderName));
throw new RequestNotValidException(string.Format(Api.Resources.UnsupportedHeaderValue, preferHeaderValue.FirstOrDefault(), PreferHeaderName));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ public static bool IsLatencyOverEfficiencyEnabled(this HttpContext outerHttpCont
return defaultValue;
}

/// <summary>
/// Retrieves from the HTTP header information on using query caching.
/// </summary>
/// <param name="outerHttpContext">HTTP context</param>
/// <returns>Query cache header value</returns>
public static string GetQueryCache(this HttpContext outerHttpContext)
{
if (outerHttpContext != null && outerHttpContext.Request.Headers.TryGetValue(KnownHeaders.QueryCacheEnabled, out StringValues headerValues))
{
return headerValues.FirstOrDefault();
}

return null;
}

/// <summary>
/// Retrieves from the HTTP header information about the conditional-query processing logic to be adopted.
/// </summary>
Expand Down Expand Up @@ -110,5 +125,15 @@ public static bool DecorateRequestContextWithOptimizedConcurrency(this IFhirRequ

return requestContext.Properties.TryAdd(KnownQueryParameterNames.OptimizeConcurrency, true);
}

public static bool DecorateRequestContextWithQueryCache(this IFhirRequestContext requestContext, string value)
{
if (requestContext == null)
{
return false;
}

return requestContext.Properties.TryAdd(KnownQueryParameterNames.QueryCaching, value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public sealed class InitialImportLockMiddleware

// hard-coding these to minimize resource consumption for locked message
private const string LockedContentType = "application/json; charset=utf-8";
private static readonly ReadOnlyMemory<byte> _lockedBody = CreateLockedBody(Resources.LockedForInitialImportMode);
private static readonly ReadOnlyMemory<byte> _lockedBody = CreateLockedBody(Api.Resources.LockedForInitialImportMode);

public InitialImportLockMiddleware(
RequestDelegate next,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public async Task Invoke(HttpContext context)
{
context.Response.Clear();
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync(Resources.ContentTypeFormUrlEncodedExpected);
await context.Response.WriteAsync(Api.Resources.ContentTypeFormUrlEncodedExpected);
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ public Uri ResolveOperationResultUrl(string operationName, string id)
routeName = RouteNames.GetBulkDeleteStatusById;
break;
default:
throw new OperationNotImplementedException(string.Format(Resources.OperationNotImplemented, operationName));
throw new OperationNotImplementedException(string.Format(Api.Resources.OperationNotImplemented, operationName));
}

var routeValues = new RouteValueDictionary()
Expand Down Expand Up @@ -317,7 +317,7 @@ public Uri ResolveOperationDefinitionUrl(string operationName)
routeName = RouteNames.SearchParameterStatusOperationDefinition;
break;
default:
throw new OperationNotImplementedException(string.Format(Resources.OperationNotImplemented, operationName));
throw new OperationNotImplementedException(string.Format(Api.Resources.OperationNotImplemented, operationName));
}

return GetRouteUri(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,14 @@ public async Task Invoke(
{
if (authorizationConfiguration.ErrorOnMissingFhirUserClaim)
{
throw new BadHttpRequestException(string.Format(Resources.FhirUserClaimMustBeURL, fhirUser));
throw new BadHttpRequestException(string.Format(Api.Resources.FhirUserClaimMustBeURL, fhirUser));
}
}
catch (ArgumentNullException)
{
if (authorizationConfiguration.ErrorOnMissingFhirUserClaim)
{
throw new BadHttpRequestException(Resources.FhirUserClaimCannotBeNull);
throw new BadHttpRequestException(Api.Resources.FhirUserClaimCannotBeNull);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public sealed class ThrottlingMiddleware : IAsyncDisposable, IDisposable

// hard-coding these to minimize resource consumption when throttling
private const string ThrottledContentType = "application/json; charset=utf-8";
private static readonly ReadOnlyMemory<byte> _throttledBody = CreateThrottledBody(Resources.TooManyConcurrentRequests);
private static readonly ReadOnlyMemory<byte> _throttledBody = CreateThrottledBody(Api.Resources.TooManyConcurrentRequests);

private readonly RequestDelegate _next;
private readonly ILogger<ThrottlingMiddleware> _logger;
Expand Down Expand Up @@ -284,7 +284,7 @@ private async Task Return429(HttpContext context)
{
Interlocked.Increment(ref _currentPeriodRejectedCount);

_logger.LogWarning(Resources.TooManyConcurrentRequests + " Limit is {Limit}. Requests in flight {Requests}", _concurrentRequestLimit, _requestsInFlight);
_logger.LogWarning(Api.Resources.TooManyConcurrentRequests + " Limit is {Limit}. Requests in flight {Requests}", _concurrentRequestLimit, _requestsInFlight);

context.Response.StatusCode = StatusCodes.Status429TooManyRequests;

Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.Health.Fhir.Core/Features/KnownHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,7 @@ public static class KnownHeaders

// #conditionalQueryParallelism - Header used to activate parallel conditional-query processing.
public const string ConditionalQueryProcessingLogic = "x-conditionalquery-processing-logic";

public const string QueryCacheEnabled = "x-ms-query-cache-enabled";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,18 @@ public static class KnownQueryParameterNames
public const string Container = "_container";

/// <summary>
/// Originally for CosmosDB workloads to hint that this request should run with a max parallel setting.
/// This setting is currently set by:
/// x-ms-query-latency-over-efficiency
/// x-conditionalquery-processing-logic
/// It is used to hint that the request should run with a max parallel setting.
/// </summary>
public const string OptimizeConcurrency = "_optimizeConcurrency";

/// <summary>
/// This setting is controlled by the x-ms-query-cache-enabled header. It controls whether to use the query cache or not.
/// </summary>
public const string QueryCaching = "_queryCaching";

/// <summary>
/// The anonymization configuration
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ public class AzureApiForFhirRuntimeConfiguration : IFhirRuntimeConfiguration
public bool IsTransactionSupported => false;

public bool IsLatencyOverEfficiencySupported => true;

public bool IsQueryCacheSupported => false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ public class AzureHealthDataServicesRuntimeConfiguration : IFhirRuntimeConfigura
public bool IsTransactionSupported => true;

public bool IsLatencyOverEfficiencySupported => false;

public bool IsQueryCacheSupported => true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,10 @@ public interface IFhirRuntimeConfiguration
/// Supports the 'latency-over-efficiency' HTTP header.
/// </summary>
bool IsLatencyOverEfficiencySupported { get; }

/// <summary>
/// Supports the query cache HTTP header.
/// </summary>
bool IsQueryCacheSupported { get; }
}
}
Loading
Loading