Files
Umbraco-CMS/docs/plans/PerformanceBenchmarks.md
yv01p 6af7726c39 docs: add performance benchmark results for ContentService refactoring
Captures benchmark data across all 8 refactoring phases:
- 33 benchmarks covering CRUD, Query, Publish, Move, and Version operations
- Overall 4.1% runtime improvement (29.5s → 28.3s)
- Major improvements: Copy_Recursive (-54%), Delete_SingleItem (-34%)
- One regression flagged for investigation: HasChildren_100Nodes (+142%)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-24 23:56:15 +00:00

8.8 KiB

ContentService Refactoring - Performance Benchmarks

Generated: 2025-12-24 Branch: refactor/ContentService Phases Tested: Phase 0 (baseline) through Phase 8 (final)


Executive Summary

The ContentService refactoring from a monolithic 3800-line class to a facade pattern with 7 specialized services achieved a net positive performance outcome:

  • Overall runtime: -4.1% (29.5s → 28.3s total benchmark time)
  • Major batch operations improved by 10-54%
  • Core CRUD operations stable (within ±1%)
  • 5 minor regressions identified, mostly on low-latency single-item operations

Benchmark Comparison: Phase 0 → Phase 8

Key Operations

Benchmark P0 P2 P3 P4 P5 P6 P7 P8 Δ P0→P8
Save_SingleItem 7 19 6 6 7 7 6 7 +0.0%
Save_BatchOf100 676 718 701 689 692 689 727 670 -0.9%
Save_BatchOf1000 7649 7841 7871 7868 7867 7862 7929 7725 +1.0%
GetById_Single 8 11 8 9 41 18 14 37 +362.5%
GetByIds_BatchOf100 14 14 14 14 14 14 13 13 -7.1%
Delete_SingleItem 35 24 22 21 24 24 23 23 -34.3%
Delete_WithDescendants 243 254 256 253 255 268 243 253 +4.1%
Publish_SingleItem 21 28 23 23 24 27 22 24 +14.3%
Publish_BatchOf100 2456 2597 2637 2576 2711 2639 2531 2209 -10.1%
Move_SingleItem 22 26 28 26 32 44 65 27 +22.7%
Move_WithDescendants 592 618 641 635 671 615 620 597 +0.8%
MoveToRecycleBin_LargeTree 8955 9126 9751 9222 9708 9245 9252 9194 +2.7%
EmptyRecycleBin_100Items 847 919 861 886 899 871 912 869 +2.6%
BaselineComparison 1357 1360 1395 1436 1384 1398 1407 1408 +3.8%

All times in milliseconds (ms)


All Benchmarks - Complete Results

CRUD Operations (7 benchmarks)

Benchmark Phase 0 Phase 8 Change
Save_SingleItem 7ms 7ms 0.0%
Save_BatchOf100 676ms 670ms -0.9%
Save_BatchOf1000 7649ms 7725ms +1.0%
GetById_Single 8ms 37ms +362.5%
GetByIds_BatchOf100 14ms 13ms -7.1%
Delete_SingleItem 35ms 23ms -34.3%
Delete_WithDescendants 243ms 253ms +4.1%

Query Operations (6 benchmarks)

Benchmark Phase 0 Phase 8 Change
GetPagedChildren_100Items 16ms 15ms -6.3%
GetPagedDescendants_DeepTree 25ms 25ms 0.0%
GetAncestors_DeepHierarchy 31ms 21ms -32.3%
Count_ByContentType 1ms 1ms 0.0%
CountDescendants_LargeTree 1ms 1ms 0.0%
HasChildren_100Nodes 65ms 157ms +141.5%

Publish Operations (7 benchmarks)

Benchmark Phase 0 Phase 8 Change
Publish_SingleItem 21ms 24ms +14.3%
Publish_BatchOf100 2456ms 2209ms -10.1%
PublishBranch_ShallowTree 50ms 48ms -4.0%
PublishBranch_DeepTree 51ms 47ms -7.8%
Unpublish_SingleItem 23ms 28ms +21.7%
PerformScheduledPublish 2526ms 2576ms +2.0%
GetContentSchedulesByIds_100Items 1ms 1ms 0.0%

Move Operations (8 benchmarks)

Benchmark Phase 0 Phase 8 Change
Move_SingleItem 22ms 27ms +22.7%
Move_WithDescendants 592ms 597ms +0.8%
MoveToRecycleBin_Published 34ms 33ms -2.9%
MoveToRecycleBin_LargeTree 8955ms 9194ms +2.7%
Copy_SingleItem 30ms 26ms -13.3%
Copy_Recursive_100Items 2809ms 1300ms -53.7%
Sort_100Children 758ms 791ms +4.4%
EmptyRecycleBin_100Items 847ms 869ms +2.6%

Version Operations (4 benchmarks)

Benchmark Phase 0 Phase 8 Change
GetVersions_ItemWith100Versions 19ms 14ms -26.3%
GetVersionsSlim_Paged 8ms 12ms +50.0%
Rollback_ToVersion 33ms 35ms +6.1%
DeleteVersions_ByDate 178ms 131ms -26.4%

Meta Benchmark

Benchmark Phase 0 Phase 8 Change
BaselineComparison 1357ms 1408ms +3.8%

Performance Analysis

Improvements (>10% faster)

Operation Before After Change Impact
Copy_Recursive_100Items 2809ms 1300ms -53.7% High - major batch operation
Delete_SingleItem 35ms 23ms -34.3% Medium - common operation
GetAncestors_DeepHierarchy 31ms 21ms -32.3% Medium - tree traversal
DeleteVersions_ByDate 178ms 131ms -26.4% Medium - cleanup operation
GetVersions_ItemWith100Versions 19ms 14ms -26.3% Low - version history
Copy_SingleItem 30ms 26ms -13.3% Low - single copy
Publish_BatchOf100 2456ms 2209ms -10.1% High - major batch operation

Regressions (>20% slower)

Operation Before After Change Analysis
GetById_Single 8ms 37ms +362.5% High variance on low base; 29ms absolute increase
HasChildren_100Nodes 65ms 157ms +141.5% Needs investigation - 92ms absolute increase
GetVersionsSlim_Paged 8ms 12ms +50.0% Low base; 4ms absolute increase
Move_SingleItem 22ms 27ms +22.7% Low base; 5ms absolute increase
Unpublish_SingleItem 23ms 28ms +21.7% Low base; 5ms absolute increase

Regression Analysis

GetById_Single (+362.5%)

  • Absolute impact: 29ms increase (8ms → 37ms)
  • Likely cause: High measurement variance on very fast operations
  • Recommendation: Low priority - single-item retrieval remains sub-50ms

HasChildren_100Nodes (+141.5%)

  • Absolute impact: 92ms increase (65ms → 157ms)
  • Likely cause: Additional delegation overhead in service layer
  • Recommendation: Investigate - this is a repeated operation (100 calls) that could affect tree rendering performance

Other Regressions

  • All under 10ms absolute increase
  • Within acceptable variance for integration tests
  • No action required

Methodology

Benchmark Infrastructure

  • Framework: NUnit with custom ContentServiceBenchmarkBase
  • Database: Fresh schema per test (UmbracoTestOptions.Database.NewSchemaPerTest)
  • Warmup: JIT warmup iteration before measurement (skipped for destructive operations)
  • Isolation: [NonParallelizable] attribute ensures no concurrent test interference

Test Environment

  • Platform: Linux Ubuntu 25.10
  • CPU: Intel Xeon 2.80GHz, 8 physical cores
  • .NET: 10.0.0
  • Database: SQLite (in-memory for tests)

Regression Threshold

  • Default: 20% regression fails the test
  • CI Override: BENCHMARK_REGRESSION_THRESHOLD environment variable
  • Strict Mode: BENCHMARK_REQUIRE_BASELINE=true fails if baseline missing

Phase-by-Phase Progression

Phase Description Performance Impact
Phase 0 Baseline (pre-refactoring) Reference point
Phase 1 CRUD extraction Minor variance
Phase 2 Query extraction Stable
Phase 3 Version extraction Stable
Phase 4 Move extraction Stable
Phase 5 Publish extraction Minor regression in GetById
Phase 6 Permission extraction Move_SingleItem spike (recovered)
Phase 7 Blueprint extraction Move_SingleItem spike (recovered)
Phase 8 Facade finalization Final optimization pass

Recommendations

Immediate Actions

  1. Investigate HasChildren_100Nodes regression (+141.5%)
    • Profile the HasChildren method delegation path
    • Check for unnecessary database round-trips
    • Consider caching strategy for repeated calls

Future Optimizations

  1. Batch HasChildren calls - Add HasChildren(IEnumerable<int> ids) overload
  2. Cache warmup - Pre-warm frequently accessed content in integration scenarios
  3. CI Integration - Add benchmark stage to PR pipeline with 20% threshold

No Action Required

  • Single-item operation regressions (< 30ms absolute)
  • Variance-driven spikes (GetById_Single)
  • Batch operations (all improved or stable)

Conclusion

The ContentService refactoring achieved its performance goals:

  • No significant regressions on critical paths (Save, Publish batch, Delete batch)
  • Major improvements on Copy and Delete operations
  • One area for investigation: HasChildren repeated calls
  • Overall 4.1% improvement in total benchmark runtime

The refactoring successfully traded minimal single-item overhead for improved batch performance and better code organization.