Caching with Nginx
Tags: nginx, devops, programming, beginners
Published: 2021-12-27 22:42:15

## Cache is your friend :smile:

A lot of performance issues can be improved using cache, there is a lot of tools out here but for this brief tutorial I'll choose the well-known Nginx.

## Setup :computer:

You can clone the branch `nginx-cache` or follow this tutorial (I'll only cover the nginx stuff)

{% github iamseki/dev-to no-readme %}

---

### App

I'll use an app from [previous post](https://dev.to/chseki/load-test-with-javascript-1f64) of mine, which calculates the nth fibonacci result given a number. The app it self is very simple and you can see it very small piece of code from the (repository)[https://github.com/iamseki/dev-to] that I mentioned before.

### Nginx

**`nginx/nginx.conf`**
```nginx
events {
  worker_connections 4096;
}

http {
  proxy_cache_path /tmp/nginx/cache levels=1:2 keys_zone=fibo_cache:10m inactive=60m use_temp_path=off;

  server {
    listen 3000;

    location /fibonacci {
      proxy_cache fibo_cache;
      proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;

      add_header X-Proxy-Cache $upstream_cache_status;

      proxy_pass http://api:8080/fibonacci;
    }

    location /hc {
      proxy_pass http://api:8080/hc;
    }
  }
}
```
**`nginx/dockerfile`**
```Dockerfile
FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
```

### Docker-compose

**`docker-compose.yml`**
```yml
version: "3"

services:
  nginx:
    build: nginx
    ports:
      - "3000:3000"
    volumes:
      - "./cache:/tmp/nginx/cache"
    container_name: "nginx"
  api:
    build: api
    ports:
      - "8080:8080"
    container_name: "api"
```

### Proving cache is our friend

Using [k6](https://k6.io/docs/) we can simulate a bunch of requests that go through Nginx and others that don't, comparing them. 
The test will reach 100 requests in the first 30 seconds([about stages in k6](https://k6.io/docs/using-k6/options/#stages)) that will randomly retrieve the nth sequence with 7 digits between (9999991 ~ 9999999) to force the usage of API resources and Nginx caching.

**`k6-test.js`**
```javascript
import http from 'k6/http';
import { sleep } from 'k6';

const SLEEP_DURATION = 0.1;

export let options = {
    stages: [
        { duration: "30s", target: 100 },
        { duration: "30s", target: 0 },
    ],
    thresholds: {
        http_req_duration: ['p(99)<100'] // 99% request must complete below 100ms
    }
}

const NGINX_LOCAL_URL = "http://localhost:3000"
const API_LOCAL_URL = "http://localhost:8080"

const BASE_URL = __ENV.API_URL === "NGINX" ? NGINX_LOCAL_URL : API_LOCAL_URL 
const HEADERS = { "Content-Type": "application/json" }

const randomInteger = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

export default () => {
    http.get(`${BASE_URL}/fibonacci/${randomInteger(9999991,9999999)}`);
    sleep(SLEEP_DURATION);
}
```

## Running :clipboard:

1. `docker-compose up -d`
2. Testing with requests passing through Nginx: `k6 run k6-test.js -e API_URL='NGINX'`
3. Testing requesting directly the API: `k6 run k6-test.js`

What if API were down ? Try it out, `docker stop api` and run the tests above again.

>Nginx will be able to answer some requests (maybe all, due to our scenario), cool right?

## Conclusion :memo:

Results from Nginx:
![Test Result - Nginx](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dji50vn9l6rsn6b80s3b.png)

Results from API:
![Test Result - API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4q2n2ljpoow8zlwtd059.png)

As we can see, caching improves considerably the response time. Sometimes the back-end will be able to handle requests even if the API is down, when I test this feature for the first time I thought it was amazing!

This is a simple example but I hope that could be helpful for people who's thinking to cache some API routes.I know this is not applicable to every scenario, but maybe can open a world of possibilities to solve performance issues without change the API it self.