Performance: prime sibling field key lookups (hook-safe)#472
Draft
cbravobernal wants to merge 4 commits into
Draft
Performance: prime sibling field key lookups (hook-safe)#472cbravobernal wants to merge 4 commits into
cbravobernal wants to merge 4 commits into
Conversation
Resolving a field key runs one query per field. When a field belongs to a field group, fetch all of the group's published fields in a single query and cache their key lookups, so sibling lookups avoid further queries: get_field() over a 50-field template drops from ~101 queries / ~17ms to ~5 / ~2.8ms on cache-less hosts. The priming query runs with suppress_filters => false, matching the per-key lookup in acf_get_field_post() that it primes, so per-language / context query filters (WPML, Polylang, ACFML) still run and resolve the same posts. Opt-out via the acf/prime_field_group_fields filter. Split from the broader performance PR for focused review because it touches the field-value load path and interacts with multilingual plugins. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Prime only on a cache miss so warm persistent-cache requests stay query-free instead of paying an extra query per field group. - Resolve sibling keys with a global post_name__in query (same ordering and filter semantics as the per-key lookup) so keys duplicated across field groups resolve to the same post as before. - Source sibling keys from acf_get_raw_fields(), reusing its cached bulk fetch instead of running a near-duplicate group-scoped query. - Memoize the parent before any bails so repeater/group sub-field lookups don't re-run the filter and parent checks per sibling. - Cap priming at 100 keys so very large groups don't hydrate unbounded post sets; remaining keys fall back to per-key lookups. - Use wp_cache_add() instead of wp_cache_get() + wp_cache_set(). - Rename to _scf_prime_sibling_field_posts() and scf/prime_field_group_fields per the scf_ naming convention for new functions and hooks. - Correct @SInCE to 6.9.2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cbravobernal
force-pushed
the
perf/sibling-field-priming
branch
from
July 2, 2026 14:15
13e8ad6 to
61bf808
Compare
Field key/name lookups compare against post_name/post_excerpt, which is case-insensitive under WordPress's default collations, and stored slugs are always sanitized to lowercase. The lookup cache keyed entries by the requested string, so a mixed-case request never hit entries primed under the stored slug, and acf_flush_field_cache() (which keys by the stored identity) could not invalidate them. Lowercasing the cache key at the lookup, priming, and flush sites makes all three agree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Flush the field lookup cache in acf_trash_field() (matching untrash/ update/delete) so trashed fields stop resolving from persistent caches. Pre-existing gap that priming would have widened. - Defer priming to the second cache miss per parent so single-field requests keep paying only their own query. - Prime only on key-type lookups; name misses paid the priming cost without ever benefiting since only key entries are written. - Batch the sibling cache writes with wp_cache_add_multiple(). - Skip trashed siblings when collecting keys so they don't consume the priming cap. - Batch-fetch evicted posts in acf_get_raw_fields() via _prime_post_caches() instead of one query per field. - Extract _scf_field_post_cache_key() and _scf_field_post_query_args() so the cache-key format and query semantics shared by lookup, priming, and flushing live in one place. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Split out from #466 for focused review, because it's the one performance change that touches the field-value load path and interacts with multilingual plugins.
What it does
Resolving a field key runs one query per field. When a field key lookup misses the cache and the field belongs to a field group, this resolves all of the group's field keys in bulk and caches them, so subsequent sibling lookups avoid further queries:
get_field()over a 50-field template drops from ~101 SQL queries / ~17ms to a handful / ~3ms on cache-less hosts. Opt-out via thescf/prime_field_group_fieldsfilter.Priming works in two steps:
acf_get_raw_fields(), reusing (and warming) the same cached bulk fetch used when the group is rendered (which now also batch-fetches evicted posts via_prime_post_caches()instead of one query each).post_name__inquery with the same semantics as the per-key lookup it replaces — same ordering (menu_order title), defaultpublishstatus, not scoped to the group, andsuppress_filters => false(both queries build on one shared args helper). Results are stored with a single batchedwp_cache_add_multiple(), first (correctly ordered) post per key wins.Priming only runs on the second key-lookup cache miss per field group in a request — warm persistent-cache requests stay query-free, single-field requests keep paying only their own query, and name-type lookups (which priming cannot serve) never pay for it. It is capped at 100 keys per group; larger groups fall back to per-key lookups for the remainder.
Also fixes a pre-existing gap surfaced in review:
acf_trash_field()now flushes the field lookup cache (matching untrash/update/delete), so trashed fields stop resolving from persistent caches.Why it needs its own review
The priming query runs with
suppress_filters => false, matching the per-key lookup inacf_get_field_post()that it primes — so per-language/context query filters (WPML, Polylang, ACFML) still run and resolve the same posts they would have for the individual lookups. No hook is bypassed. Because the resolving query is global rather than group-scoped, a key that exists in more than one field group resolves to the same post the per-key query would have selected.The cache it writes (
acf_get_field_post:key:<key>→ post ID, groupsecure-custom-fields) is byte-for-byte what the reader expects — lookup, priming, andacf_flush_field_cache()all lowercase the key to match the stored slug and the case-insensitive SQL comparison, so mixed-case requests hit primed entries and flushes stay coherent — built per sibling withacf_cache_key()so language-scoped cache keys behave as they do for individual lookups, and onlypublishposts are primed (matching the per-keyget_postsdefault).Suggested manual check before merge
On a WPML/Polylang site with two languages, confirm
get_field()over a template resolves correctly in both languages (values, not just labels).Verification
composer test:php: OK (2845 tests, 21611 assertions).npm run test:unit: 951.composer test:phpstan: clean (one pre-existing unrelated error inbin/generate-field-schema.php).@since SCF 6.9.2.See #466 (superseded by this PR + the safe-bundle PR).
Use of AI Tools
Initial implementation by Claude Code (Claude Fable 5) under human direction; the hook-safe rework and split by Claude Code (Claude Opus 4.8); code-review findings (global key resolution, miss-only priming, key cap,
scf/naming) addressed by Claude Code (Claude Fable 5). All changes human-reviewed.