Skip to content

Commit d7aeee4

Browse files
committed
Merged PR 726534: Limit the amount of open connections from the blob storage cache by sharing an HttpClient between BlobClients
If left unbounded, we have observed spikes of >65k open sockets (at which point we hit the OS limit of open files for the process - on Linux, where sockets count as files). Running builds where we limit this value all the way down to 100 didn't see any noticeable performance impact, so 30k shouldn't pose a problem. The configurable limit is per-client and per-server, but because we will reuse this HttpClient for all BlobClients and the 'server' (blob storage endpoint) is also always the same, we are effectively limiting the number of open connections in general. Related work items: #2076905
1 parent f927ccc commit d7aeee4

File tree

1 file changed

+32
-2
lines changed

1 file changed

+32
-2
lines changed

Public/Src/Cache/ContentStore/Distributed/Blob/ShardedBlobCacheTopology.cs

+32-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
using System.Collections.Concurrent;
66
using System.Diagnostics.ContractsLight;
77
using System.Linq;
8+
using System.Net.Http;
89
using System.Threading;
910
using System.Threading.Tasks;
1011
using Azure;
12+
using Azure.Core.Pipeline;
1113
using Azure.Storage.Blobs;
1214
using BuildXL.Cache.ContentStore.Interfaces.Results;
1315
using BuildXL.Cache.ContentStore.Synchronization;
@@ -20,7 +22,7 @@
2022
namespace BuildXL.Cache.ContentStore.Distributed.Blob;
2123

2224
public class ShardedBlobCacheTopology : IBlobCacheTopology
23-
{
25+
{
2426
protected Tracer Tracer { get; } = new Tracer(nameof(ShardedBlobCacheTopology));
2527

2628
public record Configuration(
@@ -39,6 +41,15 @@ public record Configuration(
3941
private readonly BlobCacheContainerName[] _containers;
4042
private readonly IShardingScheme<int, BlobCacheStorageAccountName> _scheme;
4143

44+
/// <remarks>
45+
/// We will reuse an HttpClient for the transport backing the blob clients. HttpClient is meant to be reused anyway
46+
/// (https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-7.0#instancing)
47+
/// but crucially we have the need to configure the amount of open connections: when using the defaults,
48+
/// the number of connections is unbounded, and we have observed builds where there end up being tens of thousands
49+
/// of open sockets, which can (and did) hit the per-process limit of open files, crashing the engine.
50+
/// </summary>
51+
private readonly HttpClient _httpClient;
52+
4253
private readonly record struct Location(BlobCacheStorageAccountName Account, BlobCacheContainerName Container);
4354

4455
/// <summary>
@@ -62,6 +73,19 @@ public ShardedBlobCacheTopology(Configuration configuration)
6273

6374
_scheme = _configuration.ShardingScheme.Create();
6475
_containers = GenerateContainerNames(_configuration.Universe, _configuration.Namespace, _configuration.ShardingScheme);
76+
77+
_httpClient = new HttpClient(
78+
new HttpClientHandler()
79+
{
80+
// If left unbounded, we have observed spikes of >65k open sockets (at which point we hit
81+
// the OS limit of open files for the process - on Linux, where sockets count as files).
82+
// Running builds where we limit this value all the way down to 100 didn't see
83+
// any noticeable performance impact, so 30k shouldn't pose a problem.
84+
// The configurable limit is per-client and per-server, but because we will reuse this HttpClient
85+
// for all BlobClients and the 'server' (blob storage endpoint) is also always the same,
86+
// we are effectively limiting the number of open connections in general.
87+
MaxConnectionsPerServer = 30_000
88+
});
6589
}
6690

6791
internal static BlobCacheContainerName[] GenerateContainerNames(string universe, string @namespace, ShardingScheme scheme)
@@ -155,7 +179,13 @@ private Task<Result<BlobContainerClient>> CreateClientAsync(OperationContext con
155179
async context =>
156180
{
157181
var credentials = await _configuration.SecretsProvider.RetrieveBlobCredentialsAsync(context, account, container);
158-
var containerClient = credentials.CreateContainerClient(container.ContainerName);
182+
183+
BlobClientOptions blobClientOptions = new(BlobClientOptions.ServiceVersion.V2021_02_12)
184+
{
185+
Transport = new HttpClientTransport(_httpClient)
186+
};
187+
188+
var containerClient = credentials.CreateContainerClient(container.ContainerName, blobClientOptions);
159189

160190
try
161191
{

0 commit comments

Comments
 (0)