Skip to content

URL and Alias Caches: Optimize for invariant documents#21558

Merged
AndyButland merged 21 commits intomainfrom
v17/improvements/url-and-alias-cache-for-invariant-documents
Mar 4, 2026
Merged

URL and Alias Caches: Optimize for invariant documents#21558
AndyButland merged 21 commits intomainfrom
v17/improvements/url-and-alias-cache-for-invariant-documents

Conversation

@AndyButland
Copy link
Copy Markdown
Contributor

@AndyButland AndyButland commented Jan 28, 2026

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 languageId instead 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)

  • Makes languageId nullable in umbracoDocumentUrl and umbracoDocumentUrlAlias tables.
  • Converts existing invariant records to use NULL and removes duplicates.
  • Triggers cache rebuild on startup.

Services

  • DocumentUrlService and DocumentUrlAliasService now use int? for languageId in cache keys.
  • Lookup methods try culture-specific match first, then fall back to invariant (NULL).
  • Creates records with NULL languageId for invariant content.

Content Type Variation Changes

  • Added ContentTypeChangeTypes.VariationChanged flag.
  • New DocumentUrlServiceContentTypeChangedNotificationHandler rebuilds URL/alias caches when content type variation changes.
  • Ensures cache consistency when admins change content types from invariant to variant or vice versa.

Edge Cases

Custom URL Providers

People can implement all sorts of weirdness in custom IUrlSegmentProvider implementations, taking culture into account in odd ways, even for invariant documents. There could be scenarios with custom URL segment providers creating culture variant segments for invariant content.

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:

  • Run the migration, verify the schema of the umbracoDocumentUrl and umbracoDocumentUrlAlias tables.
  • Verify the data has the correct records and values for languageId in both tables for variant and invariant content.
  • Verify the data is updated correctly on document amends - e.g. renames to change the URL segment, addition and update of a property with alias umbracoUrlAlias.
  • Verify routing by URL and and alias works as expected.
  • Verify URLs generated for documents work as expected.

Copilot AI review requested due to automatic review settings January 28, 2026 13:28
@AndyButland AndyButland changed the title DocumentUrlService: Optimize URL and alias cache for invariant documents URL and Alias Caches: Optimize for invariant documents Jan 28, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 with NULL languageId, and update lookup logic to fall back from culture-specific keys to invariant entries.
  • Introduce a new migration (OptimizeInvariantUrlRecords) that alters schema and data for umbracoDocumentUrl and umbracoDocumentUrlAlias, and triggers URL/alias cache rebuilds on startup.
  • Add a ContentTypeChangeTypes.VariationChanged flag and DocumentUrlServiceContentTypeChangedNotificationHandler wired 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.

@AndyButland AndyButland marked this pull request as draft January 28, 2026 13:46
@AndyButland AndyButland marked this pull request as ready for review January 28, 2026 16:18
Copy link
Copy Markdown
Member

@Zeegaan Zeegaan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just have one concern 😁

@AndyButland AndyButland requested a review from Zeegaan March 4, 2026 06:07
Copy link
Copy Markdown
Member

@Zeegaan Zeegaan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM now, thank you for the changes 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants