Skip to content

[WIP] perf(scd): skip redundant per-file deploy for inherited locale/theme variants via bulk copy#40619

Draft
SamJUK wants to merge 2 commits intomagento:2.4-developfrom
SamJUK:perf/scd-copy-from-deployed-parent
Draft

[WIP] perf(scd): skip redundant per-file deploy for inherited locale/theme variants via bulk copy#40619
SamJUK wants to merge 2 commits intomagento:2.4-developfrom
SamJUK:perf/scd-copy-from-deployed-parent

Conversation

@SamJUK
Copy link
Copy Markdown
Contributor

@SamJUK SamJUK commented Mar 22, 2026

Description (*)

When deploying locale/theme variant packages, the vast majority of files are inherited unchanged from a parent package. Currently both the quick and standard strategies re-run the full LESS pipeline for every file in every variant, even though LESS output does not vary by locale and the files are identical to the parent.

This PR replaces per-file deploy operations with a single recursive directory copy for packages whose files are predominantly inherited, across both the quick and standard deploy strategies.

Performance results (5 locales: en_GB en_US fr_FR de_DE nl_NL, cold static dir, serial mode):

Store Themes Strategy Before After Improvement
Store 1 (2.4.8-p4, PHP 8.3) 4 quick 65.2s 16.4s 75% faster
Store 1 4 standard 62.8s 22.7s 64% faster
Store 2 (2.4.8-p3, PHP 8.3) 7 quick 104.8s 20.0s 81% faster
Store 2 7 standard 98.6s 30.7s 69% faster
Store 3 (2.4.8-p4, PHP 8.3) 11 quick 176.7s 19.8s 89% faster
Store 3 11 standard 169.2s 45.7s 73% faster

The compact strategy is unaffected (~2% noise) — it pre-compiles LESS once then copies cheaply, so shouldBulkCopy() correctly returns false.

Breakdown

Root cause - quick strategy (dead code):
checkIfCanCopy() contained the condition $file->getOrigPackage() === $parentPackage. origPackage is set to the deepest ancestor virtual package (e.g. blank/default), not the locale parent assigned by the strategy. This condition virtually never passed, so every file fell through to deployFile() regardless of whether a copy was valid.

Root cause - standard strategy:
The standard strategy had no parent-package concept at all. Every locale of a theme was deployed fully independently, re-running LESS compilation for each.

Parallel safety:
copyTree() requires the parent locale directory to be fully deployed before a variant worker begins copying. Without explicit queue dependencies, -j N workers can fork a variant before the base locale finishes. Both strategies now pass the parent as an explicit queue dependency.

Deadlock fix (quick strategy):
QuickDeploy::preparePackages() assigns setParent() for both locale parents and theme-hierarchy parents (e.g. blank as parent of luma). When --no-parent or --theme filters exclude theme-hierarchy parents from the package pool, passing them as queue dependencies causes Queue::process() to wait indefinitely. The fix pre-computes $queuedPaths and only uses parents present in that set as dependencies.

Manual testing scenarios (*)

Basic deploy (all strategies):

  1. Clear pub/static/: rm -rf pub/static/frontend pub/static/adminhtml pub/static/base
  2. Run: php bin/magento setup:static-content:deploy -f en_GB en_US fr_FR -s quick
  3. Verify deploy completes without errors and file count matches a stock deploy
  4. Repeat with -s standard

Parallel deploy:

  1. Clear pub/static/
  2. Run: php bin/magento setup:static-content:deploy -f en_GB en_US fr_FR -s quick -j 4
  3. Verify no race conditions, file counts match serial deploy
  4. Repeat with -s standard -j 4

Filter options (deadlock regression):

  1. Run: php bin/magento setup:static-content:deploy -f en_GB en_US --theme Magento/luma --no-parent -s quick
  2. Verify deploy completes (does not hang waiting for Magento/blank which is absent from queue)

Output correctness:

  1. Deploy with stock code, capture: find pub/static -type f | sort > /tmp/stock.txt
  2. Deploy with this PR, capture: find pub/static -type f | sort > /tmp/patched.txt
  3. Verify: diff -r /tmp/stock.txt /tmp/patched.txt produces no output

Contribution checklist (*)

  • Pull request has a meaningful description of its purpose
  • All commits are accompanied by meaningful commit messages
  • All new or changed code is covered with unit/integration tests (if applicable)
  • README.md files for modified modules are updated and included in the pull request if any README.md predefined sections require an update
  • All automated tests passed successfully (all builds are green)

SamJUK and others added 2 commits March 22, 2026 21:58
…mpilation

When deploying a locale/theme variant whose files are all (or mostly) inherited
from a parent package, copying the already-deployed parent directory is orders of
magnitude faster than re-running the LESS pipeline for each file individually.

Changes:
- DeployPackage::deployEmulated() checks shouldBulkCopy() before the per-file loop;
  if the threshold is met it calls DeployStaticFile::copyTree() to copy the entire
  parent tree in one pass, then only processes non-inherited files individually.
- shouldBulkCopy(): fires when >= BULK_COPY_THRESHOLD (0.5) of files are inherited
  from the parent package. The 0.5 threshold ensures we only skip the per-file loop
  when the bulk of the work would be redundant.
- isInheritedFile(): O(1) hash lookup - isset($parentPackage->getFiles()[$fileId])
- copyTree(): uses pubStaticDir->copyFile() / pubStaticDir->create() rather than
  native copy()/mkdir(). Benchmarking shows the abstraction is ~2.5x faster per file
  (0.038ms vs 0.1ms) due to @copy() suppression in the File driver.
- Fixed checkIfCanCopy(): was comparing origPackage (deepest ancestor virtual package)
  to the direct locale parent; virtually never matched. Now compares getPackage().
- QuickDeploy: pre-compute $queuedPaths and only pass a parent as a queue dependency
  when that parent is actually in the queue. Prevents deadlocks when --no-parent or
  --theme filters exclude theme-hierarchy parents from the deploy set.
- Extracted magic number 0.5 to DeployPackage::BULK_COPY_THRESHOLD constant.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Applies the same bulk-copy optimisation to the standard deploy strategy that
commit 4da59cb introduced for the quick strategy.

Changes:
- StandardDeploy::deploy() identifies base locale packages per theme and assigns
  them as parents for variant locale packages in the same theme.
- Locale packages are sorted so base packages (no parent) are queued first,
  ensuring the parent directory exists before any variant tries to copy it.
  Uses usort() in place of the previous double array_filter + array_merge.
- Each variant package is queued with its parent as a dependency, so parallel
  (-j N) workers cannot start copying before the parent deploy completes.

Benchmark results (3 stores, quick strategy shown for comparison):
  Store 1  (4 themes,  2.4.8-p4): standard 64% faster, quick 75% faster
  Store 2  (7 themes,  2.4.8-p3): standard 69% faster, quick 81% faster
  Store 3  (11 themes, 2.4.8-p4): standard 73% faster, quick 89% faster

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@m2-assistant
Copy link
Copy Markdown

m2-assistant bot commented Mar 22, 2026

Hi @SamJUK. Thank you for your contribution!
Here are some useful tips on how you can test your changes using Magento test environment.
❗ Automated tests can be triggered manually with an appropriate comment:

  • @magento run all tests - run or re-run all required tests against the PR changes
  • @magento run <test-build(s)> - run or re-run specific test build(s)
    For example: @magento run Unit Tests

<test-build(s)> is a comma-separated list of build names.

Allowed build names are:
  1. Database Compare
  2. Functional Tests CE
  3. Functional Tests EE
  4. Functional Tests B2B
  5. Integration Tests
  6. Magento Health Index
  7. Sample Data Tests CE
  8. Sample Data Tests EE
  9. Sample Data Tests B2B
  10. Static Tests
  11. Unit Tests
  12. WebAPI Tests
  13. Semantic Version Checker

You can find more information about the builds here
ℹ️ Run only required test builds during development. Run all test builds before sending your pull request for review.


For more details, review the Code Contributions documentation.
Join Magento Community Engineering Slack and ask your questions in #github channel.

@ct-prd-pr-scan
Copy link
Copy Markdown

The security team has been informed about this pull request due to the presence of risky security keywords. For security vulnerability reports, please visit Adobe's vulnerability disclosure program on HackerOne or email psirt@adobe.com.

@SamJUK SamJUK changed the title [WIP] perf(scd): bulk copy parent package output to skip per-file LESS recompilation [WIP] perf(scd): skip redundant per-file deploy for inherited locale/theme variants via bulk copy Mar 22, 2026
@ct-prd-pr-scan
Copy link
Copy Markdown

The security team has been informed about this pull request due to the presence of risky security keywords. For security vulnerability reports, please visit Adobe's vulnerability disclosure program on HackerOne or email psirt@adobe.com.

1 similar comment
@ct-prd-pr-scan
Copy link
Copy Markdown

The security team has been informed about this pull request due to the presence of risky security keywords. For security vulnerability reports, please visit Adobe's vulnerability disclosure program on HackerOne or email psirt@adobe.com.

@SamJUK
Copy link
Copy Markdown
Contributor Author

SamJUK commented Mar 22, 2026

@magento run all tests

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant