Overview
Stats.AggregateCdnDownloadsInGallery is a .NET Framework 4.7.2 console application that runs as a scheduled background job. Its sole responsibility is to keep the Gallery SQL database’s download-count columns (Packages.DownloadCount and PackageRegistrations.DownloadCount) synchronized with the authoritative download statistics produced by the CDN analytics pipeline.
The source of truth for download numbers is a JSON feed (downloads.v1.json) published by an Azure Synapse pipeline. This job fetches that feed, compares each package’s new count against the value already stored in the Gallery database, and writes an update only when the new count is strictly greater than the current one — preventing accidental regressions.
Download counts are never decreased by this job. If the incoming count is lower than what is stored, a warning is logged (event ID 901) but no SQL update is issued. This guards against data-pipeline anomalies overwriting legitimate historical totals.
Role in the System
PackageRegistrations.DownloadCount to display the download badges visible on every package page. Without this job running regularly, those counts would become stale.
Other jobs that consume the same downloads.v1.json feed (Auxiliary2AzureSearch, Db2AzureSearch) use it to keep Azure Search indexes current, but they do not write to the Gallery SQL database — that is the exclusive responsibility of this job.
Reads from
Azure Synapse
downloads.v1.json feed via IDownloadsV1JsonClientWrites to
Gallery SQL —
dbo.Packages and dbo.PackageRegistrations download count columnsDeployed as
Windows Service via NSSM, installed through Octopus Deploy scripts
Feature flags
Integrates
IFeatureFlagRefresher from NuGet.Services.FeatureFlags for runtime flag pollingKey Files and Classes
| File | Class / Type | Purpose |
|---|---|---|
Program.cs | Program | Entry point; constructs AggregateCdnDownloadsJob and delegates to JobRunner.Run() |
AggregateCdnDownloadsJob.cs | AggregateCdnDownloadsJob | Core job logic: reads download JSON, batches updates, performs SQL bulk-copy and update |
Configuration/AggregateCdnDownloadsConfiguration.cs | AggregateCdnDownloadsConfiguration | Configures BatchSize, BatchSleepSeconds, and optional CommandTimeoutSeconds |
Configuration/DownloadsV1JsonConfiguration.cs | DownloadsV1JsonConfiguration | Holds SynapsePipelineUrl — the URL of the Synapse-published downloads JSON feed |
DownloadCountData.cs | DownloadCountData | Simple DTO: PackageId, PackageVersion, TotalDownloadCount — one record per package version from the feed |
PackageRegistrationData.cs | PackageRegistrationData | Maps a Gallery DB row: Key, LowercasedId, OriginalId, DownloadCount |
LogEvents.cs | LogEvents | Defines structured log event IDs: IncorrectIdsInGroupBatch (900) and DownloadCountDecreaseDetected (901) |
Scripts/PostDeploy.ps1 | — | Octopus Deploy post-deployment script that installs the job as a Windows service using NSSM |
Scripts/Functions.ps1 | — | Shared PowerShell helpers (Install-NuGetService) used by deploy scripts |
Dependencies
Internal Project References
| Project | Purpose |
|---|---|
NuGet.Jobs.Common | Base JsonConfigurationJob, JobRunner, GalleryDbConfiguration, SQL retry helpers (QueryWithRetryAsync) |
NuGet.Services.Metadata.Catalog | IDownloadsV1JsonClient / DownloadsV1JsonClient, AddDownloadCount delegate, ServiceCollectionExtensions.AddDownloadsV1JsonClient |
NuGet.Services.AzureSearch | Feature flag infrastructure (IFeatureFlagRefresher, ConfigureFeatureFlagServices) |
Implicit NuGet Package Dependencies (via project refs)
| Package | Used for |
|---|---|
Autofac.Extensions.DependencyInjection | DI container (Autofac + Microsoft DI bridge) |
Dapper.StrongName | QueryWithRetryAsync extension on SqlConnection |
Microsoft.Extensions.Options | IOptionsSnapshot<T> configuration binding |
System.Data.SqlClient | SqlBulkCopy, SqlConnection, SqlCommand |
Microsoft.Extensions.Logging | Structured logging throughout the job |
Processing Pipeline
The job executes the following steps on each run:Notable Patterns and Quirks
Batch sizing is count-based, not record-count-based.
PopGroupBatch accumulates groups until the estimated SQL update row count — (number of package IDs) + (total number of versions across those IDs) — would exceed BatchSize. This accounts for the fact that each batch issues one UPDATE per version row plus one UPDATE per package ID row.One-way ratchet for download counts. The job will never write a lower count than what is already stored. If the statistics pipeline produces a lower number (e.g., due to a reprocessing artifact), the discrepancy is logged at
Warning level with event ID 901 and no SQL write occurs.No NuGet package references in the
.csproj. All NuGet dependencies are pulled in transitively through the three internal project references. There are no direct <PackageReference> entries in Stats.AggregateCdnDownloadsInGallery.csproj.