Skip to content

Performance: Batch the content cache delete on structural content type changes - #23329

Merged
AndyButland merged 2 commits into
v17/devfrom
v17/improvement/chunk-delete-content-cache-for-updated-content-type
Jul 13, 2026
Merged

Performance: Batch the content cache delete on structural content type changes#23329
AndyButland merged 2 commits into
v17/devfrom
v17/improvement/chunk-delete-content-cache-for-updated-content-type

Conversation

@AndyButland

@AndyButland AndyButland commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Description

Follow-up to the investigation into SQL command timeouts when saving a structural change to a document type (e.g. removing a property) that backs a lot of content.

When a content type changes structurally, the published cache (cmsContentNu) is rebuilt for every affected document. The first step of that rebuild deleted all of the type's rows in a single, unbounded DELETE. On sites with a lot of content those rows carry large LOB columns (data NVARCHAR(MAX), dataRaw VARBINARY), so the one statement generates heavy transaction-log I/O, can escalate to a table lock, and — as seen on a customer install — can exceed even a raised CommandTimeout, rolling the whole save back.

This PR replaces that single delete with a batched delete:

  • The affected node ids are read from the (stable) source tables once, then their cmsContentNu rows are deleted in batches by clustered primary key (nodeId). This deliberately avoids re-scanning the source per batch, so it stays predictable even without a contentTypeId index.
  • Each batch runs through the existing executeStep delegate, so under ContentTypeRebuildMode.Deferred each batch commits in its own transaction — releasing its locks and log space between batches instead of accumulating across the whole delete. Under the immediate path the batches share the ambient transaction, but each DELETE statement is still individually bounded.
  • The batch size is configurable via NuCacheSettings.ContentTypeRebuildDeleteBatchSize (default 2000, capped at the SQL parameter limit). It sits next to SqlPageSize, which governs the insert/repopulation paging of the same rebuild.
  • The count of affected nodes is derived from the fetched id list, so the previously separate SELECT COUNT(*) step is removed.
  • Debug-level logging reports each batch as it is deleted (visible by enabling Debug for Umbraco.Cms.Infrastructure.HybridCache.Persistence.DatabaseCacheRepository).

The full-clear path (a full cache rebuild, where the table has typically already been truncated) is unchanged and remains a single statement.

This is a self-contained improvement and pairs with the separately-submitted index on umbracoContent(contentTypeId) in #23328.

Testing

Automated

An integration test covering multi-batch deletion has been added.

Manual

With ContentTypeRebuildMode: Deferred and a small ContentTypeRebuildDeleteBatchSize (e.g. 5), make a structural change to a document type that has several content items and watch the debug log — the rebuild deletes the rows in batches, each committed independently, e.g.:

Rebuild: deleting cmsContentNu rows for object type ... — 42 node(s) in 9 batch(es) of up to 5.
Rebuild: deleted cmsContentNu batch 1/9 (5 node(s)) ...
...
Rebuild: deleted cmsContentNu batch 9/9 (2 node(s)) ...

Copilot AI review requested due to automatic review settings July 8, 2026 13:35
@claude

claude Bot commented Jul 8, 2026

Copy link
Copy Markdown

Claude finished @AndyButland's task in 4m 52s —— View job


PR Review

Target: origin/v17/dev · Based on commit: b750dde0

Replaces the single unbounded DELETE in the content/media/member cache rebuild with a batched delete driven by a configurable NuCacheSettings.ContentTypeRebuildDeleteBatchSize (default 2 000, capped at Constants.Sql.MaxParameterCount). Node IDs are read once from the source tables then deleted in batches; in deferred mode each batch commits independently, releasing locks between steps.

  • Modified public API: NuCacheSettings — new property ContentTypeRebuildDeleteBatchSize
  • Other changes: New ContentTypeRebuildDeleteBatchSize configuration key is now readable via appsettings.json under Umbraco:CMS:NuCache. Default is 2 000; operators can lower it if they see lock escalation on cmsContentNu during a rebuild.

Suggestions

  • tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DatabaseCacheRepositoryTests.cs:122: Test name Rebuild_Deletes_Stale_Rows_In_Batches_And_Repopulates doesn't follow the repo's Can_/Cannot_ naming convention. Consider Can_Rebuild_ContentType_Cache_Rows_In_Multiple_Batches. (Inline comment posted.)

  • tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DatabaseCacheRepositoryTests.cs:34: PostConfigure<NuCacheSettings> in CustomTestSetup sets the batch size to 2 for all tests in the class, not just the new one. The existing tests don't exercise the batched delete path directly, so this doesn't break them — but any rebuild they trigger now issues more SQL statements than before. Consider scoping the override to only the new test (e.g., move to a [SetUp] method guarded by the test name, or extract the new test into its own fixture).


Approved with Suggestions for improvement

Good to go — the approach is well-reasoned (stable-table ID fetch, configurable batch size, safety cap, per-batch logging, good integration test), and no breaking changes. Please do a manual sanity check before merging.

@claude claude Bot added area/backend category/performance Fixes for performance (generally cpu or memory) fixes labels Jul 8, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Improves the hybrid published cache rebuild path for structural content type changes by replacing a single large DELETE against cmsContentNu with a predictable batched delete, reducing transaction log pressure and lock escalation risk on large datasets.

Changes:

  • Introduces batched deletion of cmsContentNu rows by nodeId during rebuilds, with per-batch executeStep execution for deferred rebuild transaction boundaries.
  • Adds NuCacheSettings.ContentTypeRebuildDeleteBatchSize (default 2000, capped to Constants.Sql.MaxParameterCount) to control delete batch sizing.
  • Adds an integration test that marks existing cache rows as stale and verifies a rebuild removes stale rows and fully repopulates cache rows for the affected content type.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DatabaseCacheRepositoryTests.cs Adds an integration test and config override to exercise and validate the new batched delete behavior during rebuild.
src/Umbraco.PublishedCache.HybridCache/Persistence/DatabaseCacheRepository.cs Replaces single unbounded deletes with a new batched-delete helper used by content/media/member rebuild paths.
src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs Adds a new configuration option for delete batch size with documentation and default value.

@sonarqubecloud

sonarqubecloud Bot commented Jul 9, 2026

Copy link
Copy Markdown

@Zeegaan Zeegaan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me 🚀

@AndyButland
AndyButland merged commit 90b442f into v17/dev Jul 13, 2026
30 of 31 checks passed
@AndyButland
AndyButland deleted the v17/improvement/chunk-delete-content-cache-for-updated-content-type branch July 13, 2026 04:56
@AndyButland

Copy link
Copy Markdown
Contributor Author

Docs PR is here: umbraco/UmbracoDocs#8235

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

Labels

area/backend category/performance Fixes for performance (generally cpu or memory) fixes release/17.6.0 release/18.1.0 status/needs-docs Requires new or updated documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants