DEV Community

Christian Seki
Christian Seki

Posted on

Caching with Nginx

Cache is your friend 😄

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 💻

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

GitHub logo iamseki / dev-to

Implementations of dev.to blog posts


App

I'll use an app from previous post 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

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;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

nginx/dockerfile

FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
Enter fullscreen mode Exit fullscreen mode

Docker-compose

docker-compose.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"
Enter fullscreen mode Exit fullscreen mode

Proving cache is our friend

Using k6 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) 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

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);
}
Enter fullscreen mode Exit fullscreen mode

Running 📋

  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 📝

Results from Nginx:
Test Result - Nginx

Results from API:
Test Result - API

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.

Oldest comments (0)