Skip to content

Commit

Permalink
feat: add memcache cache
Browse files Browse the repository at this point in the history
  • Loading branch information
Michad committed Jun 8, 2024
1 parent 334dcee commit 882d010
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 30 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The following features are currently available:

* Provide a uniform ZXY mapping interface for incoming requests.
* Proxy map tiles to ZXY, WMS, TMS, or WMTS backed map layers
* Cache map tiles in disk, memory, s3, redis ...
* Cache map tiles in disk, memory, s3, redis, or memcache
* Generic support for any content type
* Incoming authentication using a static key or JWT
* Configurable timeout, logging, and error handling rules
Expand Down Expand Up @@ -323,7 +323,13 @@ The following are the known incompatibilities with tilestache configurations:
* No `dirs` parameter - Files are currently stored in a flat structure rather than creating separate directories
* No `gzip` parameter - Might be added in the future
* The `path` parameter must be supplied as a file path, not a URI
* Redis cache supports a wider variety of configuration options. It's recommended but not required that you consider utilizing a Cluster or Ring deployment if you previously used a single server.
* Memcache cache:
* No `revision` parameter - Put the revision inside the key prefix
* The `key prefix` parameter is replaced with `keyprefix`
* The `servers` array is now an array of objects containing `host` and `port` instead of an array of strings with those combined
* Redis cache:
* Supports a wider variety of configuration options. It's recommended but not required that you consider utilizing a Cluster or Ring deployment if you previously used a single server.
* The `key prefix` parameter is replaced with `keyprefix`
* S3 cache:
* No `use_locks` parameter - Caches are currently lockless
* No `reduced_redundancy` parameter - Instead use the more flexible `storageclass` parameter with the "REDUCED_REDUNDANCY" option
Expand Down
24 changes: 23 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,29 @@ Example:

### Memcache

TODO. Not yet implemented.
Cache tiles using memcache.

Name should be "memcache"

Configuration options:

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| host | String | No | 127.0.0.1 | The host of the memcache server. A convenience equivalent to supplying `servers` with a single entry. Do not supply both this and `servers` |
| port | int | No | 6379 | The port of the memcache server. A convenience equivalent to supplying `servers` with a single entry. Do not supply both this and `servers` |
| keyprefix | string | No | None | A prefix to use for keys stored in cache. Helps avoid collisions when multiple applications use the same memcache |
| ttl | uint32 | No | 1 day | How long cache entries should persist for in seconds. Cannot be disabled. |
| servers | Array of `host` and `port` | No | host and port | The list of servers to connect to supplied as an array of objects, each with a host and key parameter. This should only have a single entry when operating in standalone mode. If this is unspecified it uses the standalone `host` and `port` parameters as a default, therefore this shouldn't be specified at the same time as those |

Example:

```yaml
cache:
name: memcache
host: 127.0.0.1
port: 11211
```


### Memory

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.22.3

require (
github.com/aws/aws-sdk-go v1.53.14
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874
github.com/go-redis/cache/v9 v9.0.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/aws/aws-sdk-go v1.53.14 h1:SzhkC2Pzag0iRW8WBb80RzKdGXDydJR9LAMs2GyKJ2M=
github.com/aws/aws-sdk-go v1.53.14/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
Expand Down
28 changes: 28 additions & 0 deletions internal/caches/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package caches

import (
"fmt"
"strconv"

"github.com/Michad/tilegroxy/internal"
"github.com/Michad/tilegroxy/internal/config"
Expand Down Expand Up @@ -77,8 +78,35 @@ func ConstructCache(rawConfig map[string]interface{}, errorMessages *config.Erro
return nil, err
}
return ConstructRedis(&config, errorMessages)
} else if rawConfig["name"] == "memcache" {
var config MemcacheConfig
err := mapstructure.Decode(rawConfig, &config)
if err != nil {
return nil, err
}
return ConstructMemcache(&config, errorMessages)
}

name := fmt.Sprintf("%#v", rawConfig["name"])
return nil, fmt.Errorf(errorMessages.InvalidParam, "cache.name", name)
}

// Utility type used in a couple caches
type HostAndPort struct {
Host string
Port uint16
}

func (hp HostAndPort) String() string {
return hp.Host + ":" + strconv.Itoa(int(hp.Port))
}

func HostAndPortArrayToStringArray(servers []HostAndPort) []string {
addrs := make([]string, len(servers))

for i, addr := range servers {
addrs[i] = addr.String()
}

return addrs
}
68 changes: 65 additions & 3 deletions internal/caches/memcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,77 @@

package caches

import "github.com/Michad/tilegroxy/internal"
import (
"fmt"

"github.com/Michad/tilegroxy/internal"
"github.com/Michad/tilegroxy/internal/config"
"github.com/bradfitz/gomemcache/memcache"
)

type MemcacheConfig struct {
HostAndPort `mapstructure:",squash"`
Servers []HostAndPort //The list of servers to use.
KeyPrefix string //Prefix to keynames stored in cache
Ttl uint32 //Cache expiration in seconds. Max of 30 days. Default to 1 day
}

const (
memcacheDefaultHost = "127.0.0.1"
memcacheDefaultPort = 11211
memcacheDefaultTtl = 60 * 60 * 24
memcacheMaxTtl = 30 * 60 * 60 * 24
)

type Memcache struct {
*MemcacheConfig
client *memcache.Client
}

func ConstructMemcache(config *MemcacheConfig, errorMessages *config.ErrorMessages) (*Memcache, error) {
if config.Servers == nil || len(config.Servers) == 0 {
if config.Host == "" {
config.Host = memcacheDefaultHost
}
if config.Port == 0 {
config.Port = memcacheDefaultPort
}

config.Servers = []HostAndPort{{config.Host, config.Port}}
} else {
if config.Host != "" {
return nil, fmt.Errorf(errorMessages.ParamsMutuallyExclusive, "config.memcache.host", "config.memcache.servers")
}
}

if config.Ttl == 0 {
config.Ttl = memcacheDefaultTtl
}
if config.Ttl > memcacheMaxTtl {
config.Ttl = memcacheMaxTtl
}

addrs := HostAndPortArrayToStringArray(config.Servers)
mc := memcache.New(addrs...)

err := mc.Ping()

return &Memcache{config, mc}, err

}

func (c Memcache) Lookup(t internal.TileRequest) (*internal.Image, error) {
return nil, nil
it, err := c.client.Get(c.KeyPrefix + t.String())

if err != nil {
return nil, err
}

result := internal.Image(it.Value)

return &result, nil
}

func (c Memcache) Save(t internal.TileRequest, img *internal.Image) error {
return nil
return c.client.Set(&memcache.Item{Key: c.KeyPrefix + t.String(), Value: *img, Expiration: int32(c.Ttl)})
}
46 changes: 24 additions & 22 deletions internal/caches/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ import (
"github.com/redis/go-redis/v9"
)

type RedisServer struct {
Host string
Port uint16
}

const (
ModeStandalone = "standalone"
ModeCluster = "cluster"
Expand All @@ -42,16 +37,23 @@ const (
var AllModes = []string{ModeStandalone, ModeCluster, ModeRing}

type RedisConfig struct {
RedisServer //Host and Port for a single server. A convenience equivalent to supplying Servers with a single entry
Db int //Database number, defaults to 0
KeyPrefix string //Prefix to keynames stored in cache
Username string //Username to use to authenticate
Password string //Password to use to authenticate
Mode string //Controls operating mode. One of AllModes. Defaults to standalone
Ttl uint32 //Cache expiration in seconds. Default to 1 day
Servers []RedisServer //The list of servers to use.
HostAndPort `mapstructure:",squash"` //Host and Port for a single server. A convenience equivalent to supplying Servers with a single entry
Db int //Database number, defaults to 0
KeyPrefix string //Prefix to keynames stored in cache
Username string //Username to use to authenticate
Password string //Password to use to authenticate
Mode string //Controls operating mode. One of AllModes. Defaults to standalone
Ttl uint32 //Cache expiration in seconds. Max of 1 year. Default to 1 day
Servers []HostAndPort //The list of servers to use.
}

const (
redisDefaultHost = "127.0.0.1"
redisDefaultPort = 6379
redisDefaultTtl = 60 * 60 * 24
redisMaxTtl = 60 * 60 * 24 * 365
)

type Redis struct {
*RedisConfig
cache *cache.Cache
Expand All @@ -70,32 +72,32 @@ func ConstructRedis(config *RedisConfig, errorMessages *config.ErrorMessages) (*

if config.Servers == nil || len(config.Servers) == 0 {
if config.Host == "" {
config.Host = "127.0.0.1"
config.Host = redisDefaultHost
}
if config.Port == 0 {
config.Port = 6379
config.Port = redisDefaultPort
}

config.Servers = []RedisServer{{config.Host, config.Port}}
config.Servers = []HostAndPort{{config.Host, config.Port}}
} else {
if config.Host != "" {
return nil, fmt.Errorf(errorMessages.ParamsMutuallyExclusive, "config.redis.host", "config.redis.servers")
}
}

if config.Ttl == 0 {
config.Ttl = 60 * 60 * 24
config.Ttl = redisDefaultTtl
}
if config.Ttl > redisMaxTtl {
config.Ttl = redisMaxTtl
}

if config.Mode == ModeCluster {
if config.Db != 0 {
return nil, fmt.Errorf(errorMessages.ParamsMutuallyExclusive, "cache.redis.db", "cache.redis.cluster")
}

addrs := make([]string, len(config.Servers))

for _, addr := range config.Servers {
addrs = append(addrs, addr.Host+":"+strconv.Itoa(int(addr.Port)))
}
addrs := HostAndPortArrayToStringArray(config.Servers)

client := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: addrs,
Expand Down
5 changes: 3 additions & 2 deletions test_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ authentication:
# region: us-east-1
# profile: tilegroxy
cache:
name: redis
mode: standalone
name: memcache
host: 127.0.0.1
port: 11211
layers:
-
id: test
Expand Down

0 comments on commit 882d010

Please sign in to comment.