Phil Karlton
"There are only two hard things in Computer Science: cache invalidation and naming things."
A cache is a component that stores data temporarily so that future requests for that data can be served faster. This temporal storage is used to shorten our data access times, reduce latency, and improve I/O. We can improve the overall performance using different types of caches in our microservice architecture. Let's take a look at this subject.
To maintain the cache, we have algorithms that provide instructions which tell us how the cache should be maintained. The most common algorithms are as follows:
The perfect time to start thinking about your cache strategy is when you are designing each of the microservices required by your app. Every time your service returns data, you need to ask to yourself some questions:
You can add a cache layer at any place you want in your application. For example, if you are using Percona/MySQL/MariaDB as a data storage, you can enable and set up the query cache correctly. This little setup will give your database a boost.
You need to think about cache even when you are coding. You can do lazy loading on objects and data or build a custom cache layer to improve the overall performance. Imagine that you are requesting and processing data from an external storage, the requested data can be repeated several times in the same execution. Doing something similar to the following piece of code will reduce the calls to your external storage:
<?php class MyClass { protected $myCache = []; public function getMyDataById($id) { if (empty($this->myCache[$id])) { $externalData = $this->getExternalData($id); if ($externalData !== false) { $this->myCache[$id] = $externalData; } } return $this->myCache[$id]; } }
Note that our examples omit big chunks of code, such as namespaces or other functions. We only want to give you the overall idea so that you can create your own code.
In this case, we will store our data in the $myCache
variable every time we make a request to our external storage using an ID as the key identifier. The next time we request an element with the same ID as a previous one, we will get the element from $myCache
instead of requesting the data from the external storage. Note that this strategy is only successful if you can reuse the data in the same PHP execution.
In PHP, you have access to the most popular cache servers, such as memcached and Redis; both of them store their data in a key-value format. Having access to these powerful tools will allow us to increase the performance of our microservices application.
Let's rebuild our preceding example using Redis
as our cache storage. In the following piece of code, we will assume that you have a Redis
library available in your environment (for example, phpredis) and a Redis
server running:
<?php class MyClass { protected $myCache = null; public function __construct() { $this->myCache = new Redis(); $this->myCache->connect('127.0.0.1', 6379); } public function getMyDataById($id) { $externalData = $this->myCache->get($id); if ($externalData === false) { $externalData = $this->getExternalData($id); if ($externalData !== false) { $this->myCache->set($id, $externalData); } } return $externalData; } }
Here, we connected to the Redis server first and adapted the getMyDataById
function to use our new Redis instance. This example can be more complicated, for example, by adding the dependence injection and storing a JSON in the cache, among other infinite options. One of the benefits of using a cache engine instead of building your own is that all of them come with a lot of cool and useful features. Imagine that you want to keep the data in cache for only 10 seconds; this is very easy to do with Redis--simply change the set call with $this->myCache->set($id, $externalData, 10)
and after ten seconds your record will be wiped from the cache.
Something even more important than adding data to the cache engine is invalidating or removing the data you have stored. In some cases, it is fine to use old data but in other cases, using old data can cause problems. If you do not add a TTL to make the data expire automatically, ensure that you have a way of removing or invalidating the data when it is required.
Keep this example and the previous one in mind, we will be using both strategies in our microservice application.
As a developer, you don't need to be tied to a specific cache engine; wrap it, create an abstraction, and use that abstraction so that you can change the underlying engine at any point without changing all the code.
This general caching strategy can be used in any scope of your application--you can use it inside the code of your microservice or even between microservices. In our application example, we will deal with secrets; their data doesn't change very often, so we can store all this information on our cache layer (Redis) the first time they are accessed.
Future petitions will obtain the secrets' data from our cache layer instead of getting it from our data storage, improving the performance of our app. Note that the service that retrieves and stores the secrets data is the one that is responsible for managing this cache.
Let's see some other caching strategies that we will be using in our microservices application.
This strategy uses some HTTP headers to determine whether the browser can use a local copy of the response or it needs to request a fresh copy from the origin server. This cache strategy is managed outside your application, so you don't have much control over it.
Some of the HTTP headers we can use are as listed:
The following are the available options:
In our example application, we will have a public UI that can be reached through any web browser. Using the right HTTP headers, we can avoid requests for the same assets again and again. For example, our CSS and JavaScript files won't change frequently, so we can set up an expiry date in the future and the browser will keep a copy of them; the future requests will use the local copy instead of requesting a new copy.
You can add an expires header to all .jpg
, .jpeg
, .png
, .gif
, .ico
, .css
, and .js
files with a date of 123 days in the future from the browser access time in NGINX with a simple rule:
location ~* .(jpg|jpeg|png|gif|ico|css|js)$ { expires 123d; }
Some static elements are very cache-friendly, among them you can cache the following ones:
These elements tend to change infrequently, so they can be cached for longer periods of time. To alleviate your servers' load, you can use a Content Delivery Network (CDN) so that these infrequently changed files can be served by these external servers.
Basically, there are two types of CDNs:
You need to have this in mind when you are designing your microservice application because you may allow your users to upload some files.
Where are you going to store these files? If they are to be public, why not use CDN to deliver these files instead of them being gutted from your servers.
Some of the well-known CDNs are CloudFlare, Amazon CloudFront, and Fastly, among others. What they all have in common is that they have multiple data centers around the world, allowing them to try to give you a copy of your file from the closest server.
By combining HTTP with static files caching strategies, you will reduce the asset requests on your server to a minimum. We will not explain other cache strategies, such as full page caching; with what we have covered, you have enough to start building a successful microservice application.
3.21.39.142