Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public BatchAsyncContainerExecutor GetExecutorForContainer(
BatchAsyncContainerExecutor newExecutor = new BatchAsyncContainerExecutor(
container,
cosmosClientContext,
Constants.MaxOperationsInDirectModeBatchRequest,
ConfigurationManager.GetMaxOperationsInDirectModeBatchRequest(),
Copy link
Member

Choose a reason for hiding this comment

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

Its reading everytime and checking ENVIRONMENT variables which is expensive.
Please read it once and use afterewards

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented caching to read environment variable only once using Lazy<T>. The value is now cached on first access and reused for subsequent calls, eliminating the performance overhead. Added test-friendly controls to disable caching during unit tests. (d515dd8)

DefaultMaxBulkRequestBodySizeInBytes);
if (!this.executorsPerContainer.TryAdd(containerLink, newExecutor))
{
Expand Down
43 changes: 43 additions & 0 deletions Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ internal static class ConfigurationManager
/// </summary>
internal static readonly string TcpChannelMultiplexingEnabled = "AZURE_COSMOS_TCP_CHANNEL_MULTIPLEX_ENABLED";

/// <summary>
/// A read-only string containing the environment variable name for configuring the maximum number of operations
/// allowed in a direct mode batch request.
/// </summary>
internal static readonly string MaxOperationsInDirectModeBatchRequest = "COSMOS_MAX_OPERATIONS_IN_DIRECT_MODE_BATCH_REQUEST";
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
internal static readonly string MaxOperationsInDirectModeBatchRequest = "COSMOS_MAX_OPERATIONS_IN_DIRECT_MODE_BATCH_REQUEST";
internal static readonly string MaxOperationsInDirectModeBatchRequest = "AZURE_COSMOS_MAX_OPERATIONS_IN_BATCH_REQUEST";

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated environment variable name to "AZURE_COSMOS_MAX_OPERATIONS_IN_BATCH_REQUEST" as suggested and updated corresponding tests. (d515dd8)


public static T GetEnvironmentVariable<T>(string variable, T defaultValue)
{
string value = Environment.GetEnvironmentVariable(variable);
Expand Down Expand Up @@ -357,5 +363,42 @@ public static bool IsTcpChannelMultiplexingEnabled()
variable: ConfigurationManager.TcpChannelMultiplexingEnabled,
defaultValue: false);
}

/// <summary>
/// Gets the maximum number of operations allowed in a direct mode batch request.
/// This value can be customized using the COSMOS_MAX_OPERATIONS_IN_DIRECT_MODE_BATCH_REQUEST environment variable.
/// If the environment variable is not set, the default value from Constants.MaxOperationsInDirectModeBatchRequest is used.
/// The configured value must be positive and less than or equal to the default constant value.
/// </summary>
/// <returns>The maximum number of operations allowed in a direct mode batch request.</returns>
public static int GetMaxOperationsInDirectModeBatchRequest()
{
string environmentValue = Environment.GetEnvironmentVariable(ConfigurationManager.MaxOperationsInDirectModeBatchRequest);

if (string.IsNullOrEmpty(environmentValue))
{
return Documents.Constants.MaxOperationsInDirectModeBatchRequest;
}

if (int.TryParse(environmentValue, out int parsedValue))
{
if (parsedValue <= 0)
{
throw new ArgumentException(
$"Environment variable {ConfigurationManager.MaxOperationsInDirectModeBatchRequest} must be a positive integer. Current value: {environmentValue}");
}

if (parsedValue > Documents.Constants.MaxOperationsInDirectModeBatchRequest)
{
throw new ArgumentException(
$"Environment variable {ConfigurationManager.MaxOperationsInDirectModeBatchRequest} must be less than or equal to {Documents.Constants.MaxOperationsInDirectModeBatchRequest}. Current value: {environmentValue}");
}

return parsedValue;
}

throw new ArgumentException(
$"Environment variable {ConfigurationManager.MaxOperationsInDirectModeBatchRequest} must be a valid integer. Current value: {environmentValue}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,40 @@ public void Null_When_OptionsOff()
Assert.IsNull(container.BatchExecutor);
}

[TestMethod]
public void GetExecutorForContainer_UsesCustomMaxOperationsFromEnvironment()
{
// Arrange
const string environmentVariableName = "COSMOS_MAX_OPERATIONS_IN_DIRECT_MODE_BATCH_REQUEST";
const int customMaxOperations = 150;

// Store original value to restore later
string originalValue = Environment.GetEnvironmentVariable(environmentVariableName);

try
{
Environment.SetEnvironmentVariable(environmentVariableName, customMaxOperations.ToString());

CosmosClientContext context = this.MockClientContext();
DatabaseInternal db = new DatabaseInlineCore(context, "test");
ContainerInternal container = new ContainerInlineCore(context, db, "test");

// Act
BatchAsyncContainerExecutor executor = container.BatchExecutor;

// Assert
Assert.IsNotNull(executor);
// The executor should be created with the custom max operations value
// We verify this indirectly by ensuring the executor was created successfully
// The actual value is verified in the ConfigurationManager tests
}
finally
{
// Restore original environment variable value
Environment.SetEnvironmentVariable(environmentVariableName, originalValue);
}
}

private CosmosClientContext MockClientContext(bool allowBulkExecution = true)
{
Mock<CosmosClient> mockClient = new Mock<CosmosClient>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos.Tests
{
using System;
using Microsoft.Azure.Documents;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class ConfigurationManagerTests
{
private const string EnvironmentVariableName = "COSMOS_MAX_OPERATIONS_IN_DIRECT_MODE_BATCH_REQUEST";

[TestCleanup]
public void TestCleanup()
{
// Clean up environment variable after each test
Environment.SetEnvironmentVariable(EnvironmentVariableName, null);
}

[TestMethod]
public void GetMaxOperationsInDirectModeBatchRequest_WhenEnvironmentVariableNotSet_ReturnsDefault()
{
// Arrange
Environment.SetEnvironmentVariable(EnvironmentVariableName, null);

// Act
int result = ConfigurationManager.GetMaxOperationsInDirectModeBatchRequest();

// Assert
Assert.AreEqual(Constants.MaxOperationsInDirectModeBatchRequest, result);
}

[TestMethod]
public void GetMaxOperationsInDirectModeBatchRequest_WhenEnvironmentVariableSetToValidValue_ReturnsValue()
{
// Arrange
const int expectedValue = 50;
Environment.SetEnvironmentVariable(EnvironmentVariableName, expectedValue.ToString());

// Act
int result = ConfigurationManager.GetMaxOperationsInDirectModeBatchRequest();

// Assert
Assert.AreEqual(expectedValue, result);
}

[TestMethod]
public void GetMaxOperationsInDirectModeBatchRequest_WhenEnvironmentVariableSetToLargeValue_ReturnsValue()
{
// Arrange
const int expectedValue = 50; // Changed to a smaller value that should be within bounds
Environment.SetEnvironmentVariable(EnvironmentVariableName, expectedValue.ToString());

// Act
int result = ConfigurationManager.GetMaxOperationsInDirectModeBatchRequest();

// Assert
Assert.AreEqual(expectedValue, result);
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void GetMaxOperationsInDirectModeBatchRequest_WhenEnvironmentVariableSetToValueGreaterThanMax_ThrowsArgumentException()
{
// Arrange
// Set to a value that's likely greater than the default constant
const int valueGreaterThanMax = 10000;
Environment.SetEnvironmentVariable(EnvironmentVariableName, valueGreaterThanMax.ToString());

// Act
ConfigurationManager.GetMaxOperationsInDirectModeBatchRequest();

// Assert - ExpectedException
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void GetMaxOperationsInDirectModeBatchRequest_WhenEnvironmentVariableSetToZero_ThrowsArgumentException()
{
// Arrange
Environment.SetEnvironmentVariable(EnvironmentVariableName, "0");

// Act
ConfigurationManager.GetMaxOperationsInDirectModeBatchRequest();

// Assert - ExpectedException
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void GetMaxOperationsInDirectModeBatchRequest_WhenEnvironmentVariableSetToNegativeValue_ThrowsArgumentException()
{
// Arrange
Environment.SetEnvironmentVariable(EnvironmentVariableName, "-1");

// Act
ConfigurationManager.GetMaxOperationsInDirectModeBatchRequest();

// Assert - ExpectedException
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void GetMaxOperationsInDirectModeBatchRequest_WhenEnvironmentVariableSetToInvalidString_ThrowsArgumentException()
{
// Arrange
Environment.SetEnvironmentVariable(EnvironmentVariableName, "invalid");

// Act
ConfigurationManager.GetMaxOperationsInDirectModeBatchRequest();

// Assert - ExpectedException
}

[TestMethod]
public void GetMaxOperationsInDirectModeBatchRequest_WhenEnvironmentVariableSetToEmptyString_ReturnsDefault()
{
// Arrange
Environment.SetEnvironmentVariable(EnvironmentVariableName, "");

// Act
int result = ConfigurationManager.GetMaxOperationsInDirectModeBatchRequest();

// Assert
Assert.AreEqual(Constants.MaxOperationsInDirectModeBatchRequest, result);
}

[TestMethod]
public void GetMaxOperationsInDirectModeBatchRequest_WhenEnvironmentVariableSetToOne_ReturnsOne()
{
// Arrange
const int expectedValue = 1;
Environment.SetEnvironmentVariable(EnvironmentVariableName, expectedValue.ToString());

// Act
int result = ConfigurationManager.GetMaxOperationsInDirectModeBatchRequest();

// Assert
Assert.AreEqual(expectedValue, result);
}
}
}