Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

APIv4 - RequestSpec caching #29066

Merged
merged 13 commits into from
Jan 24, 2024
Merged

Conversation

colemanw
Copy link
Member

@colemanw colemanw commented Jan 22, 2024

Overview

Implements in-memory caching for APIv4 getFields. This significantly speeds up SearchKit, especially when the display includes links or in-place editable fields.

Before

Every call to getFields would rebuild FieldSpec objects from scratch and call every SpecProvider for modifications, then transform those objects into an array & filter it down to maybe just the one field you wanted in the first place.

After

All of that is cached.

Technical Details

Caching happens as late as I could manage it... after the FieldSpec objects have been modified by every SpecProvider and converted to their final array form, but before pseudoconstant options are loaded (there's already a cache for those somewhere, I think).

Getting a cache key for this was tricky since getFields accepts an array of $values which could contain possibly anything. I solved this by tracking which values are actually used by the specProviders. Every time a specProvider calls RequestSpec->getValue() we keep track of that and it gets added to the cacheKey.
Probably 90% of entities don't have any fancy specProviders so our tracker knows that all $values can be safely ignored & this keeps caching for them very simple. For more complex entities, there will be considerably more copies of the fields in memory; hopefully that won't be a problem.

Civi::cache('metadata')->clear() will trigger a flush of this in-memory cache as well, although that dispatcher was broken so I had to fix it via #29072.

Breaking Changes

The changes in this PR are mostly internal. While a couple of them could hypothetically affect extensions that have very deep integrations with APIv4, a universe search shows that to be mostly hypothetical.

  1. The params passed to FieldSpec::setOptionsCallback function now gives $field as an array rather than an object. This is a pretty obscure feature outside of core. I was only able to find one such example in universe (and it was written by me). I submitted a PR to update that callback.
  2. SpecProviderInterface::applies no longer receives $values as a 3rd param. This was only added recently and never documented so I can't find anything in universe outside of core, nor would I expect to.

Copy link

civibot bot commented Jan 22, 2024

🤖 Thank you for contributing to CiviCRM! ❤️ We will need to test and review this PR. 👷

Introduction for new contributors...
  • If this is your first PR, an admin will greenlight automated testing with the command ok to test or add to whitelist.
  • A series of tests will automatically run. You can see the results at the bottom of this page (if there are any problems, it will include a link to see what went wrong).
  • A demo site will be built where anyone can try out a version of CiviCRM that includes your changes.
  • If this process needs to be repeated, an admin will issue the command test this please to rerun tests and build a new demo site.
  • Before this PR can be merged, it needs to be reviewed. Please keep in mind that reviewers are volunteers, and their response time can vary from a few hours to a few weeks depending on their availability and their knowledge of this particular part of CiviCRM.
  • A great way to speed up this process is to "trade reviews" with someone - find an open PR that you feel able to review, and leave a comment like "I'm reviewing this now, could you please review mine?" (include a link to yours). You don't have to wait for a response to get started (and you don't have to stop at one!) the more you review, the faster this process goes for everyone 😄
  • To ensure that you are credited properly in the final release notes, please add yourself to contributor-key.yml
  • For more information about contributing, see CONTRIBUTING.md.
Quick links for reviewers...

➡️ Online demo of this PR 🔗

@civibot civibot bot added the master label Jan 22, 2024
@colemanw colemanw force-pushed the requestSpec branch 3 times, most recently from 3813b7f to 08df917 Compare January 22, 2024 22:53
@colemanw colemanw marked this pull request as ready for review January 23, 2024 18:05
colemanw added a commit to colemanw/de.systopia.eck that referenced this pull request Jan 23, 2024
@eileenmcnaughton
Copy link
Contributor

eileenmcnaughton commented Jan 23, 2024

@colemanw I feel good about test cover here but can you explain the modifyApiSpec() change a bit more - is this one not cached differently to the modify in the ImportSpecProvider?

& secondary to that - do we need a docs update ?

@colemanw
Copy link
Member Author

colemanw commented Jan 23, 2024

@eileenmcnaughton this PR basically puts a cache in front of every SpecProvider, so the static caching in ImportSpecProvider::getJobType is now redundant & should be removed to save memory. update: done

I'll expand the PR description a bit. Docs update might be in order too, I'll check...

@colemanw colemanw force-pushed the requestSpec branch 2 times, most recently from 00bcb3e to f0ddcad Compare January 23, 2024 19:58
Optimization is not very helpful now that custom groups and fields are cached.
Getting rid of it opens the door to a better optimization for caching the entire spec.
This was only being used by the ActionMapping classes and we want to avoid it so
values are always fetched with a getter rather than from a massive array.
This is an improved version of getFieldValue() that uses late-static binding and returns formatted values.
This adds in-memory caching to speed up cases where getFields is called hundreds of times per page.

Because the array of $values can be literally anything, we cut it down to just the relevant
ones by tracking which values are actually used by the specProviders.
APIv4 now caches the output of all SpecProviders, so there is no need for them to use internal caching.
@colemanw
Copy link
Member Author

@eileenmcnaughton I've rebased this now that #29072 is merged. This is ready as far as I'm concerned.

@eileenmcnaughton
Copy link
Contributor

timing 5.69

image

master with other recent patches, but not this one

image

master with this patch too

image

So this shaved a further 15 seconds-ish off the time the page took to load. That's a definite win - although even 18.72 seconds still feels like there is quite a bit of room for improvement. I may yet do another cache grind to see but this seems like a clear improvement and our cumulative improvement is dramatic

@eileenmcnaughton eileenmcnaughton merged commit 3d03103 into civicrm:master Jan 24, 2024
3 checks passed
@eileenmcnaughton eileenmcnaughton deleted the requestSpec branch January 24, 2024 03:36
@eileenmcnaughton
Copy link
Contributor

@colemanw I may have been too hasty here - the fields seem to no longer be editable! Still probably easier to address as a follow up since this moves a lot of stuff

@colemanw
Copy link
Member Author

@eileenmcnaughton hmm, I thought we had tests for that. Quick test shows the fields as editable for me. Can you give a example search?

@eileenmcnaughton
Copy link
Contributor

OK - update - the editable was broken without this but works in 5.68 - but is broken in 5.69. Importantly the import search kits rely on a non-standard primary key which could be what broke?

@eileenmcnaughton
Copy link
Contributor

@colemanw hang on - it might be that they are not editble because the rows imported - let me try changing the status to error & see

@eileenmcnaughton
Copy link
Contributor

Yep - that's it! All good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants