VPR-143 fix(cms): split file lookup by identifier, index oldURL#160
Conversation
Fixes the #1 Query Store resource consumer. The catch-all LINQ "(@p IS NULL OR col = @p)" shape forced a full scan of all ~31K rows on every /CMS/Files?oldURL=... hit, with nvarchar(max) CONVERT_IMPLICIT on files.oldURL. - Split CMS.GetFiles(...) into per-identifier methods (GetFileByGuid, GetFileByOldUrl, GetFileByFriendlyName, GetFileByFolderAndName), each TagWith-stamped. GetFile is now a dispatcher; callers unchanged. - HasMaxLength(256) + IsUnicode(false) on OldUrl/FilePath makes EF emit varchar(256) params, eliminating the CONVERT_IMPLICIT. - Added IX_files_oldURL covering index to the File entity config. - [CMS-FILE-404] NLog warning on ProvideFile misses (sanitized id/friendlyName/oldURL/UA/referer/IP) for evidence-based blocks. Schema change is applied manually per environment - see JIRA VPR-143 for the ALTER COLUMN and CREATE INDEX script.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #160 +/- ##
==========================================
- Coverage 43.27% 43.25% -0.03%
==========================================
Files 862 862
Lines 50319 50386 +67
Branches 4696 4703 +7
==========================================
+ Hits 21777 21795 +18
- Misses 28019 28068 +49
Partials 523 523
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Pull request overview
This PR targets a performance hotspot in CMS file lookups (notably /CMS/Files?oldURL=...) by changing EF query shapes and schema mapping so SQL Server can use an index efficiently and avoid scan-heavy “optional parameter” predicates.
Changes:
- Split
CMS.GetFile(...)into per-identifier lookup methods (GetFileByGuid,GetFileByOldUrl,GetFileByFriendlyName,GetFileByFolderAndName) withTagWith(...)for easier Query Store analysis and better plan caching. - Updated EF model configuration for
files.oldURLandfiles.filePath(max length + non-unicode) and added a covering index onoldURL. - Added warning logging for file misses in
ProvideFileincluding request metadata (UA/referer/IP) with log sanitization.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| web/Classes/SQLContext/VIPERContext.cs | Adds IX_files_oldURL covering index and adjusts OldUrl/FilePath mapping to varchar(256) via EF configuration. |
| web/Areas/CMS/Data/CMS.cs | Refactors file lookup into per-identifier queries and adds [CMS-FILE-404] warning logging on ProvideFile misses. |
Addresses Copilot review on PR #160. - Remove OrderBy(FilePath) from GetFileByOldUrl. It was carried over from the old GetFiles catch-all (which returned a list) but the new method returns FirstOrDefault, so the sort adds plan work without affecting correctness. FilePath isn't an IX_files_oldURL key column. - Wrap RemoteIpAddress through LogSanitizer.SanitizeString in [CMS-FILE-404]. Defense in depth: when ForwardedHeaders parses XFF from the F5, a spoofed header could theoretically land in the log. Matches the sanitization pattern used on every other field.
Addresses Copilot review on PR #160. Without this, _logger on Data.CMS was always null for /CMS/Files traffic (the ctor arg defaulted to null and CMSController didn't pass one), so the not-found warnings were silently dropped — defeating the whole observability point of the log.
Bundle ReportBundle size has no change ✅ |
|
@bsedwards I deployed the query improvements and index last night at around 6:30pm and on SSMS it appears that fixed the query's performance: |

Fixes the #1 Query Store resource consumer. The catch-all LINQ "(@p IS NULL OR col = @p)" shape forced a full scan of all ~31K rows on every /CMS/Files?oldURL=... hit, with nvarchar(max) CONVERT_IMPLICIT on files.oldURL.
Schema change is applied manually per environment - see JIRA VPR-143 for the ALTER COLUMN and CREATE INDEX script.