Skip to content

perf(dav): build system tag properties once per PROPFIND - #64298

Open
fa0311 wants to merge 3 commits into
nextcloud:masterfrom
fa0311:perf/systemtags-propfind
Open

fa0311 wants to merge 3 commits into
nextcloud:masterfrom
fa0311:perf/systemtags-propfind

Conversation

@fa0311

@fa0311 fa0311 commented Sep 14, 2026 •

Copy link
Copy Markdown
  • Resolves: #

Summary

Listing a folder with nc:system-tags requested gets slow once files carry many tags. SystemTagPlugin already preloads the tag mappings of the whole folder, but everything after that still ran per file: fetching the tag objects (one getTagsByIds() for every file that brought a new tag), sorting with naturalSortCompare(), checking visibility, and writing every <nc:system-tag> element through the Sabre writer again.

Now the plugin loads the distinct tags once, sorts them once and keeps the natural-sort rank per tag, so a file's tags are ordered by bucketing instead of usort. SystemTagList serializes a tag once and reuses the fragment via writeRaw(); the fragments live in a request-scoped SystemTagFragmentCache. Tags with the same name keep their previous relative order, and the response is byte-identical (checked with cmp on a 55 MB response with <, >, & and " in tag names).

On a folder with 10k files and ~385k tag mappings, PROPFIND with oc:fileid + nc:system-tags goes from 6.2 s / ~700 queries to 2.4 s / ~40 queries; the full Files app property set from 8.2 s to 4.1 s.

Checklist

AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@fa0311
fa0311 force-pushed the perf/systemtags-propfind branch 2 times, most recently from 630ab2d to c5079fa Compare September 14, 2026 08:58
Preload the tags of a folder's children in one query, sort them once
and reuse the serialized <nc:system-tag> element for every file instead
of rebuilding it. The response stays byte-identical.

Signed-off-by: ふぁ <yuki@yuki0311.com>
Assisted-by: ClaudeCode:claude-fable-5-1
@fa0311
fa0311 force-pushed the perf/systemtags-propfind branch from c5079fa to 56a1687 Compare September 14, 2026 09:01
@fa0311 fa0311 changed the title perf(dav): serialize system tags once per request in PROPFIND perf(dav): build system tag properties once per PROPFIND Sep 14, 2026
@fa0311
fa0311 marked this pull request as ready for review September 14, 2026 09:03
@fa0311
fa0311 requested a review from a team as a code owner September 14, 2026 09:03
@fa0311
fa0311 requested review from CarlSchwan, come-nc, icewind1991 and provokateurin and removed request for a team September 14, 2026 09:03
Comment thread apps/dav/lib/SystemTag/SystemTagPlugin.php
public function set(string $key, string $fragment): void {
$this->fragments[$key] = $fragment;
}
}

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.

We do not need a class for this, it’s just an array.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed. The plugin keeps the serialized elements in a plain array keyed by tag id, and SystemTagList only receives the fragments of its tags in list order and writes them with writeRaw().

While at it I dropped the separate XMLWriter: the fragments are now built with a writer from the server's XML service, the same way PaginatePlugin::getXmlRendererGenerator() renders entries, so there is a single serializer and the output stays byte-identical.

private array $tags,
ISystemTagManager $tagManager,
?IUser $user,
private SystemTagFragmentCache $fragments = new SystemTagFragmentCache(),

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.

How big does it get? Do we want to use a CappedMemoryCache here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Each fragment is 120–160 bytes and there is one per distinct tag seen in the request, freed with the plugin instance at the end of the request. Even 10k distinct tags in one listing would be about 2 MB, less than the ISystemTag objects the plugin already caches for the same ids.

A CappedMemoryCache (512 entries by default) would evict exactly on the large folders where this matters, so I left it uncapped.

…riter

Address review: keep the per-file tag lists sorted and visibility-filtered
from the preload, build each system-tag element once with the response
writer instead of a separate XMLWriter, and drop the cache class, the
lazy getTagsForFile() path and the per-file canUserAssignTag() calls.
Equal names are ordered by tag id.

Signed-off-by: ふぁ <yuki@yuki0311.com>
Assisted-by: ClaudeCode:claude-fable-5-1
… folders

SystemTagList takes the serialized element of each tag in the same order
as the tags, so a missing fragment cannot slip through as a warning. A
folder that was already preloaded as a child still preloads its own
children, which the nested-folder test now covers. The fragment writer
is created per preload instead of being kept on the plugin.

Signed-off-by: ふぁ <yuki@yuki0311.com>
Assisted-by: ClaudeCode:claude-fable-5-1
@fa0311

fa0311 commented Sep 15, 2026 •

Copy link
Copy Markdown
Author

Updated with two commits addressing the review.
Re-measured on the same folder (10,279 files, 385,684 tag mappings): fileid + system-tags PROPFIND 6.4 s / ~2,000 queries → 2.1 s / ~50 queries, full Files property set 8.1 s → 3.5 s, responses byte-identical (cmp on the 55 MB and 66 MB bodies).

@github-actions

Copy link
Copy Markdown
Contributor

Hello there,
Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.

We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.

Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6

Thank you for contributing to Nextcloud and we hope to hear from you soon!

(If you believe you should not receive this message, you can add yourself to the blocklist.)

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

None yet

Development

Successfully merging this pull request may close these issues.

2 participants