diff --git a/CHANGELOG.md b/CHANGELOG.md index a36673e4485a754b6979045fa47c6b9aeb86bb05..a46257ac5d0403522fbca600097484585153d297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * Add webp image support. Thanks @Sorunome! * Add apng image support. Thanks @Sorunome! +* Experimental support for Redis as a cache (in preparation for proper load balancing/HA support). ### Changed diff --git a/cmd/media_repo/main.go b/cmd/media_repo/main.go index ee80e9df453ea682b48487ba76781efb1f3c250b..3c6a0f48ae00ae14f60ade98d2f54c77c102b6ed 100644 --- a/cmd/media_repo/main.go +++ b/cmd/media_repo/main.go @@ -45,6 +45,7 @@ func main() { logrus.Info("Starting up...") runtime.RunStartupSequence() + internal_cache.ReplaceInstance() // init the cache as we may be using Redis, and it'd be good to get going sooner logrus.Info("Checking background tasks...") err = scanAndStartUnfinishedTasks() @@ -74,7 +75,6 @@ func main() { logrus.Info("Stopping recurring tasks...") tasks.StopAll() - internal_cache.Get().Stop() } // Set up a listener for SIGINT diff --git a/cmd/media_repo/reloads.go b/cmd/media_repo/reloads.go index 63452d2d0e373d078c5c941b34106584199caf9a..6deb426d3927f357bf19b613904c469d5ebaeed3 100644 --- a/cmd/media_repo/reloads.go +++ b/cmd/media_repo/reloads.go @@ -5,6 +5,7 @@ import ( "github.com/turt2live/matrix-media-repo/api/webserver" "github.com/turt2live/matrix-media-repo/common/globals" "github.com/turt2live/matrix-media-repo/common/runtime" + "github.com/turt2live/matrix-media-repo/internal_cache" "github.com/turt2live/matrix-media-repo/ipfs_proxy" "github.com/turt2live/matrix-media-repo/metrics" "github.com/turt2live/matrix-media-repo/storage" @@ -19,6 +20,7 @@ func setupReloads() { reloadRecurringTasksOnChan(globals.RecurringTasksReloadChan) reloadIpfsOnChan(globals.IPFSReloadChan) reloadAccessTokensOnChan(globals.AccessTokenReloadChan) + reloadCacheOnChan(globals.CacheReplaceChan) } func stopReloads() { @@ -30,6 +32,7 @@ func stopReloads() { globals.AccessTokenReloadChan <- false globals.RecurringTasksReloadChan <- false globals.IPFSReloadChan <- false + globals.CacheReplaceChan <- false } func reloadWebOnChan(reloadChan chan bool) { @@ -130,3 +133,17 @@ func reloadAccessTokensOnChan(reloadChan chan bool) { } }() } + +func reloadCacheOnChan(reloadChan chan bool) { + go func() { + defer close(reloadChan) + for { + shouldReload := <-reloadChan + if shouldReload { + internal_cache.ReplaceInstance() + } else { + internal_cache.Get().Stop() + } + } + }() +} diff --git a/common/config/conf_min_shared.go b/common/config/conf_min_shared.go index 8e05b88f9c2bda0dfe1a6c385bde6a1d57813175..4059b92866ebeb9ce58e07f68cbd868987a77c72 100644 --- a/common/config/conf_min_shared.go +++ b/common/config/conf_min_shared.go @@ -56,6 +56,10 @@ func NewDefaultMinimumRepoConfig() MinimumRepoConfig { RepoPath: "./ipfs", }, }, + Redis: RedisConfig{ + Enabled: false, + Shards: []RedisShardConfig{}, + }, }, AccessTokens: AccessTokenConfig{ MaxCacheTimeSeconds: 0, diff --git a/common/config/models_domain.go b/common/config/models_domain.go index 5a14158af050b444a9f35d80fe3c339c7426a233..db3ec77fb99dd1337526e75b998887f4ff631dd5 100644 --- a/common/config/models_domain.go +++ b/common/config/models_domain.go @@ -7,9 +7,9 @@ type ArchivingConfig struct { } type UploadsConfig struct { - MaxSizeBytes int64 `yaml:"maxBytes"` - MinSizeBytes int64 `yaml:"minBytes"` - ReportedMaxSizeBytes int64 `yaml:"reportedMaxBytes"` + MaxSizeBytes int64 `yaml:"maxBytes"` + MinSizeBytes int64 `yaml:"minBytes"` + ReportedMaxSizeBytes int64 `yaml:"reportedMaxBytes"` } type DatastoreConfig struct { @@ -74,6 +74,7 @@ type TimeoutsConfig struct { type FeatureConfig struct { MSC2448Blurhash MSC2448Config `yaml:"MSC2448"` IPFS IPFSConfig `yaml:"IPFS"` + Redis RedisConfig `yaml:"redis"` } type MSC2448Config struct { @@ -97,6 +98,16 @@ type IPFSDaemonConfig struct { RepoPath string `yaml:"repoPath"` } +type RedisConfig struct { + Enabled bool `yaml:"enabled"` + Shards []RedisShardConfig `yaml:"shards,flow"` +} + +type RedisShardConfig struct { + Name string `yaml:"name"` + Address string `yaml:"addr"` +} + type AccessTokenConfig struct { MaxCacheTimeSeconds int `yaml:"maxCacheTimeSeconds"` UseAppservices bool `yaml:"useLocalAppserviceConfig"` diff --git a/common/config/watch.go b/common/config/watch.go index c7f02d03b5e62dd6364c4be33157404b5e963322..5dede9f0eb3610500248e7ca17dadbc75be5a236 100644 --- a/common/config/watch.go +++ b/common/config/watch.go @@ -95,6 +95,20 @@ func onFileChanged() { globals.IPFSReloadChan <- true } + redisEnabledChange := configNew.Features.Redis.Enabled != configNow.Features.Redis.Enabled + redisShardsChange := hasRedisShardConfigChanged(configNew, configNow) + cacheEnabledChange := configNew.Downloads.Cache.Enabled != configNow.Downloads.Cache.Enabled + cacheMaxSizeChange := configNew.Downloads.Cache.MaxSizeBytes != configNow.Downloads.Cache.MaxSizeBytes + cacheMaxFileSizeChange := configNew.Downloads.Cache.MaxFileSizeBytes != configNow.Downloads.Cache.MaxFileSizeBytes + cacheTrackedMinChange := configNew.Downloads.Cache.TrackedMinutes != configNow.Downloads.Cache.TrackedMinutes + cacheMinDownloadsChange := configNew.Downloads.Cache.MinDownloads != configNow.Downloads.Cache.MinDownloads + cacheMinCacheTimeChange := configNew.Downloads.Cache.MinCacheTimeSeconds != configNow.Downloads.Cache.MinCacheTimeSeconds + cacheMinEvictedTimeChange := configNew.Downloads.Cache.MinEvictedTimeSeconds != configNow.Downloads.Cache.MinEvictedTimeSeconds + if redisEnabledChange || redisShardsChange || cacheEnabledChange || cacheMaxSizeChange || cacheMaxFileSizeChange || cacheTrackedMinChange || cacheMinDownloadsChange || cacheMinCacheTimeChange || cacheMinEvictedTimeChange { + logrus.Warn("Cache configuration changed - reloading") + globals.CacheReplaceChan <- true + } + // Always flush the access token cache logrus.Warn("Flushing access token cache") globals.AccessTokenReloadChan <- true @@ -117,3 +131,26 @@ func hasWebFeatureChanged(configNew *MainRepoConfig, configNow *MainRepoConfig) return false } + +func hasRedisShardConfigChanged(configNew *MainRepoConfig, configNow *MainRepoConfig) bool { + oldShards := configNow.Features.Redis.Shards + newShards := configNew.Features.Redis.Shards + if len(oldShards) != len(newShards) { + return true + } + + for _, s1 := range oldShards { + has := false + for _, s2 := range newShards { + if s1.Name == s2.Name && s1.Address == s2.Address { + has = true + break + } + } + if !has { + return true + } + } + + return false +} \ No newline at end of file diff --git a/common/globals/reload.go b/common/globals/reload.go index b488aec1040082b6d04716782a1139384b674269..f1aa971fff146621cdaaf631aa2fedd8a0d10c34 100644 --- a/common/globals/reload.go +++ b/common/globals/reload.go @@ -6,4 +6,5 @@ var DatabaseReloadChan = make(chan bool) var DatastoresReloadChan = make(chan bool) var RecurringTasksReloadChan = make(chan bool) var IPFSReloadChan = make(chan bool) -var AccessTokenReloadChan = make(chan bool) \ No newline at end of file +var AccessTokenReloadChan = make(chan bool) +var CacheReplaceChan = make(chan bool) \ No newline at end of file diff --git a/config.sample.yaml b/config.sample.yaml index 99867d39e86b706005c8c1ba17cdc6334138cad9..0e99a7236087b95e7a2ddfb725c5ec003f390b5d 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -485,4 +485,24 @@ featureSupport: # If the Daemon is enabled, set this to the location where the IPFS files should # be stored. If you're using Docker, this should be something like "/data/ipfs" # so it can be mapped to a volume. - repoPath: "./ipfs" \ No newline at end of file + repoPath: "./ipfs" + + # Support for redis as a cache mechanism + # + # Note: Enabling Redis support will mean that the existing cache mechanism will do nothing. + # It can be safely disabled once Redis support is enabled. + # + # See docs/redis.md for more information on how this works and how to set it up. + redis: + # Whether or not use Redis instead of in-process caching. + enabled: false + + # The Redis shards that should be used by the media repo in the ring. The names of the + # shards are for your reference and have no bearing on the connection, but must be unique. + shards: + - name: "server1" + addr: ":7000" + - name: "server2" + addr: ":7001" + - name: "server3" + addr: ":7002" \ No newline at end of file diff --git a/docs/config.md b/docs/config.md index 25160c8302d3b79276530c43e9dbd687fd77ae06..831ac5af964116e7f70910b2eee4bf020d892fdc 100644 --- a/docs/config.md +++ b/docs/config.md @@ -55,6 +55,7 @@ Any options from the main config can then be overridden per-domain with the exce * `thumbnails.expireAfterDays` - because thumbnails aren't associated with any particular domain. * `urlPreviews.expireAfterDays` - because previews aren't associated with any particular domain. * `featureSupport.IPFS.builtInDaemon` - because spawning multiple daemons doesn't make sense. +* `featureSupport.redis` - because the cache is repo-wide. To override a value, simply provide it in any valid per-domain config: diff --git a/docs/redis.md b/docs/redis.md new file mode 100644 index 0000000000000000000000000000000000000000..0cee83af324b18f0dfc150de2e1be64442821416 --- /dev/null +++ b/docs/redis.md @@ -0,0 +1,49 @@ +# Redis support + +**Note**: Redis support is currently experimental and not intended for usage in general cases. + +Redis can be used as a high-performance cache for the media repo, allowing for (in future) multiple media +repositories to run concurrently in limited jobs (such as some processes handling uploads, others downloads, +etc). Currently though, it is capable of speeding up deployments of disjointed media repos or in preparation +for proper load balancer support by the media repo. + +The media repo connects to a number of "shards" (Redis processes) to distribute cached keys over them. Each +shard is not expected to store persistent data and should be tolerant of total failure - the media repo assumes +that the shards will be dedicated to caching and thus will not have any expectations that a particular shard +will remain running. + +Setting up a shard is fairly simple: it's the same as deploying Redis itself. The media repo does not manage +the expiration policy for the shards, so it is recommended to give https://redis.io/topics/lru-cache a read to +pick the best eviction policy for your environment. The current recommendations are: +* A `maxmemory` of at least `1gb` for each shard. +* A `maxmemory-policy` of `allkeys-lfu` to ensure that the cache gets cleared out (the media repo does not set + an expiration time or TTL). Note: an `lru` mode is *not* recommended as the media repo will be caching all + uploads it sees, which includes remote media. A `lfu` mode ensures that recent items being cached can still + be evicted if not commonly requested. +* 1 shard for most deployments. Larger repos (or connecting many media repos to the same shards) should consider + 3 or more shards. + +The shards in the ring can be changed at runtime by updating the config and ensuring the media repo has reloaded +the config. Note that changing cache mechanisms at runtime is not recommended, and a full restart is recommended +instead. + +**Note**: Metrics reported for cache size will be inaccurate. Frequencies of requests will still be reported. + +**Note**: Quarantined media will still be stored in the cache. This is considered a bug and will need fixing. + +## Connecting multiple disjointed media repos + +Though the media repo expects to be the sole and only thing in the datacenter handling media, it is not always +possible or sane to do so. Examples including hosting providers which may have several media repos handling a +small subset of domains each. In these scenarios, it may be beneficial to set up a series of Redis shards within +each datacenter and connect all the media repos in that DC to them. This can reduce the amount of time it takes +to retrieve media from a media repo in that DC, as well as avoid downloading several copies of remote media. + +Note that even when connecting media repos to the same set of shards the repos will still attempt to upload a +copy of the media to the datastore. For example, if media repo A downloads something from matrix.org and puts +it into the cache, media repo B will first get it from the cache and upload it to its datastore when a user +requests the same media. The benefit, however, is that only 1 request to matrix.org happened instead of two. + +All media repos should be connected to the same set of shards to ensure even balancing between the shards. +Additionally, all media repos **must** be running the same major version (anything in `1.x.x`) in order to avoid +conflicts. diff --git a/go.mod b/go.mod index 7ba66ab05a129bb7543a1f8b09a82e92db15dab8..8c4ddf00cb93f58f0637aa594efb2787bc16efe2 100644 --- a/go.mod +++ b/go.mod @@ -22,8 +22,10 @@ require ( github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect github.com/fogleman/gg v1.3.0 github.com/fsnotify/fsnotify v1.4.7 + github.com/go-redis/redis/v8 v8.0.0-beta.6 github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 + github.com/golang/snappy v0.0.1 // indirect github.com/gorilla/mux v1.7.4 github.com/h2non/filetype v1.0.12 github.com/ipfs/go-cid v0.0.4 @@ -56,7 +58,7 @@ require ( github.com/tebeka/strftime v0.1.3 // indirect golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 golang.org/x/image v0.0.0-20200119044424-58c23975cae1 - golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect + golang.org/x/net v0.0.0-20200513185701-a91f0712d120 // indirect golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect gopkg.in/ini.v1 v1.52.0 // indirect gopkg.in/yaml.v2 v2.2.8 diff --git a/go.sum b/go.sum index ce5d5e0deba2c81041784c60672fc962f1dad98a..330d93e64df0542ce5b2a151b21ff4ae8a82822b 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,22 @@ bazil.org/fuse v0.0.0-20180421153158-65cc252bf669 h1:FNCRpXiquG1aoyqcIWVFmpTSKVcx2bQD38uZZeGtdlw= bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190823232136-616930265c33 h1:2/E2IVdZoHh/aCBq4Gchy2MGWkTmbReP46/Wnt9qhKs= github.com/AndreasBriese/bbloom v0.0.0-20190823232136-616930265c33/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7 h1:qELHH0AWCvf98Yf+CNIJx9vOZOfHFDDzgDRYsnNk/vs= +github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= github.com/DavidHuie/gomigrate v0.0.0-20190826182718-4adc4b3de142 h1:pfeJevnIXt4KJShhkTp8uRU0evDkRaFkAdmaNmzHMIQ= github.com/DavidHuie/gomigrate v0.0.0-20190826182718-4adc4b3de142/go.mod h1:F3GZLX+VN44AjFiyKD8++nq8sVE0Sw3bOhhQ3mUffnM= github.com/Jeffail/tunny v0.0.0-20190930221602-f13eb662a36a h1:sk14oPN106XTe3WzOIaVGq+cFh1sh4z++2pAg2j4XCo= github.com/Jeffail/tunny v0.0.0-20190930221602-f13eb662a36a/go.mod h1:BX3q3G70XX0UmIkDWfDHoDRquDS1xFJA5VTbMf+14wM= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/Stebalien/go-bitfield v0.0.0-20180330043415-076a62f9ce6e/go.mod h1:3oM7gXIttpYDAJXpVNnSCiUMYBLIZ6cb1t+Ip982MRo= @@ -30,6 +36,8 @@ github.com/alioygur/is v1.0.3/go.mod h1:fmXi78K26iMaOs0fINRVLl1TIPCYcLfOopoZ5+mc github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/benbjohnson/clock v1.0.3 h1:vkLuvpK4fmtSCuo60+yC63p7y0BmQ8gm5ZXGuBCJyXg= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -58,6 +66,9 @@ github.com/cenk/backoff v2.2.1+incompatible h1:djdFT7f4gF2ttuzRKPbMOWgZajgesItGL github.com/cenk/backoff v2.2.1+incompatible/go.mod h1:7FtoeaSnHoZnmZzz47cM35Y9nSW7tNyaidugnHTaFDE= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/webp v1.1.0 h1:4Ei0/BRroMF9FaXDG2e4OxwFcuW2vcXd+A6tyqTJUQQ= @@ -65,6 +76,7 @@ github.com/chai2010/webp v1.1.0/go.mod h1:LP12PG5IFmLGHUU26tBiCBKnghxx3toZFwDjOY github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -89,6 +101,8 @@ github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhY github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9 h1:h2Ul3Ym2iVZWMQGYmulVUJ4LSkBm1erp9mUkPwtMoLg= +github.com/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M= github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= @@ -100,6 +114,9 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8 h1:6muCmMJat6z7qptVrIf/+OWPxsjAfvhw5/6t+FwEkgg= github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8/go.mod h1:nYia/MIs9OyvXXYboPmNOj0gVWo97Wx0sde+ZuKkoM4= github.com/elgris/jsondiff v0.0.0-20160530203242-765b5c24c302/go.mod h1:qBlWZqWeVx9BjvqBsnC/8RUlAYpIFmPvgROcw0n1scE= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 h1:BBso6MBKW8ncyZLv37o+KNyy0HrrHgfnOaGQC2qvN+A= github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5/go.mod h1:JpoxHjuQauoxiFMl1ie8Xc/7TfLuMZ5eOCONd1sUBHg= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= @@ -115,10 +132,13 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-redis/redis/v8 v8.0.0-beta.6 h1:QeXAkG9L5cWJA+eJTBvhkftE7dwpJ0gbMYeBE2NxXS4= +github.com/go-redis/redis/v8 v8.0.0-beta.6/go.mod h1:g79Vpae8JMzg5qjk8BiwU9tK+HmU3iDVyS4UAJLFycI= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -141,13 +161,26 @@ github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -687,12 +720,18 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e h1:fI6mGTyggeIYVmGhf80XFHxTupjOexbCppgTNDkv9AA= +github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -715,6 +754,7 @@ github.com/prometheus/client_golang v1.4.1 h1:FFSuS004yOQEtDdTq+TAOLP5xUq63KqAFY github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= @@ -774,6 +814,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto= @@ -820,6 +862,8 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.1 h1:8dP3SGL7MPB94crU3bEPplMPe83FI4EouesJUeFHv50= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opentelemetry.io/otel v0.7.0 h1:u43jukpwqR8EsyeJOMgrsUgZwVI1e1eVw7yuzRkD1l0= +go.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/dig v1.7.0 h1:E5/L92iQTNJTjfgJF2KgU+/JpMaiuvK2DHLBj0+kSZk= @@ -853,12 +897,20 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20200513190911-00229845015e h1:rMqLP+9XLy+LdbCXHjJHAmTfXCr93W7oruWA6Hq1Alc= +golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg= golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -876,10 +928,11 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -898,6 +951,7 @@ golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190302025703-b6889370fb10/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -910,6 +964,8 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190926180325-855e68c8590b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= @@ -928,7 +984,10 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -937,8 +996,22 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03 h1:4HYDjxeNXAOTv3o1N2tjo8UUSlhQgAD52FVkwxnWgM8= +google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -958,6 +1031,10 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal_cache/media_cache.go b/internal_cache/media_cache.go index ba4ca11d291950ce1ac6e50710a9ecaa9f20379c..69edee6fa31fe6e26302136e24d826f2f51a1aad 100644 --- a/internal_cache/media_cache.go +++ b/internal_cache/media_cache.go @@ -15,6 +15,7 @@ import ( "github.com/turt2live/matrix-media-repo/common/config" "github.com/turt2live/matrix-media-repo/common/rcontext" "github.com/turt2live/matrix-media-repo/metrics" + "github.com/turt2live/matrix-media-repo/redis_cache" "github.com/turt2live/matrix-media-repo/storage/datastore" "github.com/turt2live/matrix-media-repo/types" "github.com/turt2live/matrix-media-repo/util" @@ -29,6 +30,7 @@ type MediaCache struct { size int64 enabled bool cleanupTimer *time.Ticker + redis *redis_cache.RedisCache } type cachedFile struct { @@ -52,7 +54,10 @@ func Get() *MediaCache { } lock.Do(func() { - if !config.Get().Downloads.Cache.Enabled { + if config.Get().Features.Redis.Enabled { + logrus.Info("Setting up Redis cache") + instance = &MediaCache{enabled: true, redis: redis_cache.NewCache()} + } else if !config.Get().Downloads.Cache.Enabled { logrus.Warn("Cache is disabled - setting up a dummy instance") instance = &MediaCache{enabled: false} } else { @@ -93,11 +98,27 @@ func Get() *MediaCache { return instance } +func ReplaceInstance() { + if instance != nil { + instance.Reset() + instance.Stop() + instance = nil + } + + // call Get() to update the instance reference + Get() +} + func (c *MediaCache) Reset() { if !c.enabled { return } + if c.redis != nil { + logrus.Warn("Not doing a cache reset - using Redis cache") + return + } + logrus.Warn("Resetting media cache") rwLock.Lock() c.cache.Flush() @@ -108,10 +129,19 @@ func (c *MediaCache) Reset() { } func (c *MediaCache) Stop() { - c.cleanupTimer.Stop() + if c.redis != nil { + _ = c.redis.Close() + } else { + c.cleanupTimer.Stop() + } } func (c *MediaCache) getUnderlyingUsedBytes() int64 { + if c.redis != nil { + // Cannot determine: return implied result + return 0 + } + var size int64 = 0 for _, entry := range c.cache.Items() { f := entry.Object.(*cachedFile) @@ -121,6 +151,11 @@ func (c *MediaCache) getUnderlyingUsedBytes() int64 { } func (c *MediaCache) getUnderlyingItemCount() int { + if c.redis != nil { + // Cannot determine: return implied result + return 0 + } + return c.cache.ItemCount() } @@ -128,6 +163,10 @@ func (c *MediaCache) IncrementDownloads(fileHash string) { if !c.enabled { return } + if c.redis != nil { + // Irrelevant data point + return + } logrus.Info("File " + fileHash + " has been downloaded") rwLock.Lock() @@ -155,7 +194,8 @@ func (c *MediaCache) GetMedia(media *types.Media, ctx rcontext.RequestContext) ( return &cachedFile{media: media, Contents: bytes.NewBuffer(data)}, nil } - return c.updateItemInCache(media.Sha256Hash, media.SizeBytes, cacheFn, ctx) + tmpl := &cachedFile{media: media} + return c.updateItemInCache(media.Sha256Hash, media.SizeBytes, cacheFn, tmpl, ctx) } func (c *MediaCache) GetThumbnail(thumbnail *types.Thumbnail, ctx rcontext.RequestContext) (*cachedFile, error) { @@ -178,10 +218,34 @@ func (c *MediaCache) GetThumbnail(thumbnail *types.Thumbnail, ctx rcontext.Reque return &cachedFile{thumbnail: thumbnail, Contents: bytes.NewBuffer(data)}, nil } - return c.updateItemInCache(thumbnail.Sha256Hash, thumbnail.SizeBytes, cacheFn, ctx) + tmpl := &cachedFile{thumbnail: thumbnail} + return c.updateItemInCache(thumbnail.Sha256Hash, thumbnail.SizeBytes, cacheFn, tmpl, ctx) } -func (c *MediaCache) updateItemInCache(recordId string, mediaSize int64, cacheFn func() (*cachedFile, error), ctx rcontext.RequestContext) (*cachedFile, error) { +func (c *MediaCache) updateItemInCache(recordId string, mediaSize int64, cacheFn func() (*cachedFile, error), template *cachedFile, ctx rcontext.RequestContext) (*cachedFile, error) { + if c.redis != nil { + b, err := c.redis.GetBytes(ctx, recordId) + if err == redis_cache.ErrCacheMiss || err == redis_cache.ErrCacheDown { + metrics.CacheMisses.With(prometheus.Labels{"cache": "media"}).Inc() + cf, err := cacheFn() + if err != nil { + return nil, err + } + b, err := ioutil.ReadAll(cf.Contents) + err = c.redis.SetStream(ctx, recordId, bytes.NewBuffer(b)) + if err != nil && err != redis_cache.ErrCacheDown { + return nil, err + } + cf.Contents = bytes.NewBuffer(b) + return cf, nil + } else if err != nil { + return nil, err + } + metrics.CacheNumItems.With(prometheus.Labels{"cache": "media"}).Inc() + template.Contents = bytes.NewBuffer(b) + return template, nil + } + downloads := c.tracker.NumDownloads(recordId) enoughDownloads := downloads >= config.Get().Downloads.Cache.MinDownloads canCache := c.canJoinCache(recordId) diff --git a/redis_cache/redis.go b/redis_cache/redis.go new file mode 100644 index 0000000000000000000000000000000000000000..76b04c7818e39d901bd4983622572b22e2a3862f --- /dev/null +++ b/redis_cache/redis.go @@ -0,0 +1,98 @@ +package redis_cache + +import ( + "bytes" + "context" + "errors" + "io" + "io/ioutil" + "time" + + "github.com/go-redis/redis/v8" + "github.com/sirupsen/logrus" + "github.com/turt2live/matrix-media-repo/common/config" + "github.com/turt2live/matrix-media-repo/common/rcontext" +) + +var ErrCacheMiss = errors.New("missed cache") +var ErrCacheDown = errors.New("all shards appear to be down") + +type RedisCache struct { + ring *redis.Ring +} + +func NewCache() *RedisCache { + addresses := make(map[string]string) + for _, c := range config.Get().Features.Redis.Shards { + addresses[c.Name] = c.Address + } + ring := redis.NewRing(&redis.RingOptions{ + Addrs: addresses, + DialTimeout: 10 * time.Second, + }) + + logrus.Info("Contacting Redis shards...") + _ = ring.ForEachShard(context.Background(), func(ctx context.Context, client *redis.Client) error { + logrus.Infof("Pinging %s", client.String()) + r, err := client.Ping(ctx).Result() + if err != nil { + return err + } + logrus.Infof("%s replied with: %s", client.String(), r) + return nil + }) + + return &RedisCache{ring: ring} +} + +func (c *RedisCache) Close() error { + return c.ring.Close() +} + +func (c *RedisCache) SetStream(ctx rcontext.RequestContext, key string, s io.Reader) error { + b, err := ioutil.ReadAll(s) + if err != nil { + return err + } + return c.SetBytes(ctx, key, b) +} + +func (c *RedisCache) GetStream(ctx rcontext.RequestContext, key string) (io.Reader, error) { + b, err := c.GetBytes(ctx, key) + if err != nil { + return nil, err + } + return bytes.NewBuffer(b), nil +} + +func (c *RedisCache) SetBytes(ctx rcontext.RequestContext, key string, b []byte) error { + if c.ring.PoolStats().TotalConns == 0 { + return ErrCacheDown + } + _, err := c.ring.Set(ctx.Context, key, b, time.Duration(0)).Result() // no expiration (zero) + if err != nil && c.ring.PoolStats().TotalConns == 0 { + ctx.Log.Error(err) + return ErrCacheDown + } + return err +} + +func (c *RedisCache) GetBytes(ctx rcontext.RequestContext, key string) ([]byte, error) { + if c.ring.PoolStats().TotalConns == 0 { + return nil, ErrCacheDown + } + r := c.ring.Get(ctx.Context, key) + if r.Err() != nil { + if r.Err() == redis.Nil { + return nil, ErrCacheMiss + } + if c.ring.PoolStats().TotalConns == 0 { + ctx.Log.Error(r.Err()) + return nil, ErrCacheDown + } + return nil, r.Err() + } + + b, err := r.Bytes() + return b, err +}