Caching

We stored our active currency and account_id values by passing the values back and forth between our requests. That’s fine for a simple form, but inappropriate for some use cases. There’s another way to preserve data between requests, even for our stateless Caliper SDK server: caching.

By default, caching is implemented using a technology we call “memcache”. Your stack should have been delivered with an instance of memcache up and running, and ready to store data.

Every Q2RequestHandler is initialized with a cache object. Values are stored by passing a key, value, and expiration time, in seconds, to self.cache.set():

self.cache.set('key', 'value', 15)

This can be accessed with self.cache.get() in another method, when the original scope no longer exists:

desired_value = self.cache.get('key')

This value will be cleared from cache and inaccessible after 15 seconds from the time it was stored.

A cached value can also be deleted by passing the key you would like to remove to self.cache.delete():

self.cache.delete('key')

Multiple keys can be deleted simultaneously:

self.cache.delete_many(['key1', 'key2', 'key3'])

We’ll leave our current solutions in place, so how else can we use this tool?

Many API services prefer that clients store and re-use the results for some period if possible to reduce stress on the server. Using our cache, we can easily accommodate this:

async def get_currency_rates(self):
    cached_quotes_dictionary = self.cache.get('quotes_dictionary')
    if not cached_quotes_dictionary:
        response = await q2_requests.get(
            self.logger,
            'https://local-dev-api.q2developer.com/projects/currencyRates',
            headers={
                'apikey': 'S6TczFkiWjDZy7FElvqKYMogzdcEcoQy'
            }
        )
        response_as_dict = response.json()
        quotes_dictionary = response_as_dict.get('quotes', {})
        self.cache.set('quotes_dictionary', quotes_dictionary, 3600)

    else:
        quotes_dictionary = cached_quotes_dictionary

    return quotes_dictionary

Before making our request, we check our cache for the quotes. If we’ve never made this request, or if it has been 3600 seconds and the quotes are possibly out of date, we make our third-party request as before and store the result. Otherwise, we simply use what we have in cache.

For more concise code, we can leverage the cache library’s decorator to cache the entire function and handle some of the logic for us.

from q2_sdk.core.cache import cache

#...

@cache(timeout=3600, key="quotes_dictionary")
async def get_currency_rates(self):
    response = await q2_requests.get(
        self.logger,
        'https://local-dev-api.q2developer.com/projects/currencyRates',
        headers={
            'apikey': 'S6TczFkiWjDZy7FElvqKYMogzdcEcoQy'
        }
    )
    response_as_dict = response.json()
    quotes_dictionary = response_as_dict.get('quotes', {})

    return quotes_dictionary

Notice when you run your server, you are provided with logs containing bits of information about your stack, including the SDK version you are running on, your HQ url, and service url. We will go into more detail about logging in the next part of the tutorial. For now, let’s run our service url in a new tab.

Note

The SDK logs also contain port information which can be used to call our service (i.e., localhost:1980)

Our web server provides a list of registered endpoints that are given to you by default, one of which is the cache endpoint. Let’s navigate to it by adding ‘/cache’ at the end of our service url:

../../_images/deleting_cache.png

Note

You may see a slightly different key on your end, either CHANGEME:quotes_dictionary or 022222222:quotes_dictionary. What’s happening here? For security purposes we scope your cache reads behind the scenes to an appropriate level per extension type. Since you are working in an HqHandler derived extension here, you are scoped to the stack level, and the stacks in the sandbox have one of those two values as their “customerKey” value. You should be able to interact with the cache normally, just know that there’s some magic behind the scenes to make your world safer.

Here you will find all of the cached keys that memcache is aware of. We can clear out our keys by hitting the ‘delete all’ button. You can also perform a lot of the same caching operations discussed on this page with the q2 cache command. An alternative to the hitting ‘delete all’ button would be to run q2 cache clear.

Don’t forget about caching! It allows a large reduction in required code for multi-step and advanced workflows.

This extension is starting to get complex: it might be a good time to implement logging for debugging and monitoring purposes. We’ll take that on in the next step.