Skip to content

Commit 9ad8a18

Browse files
committed
Merged PR 674259: Bulk single pins
When BuildXL builds get cache hits from the fingerprint store, they will perform a number of pins for path sets. Those pins come as non-bulk pin calls, often in tandem. Having so many parallel calls winds up slowing down all concurrent operations. This PR batches them and calls the bulk API instead. Here's an example: ![image.png](https://dev.azure.com/mseng/9ed2c125-1cd5-4a17-886b-9d267f3a5fab/_apis/git/repositories/50d331c7-ea65-45eb-833f-0303c6c2387e/pullRequests/674259/attachments/image.png)
1 parent cab2152 commit 9ad8a18

File tree

2 files changed

+119
-10
lines changed

2 files changed

+119
-10
lines changed

Public/Src/Cache/ContentStore/Vsts/BlobReadOnlyContentSession.cs

+117-10
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@
2020
using BuildXL.Cache.ContentStore.Interfaces.FileSystem;
2121
using BuildXL.Cache.ContentStore.Interfaces.Results;
2222
using BuildXL.Cache.ContentStore.Interfaces.Sessions;
23-
using BuildXL.Cache.ContentStore.Interfaces.Stores;
2423
using BuildXL.Cache.ContentStore.Interfaces.Utils;
2524
using BuildXL.Cache.ContentStore.Sessions;
2625
using BuildXL.Cache.ContentStore.Tracing;
2726
using BuildXL.Cache.ContentStore.UtilitiesCore;
27+
using BuildXL.Utilities.Collections;
28+
using BuildXL.Utilities.Tasks;
2829
using BuildXL.Utilities.Tracing;
2930
using Microsoft.VisualStudio.Services.BlobStore.Common;
3031
using Microsoft.VisualStudio.Services.BlobStore.WebApi;
@@ -58,6 +59,14 @@ private enum Counters
5859
VstsDownloadUriFetchedInMemory
5960
}
6061

62+
/// <summary>
63+
/// Reused http client for http downloads
64+
/// </summary>
65+
/// <remarks>
66+
/// <see cref="HttpClient"/> is meant to be static
67+
/// </remarks>
68+
private static readonly HttpClient HttpClient = new HttpClient();
69+
6170
/// <inheritdoc />
6271
public BackingContentStoreExpiryCache ExpiryCache { get; } = new BackingContentStoreExpiryCache();
6372

@@ -113,12 +122,19 @@ private enum Counters
113122

114123
private const int DefaultReadSizeInBytes = 64 * 1024;
115124

116-
private readonly ParallelHttpDownload.DownloadConfiguration _parallelSegmentDownloadConfig;
117-
118125
/// <summary>
119-
/// Reused http client for http downloads
126+
/// This is the maximum number of requests that BlobStore is willing to process in parallel.
120127
/// </summary>
121-
private readonly HttpClient _httpClient = new HttpClient();
128+
private const int BulkPinMaximumHashes = 1000;
129+
130+
private readonly ParallelHttpDownload.DownloadConfiguration _parallelSegmentDownloadConfig;
131+
132+
private record BackgroundPinRequest(
133+
ContentHash ContentHash,
134+
DateTime EndTime,
135+
TaskSourceSlim<PinResult> PinResult);
136+
137+
private NagleQueue<BackgroundPinRequest>? _backgroundPinQueue;
122138

123139
/// <summary>
124140
/// Initializes a new instance of the <see cref="BlobReadOnlyContentSession"/> class.
@@ -150,6 +166,76 @@ public BlobReadOnlyContentSession(
150166
_blobCounters = CounterTracker.CreateCounterCollection<Counters>(counterTracker);
151167
}
152168

169+
/// <inheritdoc />
170+
protected override Task<BoolResult> StartupCoreAsync(OperationContext context)
171+
{
172+
_backgroundPinQueue = NagleQueue<BackgroundPinRequest>.Create(
173+
(batch) => PerformBackgroundBulkPinAsync(context, batch),
174+
Environment.ProcessorCount,
175+
TimeSpan.FromMilliseconds(50),
176+
BulkPinMaximumHashes);
177+
178+
return base.StartupCoreAsync(context);
179+
}
180+
181+
/// <inheritdoc />
182+
protected async override Task<BoolResult> ShutdownCoreAsync(OperationContext context)
183+
{
184+
BoolResult result = BoolResult.Success;
185+
try
186+
{
187+
await _backgroundPinQueue!.DisposeAsync();
188+
}
189+
catch (Exception exception)
190+
{
191+
result &= new BoolResult(exception, message: "Failed to dispose the background pin queue");
192+
}
193+
194+
result &= await base.ShutdownCoreAsync(context);
195+
196+
return result;
197+
}
198+
199+
private Task PerformBackgroundBulkPinAsync(OperationContext context, BackgroundPinRequest[] batch)
200+
{
201+
return context.PerformNonResultOperationAsync(Tracer, async () =>
202+
{
203+
var contentHashes = new ContentHash[batch.Length];
204+
var endDateTime = DateTime.MinValue;
205+
206+
var i = 0;
207+
foreach (var pinRequest in batch)
208+
{
209+
contentHashes[i++] = pinRequest.ContentHash;
210+
211+
if (pinRequest.EndTime > endDateTime)
212+
{
213+
endDateTime = pinRequest.EndTime;
214+
}
215+
}
216+
217+
try
218+
{
219+
var results = await TaskUtilities.SafeWhenAll(await PinCoreImplAsync(context, contentHashes, endDateTime));
220+
foreach (var indexed in results)
221+
{
222+
batch[indexed.Index].PinResult.TrySetResult(indexed.Item);
223+
}
224+
}
225+
catch (Exception exception)
226+
{
227+
foreach (var pinRequest in batch)
228+
{
229+
pinRequest.PinResult.TrySetException(exception);
230+
}
231+
}
232+
233+
return Unit.Void;
234+
},
235+
traceOperationStarted: false,
236+
extraEndMessage: _ => $"Count=[{batch.Length}]");
237+
}
238+
153239
/// <inheritdoc />
154240
protected override void DisposeCore() => TempDirectory.Dispose();
155241

@@ -159,8 +245,20 @@ protected override async Task<PinResult> PinCoreAsync(
159245
{
160246
try
161247
{
162-
var bulkResults = await PinAsync(context, new[] { contentHash }, context.Token, urgencyHint);
163-
return await bulkResults.SingleAwaitIndexed();
248+
var endTime = DateTime.UtcNow + TimeToKeepContent;
249+
250+
var inMemoryResult = CheckPinInMemory(contentHash, endTime);
251+
if (inMemoryResult.Succeeded)
252+
{
253+
return inMemoryResult;
254+
}
255+
256+
var request = new BackgroundPinRequest(
257+
ContentHash: contentHash,
258+
EndTime: endTime,
259+
PinResult: TaskSourceSlim.Create<PinResult>());
260+
_backgroundPinQueue!.Enqueue(request);
261+
return await request.PinResult.Task;
164262
}
165263
catch (Exception e)
166264
{
@@ -257,10 +355,16 @@ protected override async Task<PlaceFileResult> PlaceFileCoreAsync(
257355

258356
/// <inheritdoc />
259357
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
260-
protected override Task<IEnumerable<Task<Indexed<PinResult>>>> PinCoreAsync(OperationContext context, IReadOnlyList<ContentHash> contentHashes, UrgencyHint urgencyHint, Counter retryCounter, Counter fileCounter)
358+
protected override async Task<IEnumerable<Task<Indexed<PinResult>>>> PinCoreAsync(OperationContext context, IReadOnlyList<ContentHash> contentHashes, UrgencyHint urgencyHint, Counter retryCounter, Counter fileCounter)
261359
{
360+
if (contentHashes.Count == 1)
361+
{
362+
var result = await PinCoreAsync(context, contentHashes[0], urgencyHint, retryCounter);
363+
return new[] { Task.FromResult(new Indexed<PinResult>(result!, 0)) };
364+
}
365+
262366
var endDateTime = DateTime.UtcNow + TimeToKeepContent;
263-
return PinCoreImplAsync(context, contentHashes, endDateTime);
367+
return await PinCoreImplAsync(context, contentHashes, endDateTime);
264368
}
265369

266370
private async Task<IEnumerable<Task<Indexed<PinResult>>>> PinCoreImplAsync(OperationContext context, IReadOnlyList<ContentHash> contentHashes, DateTime keepUntil)
@@ -281,6 +385,9 @@ private async Task<IEnumerable<Task<Indexed<PinResult>>>> PinCoreImplAsync(Opera
281385
}
282386

283387
/// <inheritdoc />
388+
/// <remarks>
389+
/// PlaceBulk is both unsupported and unused in this implementation.
390+
/// </remarks>
284391
protected override Task<IEnumerable<Task<Indexed<PlaceFileResult>>>> PlaceFileCoreAsync(OperationContext context, IReadOnlyList<ContentHashWithPath> hashesWithPaths, FileAccessMode accessMode, FileReplacementMode replacementMode, FileRealizationMode realizationMode, UrgencyHint urgencyHint, Counter retryCounter)
285392
=> throw new NotImplementedException();
286393

@@ -483,7 +590,7 @@ await ParallelHttpDownload.Download(
483590
_parallelSegmentDownloadConfig,
484591
new AppTraceSourceContextAdapter(context, Tracer.Name, SourceLevels.All),
485592
VssClientHttpRequestSettings.Default.SessionId,
486-
_httpClient);
593+
HttpClient);
487594
var uri = await GetUriAsync(context, contentHash);
488595
if (uri == null)
489596
{

Public/Src/Cache/ContentStore/Vsts/BuildXL.Cache.ContentStore.Vsts.dsc

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ namespace Vsts {
2727
importFrom("Microsoft.VisualStudio.Services.Client").pkg,
2828
importFrom("Microsoft.VisualStudio.Services.InteractiveClient").pkg,
2929
...BuildXLSdk.systemThreadingTasksDataflowPackageReference,
30+
...BuildXLSdk.systemThreadingChannelsPackages,
31+
...BuildXLSdk.bclAsyncPackages,
3032
],
3133
internalsVisibleTo: [
3234
"BuildXL.Cache.MemoizationStore.Vsts",

0 commit comments

Comments
 (0)