📦 Added @cache and @traceExecutionTime directives to GraphQL by PoP

— 6 minute read

I seem to be in a custom-directive-creation spree for GraphQL by PoP: 2 days ago I added directive @removeIfNull (as to be able to distinguish between null and omission values in the response), and today I created directives @cache and traceExecutionTime. Let's check them out.

@cache directive permalink

The @cache directive enables to cache the result of a heavy-to-compute operation. The first time the field is resolved, the @cache directive will save the value in disk or memory (Redis, Memcached), either with an expiry date or not, and from then on whenever querying this field the cached value will be retrieved and the operation will not be performed.

Please notice: the @cache directive is different than the @cacheControl directive, which sends the Cache-Control header with a max-age to have the browser/CDN/webserver cache the response through HTTP caching.

With these two directives, the caching solution in GraphQL by PoP is now very robust: HTTP caching + Field-computation caching!

To find out more: the @cacheControl directive is demonstrated in this blog post (it shows examples using the PoP Query Language, but it works the same way for GraphQL when passing the query through GET, or when using persisted queries).

For instance, this query executes the @translate directive, which does a single connection to the Google Translate API and performs the translation of the posts' titles:

Assuming this is an expensive call, we would like to cache the field's value after the first response. This query achieves that through the @cache directive, passing a time expiration of 10 seconds (not passing this value, the cache does not expire). To visualize it, run this query and then, within 10 seconds, run it again:

Please notice that directives in GraphQL are applied in order, so the following queries are different:

  • title @translate @cache
  • title @cache @translate

In the 1st case, it executes @translate and then @cache, so the translation is being cached; in the 2 case, it executes @cache and then @translate, so the caching only stores the value of the title field and not its translation.

How do we know that the 2nd time the response came from the cache? If you notice, the endpoint is passed a parameter actions[]=show-logs which prints logs under the extensions top-level entry. The first time we execute the query, we obtain this response:

1st execution of query with @cache directive
1st execution of query with @cache directive

The 2nd time, executing the same query within 10 seconds, we obtain this response, in which a log informs that the value is coming from the cache:

2nd execution of query with @cache directive
2nd execution of query with @cache directive

Please notice how the log indicates which are the items that have been cached: in this case, the same 3 items being filtered. If we increase the limit to 6, and run again within 10 seconds, the already-cached 3 items will be retrieved from the cache, and the other 3, which have not been cached yet, will be retrieved fresh through Google Translate:

3rd execution of query with @cache directive
3rd execution of query with @cache directive

If we run it again, now all 6 items will be cached:

4th execution of query with @cache directive
4th execution of query with @cache directive

Needless to say, the query retrieving cached fields feels faster. But how much faster? Can we quantify it?

@traceExecutionTime directive permalink

Yes, we can quantify it, because I also implemented the perfect companion: the @traceExecutionTime directive tracks how much time it takes to resolve the field (including all the involved directives), and adds the result to the log. Let's check it out using the same earlier example.

Let's run this query with @traceExecutionTime first, and within 10 seconds again:

For the first execution, resolving the field containing the @translate directive took 80.111 milliseconds to execute (from connecting to the Google Translate API):

1st execution of query with @cache and @traceExecutionTime directives
1st execution of query with @cache and @traceExecutionTime directives

For the second execution, the results from translating the titles were all cached, so the connection to Google Translate was avoided and the field was resolved in less than 1 millisecond:

2nd execution of query with @cache and @traceExecutionTime directives
2nd execution of query with @cache and @traceExecutionTime directives

That is 80 times faster! How cool is that!? 👏👏👏

So, can I install GraphQL by PoP? How? permalink

Yes, you can install it following these instructions, but the documentation right now is all over the place and not easy to follow (there is a bit in this blog, some bits in this GitHub repo and a few others, some other stuff in a few Smashing Magazine and LogRocket blog articles). It's certainly not ideal.

But don't despair! I'm working on a new documentation site, and then it will be perfect! It should be ready in a few weeks time... I will post updates in this blog and on my Twitter account.

Hasta la vista 👋