Cache identifiers for better performance on getIriFromItem#997
Cache identifiers for better performance on getIriFromItem#997soyuka merged 1 commit intoapi-platform:masterfrom
Conversation
|
I like the idea, but the implementation is wrong. We should decorate the |
|
👍 indeed |
| $identifiers = []; | ||
| $resourceClass = $this->getObjectClass($item); | ||
|
|
||
| $cacheKey = self::CACHE_KEY_PREFIX.md5(serialize([$resourceClass])); |
There was a problem hiding this comment.
Why not using $resourceClass directly as it's a string?
There was a problem hiding this comment.
Indeed, copy paste you know 👯♂️
I made this in a hurry just to see if my first assumption was correct! I'll refactor this and decorate the IriConverter as suggested by @teohhanhui. Will be done this week!
1fcc90f to
6babac9
Compare
|
Okay so I did this with a decorator, but because what's slow is You can check the code to understand what I mean... A solution would be to have a new service for From my benchmarks, I know that we can still win a lot by caching the identifiers getters instead of calling the Some links (note that I'm using the normalizer dumper, 8000 items collection): |
|
@soyuka Okay, since what we're caching is a private method, then decoration indeed makes no sense.
|
|
Would |
|
I think "extract" describes better what we're doing here: extracting identifiers from an item.
No, we cannot make such assumptions. Plus, it'd be a BC break. |
|
What we can do however is providing an implementation of the |
|
@dunglas You mean having a kind of IdentifierPropertyAccessor witch would end up doing something simpler than the original class (still no assumption could be made regarding |
|
@soyuka yes, that you can register for your project if you stick to a particular convention (like public properties, or getters and setters). It prevents to trigger the introspection system, so it improves performances. |
|
Oh I'm doing this already on my project for this particular class :). So, I'll just add a |
09aff4b to
63db69e
Compare
|
ping @teohhanhui @dunglas I tried to avoid breaking changes lmk what you think. |
| <argument type="service" id="api_platform.item_identifiers_extractor.cached" /> | ||
| </service> | ||
|
|
||
| <service id="api_platform.item_identifiers_extractor" class="ApiPlatform\Core\Bridge\Symfony\Routing\ItemIdentifiersExtractor"> |
There was a problem hiding this comment.
Can't those services be private?
| use Symfony\Component\PropertyAccess\PropertyAccess; | ||
| use Symfony\Component\PropertyAccess\PropertyAccessorInterface; | ||
|
|
||
| class ItemIdentifiersExtractor implements ItemIdentifiersExtractorInterface |
There was a problem hiding this comment.
Can you make it final and add some PHPDoc? Btw, why do this belong in the Symfony Bridge? It can be used in a more general context.
There was a problem hiding this comment.
Can be moved under ApiPlatform\Core\Metadata\Property namespace then?
| private $propertyAccessor; | ||
| private $decorated; | ||
|
|
||
| public function __construct(CacheItemPoolInterface $cacheItemPool, PropertyAccessorInterface $propertyAccessor = null, ItemIdentifiersExtractorInterface $decorated = null) |
There was a problem hiding this comment.
should $decorated be null ?
| use Symfony\Component\PropertyAccess\PropertyAccess; | ||
| use Symfony\Component\PropertyAccess\PropertyAccessorInterface; | ||
|
|
||
| class ItemIdentifiersExtractor implements ItemIdentifiersExtractorInterface |
|
👍 Great work @soyuka |
|
|
||
| if ($propertyMetadata->isIdentifier()) { | ||
| if (isset($identifiers[$propertyName])) { | ||
| throw new RuntimeException(sprintf('Composite identifiers not supported in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass)); |
There was a problem hiding this comment.
@teohhanhui I'm having a hard time trying to add test coverage on this one. IMO this is not a valid case, but maybe you can give me some more context/informations about the Exception? Origin commit you authored.
|
I did a great job covering this part as it wasn't tested anywhere (except through IriConverter, but not fully covered). There's still a point to discuss: the location of the I'm not sure that it should actually be under |
6a76d54 to
2d8c093
Compare
|
All green ping @api-platform/core-team |
|
@soyuka could you please update with master ? |
| @@ -0,0 +1,88 @@ | |||
| <?php | |||
|
|
|||
| declare(strict_types=1); | |||
| @@ -0,0 +1,101 @@ | |||
| <?php | |||
|
|
|||
| declare(strict_types=1); | |||
| private $propertyAccessor; | ||
| private $decorated; | ||
|
|
||
| public function __construct(CacheItemPoolInterface $cacheItemPool, PropertyAccessorInterface $propertyAccessor = null, IdentifiersExtractorInterface $decorated) |
There was a problem hiding this comment.
Maybe should you put $propertyAccessor as last parameter (because it is optional)
| $cacheItem = $this->cacheItemPool->getItem($cacheKey); | ||
| $cacheIsHit = $cacheItem->isHit(); | ||
|
|
||
| if ($cacheIsHit) { |
There was a problem hiding this comment.
Can be merged with the previous line
| public function __construct(CacheItemPoolInterface $cacheItemPool, PropertyAccessorInterface $propertyAccessor = null, IdentifiersExtractorInterface $decorated) | ||
| { | ||
| $this->cacheItemPool = $cacheItemPool; | ||
| $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); |
There was a problem hiding this comment.
$propertyAccessor ?? PropertyAccess::createPropertyAccessor();?
| interface IdentifiersExtractorInterface | ||
| { | ||
| /** | ||
| * Find identifiers from an Item (Object). |
src/Exception/CacheException.php
Outdated
| @@ -0,0 +1,19 @@ | |||
| <?php | |||
|
|
|||
| declare(strict_types=1); | |||
| @@ -0,0 +1,135 @@ | |||
| <?php | |||
|
|
|||
| declare(strict_types=1); | |||
| $relatedCacheItem = $this->cacheItemPool->getItem($relatedCacheKey); | ||
|
|
||
| if (!$relatedCacheItem->isHit()) { | ||
| throw new CacheException('No relation cache item founded, we need more cache to continue.'); |
There was a problem hiding this comment.
Can't we avoid to use exceptions? Exception handling is slow.
| @@ -0,0 +1,111 @@ | |||
| <?php | |||
|
|
|||
| declare(strict_types=1); | |||
b636643 to
56774b4
Compare
|
@dunglas this should be good now! |
src/Api/IdentifiersExtractor.php
Outdated
| { | ||
| $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; | ||
| $this->propertyMetadataFactory = $propertyMetadataFactory; | ||
| $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); |
| $this->itemDataProvider = $itemDataProvider; | ||
| $this->routeNameResolver = $routeNameResolver; | ||
| $this->router = $router; | ||
| $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); |
There was a problem hiding this comment.
Can you change this one too? ??
b9cac9f to
5548232
Compare
Cache identifiers for better performance on getIriFromItem
Add
getIriFromItemcacheThe collection here has ~8k items, no relations. Dev mode active but caching with apcu is forced.
Blackfire before:
getIriFromItemgetIdentifiersFromItemBlackfire after:
getIriFromItemgetIdentifiersFromItemWhen discussing my performance issue with @Simperfit we noticed that
getIdentifiersFromItemwas called a lot (previous method isgetIriFromItem). This is called for every item in the collection.Profiler:
Don't look at the overall time, only the time spent serializing :p.
before
after
In this PR, we cache the properties that are identifiers for the given class.