Skip to content

Improve performance of listTemplates API - #13566

Draft
Pearl1594 wants to merge 9 commits into
4.22from
improve-listtemplates-perf
Draft

Pearl1594 wants to merge 9 commits into
4.22from
improve-listtemplates-perf

Conversation

@Pearl1594

@Pearl1594 Pearl1594 commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

Description

This PR Adds a fast-path for listTemplates Phase 1 that bypasses template_view,
behind a runtime config flag (default off).

Why

The captured incident query against template_view runs 7,000+ seconds and
drains the DB pool. View materializes ~71k rows for ~4.7k templates due to
LEFT-JOIN row multiplication, OR-join on data_center, and predicates on a
computed column. Phase 1 only needs 6 of the 13 tables.

What changed

  • BypassTemplateView ConfigKey (template.list.bypass.view, default false,
    Global, runtime-toggleable).
  • TemplateListFilter POJO + canBypass() predicate.
  • TemplateJoinDao.findDistinctTempZonePairs(filter) — hand-tuned SQL over
    vm_template + account + template_store_ref + image_store + template_zone_ref
    • data_center; OR-join replaced with COALESCE.
  • QueryManagerImpl builds the filter and dispatches to bypass when flag is
    on AND canBypass() is true. Otherwise falls through to existing path.
  • showunique pages tie-break on the same "<id>_" string order as the view path,
    so page boundaries match with the flag on or off.

Coverage

Bypass handles: id, name, keyword, hypervisor, format, type,
templatetype, isvnf, forcks, public, featured, bootable,
parenttemplateid, accountType, accountIdIN, zoneid, templateState,
removed, onlyReady (Ready, BAREMETAL/EXTERNAL format, or ISO + PERHOST),
showunique, pagination, templatefilter ∈ {self/selfexecutable
(including DOMAIN_ADMIN/RESOURCE_DOMAIN_ADMIN, scoped via domain.path LIKE),
executable, all (admin; non-admin without project resources)}.

Falls back to view path: tags, showremoved, templatefilter ∈ {featured,
community, sharedexecutable, shared, all non-admin with project
resources (e.g. projectid=-1)}.

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • Build/CI
  • Test (unit or integration test code)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

Bug Severity

  • BLOCKER
  • Critical
  • Major
  • Minor
  • Trivial

Screenshots (if appropriate):

How Has This Been Tested?

How did you try to break this feature and the system with this change?

aniishadas and others added 3 commits July 7, 2026 14:38
* added bypass logic to create template_pair from 6 tables, fallback for hard filters

* Removed batching, checking against per pair

* follow try-with-resources design

* unit tests for the bypass logic

* Fix cross-zone template lookup in listTemplates Phase 2

When temp_zone_pair has dcId=0 (cross-zone template with no
data_center), use IS NULL predicate instead of broken EQ/IN
with literal 0 which never matches NULL rows.

* Add filter for non-root domain-admin users

---------

Co-authored-by: anishadas <[email protected]>
Co-authored-by: Aaron Chung <[email protected]>
Co-authored-by: anishadas <[email protected]>
(cherry picked from commit f6c9fd1081f0e1d9386db08e86485b482bf064ab)
@codecov

codecov Bot commented Jul 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 18.35106% with 307 lines in your changes missing coverage. Please review.
✅ Project coverage is 18.00%. Comparing base (2e63c60) to head (37c52a1).

Files with missing lines Patch % Lines
...a/com/cloud/api/query/dao/TemplateJoinDaoImpl.java 8.95% 160 Missing and 23 partials ⚠️
...ain/java/com/cloud/api/query/QueryManagerImpl.java 0.00% 73 Missing ⚠️
...va/com/cloud/api/query/dao/TemplateListFilter.java 52.63% 42 Missing and 3 partials ⚠️
...oud/api/query/vo/BaseViewWithTagInformationVO.java 0.00% 3 Missing ⚠️
...in/java/com/cloud/api/query/vo/TemplateJoinVO.java 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##               4.22   #13566    +/-   ##
==========================================
  Coverage     18.00%   18.00%            
- Complexity    16219    16230    +11     
==========================================
  Files          5936     5937     +1     
  Lines        535716   536058   +342     
  Branches      65596    65670    +74     
==========================================
+ Hits          96459    96531    +72     
- Misses       428268   428511   +243     
- Partials      10989    11016    +27     
Flag Coverage Δ
uitests 4.02% <ø> (ø)
unittests 19.08% <18.35%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@weizhouapache weizhouapache added this to the 4.22.2 milestone Jul 12, 2026
@vladimirpetrov

Copy link
Copy Markdown
Contributor

@blueorangutan package

@blueorangutan

Copy link
Copy Markdown

@vladimirpetrov a [SL] Jenkins job has been kicked to build packages. It will be bundled with KVM, XenServer and VMware SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan

Copy link
Copy Markdown

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 18552

@DaanHoogland

Copy link
Copy Markdown
Contributor

thanks @Pearl1594 , do you have any performance figures? (before vs. after?)

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
2.5% Coverage on New Code (required ≥ 40%)

See analysis details on SonarQube Cloud

import com.cloud.storage.dao.StoragePoolAndAccessGroupMapDao;
import com.cloud.cluster.ManagementServerHostPeerJoinVO;

import com.cloud.template.VirtualMachineTemplate;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: these new imports break alphabetical order here, and the same classes (AccountVO, SSHKeyPairVO, SSHKeyPairDao, InstanceGroupVMMapVO, NicVO, InstanceGroupVMMapDao, NicDao) get removed from their previously-correct alphabetized spots further down in this same diff.


ConfigKey<Boolean> BypassTemplateView = new ConfigKey<>("Advanced", Boolean.class, "template.list.bypass.view",
"false",
"If true, uses an optimized query path for listing templates and ISOs, which can improve performance on " +

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: the concatenated description string has inconsistent leading/trailing space placement across the lines, worth double-checking the rendered text doesn't end up with a missing or doubled space.

return new Pair<List<TemplateJoinVO>, Integer>(objects, count);
}

// ============================================================================

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: this banner-style comment block doesn't match the rest of the codebase's usual /** javadoc */ or short // comment style.

*/
private String buildFromClause(TemplateListFilter filter) {
StringBuilder from = new StringBuilder()
.append("cloud.vm_template vt")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: hardcoding the cloud. schema prefix here is unusual for this codebase's DAOs, worth checking this holds for all deployments.

try {
TEMPLATE_JOIN_ID_FIELD = findFieldUpHierarchy(TemplateJoinVO.class, "id");
TEMPLATE_JOIN_PAIR_FIELD = findFieldUpHierarchy(TemplateJoinVO.class, "tempZonePair");
TEMPLATE_JOIN_ID_FIELD.setAccessible(true);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: reflection to populate TemplateJoinVO's private fields feels like a fairly invasive way to build this VO, a package-visible setter would be less fragile against future field renames.

@vladimirpetrov

Copy link
Copy Markdown
Contributor

@blueorangutan test

@blueorangutan

Copy link
Copy Markdown

@vladimirpetrov a [SL] Trillian-Jenkins test job (ol8 mgmt + kvm-ol8) has been kicked to run smoke tests

@blueorangutan

Copy link
Copy Markdown

[SF] Trillian Build Failed (tid-16613)

@Pearl1594

Copy link
Copy Markdown
Contributor Author

@blueorangutan test

@blueorangutan

Copy link
Copy Markdown

@Pearl1594 a [SL] Trillian-Jenkins test job (ol8 mgmt + kvm-ol8) has been kicked to run smoke tests

@blueorangutan

Copy link
Copy Markdown

[SF] Trillian Build Failed (tid-16615)

@vladimirpetrov

Copy link
Copy Markdown
Contributor

@blueorangutan package

@blueorangutan

Copy link
Copy Markdown

@vladimirpetrov a [SL] Jenkins job has been kicked to build packages. It will be bundled with KVM, XenServer and VMware SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan

Copy link
Copy Markdown

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 18696

@DaanHoogland

Copy link
Copy Markdown
Contributor

@Pearl1594 can you look at the comments/suggestions?

@DaanHoogland DaanHoogland moved this from Backlog to conflict/waiting in CloudStack Testing Sep 1, 2026

@kiranchavala kiranchavala 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.

Took the help of AI in testing
cc @Pearl1594 @nvazquez

shell scripts

whichpath-pr13566.sh
compare-pr13566.sh
setup-pr13566.sh

Test results for template.list.bypass.view

Tested on a KVM environment with the PR build: 2 zones, about 118 templates and 8 ISOs.

Test data. Owned by root admin, a domain admin, users in a child domain and a user in an unrelated domain, plus a project (owner, member and non-member). The templates include:

  • public/featured, private, cross-zone and multi-zone
  • direct-download
  • one in an error state (failed download)
  • one with tags
  • one deleted
  • one shared with another account
  • bootable and non-bootable ISOs
  • direct-download bulk templates to give paging something to work on

Method. listTemplates/listIsos calls covering these filters: all, executable, self, selfexecutable, featured, community, shared and sharedexecutable. They also cover zoneid, isready, keyword, name, hypervisor, id/ids, paging, showunique, templatetype, bootable, ispublic, projectid=<id>/-1, tags and showremoved.

Each call ran once with the flag set to false and once set to true,

I compared count plus id, name, zoneid, isready and projectid for every row.

I used the MySQL general log to check which query actually ran: AS distinct_key means the new SQL, and SELECT DISTINCT temp_zone_pair / template_view.id FROM template_view means the old view.

Routing works as intended. With the flag on, all (admin), executable, self/selfexecutable, ISOs and project-scoped calls use the new SQL. featured, community, shared, sharedexecutable, tags, showremoved and non-admin all with projectid=-1 stay on the view.

Results match in 51 of 53 cases. The two mismatches are below.


1. templatetype (system and probably isvnf / forcks) is ignored on the new path

cmk list templates templatefilter=all templatetype=SYSTEM
  flag=false -> count 2
  flag=true  -> count 118   (every template)

templateChecks() adds templateType, isVnf and forCks to the SearchCriteria, but buildTemplateListFilter() doesn't take them, and TemplateListFilter has no fields for them. The new SQL drops these filters without any error. I didn't test isvnf or forcks, but reading the code they hit the same gap. The UI uses both: isvnf for the VNF template lists and forcks for CKS node templates.

Suggested fix: add these fields to TemplateListFilter and appendCommonWhere() (vt.type = ?, vt.type = 'VNF' / != 'VNF', vt.for_cks = ?). Or, at minimum, make canBypass() return false when any of them is set.

2. showunique=true with paging returns different templates on a page

cmk list templates templatefilter=all page=2 pagesize=10 showunique=true

The count is the same, but page 2 has different templates. With the flag on, ptest-domadmin-public and ptest-user1-private appear on page 2. They don't appear there with the flag off.

The view path always sorts by sort_key, temp_zone_pair, which is the "<id>_<zone>" string. With showUnique, the new SQL sorts by sort_key, distinct_key, where distinct_key is the numeric vt.id. When sort_key ties (all templates here have 0), the two orders differ ("10_1" < "9_1" but 9 < 10), so the page boundaries move. Without showunique, both paths sort by the same string, and paging matched (I checked pages 1 and 4).

Suggested fix: always use CONCAT(vt.id, '_', IFNULL(dc.id, 0)) as the second sort column, even when the SELECT key is vt.id.

3. Possible gap: EXTERNAL format with onlyReady (from reading the code, not tested)

The view path treats ImageFormat.BAREMETAL and ImageFormat.EXTERNAL as ready:

readySc.addOr("format", SearchCriteria.Op.IN, ImageFormat.BAREMETAL, ImageFormat.EXTERNAL);

The new SQL only has vt.format = 'BAREMETAL'. With the flag on, EXTERNAL-format templates (Extensions) would likely disappear from isready=true / executable lists.

4. Description and comments are out of date: domain-admin self

The description says self/selfexecutable for DOMAIN_ADMIN/RESOURCE_DOMAIN_ADMIN falls back to the view. In practice they run on the new SQL: domainPathLike is set without requiresViewFallback, canBypass() doesn't check it, and the SQL handles domain.path LIKE ?. Results matched in my tests, so the behaviour looks correct. The PR description, the Javadoc on buildTemplateListFilter / findDistinctTempZonePairs, and the IllegalArgumentException message (which lists domainPath as unsupported) should be updated to match.


Suggested test additions. Add unit tests covering templatetype, isvnf, forcks and EXTERNAL + onlyReady on the new path, and one that pages through showunique=true and checks that the IDs on each page match the view path.

… format and view-consistent showunique paging

Add templateType, isVnf and forCks to TemplateListFilter and the bypass
WHERE clause, treat EXTERNAL like BAREMETAL for onlyReady, and tie-break
showunique pages on CONCAT(vt.id, '_') so page boundaries match the view
path. Update stale domainPath fallback comments and add unit tests.
@Pearl1594

Copy link
Copy Markdown
Contributor Author

@kiranchavala Ive addressed the comments. Thanks for the review.

@kiranchavala

Copy link
Copy Markdown
Member

@blueorangutan package

@blueorangutan

Copy link
Copy Markdown

@kiranchavala a [SL] Jenkins job has been kicked to build packages. It will be bundled with KVM, XenServer and VMware SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan

Copy link
Copy Markdown

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 19407

This branch has not been deployed

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

Projects

Status: conflict/waiting

Development

Successfully merging this pull request may close these issues.

9 participants