You want your API to be faster, more consistent, and to reduce the requests to the server? — That’s where caching comes into play. In this article, I will show you how to implement API Caching with Redis on Flask. I am taking Flask as an example here, but the concept about Caching are the same regardless of the technologies.
What’s caching?
Before we move into the practical part about implementing Caching with Redis and Flask, let’s first know what’s caching as a definition and learn it as a concept so you know then what would the use cases be.
Caching is the ability to store copies of frequently accessed data in several places along the request-response path. When a consumer requests a resource representation, the request goes through a cache or a series of caches (local cache, proxy cache, or reverse proxy) toward the service hosting the resource. If any of the caches along the request path has a fresh copy of the requested representation, it uses that copy to satisfy the request. If none of the caches can satisfy the request, the request travels to the service (or origin server as it is formally known). This is well defined with two terminologies, which are cache miss and cache hit.
Cache hit — A cache hit is a state in which data requested for processing by a component or application is found in the cache memory. It is a faster means of delivering data to the processor, as the cache already contains the requested data.
Cache miss — Cache miss is a state where the data requested for processing by a component or application is not found in the cache memory. It causes execution delays by requiring the program or application to fetch the data from other cache levels or the main memory.
As mentioned above, there are several ways to implement caching. That can be on the client-side through Web Caching, on the server-side through Data Caching (Relational Databases, Redis, etc), Application Caching through plugins that get installed on the application (ex: plugins on WordPress). For this tutorial we’re going to use Redis, to save the responses from the API, and then use those responses instead of making the requests to the server to fetch the data.
Flask and Redis — Implementation
Prerequisites:
Docker & Docker-compose
Flask
Python 3.*+
We are going to use docker to isolate our services, and then docker-compose to orchestrate the services together (putting them on the same network, communication between them, environment variables, etc). If you don’t know about Docker, I suggest you refer to the official docs here.
Project setup:
Create python virtualenv and install Flask, redis, flask-caching and requests:
$ python -m venv venv
$ source venv/Scripts/activate
$ (venv) pip install Flask redis flask_caching requests
Our application will look something like this:
/root
├── app.py - Application entrypoint
├── config.py - Config file for Flask
├── docker-compose.yml - Docker compose for app and redis
├── Dockerfile - Dockerfile for Flask API
├── .env - Environment variables
So lets go ahead and create files that are necessary for this setup:
$ touch Dockerfile docker-compose.yml .env
$ pip freeze > requirements.txt
$ touch config.py app.py
What we are going to implement?
We are just going to make a simple endpoint which fetches the university data from Hipolabs universities API, and based on the country that we sent as a query parameter, we get a list with the universities for the specified country.
Let’s go ahead and in app.py create an instance of Flask, and use that to create an endpoint that fetches universities data.
So basically, based on the query parameter country it makes the request to the external API and gets back the data in JSON format . Let’s go ahead and try it:
$ export FLASK_APP=app.py # To tell where your flask app lives
$ export FLASK_ENV=development # Set debug mode on
$ flask run
I will be using Postman to make the request because I also want to see the time that my request takes to process.
Okay, now we see that we have the results, and everything is working fine as excepted. With the red color, you can see the time that it took to get the data from that endpoint. We can try and make the same request several times, and the performance won’t change. That is because we’re always making a new request to the server. Our goal is to minimize this, and as explained at the beginning to make fewer requests to the server. So let’s go ahead and do that.
Add redis and dockerize the application
We saw that it worked fine locally, but now we want to implement caching, and for that, we’re going to need Redis. There are several approaches you can take here as:
Installing Redis (Officially compatible in Linux, not in Windows, see here)
Host a Redis instance and use that one (ex: Redis instance on Heroku)
Start the Redis instance with Docker (We are doing this)
We are going to dockerize the application and add Redis as a service so we can easily communicate from our application. Let’s go ahead and write the Dockerfile for the Flask application:
We don’t have command here to run the image, as I will use docker-compose to run the containers. Let’s configure docker-compose to run our application and Redis:
So we simply add two services, which are our application and Redis. For the application, we expose the port 5000 in and out, and for Redis, we expose 6379. Now let’s start the services with docker-compose.
$ docker-compose up -d --build
Our services should be up and running, and if we go again and make the same request as we did above when we were running the application without Docker, we will have the same output. To check if the services are running enter the following command:
$ docker ps
Now let’s configure our application to connect with Redis instance, and also to implement caching in our endpoint. We can go straight and set the variables directly in the code, but here I am trying to show you some good practices while developing with Flask and Docker. In the docker-compose from the above gist, we can see that for the environment variables I refer to the .env file, and then I use config.py to map these variables to the Flask application. For the flask-caching library to work, we need to set some environment variables, which are for Redis connection and caching type. You can read more about the configuration from the documentation of the library, based on the caching type that you want to implement.
# .e
CACHE_TYPE=redis
CACHE_REDIS_HOST=redis
CACHE_REDIS_PORT=6379
CACHE_REDIS_DB=0
CACHE_REDIS_URL=redis://redis:6379/0
CACHE_DEFAULT_TIMEOUT=500
In the .env we set some variables like caching type, host, db, etc. Since we have these variables mounted from docker-compose inside our container, now we can get those variables using the os module. Let’s get those variables in config.py and we’ll use them later to map the values to our Flask application.
From the configuration side of things, we’re good. Now let’s initialize the cache on top of Flask and integrate that with our application.
We have added a new decorator which is @cache.cached then we specify a timeout which is the time that this response will be cached in Redis memory. So basically after the first request, we will have this response stored for 30 seconds, after that there’ll be a fresh request that will update the memory again. The second parameter is query_string=True which in this case makes sense because we want to store the responses based on the query string that we store instead of the static path.
- query_string — Default False. When True, the cache key used will be the result of hashing the ordered query string parameters. This avoids creating different caches for the same query just because the parameters were passed in a different order.
And we’re done, let’s build the containers again and test this out in action with caching in place.
docker-compose up -d --build
Now let’s go to Postman again, and do the same request on the universities endpoint.
For the first time we’ll have approximately the same time as we did when we weren’t using caching, but if we do the same request again, we’ll have significant improvements and all that thanks to Redis. So what we’re doing is, we are saving the response to an in-memory database, and then while the data are still stored there they’ll be returned from there, instead of making the request from the server.
Dive deeper? — Let us see that in action, by using a GUI tool, to query our Redis store. I am using TablePlus, for the sake of the visualization, but you can also use Redis CLI to query the data. To connect to our redis instance we will specify host as localhost and then for the port we enter 6379 just as we exposed in docker-compose .
After that, we can see the data that are being stored in our Redis instance. When there’s a response saved, you can see a db0 and if we look for more, we’ll see our cached response including [key; value; type; ttl].
We can clearly see that the response that is cached is /universities?* and that is available for the time that appears on the ttl. This section was a bit outside of the scope, but it’s good to know what is happening in the background.
So with that, we have implemented API caching with Redis and Flask. For more option please refer to the documentation of the flask-caching library which is a wrapper to implement caching around different clients.
Conclusions
So we implemented API Caching using Redis. This is a simple example, but it includes lots of details regarding this topic. Caching is really important when you write applications, as it helps a lot on the performance and when possible, you should implement it, but make sure that you’re targeting the right use case.
You can find the full source code of the article on the GitHub repository, with the instructions.
vjanz/flask-cache-redis
If you found it helpful, please don’t forget to clap & share in your social network or with your friends.
If you have any question, feel free to reach out to me.
Connect with me on: LinkedIn, GitHub
References:
https://www.cloudflare.com/learning/cdn/what-is-caching/
https://redislabs.com
https://docs.docker.com/
https://flask-caching.readthedocs.io
http://universities.hipolabs.com/
Top comments (0)