patrickmn / go-cache

An in-memory key:value store/cache (similar to Memcached) library for Go, suitable for single-machine applications.
https://patrickmn.com/projects/go-cache/
MIT License
8.15k stars 874 forks source link

Sync cache between multiple instances #130

Open amintalebi opened 4 years ago

amintalebi commented 4 years ago

Hey I have an application that runs with multiple instances in a cloud environment. Cause It stores very little amounts of data and It doesn't need to be persistent, I do not find it necessary to run a separate database for it. I wanted to use go cache as a built-in datastore for my application but It doesn't support synchronization between multiple instances.

danicml commented 3 years ago

It seems to me that you are looking for a distributed cache. Go cache is a local cache, they are different use cases. To solve your problem I recommend you see memcached or redis.

sebstyle commented 3 years ago

Something to think about.. If your application services in the form of an http api behind an nginx proxy you could setup a location in nginx to manipulate keys on all your instances.. To keep the example short I left out the usual proxy pass commands like X-Forwarded-For Read the caveats at the end because, you know; the warnings, the warnings come after the spells :)

# Define upstreams
# You could include the instance running locally marked as backup so it only gets used when no other upstream is available
upstream instance_pool {
   server 127.0.0.1:8080 backup;
   server 10.0.0.1:8080;
   server 10.0.0.2:8080;
}
upstream instance_local {
    server 127.0.0.1:8080;
}
upstream instance1 {
    server 10.0.0.1:8080;
}
upstream instance2 {
    server 10.0.0.2:8080;
}
# Setup broadcast path
location ~ (cache/tokensX|cache/tokensY) {
  mirror /mirror/instance1;
  mirror /mirror/instance2;
  # You could drop the request body to mirrors if you do not need it
  #mirror_request_body off;
  # Endpoint is cache related; header X-Mirrored-By is required (origin)
  proxy_set_header X-Mirrored-By origin;
  # You could run an instance of your app on the proxy and pass the initial request to that instance
  proxy_pass http://instance_local$request_uri;
  # If you do not want to run an instance of your app on the proxy you could forward to the pool
  proxy_pass http://instance_pool$request_uri;
  # But that would result in one pool member receiving the request twice (original request + mirrored request)
  # Perhaps this can be tweaked by setting priority on a certain server in the pool when the request was made to the cache location
}
location /mirror/instance1 {
  # Marked internal so not available to public
  internal;
  # Cache related; header X-Mirrored-By is required (relayed)
  proxy_set_header X-Mirrored-By relayed;
  # You could drop the request body to instance if you do not need it
  #proxy_pass_request_body off;
  #proxy_set_header Content-Length "";
  proxy_pass http://instance1$request_uri;
}
location /mirror/instance2 {
  # Marked internal so not available to public
  internal;
  # Cache related; header X-Mirrored-By is required (relayed)
  proxy_set_header X-Mirrored-By relayed;
  # You could drop the request body to instance if you do not need it
  #proxy_pass_request_body off;
  #proxy_set_header Content-Length "";
  proxy_pass http://instance1$request_uri;
}

Now you can make an api request: PUT https://yourappproxy/cache/tokensX/newkey with payload newvalue This request then gets passed to the local instance and mirrored to all your other instances. All your instances now have newkey with newvalue in their tokensX cache. Instances that are mirrored but are offline will result in an error log entry warning that the mirror was unreachable. Do keep in mind that trying to mirror to a nonexistent instance will likely delay the entire original request untill the mirrored request times out

Should your app contain code that would trigger forwarding a certain cache operation to your other instances directly you can use the X-Mirrored-By header to check if that operation was an original request (X-Mirrored-By = origin) or mirrored request (X-Mirrored-By = relayed) So before your code forwards to other instances bypassing the proxy you should check if it was relayed or needs relaying to prevent avalanches.

Caveats

sebstyle commented 3 years ago

Some numbers benching this: Don't take them too seriously as these benchmarks were performed on a single machine running all the services (proxy / multiple app instances) and the benchmark tool itself.. Requests that do not access any cached data

Summary:
  Total:    10.0007 secs
  Slowest:  0.0054 secs
  Fastest:  0.0002 secs
  Average:  0.0004 secs
  Requests/sec: 985.9357

Requests that do use cached data

Summary:
  Total:    10.0017 secs
  Slowest:  0.0069 secs
  Fastest:  0.0002 secs
  Average:  0.0004 secs
  Requests/sec: 987.6336

Requests that trigger cache update on all instances

Summary:
  Total:    10.0025 secs
  Slowest:  0.0092 secs
  Fastest:  0.0005 secs
  Average:  0.0011 secs
  Requests/sec: 841.8916

Requests that trigger cache update on all instances where one instance (mirror) is offline

Summary:
  Total:    10.0010 secs
  Slowest:  0.0066 secs
  Fastest:  0.0005 secs
  Average:  0.0009 secs
  Requests/sec: 559.7423

One offline/slow mirror causes a significant delay in returning the request that was made to the cache path. As mentioned in the caveats, the cache mirror path is not intended for large amounts of cache operations that this benchmark generated. Tested using Hey tool https://github.com/rakyll/hey with params: -z 10s -q 1000 -n 100000 -c 1 -t 1 -m POST

/ edit

For tests database vs cache hey params: -z 10s -q 1000 -n 100000 -c 10 -t 1 -m POST Fetching the data from database

Summary:
  Total:    10.0077 secs
  Slowest:  0.0251 secs
  Fastest:  0.0007 secs
  Average:  0.0039 secs
  Requests/sec: 2585.1126

Fetching the data from cache

Summary:
  Total:    10.0015 secs
  Slowest:  0.0291 secs
  Fastest:  0.0002 secs
  Average:  0.0021 secs
  Requests/sec: 4725.9928

Fetching the data from cache while manipulating cache on all instances

Summary:
  Total:    10.0048 secs
  Slowest:  0.0406 secs
  Fastest:  0.0005 secs
  Average:  0.0070 secs
  Requests/sec: 1423.7224

The requests for manipulating the cache in this test were send to https://yourappproxy/cache/tokenX/ involving dns and ssl. There are tweaks that can be applied to increase rps for manipulating cache.