Using nginx as a cache for elasticsearch
05/12/2019
Elasticsearch has it’s own caching but there are situations where adding a more application aware cache can speed up response times and reduce the load on elasticsearch.
The search api is a good candidate since complicated queries can take a long time (seconds) especially if the query has painless scripts which access source nested documents.
Setting up an nginx cache is pretty straightforward, it looks something like this:
# this creates a cache in /tmp/cache called escache. 10MB size, inactive after 10m.
proxy_cache_path /tmp/cache levels=1:2 keys_zone=escache:10m inactive=10m;
server {
# simple regex to only cache requests that end in `_search`
location ~ /.*\/_search\/*$ {
# (I'm assuming the configuration already has a reverse proxy)
# tell nginx which cache to use
proxy_cache escache;
# the search api also works with GET, but we only cache POST
proxy_cache_methods POST;
proxy_cache_key "$request_uri";
proxy_cache_valid 10m;
# prevents ES from being overloaded by same request
proxy_cache_lock on;
# lets you know how the response was handled for debugging
add_header X-Cached $upstream_cache_status;
}
}
The trick with the search api is that we need a good cache key. We can’t just use the URI because the search api almost always contains a body, which determines how elasticsearch will process the request.
We could use the request body directly in the cache key:
proxy_cache_key "$request_uri|$request_body";
# tuning buffers is required if you want to use request_body in the cache key directly
proxy_buffers 8 32k;
proxy_buffer_size 64k;
But that requires tuning the buffers, which can be tricky. If you expect small request bodies (<100KB), modifying buffers is probably fine. However if you ever expect large queries, and you can control headers in your requests, we can avoid fiddling with request buffers by using a custom header in the cache key.
proxy_cache_key "$request_uri|$http_custom_search_key";
# only enable the cache if the header is present
set $no_cache "true";
if ($http_custom_search_key) {
set $no_cache "0";
}
proxy_no_cache $no_cache;
proxy_cache_bypass $no_cache;
Now we just need to add the header in our application whenever we want a request cached.
const request = {
body: {
query: {
// your query
}
}
}
};
const requestMd5 = crypto
.createHash("sha256")
.update(JSON.stringify(request), "utf8")
.digest("hex");
const request = {
...request,
headers: {
"custom-search-key": requestMd5
}
}
const result = await this.elastic.search(request);