Skip to content

Commit a944a13

Browse files
authored
Optimize the push package API (#8371)
Previously, the functional tests on PROD failed repeatedly. The functional tests push many packages in parallel, which caused an expensive SQL query to time out. This optimizes the push package API by avoiding a costly SQL query on the "hot" path. Addresses #8368
1 parent 9f32034 commit a944a13

5 files changed

Lines changed: 49 additions & 7 deletions

File tree

src/NuGetGallery.Services/PackageManagement/IPackageService.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading.Tasks;
77
using NuGet.Packaging;
88
using NuGet.Services.Entities;
9+
using NuGet.Versioning;
910
using NuGetGallery.Packaging;
1011

1112
namespace NuGetGallery
@@ -155,5 +156,13 @@ Package FilterLatestPackageBySuffix(IReadOnlyCollection<Package> packages,
155156
/// <exception cref="ArgumentNullException">Thrown if <paramref name="registration" />
156157
/// is <c>null</c>.</exception>
157158
Task SetRequiredSignerAsync(PackageRegistration registration, User signer, bool commitChanges = true);
159+
160+
/// <summary>
161+
/// Get a package's status, or <c>null</c> if the package does not exist.
162+
/// </summary>
163+
/// <param name="packageId">The package's ID.</param>
164+
/// <param name="packageVersion">The package's version.</param>
165+
/// <returns>The package's status, or <c>null</c> is the package does not exist.</returns>
166+
PackageStatus? GetPackageStatus(string packageId, NuGetVersion packageVersion);
158167
}
159168
}

src/NuGetGallery.Services/PackageManagement/PackageService.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,5 +945,20 @@ public async Task SetRequiredSignerAsync(PackageRegistration registration, User
945945
_telemetryService.TrackRequiredSignerSet(registration.Id);
946946
}
947947
}
948+
949+
public PackageStatus? GetPackageStatus(string packageId, NuGetVersion packageVersion)
950+
{
951+
var normalizedVersion = packageVersion.ToNormalizedString();
952+
953+
// Note the casting to a nullable enum in the "Select". This is required to
954+
// return "null" if there are no rows. Otherwise, "FirstOrDefault" would return
955+
// "PackageStatus.Available" as the default value.
956+
return _packageRepository
957+
.GetAll()
958+
.Where(p => p.PackageRegistration.Id == packageId)
959+
.Where(p => p.Version == normalizedVersion)
960+
.Select(p => (PackageStatus?)p.PackageStatusKey)
961+
.FirstOrDefault();
962+
}
948963
}
949964
}

src/NuGetGallery/Controllers/ApiController.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -657,11 +657,17 @@ await AuditingService.SaveAuditRecordAsync(
657657
string.Format(CultureInfo.CurrentCulture, Strings.PackageIsLocked, packageRegistration.Id));
658658
}
659659

660-
var existingPackage = PackageService.FindPackageByIdAndVersionStrict(id, version.ToStringSafe());
661-
if (existingPackage != null)
660+
// A package can only be reuploaded if it never passed validation.
661+
var existingStatus = PackageService.GetPackageStatus(id, version);
662+
if (existingStatus != null)
662663
{
663-
if (existingPackage.PackageStatusKey == PackageStatus.FailedValidation)
664+
if (existingStatus == PackageStatus.FailedValidation)
664665
{
666+
// Allow this new upload to replace the existing package.
667+
// We avoided loading the full package entity until now as it is
668+
// a relatively expensive operation and this path is uncommon.
669+
var existingPackage = PackageService.FindPackageByIdAndVersionStrict(id, version.ToStringSafe());
670+
665671
TelemetryService.TrackPackageReupload(existingPackage);
666672

667673
await PackageDeleteService.HardDeletePackagesAsync(

tests/NuGetGallery.Facts/Controllers/ApiControllerFacts.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,13 @@ public async Task WillReturnConflictIfAPackageWithTheIdAndSameNormalizedVersionA
850850
var controller = new TestableApiController(GetConfigurationService());
851851
controller.SetCurrentUser(new User() { EmailAddress = "[email protected]" });
852852
controller.MockPackageService.Setup(x => x.FindPackageRegistrationById(id)).Returns(packageRegistration);
853-
controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersionStrict(id, version)).Returns(conflictingPackage);
853+
controller
854+
.MockPackageService
855+
.Setup(x => x.GetPackageStatus(
856+
id,
857+
It.Is<NuGetVersion>(v => v.ToNormalizedString() == version)))
858+
.Returns(status);
859+
854860
controller.SetupPackageFromInputStream(nuGetPackage);
855861

856862
// Act
@@ -897,8 +903,16 @@ public async Task WillAllowReuploadingPackageIfFailedValidation()
897903

898904
var controller = new TestableApiController(GetConfigurationService());
899905
controller.SetCurrentUser(currentUser);
906+
900907
controller.MockPackageService.Setup(x => x.FindPackageRegistrationById(id)).Returns(packageRegistration);
901908
controller.MockPackageService.Setup(x => x.FindPackageByIdAndVersionStrict(id, version)).Returns(conflictingPackage);
909+
controller
910+
.MockPackageService
911+
.Setup(x => x.GetPackageStatus(
912+
id,
913+
It.Is<NuGetVersion>(v => v.ToNormalizedString() == version)))
914+
.Returns(PackageStatus.FailedValidation);
915+
902916
controller.SetupPackageFromInputStream(nuGetPackage);
903917

904918
// Act

tests/NuGetGallery.FunctionalTests/PackageCreation/ApiPushTests.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,7 @@ public async Task DuplicatePushesAreRejectedAndNotDeleted()
3535
// Arrange
3636
var packageId = $"{nameof(DuplicatePushesAreRejectedAndNotDeleted)}.{Guid.NewGuid():N}";
3737

38-
// TODO: Increase this back to 10.
39-
// See: https://github.com/NuGet/NuGetGallery/issues/8368
40-
int pushVersionCount = 1;
38+
int pushVersionCount = 10;
4139
var duplicatePushTasks = new List<Task>();
4240
for (var duplicateTaskIndex = 0; duplicateTaskIndex < pushVersionCount; duplicateTaskIndex++)
4341
{

0 commit comments

Comments
 (0)