API caching in Zato. Part 1 - Introduction.

One of many exciting features that the upcoming Zato 3.0 release will bring is API caching - this post provides an overview of functionality that is already available if you install Zato directly from source code.

What is new

  • In-RAM caches accessible to Python-based services and external clients (including REST and other format or protocols)
  • A convenient set of practical cache commands based on real-world needs
  • GUI and cache contents search
  • HTTP channel caching
  • Integration with memcached

Built-in caches

In-RAM caches are perfect for rarely changing yet frequently read business data. For instance:

  • A customer's products, stored in a remote SQL database, will change infrequently but may be read very often
  • A user's permissions may be read from multiple databases and the process may take several seconds but once read, they will not change much and fast access is of primary importance.

Each Zato cluster has a default cache and there can be multiple independent caches for different purposes each with its own configuration.

In v3.0, there is no persistent storage for caches and synchronization between servers in a Zato cluster is performed in background. Both aspects will be extended in future releases.

Real-world commands

Built-in caches offer all the standard CRUD commands operating on cache entries but they come in several versions, each of which is meant to facilitate operations in real-world applications.

All of commands are available to Python services and external API clients through dedicated REST endpoints.

More commands will be added in the future, based on feedback from users employing caches in projects.

CommandDescription
getReturns an entry matching input key
get-by-prefixReturns all entries whose keys start with a given prefix
get-by-suffixReturns all entries whose keys end with a given suffix
get-by-regexReturns all entries whose keys match a regular expression
get-containsReturns entries with keys that contain a single input substring
get-contains-allReturns entries with keys that contain all of input substrings
get-contains-anyReturns entries with keys that contain at least one of of input substrings
get-not-containsReturns entries with keys that do not contain the input substring
setSets input value for a given key
set-by-prefixSets input value for all keys starting with a given prefix
set-by-suffixSets input value for all keys ending with a given suffix
set-by-regexSets input value for all keys matching a regular expression
set-containsSets input value for all keys that contain a single input substring
set-contains-allSets input value for keys that contain all of input substrings
set-contains-anySets input value for keys that contain at least one of input substrings
set-not-containsSets input value for keys that do not contain the input substring
deleteDeletes a given key
delete-by-prefixDeletes all keys starting with a given prefix
delete-by-suffixDeletes all keys ending with a given suffix
delete-by-regexDeletes all keys matching a regular expression
delete-containsDeletes all keys that contain a single input substring
delete-contains-allDeletes keys that contain all of input substrings
delete-contains-anyDeletes keys that contain at least one of input substrings
delete-not-containsDeletes keys that do not contain the input substring
expireSets expiration for a given key
expire-by-prefixSets expiration for all keys starting with a given prefix
expire-by-suffixSets expiration for all keys ending with a given suffix
expire-by-regexSets expiration for all keys matching a regular expression
expire-containsSets expiration for all keys that contain a single input substring
expire-contains-allSets expiration for keys that contain all of input substrings
expire-contains-anySets expiration for keys that contain at least one of input substrings
expire-not-containsSets expiration for keys that do not contain the input substring
keysReturns all keys in cache
valuesReturns all values in cache
itemsReturns all items (entries) in cache
clearClears out the cache completely

Additionally, Python code can access dict-like methods .iterkeys, .itervalues and .iteritems that return iterators over keys or values without building result lists upfront.

Cache configuration

Each cache can have its own policy that is best suited to a given purpose:

  • Cache size upon reaching of which the least commonly used entries will be evicted
  • Max item size - values that are too big will be rejected
  • Whether a given item's expiration time should be extended on each get or set operation

GUI

As with other Zato features, a GUI in the Zato Dashboard lets one easily manage cache definitions, add new or update existing entries from the browser, and look up items by keys, values or both.

Auto-caching in HTTP channels

Caching of responses, either REST or SOAP, is such a common activity that it is available under one click for all HTTP channels

When creating a new channel or updating an existing one, pick a definition of cache that should hold responses for that channel and servers will automatically cache them on first request.

On subsequent invocations, all responses from that channel will be served from cache until underlying keys expire or are deleted.

Usage from Python

The self.cache object is an entry point to all the caches from a service's perspective. For instance, to add new entries:

# -*- coding: utf-8 -*-

from __future__ import absolute_import, division, print_function, unicode_literals

# stdlib
from time import sleep

# Zato
from zato.server.service import Service

class CacheDemo(Service):
    """ A demo service that uses built-in caches.
    """
    def get_data(self):
        """ Returns data from external systems.
        """
        # Assume this takes a long time to return, e.g. several remote calls
        sleep(2)

        return 'MyData'

    def handle(self):

        # Get data from external resources
        data = self.get_data()

        # Store it in default cache
        self.cache.default.set('data', data)

        # Or, store it in an explicitly named cache
        cache = self.cache.get_cache('builtin', 'Customer products')
        cache.set('cust-123-data', data)

Both entries can be now found in web-admin:

REST API

All cache commands are available through REST API calls. Here, set and get are used for illustrative purposes.

Note how input can be sent in both body and query string and that the last get command obtains metadata in addition to actual business value.

$ curl -XPOST localhost:11223/zato/cache -d '{"key":"my-key", "value":"my-value"}'
$
$ curl -XGET localhost:11223/zato/cache?key=my-key
{"value": "my-value"}
$
$ curl -XGET localhost:11223/zato/cache -d '{"key":"my-key"}'
{"value": "my-value"}
$
$ curl -XGET localhost:11223/zato/cache -d '{"key":"my-key", "details":true}'
{"hits": 4, "key": "my-key", "last_read": "2017-12-18T11:57:52.202461",
"value": "my-value", "expiry": null, "prev_write": "2017-12-18T11:56:57.714813",
"prev_read": "2017-12-18T11:57:47.225827", "expires_at": null,
"last_write": "2017-12-18T11:57:43.200757", "position": 0}
$

Summary

API caching in Zato 3.0 is a new feature, based on practical needs, that without doubt will come in handy in many integration scenarios that require efficient and convenient access to data cached in RAM.

Watch this space for future posts describing in detail commands, configuration options, response hooks or runtime usage statistics API.