diff --git a/src/Microsoft.Health.Fhir.Core/Exceptions/PreconditionFailedException.cs b/src/Microsoft.Health.Fhir.Core/Exceptions/PreconditionFailedException.cs index ecc83dd75c..b21f4a29db 100644 --- a/src/Microsoft.Health.Fhir.Core/Exceptions/PreconditionFailedException.cs +++ b/src/Microsoft.Health.Fhir.Core/Exceptions/PreconditionFailedException.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------------------------------------------- +using System; using System.Diagnostics; using Microsoft.Health.Fhir.Core.Models; diff --git a/src/Microsoft.Health.Fhir.Core/Exceptions/TransactionFailureException.cs b/src/Microsoft.Health.Fhir.Core/Exceptions/TransactionFailureException.cs new file mode 100644 index 0000000000..a7e985f962 --- /dev/null +++ b/src/Microsoft.Health.Fhir.Core/Exceptions/TransactionFailureException.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System.Diagnostics; +using Microsoft.Health.Fhir.Core.Models; + +namespace Microsoft.Health.Fhir.Core.Exceptions +{ + public sealed class TransactionFailureException : FhirException + { + public TransactionFailureException(string message) + : base(message) + { + Debug.Assert(!string.IsNullOrEmpty(message), "Exception message should not be empty"); + + Issues.Add(new OperationOutcomeIssue( + OperationOutcomeConstants.IssueSeverity.Error, + OperationOutcomeConstants.IssueType.Exception, + message)); + } + } +} diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosFhirDataStore.cs b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosFhirDataStore.cs index 44230b3dd5..e5652abf95 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosFhirDataStore.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Features/Storage/CosmosFhirDataStore.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Net; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using EnsureThat; @@ -199,6 +200,13 @@ await Parallel.ForEachAsync(resources, parallelOptions, async (resource, innerCt var result = new DataStoreOperationOutcome(upsertOutcome); results.AddOrUpdate(identifier, _ => result, (_, _) => result); } + catch (CosmosException cme) when (cme.StatusCode == HttpStatusCode.Forbidden && cme.SubStatusCode == 1014) + { + // https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/troubleshoot-forbidden#partition-key-exceeding-storage + + _logger.LogWarning(cme, "PreconditionFailed: Partition reached maximum size."); + results.TryAdd(identifier, new DataStoreOperationOutcome(new PreconditionFailedException(Resources.MaxPartitionSizeErrorMessage))); + } catch (RequestRateExceededException rateExceededException) { results.TryAdd(identifier, new DataStoreOperationOutcome(rateExceededException)); @@ -432,7 +440,7 @@ await _containerScope.Value.CreateTransactionalBatch(partitionKey) continue; } - throw new InvalidOperationException(transactionalBatchResponse.ErrorMessage); + throw new TransactionFailureException(transactionalBatchResponse.ErrorMessage); } } else diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Resources.Designer.cs b/src/Microsoft.Health.Fhir.CosmosDb/Resources.Designer.cs index 2d2efdea74..ee6aeff2e0 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Resources.Designer.cs +++ b/src/Microsoft.Health.Fhir.CosmosDb/Resources.Designer.cs @@ -204,6 +204,15 @@ internal static string KeyVaultWrapUnwrapFailure { } } + /// + /// Looks up a localized string similar to Partition reached maximum size.. + /// + internal static string MaxPartitionSizeErrorMessage { + get { + return ResourceManager.GetString("MaxPartitionSizeErrorMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Not able to execute a query. Retry the operation.. /// diff --git a/src/Microsoft.Health.Fhir.CosmosDb/Resources.resx b/src/Microsoft.Health.Fhir.CosmosDb/Resources.resx index c3a0475cf5..29a424e98a 100644 --- a/src/Microsoft.Health.Fhir.CosmosDb/Resources.resx +++ b/src/Microsoft.Health.Fhir.CosmosDb/Resources.resx @@ -174,4 +174,8 @@ Not able to execute a query. Retry the operation. + + Partition reached maximum size. + Error raised when a partition reaches its max size. + \ No newline at end of file diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandler.cs b/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandler.cs index c03e682707..70ea90ccf3 100644 --- a/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandler.cs +++ b/src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandler.cs @@ -964,7 +964,16 @@ private void RefreshProfilesIfApplicable() { if (_forceProfilesRefresh) { - _profilesResolver.Refresh(); + Stopwatch watch = Stopwatch.StartNew(); + try + { + _profilesResolver.Refresh(); + _logger.LogInformation("FHIR Profiles cache is refreshed. Elapsed time: {ElapsedMilliseconds}", watch.ElapsedMilliseconds); + } + catch (Exception ex) + { + _logger.LogError(ex, "FHIR Profiles cache failed while refreshing. Elapsed time: {ElapsedMilliseconds}", watch.ElapsedMilliseconds); + } } } @@ -986,6 +995,11 @@ private static OperationOutcome CreateOperationOutcome(OperationOutcome.IssueSev private static string SanitizeString(string input) { + if (string.IsNullOrWhiteSpace(input)) + { + return string.Empty; + } + return input .Replace(Environment.NewLine, string.Empty, StringComparison.OrdinalIgnoreCase) .Replace("\r", " ", StringComparison.OrdinalIgnoreCase)