Skip to content
This repository has been archived by the owner on Mar 16, 2021. It is now read-only.

Commit

Permalink
[Package Renames] Add feature flag to disable popularity transfers (#773
Browse files Browse the repository at this point in the history
)

Adds the feature flag `Search.TransferPopularity` that, if disabled, removes all popularity transfers on the search index. Note that download overrides are still applied even if the popularity transfer feature flag is disabled.

Addresses NuGet/NuGetGallery#7944
The feature flag staleness monitoring is tracked by NuGet/NuGetGallery#7966
  • Loading branch information
loic-sharma authored May 1, 2020
1 parent 42d2c4f commit 136e8b1
Show file tree
Hide file tree
Showing 13 changed files with 363 additions and 5 deletions.
1 change: 0 additions & 1 deletion src/NuGet.Jobs.Db2AzureSearch/Job.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NuGet.Services.AzureSearch;
using NuGet.Services.AzureSearch.AuxiliaryFiles;
using NuGet.Services.AzureSearch.Db2AzureSearch;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class UpdateDownloadsCommand : IAzureSearchCommand
private readonly ISearchIndexActionBuilder _indexActionBuilder;
private readonly Func<IBatchPusher> _batchPusherFactory;
private readonly ISystemTime _systemTime;
private readonly IFeatureFlagService _featureFlags;
private readonly IOptionsSnapshot<Auxiliary2AzureSearchConfiguration> _options;
private readonly IAzureSearchTelemetryService _telemetryService;
private readonly ILogger<Auxiliary2AzureSearchCommand> _logger;
Expand All @@ -53,6 +54,7 @@ public UpdateDownloadsCommand(
ISearchIndexActionBuilder indexActionBuilder,
Func<IBatchPusher> batchPusherFactory,
ISystemTime systemTime,
IFeatureFlagService featureFlags,
IOptionsSnapshot<Auxiliary2AzureSearchConfiguration> options,
IAzureSearchTelemetryService telemetryService,
ILogger<Auxiliary2AzureSearchCommand> logger)
Expand All @@ -67,6 +69,7 @@ public UpdateDownloadsCommand(
_indexActionBuilder = indexActionBuilder ?? throw new ArgumentNullException(nameof(indexActionBuilder));
_batchPusherFactory = batchPusherFactory ?? throw new ArgumentNullException(nameof(batchPusherFactory));
_systemTime = systemTime ?? throw new ArgumentNullException(nameof(systemTime));
_featureFlags = featureFlags ?? throw new ArgumentNullException(nameof(featureFlags));
_options = options ?? throw new ArgumentNullException(nameof(options));
_telemetryService = telemetryService ?? throw new ArgumentNullException(nameof(telemetryService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
Expand Down Expand Up @@ -132,7 +135,7 @@ private async Task<bool> PushIndexChangesAsync()

// The "new" data is the latest popularity transfers data from the database.
_logger.LogInformation("Fetching new popularity transfer data from database.");
var newTransfers = await _databaseFetcher.GetPackageIdToPopularityTransfersAsync();
var newTransfers = await GetPopularityTransfersAsync();

_logger.LogInformation("Fetching new download overrides from blob storage.");
var downloadOverrides = await _auxiliaryFileClient.LoadDownloadOverridesAsync();
Expand Down Expand Up @@ -171,6 +174,19 @@ await _popularityTransferDataClient.ReplaceLatestIndexedAsync(
return true;
}

private async Task<SortedDictionary<string, SortedSet<string>>> GetPopularityTransfersAsync()
{
if (!_featureFlags.IsPopularityTransferEnabled())
{
_logger.LogWarning(
"Popularity transfers feature flag is disabled. " +
"All popularity transfers will be removed.");
return new SortedDictionary<string, SortedSet<string>>(StringComparer.OrdinalIgnoreCase);
}

return await _databaseFetcher.GetPackageIdToPopularityTransfersAsync();
}

private void ApplyDownloadTransfers(
DownloadData newData,
SortedDictionary<string, SortedSet<string>> oldTransfers,
Expand Down
10 changes: 10 additions & 0 deletions src/NuGet.Services.AzureSearch/AzureSearchJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Rest;
using NuGet.Jobs;
using NuGet.Jobs.Validation;

namespace NuGet.Services.AzureSearch
{
public abstract class AzureSearchJob<T> : JsonConfigurationJob where T : IAzureSearchCommand
{
private const string FeatureFlagConfigurationSectionName = "FeatureFlags";

public override async Task Run()
{
ServicePointManager.DefaultConnectionLimit = 64;
ServicePointManager.MaxServicePointIdleTime = 10000;

var featureFlagRefresher = _serviceProvider.GetRequiredService<IFeatureFlagRefresher>();
await featureFlagRefresher.StartIfConfiguredAsync();

var tracingInterceptor = _serviceProvider.GetRequiredService<IServiceClientTracingInterceptor>();
try
{
Expand All @@ -30,6 +36,8 @@ public override async Task Run()
{
ServiceClientTracing.RemoveTracingInterceptor(tracingInterceptor);
}

await featureFlagRefresher.StopAndWaitAsync();
}

protected override void ConfigureAutofacServices(ContainerBuilder containerBuilder)
Expand All @@ -40,6 +48,8 @@ protected override void ConfigureAutofacServices(ContainerBuilder containerBuild
protected override void ConfigureJobServices(IServiceCollection services, IConfigurationRoot configurationRoot)
{
services.AddAzureSearch(GlobalTelemetryDimensions);

services.Configure<FeatureFlagConfiguration>(configurationRoot.GetSection(FeatureFlagConfigurationSectionName));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class NewPackageRegistrationProducer : INewPackageRegistrationProducer
private readonly IAuxiliaryFileClient _auxiliaryFileClient;
private readonly IDatabaseAuxiliaryDataFetcher _databaseFetcher;
private readonly IDownloadTransferrer _downloadTransferrer;
private readonly IFeatureFlagService _featureFlags;
private readonly IOptionsSnapshot<Db2AzureSearchConfiguration> _options;
private readonly IOptionsSnapshot<Db2AzureSearchDevelopmentConfiguration> _developmentOptions;
private readonly ILogger<NewPackageRegistrationProducer> _logger;
Expand All @@ -32,6 +33,7 @@ public NewPackageRegistrationProducer(
IAuxiliaryFileClient auxiliaryFileClient,
IDatabaseAuxiliaryDataFetcher databaseFetcher,
IDownloadTransferrer downloadTransferrer,
IFeatureFlagService featureFlags,
IOptionsSnapshot<Db2AzureSearchConfiguration> options,
IOptionsSnapshot<Db2AzureSearchDevelopmentConfiguration> developmentOptions,
ILogger<NewPackageRegistrationProducer> logger)
Expand All @@ -40,6 +42,7 @@ public NewPackageRegistrationProducer(
_auxiliaryFileClient = auxiliaryFileClient ?? throw new ArgumentNullException(nameof(auxiliaryFileClient));
_databaseFetcher = databaseFetcher ?? throw new ArgumentNullException(nameof(databaseFetcher));
_downloadTransferrer = downloadTransferrer ?? throw new ArgumentNullException(nameof(downloadTransferrer));
_featureFlags = featureFlags ?? throw new ArgumentNullException(nameof(featureFlags));
_options = options ?? throw new ArgumentNullException(nameof(options));
_developmentOptions = developmentOptions ?? throw new ArgumentNullException(nameof(developmentOptions));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
Expand All @@ -64,7 +67,7 @@ public async Task<InitialAuxiliaryData> ProduceWorkAsync(
// auxiliary file.
var downloads = await _auxiliaryFileClient.LoadDownloadDataAsync();

var popularityTransfers = await _databaseFetcher.GetPackageIdToPopularityTransfersAsync();
var popularityTransfers = await GetPopularityTransfersAsync();
var downloadOverrides = await _auxiliaryFileClient.LoadDownloadOverridesAsync();

// Apply changes from popularity transfers and download overrides.
Expand Down Expand Up @@ -160,6 +163,19 @@ private bool ShouldWait(ConcurrentBag<NewPackageRegistration> allWork, bool log)
return false;
}

private async Task<SortedDictionary<string, SortedSet<string>>> GetPopularityTransfersAsync()
{
if (!_featureFlags.IsPopularityTransferEnabled())
{
_logger.LogWarning(
"Popularity transfers feature flag is disabled. " +
"Popularity transfers will be ignored.");
return new SortedDictionary<string, SortedSet<string>>(StringComparer.OrdinalIgnoreCase);
}

return await _databaseFetcher.GetPackageIdToPopularityTransfersAsync();
}

private Dictionary<string, long> GetTransferredDownloads(
DownloadData downloads,
SortedDictionary<string, SortedSet<string>> popularityTransfers,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public static class DependencyInjectionExtensions
{
public static ContainerBuilder AddAzureSearch(this ContainerBuilder containerBuilder)
{
containerBuilder.AddFeatureFlags();

/// Here, we register services that depend on an interface that there are multiple implementations.

/// There are multiple implementations of <see cref="ISearchServiceClientWrapper"/>.
Expand Down Expand Up @@ -226,6 +228,9 @@ public static IServiceCollection AddAzureSearch(
{
services.AddV3(telemetryGlobalDimensions);

services.AddFeatureFlags();
services.AddTransient<IFeatureFlagService, FeatureFlagService>();

services
.AddTransient<ISearchServiceClient>(p =>
{
Expand Down
25 changes: 25 additions & 0 deletions src/NuGet.Services.AzureSearch/FeatureFlagService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using NuGet.Services.FeatureFlags;

namespace NuGet.Services.AzureSearch
{
public class FeatureFlagService : IFeatureFlagService
{
private const string SearchPrefix = "Search.";

private readonly IFeatureFlagClient _featureFlagClient;

public FeatureFlagService(IFeatureFlagClient featureFlagClient)
{
_featureFlagClient = featureFlagClient ?? throw new ArgumentNullException(nameof(featureFlagClient));
}

public bool IsPopularityTransferEnabled()
{
return _featureFlagClient.IsEnabled(SearchPrefix + "TransferPopularity", defaultValue: true);
}
}
}
10 changes: 10 additions & 0 deletions src/NuGet.Services.AzureSearch/IFeatureFlagService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace NuGet.Services.AzureSearch
{
public interface IFeatureFlagService
{
bool IsPopularityTransferEnabled();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,13 @@
<Compile Include="Db2AzureSearch\Db2AzureSearchDevelopmentConfiguration.cs" />
<Compile Include="Db2AzureSearch\InitialAuxiliaryData.cs" />
<Compile Include="DownloadTransferrer.cs" />
<Compile Include="FeatureFlagService.cs" />
<Compile Include="IAzureSearchTelemetryService.cs" />
<Compile Include="IBaseDocumentBuilder.cs" />
<Compile Include="IAzureSearchCommand.cs" />
<Compile Include="IDatabaseAuxiliaryDataFetcher.cs" />
<Compile Include="IDownloadTransferrer.cs" />
<Compile Include="IFeatureFlagService.cs" />
<Compile Include="ISearchIndexActionBuilder.cs" />
<Compile Include="JobOutcome.cs" />
<Compile Include="Models\IUpdatedDocument.cs" />
Expand Down
68 changes: 68 additions & 0 deletions src/NuGet.Services.V3/DependencyInjectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using Autofac;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.RetryPolicies;
using NuGet.Jobs.Validation;
using NuGet.Protocol.Catalog;
using NuGet.Protocol.Registration;
using NuGet.Services.FeatureFlags;
using NuGet.Services.Logging;
using NuGet.Services.Metadata.Catalog;
using NuGetGallery;
using NuGetGallery.Diagnostics;
using NuGetGallery.Features;

namespace NuGet.Services.V3
{
public static class DependencyInjectionExtensions
{
private const string FeatureFlagBindingKey = nameof(FeatureFlagBindingKey);

private static readonly TimeSpan FeatureFlagServerTimeout = TimeSpan.FromSeconds(30);
private static readonly TimeSpan FeatureFlagMaxExecutionTime = TimeSpan.FromMinutes(10);

public static IServiceCollection AddV3(this IServiceCollection services, IDictionary<string, string> telemetryGlobalDimensions)
{
services
Expand Down Expand Up @@ -50,5 +63,60 @@ public static IServiceCollection AddV3(this IServiceCollection services, IDictio

return services;
}

public static void AddFeatureFlags(this ContainerBuilder containerBuilder)
{
containerBuilder
.Register(c =>
{
var options = c.Resolve<IOptionsSnapshot<FeatureFlagConfiguration>>();
var requestOptions = new BlobRequestOptions
{
ServerTimeout = FeatureFlagServerTimeout,
MaximumExecutionTime = FeatureFlagMaxExecutionTime,
LocationMode = LocationMode.PrimaryThenSecondary,
RetryPolicy = new ExponentialRetry(),
};

return new CloudBlobClientWrapper(
options.Value.ConnectionString,
requestOptions);
})
.Keyed<ICloudBlobClient>(FeatureFlagBindingKey);

containerBuilder
.Register(c => new CloudBlobCoreFileStorageService(
c.ResolveKeyed<ICloudBlobClient>(FeatureFlagBindingKey),
c.Resolve<IDiagnosticsService>(),
c.Resolve<ICloudBlobContainerInformationProvider>()))
.Keyed<ICoreFileStorageService>(FeatureFlagBindingKey);

containerBuilder
.Register(c => new FeatureFlagFileStorageService(
c.ResolveKeyed<ICoreFileStorageService>(FeatureFlagBindingKey)))
.As<IFeatureFlagStorageService>();
}

public static IServiceCollection AddFeatureFlags(this IServiceCollection services)
{
services
.AddTransient(p =>
{
var options = p.GetRequiredService<IOptionsSnapshot<FeatureFlagConfiguration>>();
return new FeatureFlagOptions
{
RefreshInterval = options.Value.RefreshInternal,
};
});

services.AddTransient<IFeatureFlagClient, FeatureFlagClient>();
services.AddTransient<IFeatureFlagTelemetryService, V3TelemetryService>();
services.AddTransient<ICloudBlobContainerInformationProvider, GalleryCloudBlobContainerInformationProvider>();

services.AddSingleton<IFeatureFlagCacheService, FeatureFlagCacheService>();
services.AddSingleton<IFeatureFlagRefresher, FeatureFlagRefresher>();

return services;
}
}
}
10 changes: 9 additions & 1 deletion src/NuGet.Services.V3/V3TelemetryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@

using System;
using System.Collections.Generic;
using NuGet.Services.FeatureFlags;
using NuGet.Services.Logging;

namespace NuGet.Services.V3
{
public class V3TelemetryService : IV3TelemetryService
public class V3TelemetryService : IV3TelemetryService, IFeatureFlagTelemetryService
{
private const string Prefix = "V3.";

Expand All @@ -27,5 +28,12 @@ public IDisposable TrackCatalogLeafDownloadBatch(int count)
{ "Count", count.ToString() },
});
}

public void TrackFeatureFlagStaleness(TimeSpan staleness)
{
_telemetryClient.TrackMetric(
Prefix + "FeatureFlagStalenessSeconds",
staleness.TotalSeconds);
}
}
}
Loading

0 comments on commit 136e8b1

Please sign in to comment.