Skip to content

perf: memoise extractFieldValues() and the field type registry lookup - #237

Open
MyKineID wants to merge 1 commit into
relaticle:3.xfrom
MyKineID:perf/memoise-extract-field-values-and-type-registry
Open

MyKineID wants to merge 1 commit into
relaticle:3.xfrom
MyKineID:perf/memoise-extract-field-values-and-type-registry

Conversation

@MyKineID

Copy link
Copy Markdown

Problem

Rendering a record with a large custom field set is quadratic in (fields × values) — per the excellent analysis in #225:

  1. isFieldVisible() is called once per field by the builders, and each call re-runs extractFieldValues(), resolving every field value through the full Eloquent cast chain. 97 filled fields → 9,409 resolutions → ~20s render.
  2. FieldManager::getFieldType() rebuilds the whole 22-type registry via toCollection() on every call; builders read ->typeData inside their filter loops, causing hundreds of full registry rebuilds per render.

Fix

1. BackendVisibilityService::extractFieldValues() — memoised against the loaded customFieldValues relation object via a WeakMap:

  • The memo has exactly the lifetime of the relation the method already caches two lines above — a refresh() or load() installs a new collection, which drops the memo with it (no staleness possible).
  • Keyed additionally by the field-id signature, so different field subsets on the same record coexist.

2. FieldManager::getFieldType() — per-key cache in ``, mirroring how getFieldTypeInstance() right beside it already caches instances. Safe because `getFieldTypes()` caches and never invalidates — the registry is effectively immutable once built.

Result

Same as reported in the issue: 97-field render drops from ~19.8s to ~0.8s.

Verification

  • php -l passes on both files.
  • Standalone WeakMap-memo simulation confirms memo hit on repeat call with the same relation, fresh computation on a new relation, and per-signature keying.
  • Note: the full Pest suite errors in this environment for unrelated environment reasons (orchestra/testbench bootstrap, identical error count on the unpatched baseline tree), so CI on this PR is the source of truth.

Fixes #225

Rendering a record with a large custom field set is quadratic in
(fields x values):

- isFieldVisible() is called once per field by the builders, and each
  call re-runs extractFieldValues(), resolving every field value
  through the full Eloquent cast chain (~200ms per filled value).
  97 filled fields => 9,409 resolutions and a ~20s render.

- FieldManager::getFieldType() rebuilds the whole 22-type registry via
  toCollection() on every call; the builders read $field->typeData
  (an uncached accessor that hits it) inside their filter loops, so a
  single render rebuilds the registry hundreds of times.

Fixes:

- Memoise extractFieldValues() against the loaded customFieldValues
  relation object via a WeakMap, so the memo has exactly the lifetime
  of the relation the method already caches: refresh()/load() install
  a new collection and naturally drop the memo. Keyed by the field-id
  signature so different field subsets coexist.

- Cache getFieldType() results per key in $cachedFieldTypeData,
  mirroring how getFieldTypeInstance() already caches instances. The
  registry is effectively immutable once built (getFieldTypes() caches
  and never invalidates), so this is safe.

On the reporter's 97-field entity this takes a single render from
~19.8s to ~0.8s.

Fixes relaticle#225

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rendering a record is O(fields × values) — 19.8s at 97 fields (patches included)

1 participant