URL and Alias Caches: Optimize for invariant documents#21558
Merged
AndyButland merged 21 commits intomainfrom Mar 4, 2026
Merged
URL and Alias Caches: Optimize for invariant documents#21558AndyButland merged 21 commits intomainfrom
AndyButland merged 21 commits intomainfrom
Conversation
…nt URL and alias cache for invariant documents. Store invariant content with NULL languageId instead of duplicating records for each language.
…ype changes from variant to invariant or vice versa.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR refactors URL/alias storage so invariant documents no longer duplicate per-language records, and wires up automatic cache rebuilding when content type variation changes, with tests and migrations to support the new semantics.
Changes:
- Switch URL and alias models, caches, and repositories to use nullable
languageId(int?) so invariant content is stored once withNULLlanguageId, and update lookup logic to fall back from culture-specific keys to invariant entries. - Introduce a new migration (
OptimizeInvariantUrlRecords) that alters schema and data forumbracoDocumentUrlandumbracoDocumentUrlAlias, and triggers URL/alias cache rebuilds on startup. - Add a
ContentTypeChangeTypes.VariationChangedflag andDocumentUrlServiceContentTypeChangedNotificationHandlerwired into DI so URL and alias caches are rebuilt when content types switch between invariant and variant, and extend unit/integration tests to validate behavior.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 21 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DocumentUrlServiceTests.cs | Adds unit tests for ConvertToCacheModel to validate handling of null language IDs and mixed invariant/variant segments. |
| tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTests.cs | Adds integration tests to verify invariant URLs work across cultures, are persisted with NULL languageId, and are updated correctly when content type variation toggles. |
| tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlAliasServiceTests.cs | Adjusts expectations for invariant aliases (now NULL languageId), registers the variation-change handler, and adds tests covering invariant/variant alias storage and updates on variation changes. |
| src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentUrlRepository.cs | Updates the internal key for URL DTOs to use int? LanguageId, so the repository can persist invariant rows with NULL language IDs and still deduplicate correctly. |
| src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentUrlAliasRepository.cs | Introduces a concrete repository for aliases that diffs and upserts PublishedDocumentUrlAlias records keyed by (UniqueId, LanguageId, Alias) and exposes an optimized SQL query to read raw alias property data. |
| src/Umbraco.Infrastructure/Persistence/Dtos/DocumentUrlDto.cs | Makes languageId nullable in the DTO and mapping attributes so the umbracoDocumentUrl table can hold invariant entries with NULL language IDs. |
| src/Umbraco.Infrastructure/Persistence/Dtos/DocumentUrlAliasDto.cs | Makes languageId nullable in the alias DTO, and ensures indices on (uniqueId, languageId, alias) and (alias, languageId) work with NULL for invariant content. |
| src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_3_0/OptimizeInvariantUrlRecords.cs | Adds a migration to alter languageId columns, convert existing records (including deduplication), and trigger cache rebuilds; currently misidentifies invariant content by using ct.variations = 1 instead of matching ContentVariation.Nothing, which would corrupt variant data and needs to be corrected. |
| src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs | Wires the new OptimizeInvariantUrlRecords migration into the 17.3.0 upgrade plan. |
| src/Umbraco.Core/Services/DocumentUrlServiceContentTypeChangedNotificationHandler.cs | Implements an async notification handler for ContentTypeChangedNotification that pages through affected content and rebuilds URLs/aliases when variation settings change. |
| src/Umbraco.Core/Services/DocumentUrlService.cs | Changes URL cache keys to use int? LanguageId, generates persisted segments with NULL for invariant content, updates cache lookup methods to fall back from culture-specific to invariant keys, and adjusts cache invalidation to handle invariant entries. |
| src/Umbraco.Core/Services/DocumentUrlAliasService.cs | Changes alias cache keys and models to nullable LanguageId, stores invariant aliases once with NULL languageId, rebuilds alias data from property values accordingly, and updates lookups to first use culture-specific keys then fall back to invariant aliases. |
| src/Umbraco.Core/Services/ContentTypeServiceBase{TRepository,TItem}.cs | Extends change tracking to add a VariationChanged bit when a content type’s Variations property changes, so downstream handlers can respond specifically to variation switches. |
| src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs | Adds the VariationChanged = 16 enum value with documentation to represent content type variation changes. |
| src/Umbraco.Core/Models/PublishedDocumentUrlSegment.cs | Updates the URL segment model to use int? LanguageId, reflecting the new invariant-storage semantics. |
| src/Umbraco.Core/Models/PublishedDocumentUrlAlias.cs | Updates the URL alias model to use int? LanguageId, allowing invariant aliases to be represented with NULL. |
| src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs | Registers DocumentUrlServiceContentTypeChangedNotificationHandler in the core DI setup so variation changes in content types trigger URL/alias cache rebuilds in the running application. |
src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_3_0/OptimizeInvariantUrlRecords.cs
Show resolved
Hide resolved
tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlAliasServiceTests.cs
Outdated
Show resolved
Hide resolved
tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlAliasServiceTests.cs
Outdated
Show resolved
Hide resolved
tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTests.cs
Outdated
Show resolved
Hide resolved
tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTests.cs
Outdated
Show resolved
Hide resolved
tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTests.cs
Show resolved
Hide resolved
tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTests.cs
Show resolved
Hide resolved
tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTests.cs
Show resolved
Hide resolved
tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTests.cs
Show resolved
Hide resolved
tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentUrlServiceTests.cs
Show resolved
Hide resolved
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…egments per culture.
AndyButland
commented
Jan 29, 2026
1 task
Zeegaan
requested changes
Mar 4, 2026
src/Umbraco.Infrastructure/Persistence/Dtos/DocumentUrlAliasDto.cs
Outdated
Show resolved
Hide resolved
This reverts commit c77a37c.
Zeegaan
approved these changes
Mar 4, 2026
Member
Zeegaan
left a comment
There was a problem hiding this comment.
LGTM now, thank you for the changes 🚀
This was referenced Apr 6, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The URLs and aliases for all documents are stored in dedicated database tables, populated on start-up if changes to URL providers are detected, maintained as documents are managed, and are used to prepare a cache for URLs and aliases. These caches are then used in routing documents and finding the URL for a document.
Up to now we've been storing one record per document, per language - even for invariant documents. This is at least a little wasteful in terms of storage, memory usage and performance for look-ups, and it could be quite considerable for multi-site setups (where content is invariant, but each site root may be for a different language). E.g. for a site with 10 languages and 1,000 invariant documents, this results in 10,000 URL records instead of the necessary 1,000.
This PR looks to optimise this so we only store the records we need by storing invariant content with null
languageIdinstead of duplicating records for each language.The database tables continue to be updated as documents are managed. One edge case - where a document type changes from invariant to variant or vice versa is detected in a notification handler and updates records to store the correct values for
languageId.Changes
Database Schema (Migration)
languageIdnullable inumbracoDocumentUrlandumbracoDocumentUrlAliastables.Services
DocumentUrlServiceandDocumentUrlAliasServicenow useint?for languageId in cache keys.Content Type Variation Changes
ContentTypeChangeTypes.VariationChangedflag.DocumentUrlServiceContentTypeChangedNotificationHandlerrebuilds URL/alias caches when content type variation changes.Edge Cases
Custom URL Providers
This has been handled in the logic of the
DocumentUrlService. If we detect different URL segments generated for different languages for invariant content, we revert to storage per language.Testing
Various integration and unit tests have been added to verify functionality.
Manual testing should involve:
umbracoDocumentUrlandumbracoDocumentUrlAliastables.languageIdin both tables for variant and invariant content.umbracoUrlAlias.