`CacheTagHelper`: `MemoryCacheEntryOptions.Size` omits `vary-by` key memory
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 281
Description
## Summary
`CacheTagHelper` sets `MemoryCacheEntryOptions.Size` to the rendered HTML byte count only. The retained `CacheTagKey` — which keeps the original `vary-by-header` / `vary-by-cookie` / `vary-by-query` / `vary-by-route` string values for the lifetime of the entry — is not included in the per-entry size. On views that vary by larger keys this under-reports each entry''s actual managed footprint, so `CacheTagHelperOptions.SizeLimit` no longer reflects real cache memory.
## Where
- `src/Mvc/Mvc.TagHelpers/src/CacheTagHelper.cs` — sets `Size = value.Length * sizeof(char)` only
- `src/Mvc/Mvc.TagHelpers/src/CacheTagKey.cs` — retains full untrimmed vary-by string values
## Observation
For an entry created from `` the cache pins:
- The rendered HTML — counted by `Size`
- The `CacheTagKey` plus every `vary-by-*` string value it captured — not counted
For views where the rendered fragment is small relative to the captured key values, the un-counted portion can dominate the per-entry footprint and the actual managed memory held by the cache becomes noticeably higher than the value reported by `_cacheSize` / surfaced via `SizeLimit`. This is most noticeable on memory-constrained hosts (containers, small App Service plans) where the operator sized `SizeLimit` assuming it reflects real working set.
## Proposed change
In `CacheTagHelper` (where `MemoryCacheEntryOptions.Size` is currently set), include the computed key size:
```csharp
var renderedSize = value.Length * sizeof(char);
var keySize = cacheTagKey.GetByteSize();
options.Size = renderedSize + keySize;
```
Add a `GetByteSize()` (or `EstimatedSize`) on `CacheTagKey` that sums:
- Prefix string length
- Each `vary-by-header` / `vary-by-cookie` / `vary-by-query` / `vary-by-route` name + value length (UTF-16 bytes)
- A small constant for object/spine overhead
## Default `SizeLimit`
After this change, apps that use `vary-by-*` with non-trivial keys will reach the cap with fewer cached entries. To avoid noticeable regressions for typical apps, consider a small bump to the default `CacheTagHelperOptions.SizeLimit`. **Suggested: `100 MB → 128 MB`**, clean round number, big enough to absorb typical key overhead but small enough not to meaningfully inflate the default memory budget.
## Notes
- No public API break — `Size` semantics are caller-defined and this only changes what the helper reports.
- Behavior change: with the same `SizeLimit`, fewer entries fit when keys are large. Worth a release-notes line.
Contributor guide
Research direction
Start in src/Mvc/Mvc.TagHelpers/src/CacheTagHelper.cs where MemoryCacheEntryOptions.Size is assigned, then inspect src/Mvc/Mvc.TagHelpers/src/CacheTagKey.cs and its retained vary-by values. Implement the requested key-size accounting and assess the proposed default SizeLimit adjustment; done means cached entries report rendered content plus key memory consistently, with relevant behavior covered by tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100