Entry and asset not cached?

Hi,

I juste implemented caching with client.

The problem is that the only keys i see when connecting to redis are keys related to content-type and environment only…

I dont uderstand why linked entry and asset are not cached…

The functions getEntry(id) and getAsset(id) are called, but they are never cached.

Can some one explain me why single emtey and asset are not cached?

Thank you

Explanation here:

But why in the getEntry and getAsset function a cacheKey is build for the entry or asset in question and passed to the requestAndBuild function?

That’s for internal use in the SDK.

Basically, its main purpose is that if you call getEntries and there are entries/assets loaded using include, those entries/assets will be built and kept in a registry. When in an entry you access a field which is a link to an entry/asset, the field resolver will ask the client to fetch that entry/asset. The client, then, will generate a key for checking whether that entry/asset has already been loaded, and look into the registry for that key.

Content types and locales change rarely, and for this reason, they are good candidates for being statically cached. Entries and assets, on the other hand, are supposed to be easily changed, and that’s why a decision for not caching them was made.

If, despite so, you wished to cache entries and assets as well, you can easily do so yourself. The best way for doing so would be do extending the default cache warmer, and then fetch and save in the cache pool entries and assets. Bear in mind that both of them can be represented as a single locale and as with all locales, so with N locales, you would have to run the same preloading N+1 times (one for each locale, and one with the special * locale).

Take a look at the default cache warmer class for some inspiration on the matter.

Davide

1 Like

Okay i understand!

The way i saw it is that i’m going to add a webhook that will refresh the cache when an Entry or an Asset is published.

Thanks you for the tips!

Hey,

So i try using a extended cache warmer and i think it is working great.

The problem i see is that every time i load a new page in my web app, the warmUp function of the cache warmer is called…

So at the end, it doesn’t change anything…

Hey Olivier,

I think that depends entirely on your application. Ideally the cache warmer should be run on deployment, and not on the application’s normal execution, otherwise of course, just like with your app, cache will be warmed up on every request.

Cheers,

Davide

Hi Davide,

Based upon your advice above I am currently trying to implement a php-based caching solution for Assets & Entries for a project that has two locales. Currently the project has only two Entries that I am using for testing.

I have managed to have some success extending the default cache warmer to fetch Entries for each locale (including the special “*” locale), but unfortunately have run into a problem: individual Entry cache records for each locale aren’t being created.

When warming the cache, rather than generating six cached Entry records (e.g. two for each locale and two for “*”), the cache warmer is only generating two. The two records being created are for the last locale in the list being looped over.

The problem I believe is caused by my use of the InstanceRepository’s generateCacheKey method. The cache key being generated for each locale is the same, therefore the cache files are being overwritten on each loop through the locales. Unless I’ve misunderstood something, do I need to somehow generate a cache key for each Entry that is unique to each locale? I’m just unsure how to do this…

Below is a snippet of the code I’m using. Could you please provide me with some advice about how to solve this problem?

$localeCodes = ["*"];
$locales = $environment->getLocales();
foreach ($locales as $locale) {
    array_push($localeCodes, $locale->getCode());
}

foreach ($localeCodes as $localeCode) {
    $localeQuery = $query;
    $localeQuery->setLocale($localeCode);
    $entries = $this->client->getEntries($localeQuery);

    foreach ($entries as $entry) {
        $item = $this->cacheItemPool->getItem(
            $instanceRepository->generateCacheKey($api, 'Entry', $entry->getId())
        );
        $item->set(\json_encode($entry));
        $this->cacheItemPool->saveDeferred($item);
    }
}

Also, once I get this working, is there anything I need to configure elsewhere to ensure the Delivery Client will fetch Entry & Asset records from my cache?

Hey there,

the InstanceRepository isn’t aware of locales, so the handling of that part must happen outside of it. If you take a look at the client class, you will see that the key is generated by concatenating the resource ID (entry/asset) and the locale, separated by a hyphen. Your code is almost there, except for how the ID is passed to the key generator:

$localeCodes = ["*"];
$locales = $environment->getLocales();
foreach ($locales as $locale) {
    array_push($localeCodes, $locale->getCode());
}

foreach ($localeCodes as $localeCode) {
    $localeQuery = $query;
    $localeQuery->setLocale($localeCode);
    $entries = $this->client->getEntries($localeQuery);

    foreach ($entries as $entry) {
        // NOTE THE DIFFERENT ID
        $entryCacheId = $entry->getId().'-'.$localeCode;
        $item = $this->cacheItemPool->getItem(
            $instanceRepository->generateCacheKey($api, 'Entry', $entryCacheId)
        );
        $item->set(\json_encode($entry));
        $this->cacheItemPool->saveDeferred($item);
    }
}

$this->cacheItemPool->commit();

It’s a bit of manual gluing, but it gets the job done. Given that multiple users are starting to ask for this, I might consider baking this into the SDK in a future version!

Cheers,

Davide

Hi Davide, thank you very much for your reply!

Unfortunately I’m now running into trouble with the filesystem adapter caching mechanism (as per the docs - https://www.contentful.com/developers/docs/php/tutorials/caching-in-the-php-cda-sdk/), in particular, the filenames now being generated. E.g:

**Fatal error** : Uncaught Cache\Adapter\Common\Exception\InvalidArgumentException: Invalid key "contentful.DELIVERY.{SPACE_ID}.master.Entry.{ENTRY_ID}-*". Valid filenames must match [a-zA-Z0-9_\.! ]. in /path/to/app/vendor/cache/filesystem-adapter/FilesystemCachePool.php...

and also,

**Fatal error** : Uncaught Cache\Adapter\Common\Exception\InvalidArgumentException: Invalid key "contentful.DELIVERY.{SPACE_ID}.master.Entry.{ENTRY_ID}-en-US". Valid filenames must match [a-zA-Z0-9_\.! ]. in /path/to/app/vendor/cache/filesystem-adapter/FilesystemCachePool.php...

(I’ve swapped out my space id & entry id from the errors above).

Will I need to look at a different cache adapter, or is there a way around this you’re aware of?

Also, out of interest, does the getEntries method in the client access the cached data as well?

Thanks again,
Tim.

According to PSR-6, adapters must guarantee support for a set of characters which doesn’t include * and -. Some implementations are more strict than others and only implement those characters. I think symfony/cache supports a bigger set, perhaps you should try that. By chance, the libraries I tested did not have this limitation, so I didn’t notice the issue until now.

However, this is a real issue, the keys should be supported on all PSR-6 implementations. I’ll have to investigate further, but thanks for bringing this up.

About getEntries: no, it does not access cached data (same with getAssets), for the simple reason that I can’t really foresee which entries will be returned by the API. I have that guarantee only by using the getEntry method, which uses the entry ID, therefore allowing me to check for the proper key.

Cheers,

Davide

Thanks Davide,

Hmm… My project actually doesn’t use getEntry at all, I’m primarily using getEntries and doing queries to fetch certain Entries from Contentful. Am I correct in thinking that caching Entries & Assets is therefore not worthwhile for my project?

In this case, is there anything I can do to limit the number of requests being made to Contentful’s APIs?

Should I perhaps look at using the Sync API instead?

Thanks,
Tim.

If you’re relying a lot on getEntries then no, caching them locally probably won’t make a difference for you.

To reduce the number of requests, I suggest playing with the include operator, so you can try to fetch all necessary entries at once. This is what I do in the PHP example app, take a look at that for inspiration.

I don’t really know what you’re doing, but the Sync API is a bit too specific for most use cases, and also adds a bit of overhead to most apps, in terms of how entries are handled. For this reason as a rule of thumb I suggest trying to use the regular API first, and only resort to the Sync API if needs be.

Cheers,

Davide