Repository navigation
Description
The nupkg size has crept upwards over time, currently measuring nearly 6MB.
Impact:
- ourselves - MyGet choked on me (one option here would be to ditch MyGet entirely and use pure NuGet trusted publishing, which I've already done for Dapper and protobuf-net)
- our customers - slower restores
- NuGet - getting the size back down even a bit is PB/yr territory
Looking in package explorer, firstly: this is not the new analyzer, which weighs in at 106KB, but: the "lib" area
We currently ship:
- net10.0
- net8.0
- net6.0
- netstandard2.0
- net472
- net461
and we use embedded pdbs.
I propose 2 things:
- drop net6.0 (I don't think anyone will fight me here) and net461 (possibly more of a fight? maybe just say < net472 share netstandard2.0?)
- switch to snupkg and on-demand only
Delta
Measured locally at 89cf0c6, one clean pack per variant (wiping obj/bin between), so the rows are directly comparable:
| variant | nupkg | MB | saved | % | snupkg |
|---|---|---|---|---|---|
| base (today) | 6,124,519 | 5.84 | — | — | — |
| drop net6.0 | 5,116,918 | 4.88 | 1,007,601 | 16.5% | — |
| drop net6.0 + net461 | 4,109,687 | 3.92 | 2,014,832 | 32.9% | — |
| snupkg only | 4,499,458 | 4.29 | 1,625,061 | 26.5% | 1,619,898 |
| both proposals | 3,021,334 | 2.88 | 3,103,185 | 50.7% | 1,079,655 |
Doing both halves the package. For scale: 2.8.58 - the last v2 - was 3,069,353 bytes, so both proposals together put v3 back under the final v2 package, analyzer included.
Three things worth pulling out of that table:
- A TFM costs ~1MB, near enough exactly. net6.0 is 1,007,601 and net461 is 1,007,231. That is a useful constant for any future "can we add a TFM" conversation.
- The two changes overlap; they do not add up. snupkg saves 1,625,061 at six TFMs but only 1,088,353 at four, because dropping a TFM already takes its PDB with it. Sum of the parts is 3,639,893, actual combined saving is 3,103,185.
- If we publish the snupkg, total bytes shipped fall 33%, not 50.7%. 6,124,519 goes to 4,100,989 across both files. The 50.7% is what a consumer restores by default, which is the number that matters for the bandwidth argument - symbols are then pulled only by people who actually want them.
At a conservative 1M downloads/day the combined change is ~3.10 TB/day, ~1.13 PB/yr not sent.
Where the size actually is
Confirming the point above, by group, as compressed bytes in the package:
| group | in-zip | share |
|---|---|---|
| 6 x TFM (dll + xml doc) | ~1.0MB each | 98.6% |
| analyzer + codefixes | 71,651 | 1.2% |
| icon, nuspec, readme, props | 11,876 | 0.2% |
Each TFM is ~875KB of dll plus ~130KB of XML doc. One correction to my own note above: the 106KB I quoted from Package Explorer is the uncompressed size of a single assembly. In the package it is 71,651 bytes for both assemblies together, i.e. 1.2%. So the analyzer is even less of the story than I said.
Caveats on the TFM drops
I had the risk ordering backwards. net6.0 is the cheap one; net461 is the one that needs care.
net6.0 - a net6.0/net7.0 consumer resolves netstandard2.0, and UNIX_SOCKET is defined only on net6.0+. That symbol does not guard an optimisation, it guards endpoint parsing: !path, and !@name for the Linux abstract namespace. So dropping net6.0 silently removes the ability to configure a unix domain socket for anyone still on .NET 6 or 7. Both are EOL, so I think that is an acceptable trade - but it is a functional removal, not a no-op, and should be called out in the release notes rather than discovered.
net461 - this is not "share netstandard2.0". The netstandard2.0 asset is compiled with VECTOR_SAFE, because the condition is only != net461. The comment sitting above that #if explains exactly what happens when 4.6.1 runs the vectorised path:
Could not load file or assembly 'System.Numerics.Vectors, Version=4.1.3.0, ...' [...] the NuGet tools do not reliably (or even occasionally) produce the correct assembly-binding-redirect entries to fix this up
So dropping the net461 asset does not give those consumers a slower path, it gives them the path we documented as broken on that runtime, with a binding redirect we already established cannot be fixed up. Doing it safely means widening the exclusion so netstandard2.0 also drops VECTOR_SAFE, which deoptimises every netstandard2.0 consumer - Mono, Unity, older .NET Core - to buy back 1MB. Size impact of that change is negligible; the trade is correctness, not bytes.
That reframes proposal 1: drop net6.0 on its own is 16.5% for a documented, EOL-only functional loss. net461 is a further 16.4% but needs the VECTOR_SAFE question answered first.
RESPite
src/RESPite/RESPite.csproj carries the same six TFMs and is a dependency, so a consumer's restore pulls both packages. Whatever we decide here should apply to both, or we leave a good part of the win on the table.
Timing
Worth doing sooner rather than later, for a reason that is not about our own convenience. Microsoft.Extensions.Caching.StackExchangeRedis, Microsoft.AspNetCore.DataProtection.StackExchangeRedis and Microsoft.AspNetCore.SignalR.StackExchangeRedis all pin the same floors, and NuGet resolves a range to its lowest satisfying version - so those exact versions get restored forever. Across 2.0.513, 2.0.593, 2.2.4, 2.6.90, 2.6.122 and 2.7.27 that is 410,006,091 downloads, 35.5% of our all-time 1,156,357,612.
The current floor has been 2.7.27 since Caching 8.0.4, and neither 9.0.x nor 10.0.x moved it. Whenever it does move to a 3.x, that single version inherits about a third of our download volume overnight, at whatever size it happens to be. Being lean before that bump is worth a great deal more than being lean after it.
Activity
NickCraver commentedon Sep 6, 2026
I would advocate we trim the older TFMs (
net6especially) rather than split symbols - unfortunately, I know of many net461 consumers yet (due to netstandard issues). Symbols packages are almost never used in practice for large systems, and we get no exact stack traces from production logs without symbols in path (this has saved an RCA many times).mgravell commentedon Sep 10, 2026
Closed by #3208 and #3213
IanKemp commentedon Sep 11, 2026
Bizarre decision to continue to support NETFX 4.6.1 over net6, when the former hasn't been supported by MS for over 4 years while the latter is barely 2 years out of support.
mgravell commentedon Sep 11, 2026
@IanKemp lol, you're not wrong, but: see Nick's comment immediately above; the netfx people are more entrenched and are harder to shift - to be honest, they're probably a lost cause, and as long as it doesn't actively hurt me, I'm a bit "meh". The net6 folks, on the other hand, are demonstrably more ... dynamic. I know that the net6 -> net8/net10 transition is way less disruptive than the netfx one, so we actually have a reasonable chance of motivating them into updating framework, rather than motivating them to stop updating the library.
NickCraver commentedon Sep 11, 2026
@IanKemp To clarify: .NET 4.6.2 support is Active/current (see here), and
net461is the only compatible TFM we have for it.netstandardhas many shimming issues in practice.