andrey1s
4 years ago
commit
7da0cd57ce
45 changed files with 3703 additions and 0 deletions
@ -0,0 +1,23 @@ |
|||
kind: pipeline |
|||
name: default |
|||
|
|||
services: |
|||
- name: redis |
|||
image: redis |
|||
- name: memcache |
|||
image: memcached |
|||
|
|||
environment: |
|||
FDEVS_CACHE_REDIS_HOST: redis:6379 |
|||
FDEVS_CACHE_MEMCACHE_HOST: memcache:11211 |
|||
|
|||
steps: |
|||
- name: test |
|||
image: golang |
|||
commands: |
|||
- go test ./... |
|||
|
|||
- name: golangci-lint |
|||
image: golangci/golangci-lint:v1.39 |
|||
commands: |
|||
- golangci-lint run |
@ -0,0 +1,17 @@ |
|||
# ---> Go |
|||
# Binaries for programs and plugins |
|||
*.exe |
|||
*.exe~ |
|||
*.dll |
|||
*.so |
|||
*.dylib |
|||
|
|||
# Test binary, built with `go test -c` |
|||
*.test |
|||
|
|||
# Output of the go coverage tool, specifically when used with LiteIDE |
|||
*.out |
|||
|
|||
# Dependency directories (remove the comment below to include it) |
|||
# vendor/ |
|||
|
@ -0,0 +1,43 @@ |
|||
linters-settings: |
|||
dupl: |
|||
threshold: 100 |
|||
funlen: |
|||
lines: 100 |
|||
statements: 50 |
|||
goconst: |
|||
min-len: 2 |
|||
min-occurrences: 2 |
|||
gocyclo: |
|||
min-complexity: 15 |
|||
golint: |
|||
min-confidence: 0 |
|||
govet: |
|||
check-shadowing: true |
|||
lll: |
|||
line-length: 140 |
|||
maligned: |
|||
suggest-new: true |
|||
misspell: |
|||
locale: US |
|||
|
|||
linters: |
|||
enable-all: true |
|||
disable: |
|||
- exhaustivestruct |
|||
- maligned |
|||
- interfacer |
|||
- scopelint |
|||
|
|||
issues: |
|||
# Excluding configuration per-path, per-linter, per-text and per-source |
|||
exclude-rules: |
|||
- path: _test\.go |
|||
linters: |
|||
- gomnd |
|||
- exhaustivestruct |
|||
- wrapcheck |
|||
- path: test/* |
|||
linters: |
|||
- gomnd |
|||
- exhaustivestruct |
|||
- wrapcheck |
@ -0,0 +1,19 @@ |
|||
MIT License Copyright (c) 2020 go-4devs |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is furnished |
|||
to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice (including the next |
|||
paragraph) shall be included in all copies or substantial portions of the |
|||
Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
|||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS |
|||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
|||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF |
|||
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,378 @@ |
|||
# cache |
|||
|
|||
[![Build Status](https://drone.gitoa.ru/api/badges/go-4devs/cache/status.svg)](https://drone.gitoa.ru/go-4devs/cache) |
|||
[![Go Report Card](https://goreportcard.com/badge/gitoa.ru/go-4devs/cache)](https://goreportcard.com/report/gitoa.ru/go-4devs/cache) |
|||
[![GoDoc](https://godoc.org/gitoa.ru/go-4devs/cache?status.svg)](http://godoc.org/gitoa.ru/go-4devs/cache) |
|||
|
|||
## Benchmark cache |
|||
|
|||
`go test -v -timeout 25m -cpu 1,2,4,8,16 -benchmem -run=^$ -bench . bench_test.go` |
|||
|
|||
```bash |
|||
goos: darwin |
|||
goarch: amd64 |
|||
BenchmarkCacheGetStruct |
|||
BenchmarkCacheGetStruct/encoding_json |
|||
BenchmarkCacheGetStruct/encoding_json 1519932 783 ns/op 320 B/op 6 allocs/op |
|||
BenchmarkCacheGetStruct/encoding_json-2 1478414 780 ns/op 320 B/op 6 allocs/op |
|||
BenchmarkCacheGetStruct/encoding_json-4 1353025 916 ns/op 320 B/op 6 allocs/op |
|||
BenchmarkCacheGetStruct/encoding_json-8 1284042 839 ns/op 320 B/op 6 allocs/op |
|||
BenchmarkCacheGetStruct/encoding_json-16 1422788 848 ns/op 320 B/op 6 allocs/op |
|||
BenchmarkCacheGetStruct/encoding_gob |
|||
BenchmarkCacheGetStruct/encoding_gob 83661 15323 ns/op 6944 B/op 180 allocs/op |
|||
BenchmarkCacheGetStruct/encoding_gob-2 81745 14407 ns/op 6944 B/op 180 allocs/op |
|||
BenchmarkCacheGetStruct/encoding_gob-4 73537 15142 ns/op 6944 B/op 180 allocs/op |
|||
BenchmarkCacheGetStruct/encoding_gob-8 85412 14494 ns/op 6944 B/op 180 allocs/op |
|||
BenchmarkCacheGetStruct/encoding_gob-16 75748 15219 ns/op 6944 B/op 180 allocs/op |
|||
BenchmarkCacheGetStruct/map |
|||
BenchmarkCacheGetStruct/map 6162325 199 ns/op 96 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/map-2 5740689 195 ns/op 96 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/map-4 6018531 200 ns/op 96 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/map-8 5452492 210 ns/op 96 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/map-16 5933622 202 ns/op 96 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/map_shards |
|||
BenchmarkCacheGetStruct/map_shards 5299807 230 ns/op 96 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/map_shards-2 5087726 238 ns/op 96 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/map_shards-4 4990490 243 ns/op 96 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/map_shards-8 4899127 225 ns/op 96 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/map_shards-16 5229320 233 ns/op 96 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/ristretto |
|||
BenchmarkCacheGetStruct/ristretto 5511872 227 ns/op 96 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/ristretto-2 4664298 257 ns/op 103 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/ristretto-4 4524751 265 ns/op 103 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/ristretto-8 4425381 260 ns/op 103 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/ristretto-16 4649698 258 ns/op 103 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/lru |
|||
BenchmarkCacheGetStruct/lru 4730811 250 ns/op 144 B/op 2 allocs/op |
|||
BenchmarkCacheGetStruct/lru-2 4627194 252 ns/op 144 B/op 2 allocs/op |
|||
BenchmarkCacheGetStruct/lru-4 4627082 257 ns/op 144 B/op 2 allocs/op |
|||
BenchmarkCacheGetStruct/lru-8 4755622 252 ns/op 144 B/op 2 allocs/op |
|||
BenchmarkCacheGetStruct/lru-16 4717584 250 ns/op 144 B/op 2 allocs/op |
|||
BenchmarkCacheGetStruct/redis_json |
|||
BenchmarkCacheGetStruct/redis_json 572 2132479 ns/op 9848 B/op 34 allocs/op |
|||
BenchmarkCacheGetStruct/redis_json-2 565 2161113 ns/op 9848 B/op 34 allocs/op |
|||
BenchmarkCacheGetStruct/redis_json-4 543 2183219 ns/op 9848 B/op 34 allocs/op |
|||
BenchmarkCacheGetStruct/redis_json-8 531 2148630 ns/op 9848 B/op 34 allocs/op |
|||
BenchmarkCacheGetStruct/redis_json-16 544 2212659 ns/op 9848 B/op 34 allocs/op |
|||
BenchmarkCacheGetStruct/redis_gob |
|||
BenchmarkCacheGetStruct/redis_gob 553 2206583 ns/op 16504 B/op 208 allocs/op |
|||
BenchmarkCacheGetStruct/redis_gob-2 549 2256638 ns/op 16505 B/op 208 allocs/op |
|||
BenchmarkCacheGetStruct/redis_gob-4 540 2230342 ns/op 16504 B/op 208 allocs/op |
|||
BenchmarkCacheGetStruct/redis_gob-8 537 2178895 ns/op 16504 B/op 208 allocs/op |
|||
BenchmarkCacheGetStruct/redis_gob-16 541 2206298 ns/op 16504 B/op 208 allocs/op |
|||
BenchmarkCacheGetStruct/memcache_json |
|||
BenchmarkCacheGetStruct/memcache_json 1352 882575 ns/op 560 B/op 16 allocs/op |
|||
BenchmarkCacheGetStruct/memcache_json-2 1332 869724 ns/op 560 B/op 16 allocs/op |
|||
BenchmarkCacheGetStruct/memcache_json-4 1326 824555 ns/op 561 B/op 16 allocs/op |
|||
BenchmarkCacheGetStruct/memcache_json-8 1375 880741 ns/op 562 B/op 16 allocs/op |
|||
BenchmarkCacheGetStruct/memcache_json-16 1346 872861 ns/op 563 B/op 16 allocs/op |
|||
BenchmarkCacheGetStruct/memcache_gob |
|||
BenchmarkCacheGetStruct/memcache_gob 1431 828348 ns/op 7216 B/op 190 allocs/op |
|||
BenchmarkCacheGetStruct/memcache_gob-2 1266 875339 ns/op 7216 B/op 190 allocs/op |
|||
BenchmarkCacheGetStruct/memcache_gob-4 1327 908142 ns/op 7218 B/op 190 allocs/op |
|||
BenchmarkCacheGetStruct/memcache_gob-8 1286 840878 ns/op 7219 B/op 190 allocs/op |
|||
BenchmarkCacheGetStruct/memcache_gob-16 1540 797765 ns/op 7220 B/op 190 allocs/op |
|||
``` |
|||
|
|||
## Benchmark providers |
|||
|
|||
`go test -v -timeout 25m -cpu 1,2,4,8,16 -benchmem -run=^$ -bench . ./provider/bench_provider_test.go` |
|||
|
|||
```bash |
|||
goos: darwin |
|||
goarch: amd64 |
|||
BenchmarkCacheGetRandomKeyString |
|||
BenchmarkCacheGetRandomKeyString/encoding |
|||
BenchmarkCacheGetRandomKeyString/encoding 3100226 389 ns/op 192 B/op 2 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/encoding-2 3142849 379 ns/op 192 B/op 2 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/encoding-4 3118212 379 ns/op 192 B/op 2 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/encoding-8 3064170 387 ns/op 192 B/op 2 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/encoding-16 3128031 384 ns/op 192 B/op 2 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/map |
|||
BenchmarkCacheGetRandomKeyString/map 7342993 157 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/map-2 7268864 158 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/map-4 7233045 162 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/map-8 7393652 159 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/map-16 7463053 159 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/shard |
|||
BenchmarkCacheGetRandomKeyString/shard 3330136 351 ns/op 64 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/shard-2 3518775 335 ns/op 64 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/shard-4 3477537 336 ns/op 64 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/shard-8 3514064 335 ns/op 64 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/shard-16 3412119 341 ns/op 64 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/lru |
|||
BenchmarkCacheGetRandomKeyString/lru 5013633 249 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/lru-2 4871456 247 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/lru-4 4786940 238 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/lru-8 4721556 238 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/lru-16 4870622 241 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/ristretto |
|||
BenchmarkCacheGetRandomKeyString/ristretto 5569208 205 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/ristretto-2 3892068 295 ns/op 7 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/ristretto-4 4490196 266 ns/op 7 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/ristretto-8 4381441 266 ns/op 7 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/ristretto-16 4185096 273 ns/op 7 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/memcache |
|||
BenchmarkCacheGetRandomKeyString/memcache 1492 811587 ns/op 528 B/op 12 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/memcache-2 1400 840429 ns/op 528 B/op 12 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/memcache-4 1381 793654 ns/op 528 B/op 12 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/memcache-8 1455 826461 ns/op 530 B/op 12 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/memcache-16 1380 803712 ns/op 532 B/op 12 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/redis |
|||
BenchmarkCacheGetRandomKeyString/redis 540 2908289 ns/op 9704 B/op 30 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/redis-2 514 2287030 ns/op 9704 B/op 30 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/redis-4 542 2195917 ns/op 9704 B/op 30 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/redis-8 536 2209508 ns/op 9704 B/op 30 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/redis-16 544 2275867 ns/op 9704 B/op 30 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/pebble |
|||
BenchmarkCacheGetRandomKeyString/pebble 672912 1801 ns/op 1408 B/op 6 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/pebble-2 773318 1691 ns/op 1408 B/op 6 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/pebble-4 729020 1556 ns/op 1408 B/op 6 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/pebble-8 778066 1491 ns/op 1408 B/op 6 allocs/op |
|||
BenchmarkCacheGetRandomKeyString/pebble-16 838596 1441 ns/op 1408 B/op 6 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt |
|||
BenchmarkCacheGetRandomKeyInt/encoding |
|||
BenchmarkCacheGetRandomKeyInt/encoding 2825020 410 ns/op 207 B/op 2 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/encoding-2 2932910 409 ns/op 207 B/op 2 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/encoding-4 2837827 408 ns/op 207 B/op 2 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/encoding-8 2842040 418 ns/op 207 B/op 2 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/encoding-16 2866555 409 ns/op 207 B/op 2 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/map |
|||
BenchmarkCacheGetRandomKeyInt/map 7312549 150 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/map-2 7884612 150 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/map-4 7450554 158 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/map-8 7471407 156 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/map-16 7469587 158 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/shard |
|||
BenchmarkCacheGetRandomKeyInt/shard 6709964 187 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/shard-2 6430581 183 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/shard-4 6375858 187 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/shard-8 6399346 180 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/shard-16 6580282 175 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/lru |
|||
BenchmarkCacheGetRandomKeyInt/lru 5183596 225 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/lru-2 5217847 220 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/lru-4 5078146 223 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/lru-8 4722044 225 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/lru-16 4989286 224 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/ristretto |
|||
BenchmarkCacheGetRandomKeyInt/ristretto 6920838 169 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/ristretto-2 4763511 216 ns/op 7 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/ristretto-4 5163074 220 ns/op 7 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/ristretto-8 5133212 220 ns/op 7 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/ristretto-16 5089780 219 ns/op 7 B/op 0 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/memcache |
|||
BenchmarkCacheGetRandomKeyInt/memcache 1332 820272 ns/op 544 B/op 14 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/memcache-2 1408 840124 ns/op 544 B/op 14 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/memcache-4 1443 809845 ns/op 544 B/op 14 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/memcache-8 1449 832162 ns/op 545 B/op 14 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/memcache-16 1333 855560 ns/op 547 B/op 14 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/redis |
|||
BenchmarkCacheGetRandomKeyInt/redis 525 2211523 ns/op 9767 B/op 31 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/redis-2 542 2146253 ns/op 9767 B/op 31 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/redis-4 531 2271602 ns/op 9767 B/op 31 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/redis-8 522 2273678 ns/op 9767 B/op 31 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/redis-16 552 2180911 ns/op 9767 B/op 31 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/pebble |
|||
BenchmarkCacheGetRandomKeyInt/pebble 752023 1575 ns/op 1359 B/op 7 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/pebble-2 699300 1557 ns/op 1359 B/op 7 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/pebble-4 730688 1534 ns/op 1359 B/op 7 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/pebble-8 768183 1508 ns/op 1359 B/op 7 allocs/op |
|||
BenchmarkCacheGetRandomKeyInt/pebble-16 735848 1506 ns/op 1359 B/op 7 allocs/op |
|||
BenchmarkCacheGetStruct |
|||
BenchmarkCacheGetStruct/encoding |
|||
BenchmarkCacheGetStruct/encoding 2252955 524 ns/op 208 B/op 4 allocs/op |
|||
BenchmarkCacheGetStruct/encoding-2 2332430 515 ns/op 208 B/op 4 allocs/op |
|||
BenchmarkCacheGetStruct/encoding-4 2251696 525 ns/op 208 B/op 4 allocs/op |
|||
BenchmarkCacheGetStruct/encoding-8 2235301 520 ns/op 208 B/op 4 allocs/op |
|||
BenchmarkCacheGetStruct/encoding-16 2224682 527 ns/op 208 B/op 4 allocs/op |
|||
BenchmarkCacheGetStruct/map |
|||
BenchmarkCacheGetStruct/map 8009500 141 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/map-2 8406175 143 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/map-4 8249924 145 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/map-8 8324671 145 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/map-16 8102042 145 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/shard |
|||
BenchmarkCacheGetStruct/shard 7179788 164 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/shard-2 7332114 164 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/shard-4 6999268 174 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/shard-8 7028054 170 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/shard-16 6986014 170 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/lru |
|||
BenchmarkCacheGetStruct/lru 5818656 207 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/lru-2 5859214 204 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/lru-4 5518066 210 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/lru-8 5618907 209 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/lru-16 5617592 214 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetStruct/ristretto |
|||
BenchmarkCacheGetStruct/ristretto 7409641 158 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/ristretto-2 6809439 175 ns/op 7 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/ristretto-4 6004058 194 ns/op 7 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/ristretto-8 6170220 192 ns/op 7 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/ristretto-16 6219170 190 ns/op 7 B/op 0 allocs/op |
|||
BenchmarkCacheGetStruct/memcache |
|||
BenchmarkCacheGetStruct/memcache 1412 801366 ns/op 424 B/op 14 allocs/op |
|||
BenchmarkCacheGetStruct/memcache-2 1395 845730 ns/op 424 B/op 14 allocs/op |
|||
BenchmarkCacheGetStruct/memcache-4 1454 754811 ns/op 424 B/op 14 allocs/op |
|||
BenchmarkCacheGetStruct/memcache-8 1509 754192 ns/op 425 B/op 14 allocs/op |
|||
BenchmarkCacheGetStruct/memcache-16 1354 800273 ns/op 428 B/op 14 allocs/op |
|||
BenchmarkCacheGetStruct/redis |
|||
BenchmarkCacheGetStruct/redis 553 2131603 ns/op 9720 B/op 32 allocs/op |
|||
BenchmarkCacheGetStruct/redis-2 548 2139096 ns/op 9720 B/op 32 allocs/op |
|||
BenchmarkCacheGetStruct/redis-4 537 2211997 ns/op 9720 B/op 32 allocs/op |
|||
BenchmarkCacheGetStruct/redis-8 524 2189316 ns/op 9720 B/op 32 allocs/op |
|||
BenchmarkCacheGetStruct/redis-16 548 2185637 ns/op 9720 B/op 32 allocs/op |
|||
BenchmarkCacheGetStruct/pebble |
|||
BenchmarkCacheGetStruct/pebble 1427671 796 ns/op 1248 B/op 7 allocs/op |
|||
BenchmarkCacheGetStruct/pebble-2 1448547 830 ns/op 1248 B/op 7 allocs/op |
|||
BenchmarkCacheGetStruct/pebble-4 1405844 835 ns/op 1248 B/op 7 allocs/op |
|||
BenchmarkCacheGetStruct/pebble-8 1441484 831 ns/op 1248 B/op 7 allocs/op |
|||
BenchmarkCacheGetStruct/pebble-16 1387006 827 ns/op 1248 B/op 7 allocs/op |
|||
BenchmarkCacheSetStruct |
|||
BenchmarkCacheSetStruct/encoding |
|||
BenchmarkCacheSetStruct/encoding 1000000 1625 ns/op 651 B/op 5 allocs/op |
|||
BenchmarkCacheSetStruct/encoding-2 1720123 945 ns/op 457 B/op 5 allocs/op |
|||
BenchmarkCacheSetStruct/encoding-4 1669809 705 ns/op 183 B/op 4 allocs/op |
|||
BenchmarkCacheSetStruct/encoding-8 1657442 706 ns/op 183 B/op 4 allocs/op |
|||
BenchmarkCacheSetStruct/encoding-16 1648228 709 ns/op 184 B/op 4 allocs/op |
|||
BenchmarkCacheSetStruct/map |
|||
BenchmarkCacheSetStruct/map 1000000 1280 ns/op 410 B/op 9 allocs/op |
|||
BenchmarkCacheSetStruct/map-2 1878842 1517 ns/op 341 B/op 7 allocs/op |
|||
BenchmarkCacheSetStruct/map-4 1790534 692 ns/op 263 B/op 6 allocs/op |
|||
BenchmarkCacheSetStruct/map-8 1792663 665 ns/op 263 B/op 6 allocs/op |
|||
BenchmarkCacheSetStruct/map-16 1762833 677 ns/op 263 B/op 6 allocs/op |
|||
BenchmarkCacheSetStruct/shard |
|||
BenchmarkCacheSetStruct/shard 1000000 1437 ns/op 411 B/op 9 allocs/op |
|||
BenchmarkCacheSetStruct/shard-2 1716608 830 ns/op 346 B/op 7 allocs/op |
|||
BenchmarkCacheSetStruct/shard-4 1647408 736 ns/op 263 B/op 6 allocs/op |
|||
BenchmarkCacheSetStruct/shard-8 1657657 710 ns/op 263 B/op 6 allocs/op |
|||
BenchmarkCacheSetStruct/shard-16 1651122 711 ns/op 263 B/op 6 allocs/op |
|||
BenchmarkCacheSetStruct/lru |
|||
BenchmarkCacheSetStruct/lru 1669929 717 ns/op 330 B/op 8 allocs/op |
|||
BenchmarkCacheSetStruct/lru-2 1666970 686 ns/op 330 B/op 8 allocs/op |
|||
BenchmarkCacheSetStruct/lru-4 1569268 707 ns/op 330 B/op 8 allocs/op |
|||
BenchmarkCacheSetStruct/lru-8 1569517 701 ns/op 330 B/op 8 allocs/op |
|||
BenchmarkCacheSetStruct/lru-16 1569993 720 ns/op 330 B/op 8 allocs/op |
|||
BenchmarkCacheSetStruct/ristretto |
|||
BenchmarkCacheSetStruct/ristretto 1665415 1203 ns/op 406 B/op 5 allocs/op |
|||
BenchmarkCacheSetStruct/ristretto-2 1000000 1111 ns/op 325 B/op 5 allocs/op |
|||
BenchmarkCacheSetStruct/ristretto-4 1000000 1204 ns/op 319 B/op 5 allocs/op |
|||
BenchmarkCacheSetStruct/ristretto-8 1000000 1193 ns/op 319 B/op 5 allocs/op |
|||
BenchmarkCacheSetStruct/ristretto-16 946750 1171 ns/op 324 B/op 5 allocs/op |
|||
BenchmarkCacheSetStruct/memcache |
|||
BenchmarkCacheSetStruct/memcache 1572 733672 ns/op 286 B/op 9 allocs/op |
|||
BenchmarkCacheSetStruct/memcache-2 1341 799704 ns/op 286 B/op 9 allocs/op |
|||
BenchmarkCacheSetStruct/memcache-4 1492 810459 ns/op 287 B/op 9 allocs/op |
|||
BenchmarkCacheSetStruct/memcache-8 1500 807919 ns/op 289 B/op 9 allocs/op |
|||
BenchmarkCacheSetStruct/memcache-16 1598 773923 ns/op 290 B/op 9 allocs/op |
|||
BenchmarkCacheSetStruct/redis |
|||
BenchmarkCacheSetStruct/redis 848 1312946 ns/op 9788 B/op 35 allocs/op |
|||
BenchmarkCacheSetStruct/redis-2 834 1370112 ns/op 9789 B/op 35 allocs/op |
|||
BenchmarkCacheSetStruct/redis-4 858 1367748 ns/op 9789 B/op 35 allocs/op |
|||
BenchmarkCacheSetStruct/redis-8 906 1348890 ns/op 9790 B/op 35 allocs/op |
|||
BenchmarkCacheSetStruct/redis-16 856 1377737 ns/op 9791 B/op 35 allocs/op |
|||
BenchmarkCacheSetStruct/pebble |
|||
BenchmarkCacheSetStruct/pebble 172 6891869 ns/op 179 B/op 4 allocs/op |
|||
BenchmarkCacheSetStruct/pebble-2 176 7100201 ns/op 189 B/op 4 allocs/op |
|||
BenchmarkCacheSetStruct/pebble-4 176 6765299 ns/op 417 B/op 4 allocs/op |
|||
BenchmarkCacheSetStruct/pebble-8 174 6709812 ns/op 196 B/op 4 allocs/op |
|||
BenchmarkCacheSetStruct/pebble-16 176 6872531 ns/op 207 B/op 4 allocs/op |
|||
BenchmarkCacheGetParallel |
|||
BenchmarkCacheGetParallel/encoding |
|||
BenchmarkCacheGetParallel/encoding 3755816 393 ns/op 192 B/op 2 allocs/op |
|||
BenchmarkCacheGetParallel/encoding-2 6620756 200 ns/op 192 B/op 2 allocs/op |
|||
BenchmarkCacheGetParallel/encoding-4 10706964 126 ns/op 192 B/op 2 allocs/op |
|||
BenchmarkCacheGetParallel/encoding-8 15889144 83.4 ns/op 192 B/op 2 allocs/op |
|||
BenchmarkCacheGetParallel/encoding-16 18838454 67.2 ns/op 192 B/op 2 allocs/op |
|||
BenchmarkCacheGetParallel/map |
|||
BenchmarkCacheGetParallel/map 8287477 137 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/map-2 11197053 101 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/map-4 19310756 58.6 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/map-8 28979271 37.3 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/map-16 40122621 25.3 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/shard |
|||
BenchmarkCacheGetParallel/shard 6388084 175 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/shard-2 9824578 119 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/shard-4 16162353 70.2 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/shard-8 23337940 45.7 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/shard-16 34489749 31.5 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/lru |
|||
BenchmarkCacheGetParallel/lru 5497556 219 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetParallel/lru-2 5108966 239 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetParallel/lru-4 4236541 277 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetParallel/lru-8 3867518 313 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetParallel/lru-16 3719572 323 ns/op 48 B/op 1 allocs/op |
|||
BenchmarkCacheGetParallel/ristretto |
|||
BenchmarkCacheGetParallel/ristretto 6272048 170 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/ristretto-2 10652374 103 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/ristretto-4 15653863 73.7 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/ristretto-8 17346794 64.7 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/ristretto-16 18895278 57.4 ns/op 0 B/op 0 allocs/op |
|||
BenchmarkCacheGetParallel/memcache |
|||
BenchmarkCacheGetParallel/memcache 1398 868052 ns/op 432 B/op 12 allocs/op |
|||
BenchmarkCacheGetParallel/memcache-2 2768 446176 ns/op 432 B/op 12 allocs/op |
|||
BenchmarkCacheGetParallel/memcache-4 4094 244463 ns/op 439 B/op 12 allocs/op |
|||
BenchmarkCacheGetParallel/memcache-8 8526 141027 ns/op 642 B/op 12 allocs/op |
|||
BenchmarkCacheGetParallel/memcache-16 10852 108739 ns/op 871 B/op 13 allocs/op |
|||
BenchmarkCacheGetParallel/redis |
|||
BenchmarkCacheGetParallel/redis 524 2255655 ns/op 9704 B/op 30 allocs/op |
|||
BenchmarkCacheGetParallel/redis-2 933 1244186 ns/op 9704 B/op 30 allocs/op |
|||
BenchmarkCacheGetParallel/redis-4 1560 790918 ns/op 9704 B/op 30 allocs/op |
|||
BenchmarkCacheGetParallel/redis-8 1885 613956 ns/op 9704 B/op 30 allocs/op |
|||
BenchmarkCacheGetParallel/redis-16 2102 558509 ns/op 9704 B/op 30 allocs/op |
|||
BenchmarkCacheGetParallel/pebble |
|||
BenchmarkCacheGetParallel/pebble 1377561 970 ns/op 1248 B/op 5 allocs/op |
|||
BenchmarkCacheGetParallel/pebble-2 2307592 630 ns/op 1248 B/op 5 allocs/op |
|||
BenchmarkCacheGetParallel/pebble-4 3651379 352 ns/op 1248 B/op 5 allocs/op |
|||
BenchmarkCacheGetParallel/pebble-8 5771799 222 ns/op 1248 B/op 5 allocs/op |
|||
BenchmarkCacheGetParallel/pebble-16 7370930 187 ns/op 1248 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel |
|||
BenchmarkCacheSetParallel/encoding |
|||
BenchmarkCacheSetParallel/encoding 2877936 417 ns/op 176 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel/encoding-2 3406563 377 ns/op 176 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel/encoding-4 3595508 334 ns/op 176 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel/encoding-8 3169240 378 ns/op 176 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel/encoding-16 3012076 400 ns/op 176 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel/map |
|||
BenchmarkCacheSetParallel/map 2573331 523 ns/op 256 B/op 7 allocs/op |
|||
BenchmarkCacheSetParallel/map-2 3006007 453 ns/op 256 B/op 7 allocs/op |
|||
BenchmarkCacheSetParallel/map-4 3168489 392 ns/op 256 B/op 7 allocs/op |
|||
BenchmarkCacheSetParallel/map-8 2839058 440 ns/op 256 B/op 7 allocs/op |
|||
BenchmarkCacheSetParallel/map-16 2834168 431 ns/op 256 B/op 7 allocs/op |
|||
BenchmarkCacheSetParallel/shard |
|||
BenchmarkCacheSetParallel/shard 2250218 516 ns/op 256 B/op 7 allocs/op |
|||
BenchmarkCacheSetParallel/shard-2 3581533 370 ns/op 256 B/op 7 allocs/op |
|||
BenchmarkCacheSetParallel/shard-4 3066703 415 ns/op 256 B/op 7 allocs/op |
|||
BenchmarkCacheSetParallel/shard-8 2774422 428 ns/op 256 B/op 7 allocs/op |
|||
BenchmarkCacheSetParallel/shard-16 2749574 432 ns/op 256 B/op 7 allocs/op |
|||
BenchmarkCacheSetParallel/lru |
|||
BenchmarkCacheSetParallel/lru 3272673 430 ns/op 240 B/op 6 allocs/op |
|||
BenchmarkCacheSetParallel/lru-2 4692276 278 ns/op 240 B/op 6 allocs/op |
|||
BenchmarkCacheSetParallel/lru-4 3994620 312 ns/op 240 B/op 6 allocs/op |
|||
BenchmarkCacheSetParallel/lru-8 3531354 341 ns/op 240 B/op 6 allocs/op |
|||
BenchmarkCacheSetParallel/lru-16 3414451 353 ns/op 240 B/op 6 allocs/op |
|||
BenchmarkCacheSetParallel/ristretto |
|||
BenchmarkCacheSetParallel/ristretto 2669528 456 ns/op 224 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel/ristretto-2 2214732 547 ns/op 224 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel/ristretto-4 2122172 564 ns/op 224 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel/ristretto-8 1858959 639 ns/op 224 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel/ristretto-16 1821427 656 ns/op 224 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel/memcache |
|||
BenchmarkCacheSetParallel/memcache 1395469 863 ns/op 352 B/op 8 allocs/op |
|||
BenchmarkCacheSetParallel/memcache-2 1672177 705 ns/op 352 B/op 8 allocs/op |
|||
BenchmarkCacheSetParallel/memcache-4 848569 1414 ns/op 406 B/op 8 allocs/op |
|||
BenchmarkCacheSetParallel/memcache-8 742070 1361 ns/op 402 B/op 8 allocs/op |
|||
BenchmarkCacheSetParallel/memcache-16 1346508 950 ns/op 353 B/op 8 allocs/op |
|||
BenchmarkCacheSetParallel/redis |
|||
BenchmarkCacheSetParallel/redis 100 21280373 ns/op 1526 B/op 31 allocs/op |
|||
BenchmarkCacheSetParallel/redis-2 908 1203995 ns/op 1520 B/op 31 allocs/op |
|||
BenchmarkCacheSetParallel/redis-4 901 1171409 ns/op 1520 B/op 31 allocs/op |
|||
BenchmarkCacheSetParallel/redis-8 948 1185400 ns/op 1521 B/op 31 allocs/op |
|||
BenchmarkCacheSetParallel/redis-16 852 1247485 ns/op 1523 B/op 31 allocs/op |
|||
BenchmarkCacheSetParallel/pebble |
|||
BenchmarkCacheSetParallel/pebble 174 6865631 ns/op 178 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel/pebble-2 189 6658668 ns/op 397 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel/pebble-4 360 3477886 ns/op 184 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel/pebble-8 717 1716858 ns/op 184 B/op 5 allocs/op |
|||
BenchmarkCacheSetParallel/pebble-16 1417 956456 ns/op 182 B/op 5 allocs/op |
|||
``` |
@ -0,0 +1,67 @@ |
|||
package cache_test |
|||
|
|||
import ( |
|||
"context" |
|||
"testing" |
|||
"time" |
|||
|
|||
glru "github.com/hashicorp/golang-lru" |
|||
"github.com/stretchr/testify/require" |
|||
"gitoa.ru/go-4devs/cache" |
|||
"gitoa.ru/go-4devs/cache/provider/lru" |
|||
"gitoa.ru/go-4devs/cache/provider/memcache" |
|||
"gitoa.ru/go-4devs/cache/provider/memory" |
|||
"gitoa.ru/go-4devs/cache/provider/pebble" |
|||
"gitoa.ru/go-4devs/cache/provider/redis" |
|||
"gitoa.ru/go-4devs/cache/provider/ristretto" |
|||
"gitoa.ru/go-4devs/cache/test" |
|||
"gitoa.ru/go-4devs/encoding/gob" |
|||
) |
|||
|
|||
type cacheBench struct { |
|||
name string |
|||
cache *cache.Cache |
|||
} |
|||
|
|||
func cacheBenchList() []cacheBench { |
|||
client, _ := glru.New(10000) |
|||
db, cl := test.PebbleDB() |
|||
|
|||
defer cl() |
|||
|
|||
return []cacheBench{ |
|||
{"encoding json", cache.New(memory.NewEncoding())}, |
|||
{"encoding gob", cache.New(memory.NewEncoding(), cache.WithDataOption(cache.WithMarshal(gob.Unmarshal, gob.Marshal)))}, |
|||
{"map", cache.New(memory.NewMap())}, |
|||
{"map shards", cache.New(memory.NewMapShard())}, |
|||
{"ristretto", cache.New(ristretto.New(test.RistrettoClient()))}, |
|||
{"lru", cache.New(lru.New(client))}, |
|||
{"redis json", cache.New(redis.New(test.RedisClient()))}, |
|||
{"redis gob", cache.New(redis.New(test.RedisClient()), cache.WithDataOption(cache.WithMarshal(gob.Unmarshal, gob.Marshal)))}, |
|||
{"memcache json", cache.New(memcache.New(test.MemcacheClient()))}, |
|||
{"memcache gob", cache.New(memcache.New(test.MemcacheClient()), cache.WithDataOption(cache.WithMarshal(gob.Unmarshal, gob.Marshal)))}, |
|||
{"pebble json", cache.New(pebble.New(db))}, |
|||
} |
|||
} |
|||
|
|||
type testStruct struct { |
|||
Key string |
|||
Val string |
|||
} |
|||
|
|||
func BenchmarkCacheGetStruct(b *testing.B) { |
|||
ctx := context.Background() |
|||
|
|||
var val testStruct |
|||
|
|||
for _, c := range cacheBenchList() { |
|||
current := c.cache |
|||
require.Nil(b, current.Set(ctx, "key", testStruct{"key", c.name}, cache.WithTTL(time.Minute))) |
|||
|
|||
b.Run(c.name, func(b *testing.B) { |
|||
for i := 0; i < b.N; i++ { |
|||
_ = current.Get(ctx, "key", &val) |
|||
} |
|||
}) |
|||
} |
|||
} |
@ -0,0 +1,121 @@ |
|||
package cache |
|||
|
|||
import ( |
|||
"context" |
|||
"time" |
|||
) |
|||
|
|||
// Configure configure cache.
|
|||
type Configure func(*Cache) |
|||
|
|||
// WithDataOption sets cache default data options.
|
|||
func WithDataOption(do ...Option) Configure { |
|||
return func(c *Cache) { |
|||
factory := c.dataFactory |
|||
c.dataFactory = func(key, value interface{}, opts ...Option) *Item { |
|||
return factory(key, value, append(do, opts...)...) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// WithDefaultNamespace sets cache default namespace.
|
|||
func WithDefaultNamespace(ns, separator string) Configure { |
|||
return WithDataOption(WithNamespace(ns, separator)) |
|||
} |
|||
|
|||
// WithDefaultTTL sets cache default ttl.
|
|||
func WithDefaultTTL(ttl time.Duration) Configure { |
|||
return WithDataOption(WithTTL(ttl)) |
|||
} |
|||
|
|||
// WithHandleSet add a handler for the set operation.
|
|||
func WithHandleSet(m ...Handle) Configure { |
|||
return WithHandleOperation(OperationSet, m...) |
|||
} |
|||
|
|||
// WithHandleGet add a handler for the get operation.
|
|||
func WithHandleGet(m ...Handle) Configure { |
|||
return WithHandleOperation(OperationGet, m...) |
|||
} |
|||
|
|||
// WithHandleDelete add a handler for the delete operation.
|
|||
func WithHandleDelete(m ...Handle) Configure { |
|||
return WithHandleOperation(OperationDelete, m...) |
|||
} |
|||
|
|||
// WithHandleOperation add a handler for the operation.
|
|||
func WithHandleOperation(op string, m ...Handle) Configure { |
|||
handle := ChainHandle(m...) |
|||
|
|||
return WithMiddleware(func(ctx context.Context, operation string, item *Item, next Provider) error { |
|||
if operation == op { |
|||
return handle(ctx, op, item, next) |
|||
} |
|||
|
|||
return next(ctx, operation, item) |
|||
}) |
|||
} |
|||
|
|||
// WithMiddleware sets middleware to provider.
|
|||
func WithMiddleware(mw ...Handle) Configure { |
|||
return func(c *Cache) { |
|||
prov := c.provider |
|||
c.provider = chain(prov, mw...) |
|||
} |
|||
} |
|||
|
|||
// New creates new cache by provider.
|
|||
func New(prov Provider, opts ...Configure) *Cache { |
|||
c := &Cache{ |
|||
provider: prov, |
|||
dataFactory: NewItem, |
|||
} |
|||
|
|||
for _, o := range opts { |
|||
o(c) |
|||
} |
|||
|
|||
return c |
|||
} |
|||
|
|||
// Cache base cache.
|
|||
type Cache struct { |
|||
dataFactory func(key, value interface{}, opts ...Option) *Item |
|||
provider Provider |
|||
} |
|||
|
|||
func (c *Cache) With(opts ...Configure) *Cache { |
|||
cache := &Cache{ |
|||
provider: c.provider, |
|||
dataFactory: c.dataFactory, |
|||
} |
|||
|
|||
for _, o := range opts { |
|||
o(cache) |
|||
} |
|||
|
|||
return cache |
|||
} |
|||
|
|||
func (c *Cache) Item(key, value interface{}, opts ...Option) *Item { |
|||
return c.dataFactory(key, value, opts...) |
|||
} |
|||
|
|||
func (c *Cache) Execute(ctx context.Context, operation string, key, value interface{}, opts ...Option) error { |
|||
return c.provider(ctx, operation, c.Item(key, value, opts...)) |
|||
} |
|||
|
|||
// Set handles middlewares and sets value by key and options.
|
|||
func (c *Cache) Set(ctx context.Context, key, value interface{}, opts ...Option) error { |
|||
return c.Execute(ctx, OperationSet, key, value, opts...) |
|||
} |
|||
|
|||
// Get handles middlewares and gets value by key and options.
|
|||
func (c *Cache) Get(ctx context.Context, key, value interface{}, opts ...Option) error { |
|||
return c.Execute(ctx, OperationGet, key, value, opts...) |
|||
} |
|||
|
|||
// Delete handles middlewares and delete value by key and options.
|
|||
func (c *Cache) Delete(ctx context.Context, key interface{}, opts ...Option) error { |
|||
return c.Execute(ctx, OperationDelete, key, nil, opts...) |
|||
} |
@ -0,0 +1,226 @@ |
|||
package cache_test |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"time" |
|||
|
|||
glru "github.com/hashicorp/golang-lru" |
|||
prom "github.com/prometheus/client_golang/prometheus" |
|||
"gitoa.ru/go-4devs/cache" |
|||
"gitoa.ru/go-4devs/cache/mw" |
|||
"gitoa.ru/go-4devs/cache/mw/prometheus" |
|||
"gitoa.ru/go-4devs/cache/provider/lru" |
|||
"gitoa.ru/go-4devs/cache/provider/memcache" |
|||
"gitoa.ru/go-4devs/cache/provider/memory" |
|||
"gitoa.ru/go-4devs/cache/provider/redis" |
|||
"gitoa.ru/go-4devs/cache/test" |
|||
"gitoa.ru/go-4devs/encoding/gob" |
|||
) |
|||
|
|||
func ExampleCache_map() { |
|||
ctx := context.Background() |
|||
c := cache.New(memory.NewMap()) |
|||
|
|||
var cached string |
|||
|
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "not found key", &cached), cached) |
|||
fmt.Printf("err: %v\n", c.Set(ctx, "key", "some value")) |
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "key", &cached), cached) |
|||
// Output:
|
|||
// err: cache miss: map, value: ''
|
|||
// err: <nil>
|
|||
// err: <nil>, value: 'some value'
|
|||
} |
|||
|
|||
func ExampleCache_encoding() { |
|||
ctx := context.Background() |
|||
c := cache.New(memory.NewEncoding(), cache.WithDataOption(cache.WithMarshal(gob.Unmarshal, gob.Marshal))) |
|||
|
|||
var cached string |
|||
|
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "not found key", &cached), cached) |
|||
fmt.Printf("err: %v\n", c.Set(ctx, "key", "some value")) |
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "key", &cached), cached) |
|||
// Output:
|
|||
// err: cache miss: encoding, value: ''
|
|||
// err: <nil>
|
|||
// err: <nil>, value: 'some value'
|
|||
} |
|||
|
|||
func ExampleCache_redis() { |
|||
ctx := context.Background() |
|||
c := cache.New(redis.New(test.RedisClient())) |
|||
|
|||
var cached string |
|||
|
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "not found redis key", &cached), cached) |
|||
fmt.Printf("err: %v\n", c.Set(ctx, "key", "some redis value", cache.WithNamespace("redis", ":"))) |
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "redis:key", &cached), cached) |
|||
// Output:
|
|||
// err: cache miss: redis pool, value: ''
|
|||
// err: <nil>
|
|||
// err: <nil>, value: 'some redis value'
|
|||
} |
|||
|
|||
func ExampleCache_memacache() { |
|||
ctx := context.Background() |
|||
c := cache.New(memcache.New(test.MemcacheClient()), cache.WithDataOption(cache.WithNamespace("memcache", ":"))) |
|||
|
|||
var cached string |
|||
|
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "not found memcached key", &cached), cached) |
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "not:found:memcached:key", &cached), cached) |
|||
fmt.Printf("err: %v\n", c.Set(ctx, "key", "some mamcache value")) |
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "key", &cached), cached) |
|||
// Output:
|
|||
// err: key is not valid: memcache, value: ''
|
|||
// err: cache miss: memcache, value: ''
|
|||
// err: <nil>
|
|||
// err: <nil>, value: 'some mamcache value'
|
|||
} |
|||
|
|||
func ExampleCache_lru() { |
|||
ctx := context.Background() |
|||
client, _ := glru.New(10) |
|||
|
|||
c := cache.New(lru.New(client), cache.WithDataOption(cache.WithTTL(time.Hour))) |
|||
|
|||
var cached string |
|||
|
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "not found lru key", &cached), cached) |
|||
fmt.Printf("err: %v\n", c.Set(ctx, "key", "some lru value")) |
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, "key", &cached), cached) |
|||
fmt.Printf("deleted err: %v\n", c.Delete(ctx, "key")) |
|||
// Output:
|
|||
// err: cache miss: lru, value: ''
|
|||
// err: <nil>
|
|||
// err: <nil>, value: 'some lru value'
|
|||
// deleted err: <nil>
|
|||
} |
|||
|
|||
func ExampleCache_withNamespace() { |
|||
ctx := context.Background() |
|||
c := cache.New(provider(), cache.WithDataOption( |
|||
cache.WithNamespace("prefix", ":"), |
|||
cache.WithTTL(time.Hour), |
|||
)) |
|||
|
|||
var cached, cached2 string |
|||
|
|||
fmt.Printf("prefix err: %v, value: '%v'\n", c.Get(ctx, "key", &cached), cached) |
|||
fmt.Printf("prefix err: %v\n", c.Set(ctx, "key", "some value", cache.WithTTL(time.Minute))) |
|||
fmt.Printf("prefix2 err: %v\n", c.Set(ctx, "key", "some value2", cache.WithNamespace("prefix2", ":"))) |
|||
fmt.Printf("prefix err: %v, value: '%v'\n", c.Get(ctx, "key", &cached), cached) |
|||
fmt.Printf("prefix2 err: %v, value: '%v'\n", c.Get(ctx, "key", &cached2, cache.WithNamespace("prefix2", ":")), cached2) |
|||
// Output:
|
|||
// prefix err: cache miss: map, value: ''
|
|||
// prefix err: <nil>
|
|||
// prefix2 err: <nil>
|
|||
// prefix err: <nil>, value: 'some value'
|
|||
// prefix2 err: <nil>, value: 'some value2'
|
|||
} |
|||
|
|||
func ExampleCache_withFallback() { |
|||
ctx := context.Background() |
|||
c := cache.New(provider(), mw.WithFallback( |
|||
func(ctx context.Context, key, value interface{}) error { |
|||
fmt.Printf("loaded key: %#v\n", key) |
|||
|
|||
return cache.TypeAssert("some loaded data", value) |
|||
}, |
|||
func(i *cache.Item, e error) bool { |
|||
return e != nil |
|||
}, |
|||
)) |
|||
|
|||
var cached, cached2 string |
|||
|
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, 1, &cached), cached) |
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, 1, &cached2), cached2) |
|||
// Output:
|
|||
// loaded key: 1
|
|||
// err: <nil>, value: 'some loaded data'
|
|||
// err: <nil>, value: 'some loaded data'
|
|||
} |
|||
|
|||
func ExampleCache_clearByContext() { |
|||
type ctxKey int |
|||
|
|||
var ( |
|||
requestID ctxKey = 1 |
|||
cached, cached2 string |
|||
) |
|||
|
|||
ctx, cancel := context.WithCancel(context.WithValue(context.Background(), requestID, "unique ctx key")) |
|||
ctx2 := context.WithValue(context.Background(), requestID, "unique ctx key2") |
|||
c := cache.New(provider(), |
|||
mw.WithClearByContext(requestID), |
|||
cache.WithDataOption(cache.WithNamespace("clear_by_ctx", "")), |
|||
) |
|||
|
|||
fmt.Printf("err: %v\n", c.Set(ctx, 1, "some ctx loaded data", cache.WithTTL(time.Hour))) |
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, 1, &cached), cached) |
|||
cancel() |
|||
time.Sleep(time.Millisecond) |
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx2, 1, &cached2), cached2) |
|||
// Output:
|
|||
// err: <nil>
|
|||
// err: <nil>, value: 'some ctx loaded data'
|
|||
// err: cache miss: map, value: ''
|
|||
} |
|||
|
|||
func ExampleCache_clearByTTL() { |
|||
ctx := context.Background() |
|||
c := cache.New(provider(), |
|||
mw.WithClearByTTL(), |
|||
cache.WithDataOption(cache.WithNamespace("clear_by_ttl", "")), |
|||
) |
|||
|
|||
var cached, cached2 string |
|||
|
|||
fmt.Printf("err: %v\n", c.Set(ctx, 1, "some ttl loaded data", cache.WithTTL(time.Microsecond*200))) |
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, 1, &cached), cached) |
|||
time.Sleep(time.Second) |
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, 1, &cached2), cached2) |
|||
// Output:
|
|||
// err: <nil>
|
|||
// err: <nil>, value: 'some ttl loaded data'
|
|||
// err: cache miss: map, value: ''
|
|||
} |
|||
|
|||
func ExampleCache_withMetrics() { |
|||
ctx := context.Background() |
|||
cacheLabel := "cache_label" |
|||
c := cache.New(provider(), |
|||
mw.WithMetrics(prometheus.Metrics{}, mw.LabelName(cacheLabel)), |
|||
cache.WithDataOption(cache.WithNamespace("metrics", ":")), |
|||
) |
|||
|
|||
var cached, cached2 string |
|||
|
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, 1, &cached), cached) |
|||
fmt.Printf("err: %v\n", c.Set(ctx, 1, "cached")) |
|||
fmt.Printf("err: %v, value: '%v'\n", c.Get(ctx, 1, &cached2), cached2) |
|||
|
|||
mfs, _ := prom.DefaultGatherer.Gather() |
|||
for _, mf := range mfs { |
|||
for _, metric := range mf.GetMetric() { |
|||
label := metric.GetLabel() |
|||
if len(label) > 0 && metric.Counter != nil { |
|||
fmt.Printf("name:%s, label:%s, value: %.0f\n", *mf.Name, *label[0].Value, mf.GetMetric()[0].Counter.GetValue()) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Output:
|
|||
// err: cache miss: map, value: ''
|
|||
// err: <nil>
|
|||
// err: <nil>, value: 'cached'
|
|||
// name:cache_hit_total, label:cache_label, value: 1
|
|||
// name:cache_miss_total, label:cache_label, value: 1
|
|||
} |
|||
|
|||
func provider() cache.Provider { |
|||
return memory.NewMap() |
|||
} |
@ -0,0 +1,208 @@ |
|||
package cache_test |
|||
|
|||
import ( |
|||
"context" |
|||
"sync/atomic" |
|||
"testing" |
|||
"time" |
|||
|
|||
"github.com/stretchr/testify/require" |
|||
"gitoa.ru/go-4devs/cache" |
|||
"gitoa.ru/go-4devs/cache/test" |
|||
) |
|||
|
|||
func TestCache_Get(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
ctx := context.Background() |
|||
pro := test.NewProviderMock(t, test.WithGet(func(t *testing.T) func(ctx context.Context, item *cache.Item) error { |
|||
t.Helper() |
|||
|
|||
return func(ctx context.Context, d *cache.Item) error { |
|||
u := test.NewUser(1) |
|||
|
|||
return cache.TypeAssert(u, d.Value) |
|||
} |
|||
})) |
|||
cache := cache.New(pro) |
|||
|
|||
var user test.User |
|||
|
|||
require.Nil(t, cache.Get(ctx, 1, &user)) |
|||
require.Equal(t, test.NewUser(1), user) |
|||
} |
|||
|
|||
func TestCache_Set(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
ctx := context.Background() |
|||
pro := test.NewProviderMock(t, test.WithSet(func(t *testing.T) func(ctx context.Context, item *cache.Item) error { |
|||
t.Helper() |
|||
|
|||
return func(ctx context.Context, d *cache.Item) error { |
|||
require.Equal(t, 1, d.Key.Key) |
|||
require.Equal(t, test.NewUser(1), d.Value) |
|||
require.Equal(t, "1", d.Key.String()) |
|||
|
|||
return nil |
|||
} |
|||
})) |
|||
cache := cache.New(pro) |
|||
|
|||
require.Nil(t, cache.Set(ctx, 1, test.NewUser(1))) |
|||
} |
|||
|
|||
func TestCache_Delete(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
ctx := context.Background() |
|||
pro := test.NewProviderMock(t, test.WithDelete(func(t *testing.T) func(ctx context.Context, item *cache.Item) error { |
|||
t.Helper() |
|||
|
|||
return func(ctx context.Context, d *cache.Item) error { |
|||
require.Equal(t, 1, d.Key.Key) |
|||
require.Empty(t, d.Value) |
|||
require.Equal(t, "1", d.Key.String()) |
|||
|
|||
return nil |
|||
} |
|||
})) |
|||
cache := cache.New(pro) |
|||
|
|||
require.Nil(t, cache.Delete(ctx, 1)) |
|||
} |
|||
|
|||
func TestCache_Get_withMiddleware(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
ctx := context.Background() |
|||
pro := test.NewProviderMock(t, |
|||
test.WithSet(func(t *testing.T) func(ctx context.Context, item *cache.Item) error { |
|||
t.Helper() |
|||
|
|||
return func(ctx context.Context, d *cache.Item) error { |
|||
require.Equal(t, 2, d.Key.Key) |
|||
require.Equal(t, test.NewUser(2), d.Value) |
|||
require.Equal(t, "mw_prefix::_2", d.Key.String()) |
|||
require.Equal(t, time.Minute, d.TTL) |
|||
|
|||
return nil |
|||
} |
|||
}), |
|||
test.WithGet(func(t *testing.T) func(ctx context.Context, item *cache.Item) error { |
|||
t.Helper() |
|||
|
|||
return func(ctx context.Context, d *cache.Item) error { |
|||
require.Equal(t, 2, d.Key.Key) |
|||
require.Equal(t, "mw_prefix----2", d.Key.String()) |
|||
|
|||
return nil |
|||
} |
|||
}), |
|||
) |
|||
c := cache.New(pro, |
|||
cache.WithDataOption( |
|||
func(i *cache.Item) { |
|||
i.Key.Prefix = "mw_prefix" |
|||
}, |
|||
cache.WithTTL(time.Hour), |
|||
), |
|||
cache.WithHandleSet( |
|||
func(ctx context.Context, op string, d *cache.Item, n cache.Provider) error { |
|||
d.Key.Separator = "::" |
|||
|
|||
return n(ctx, op, d) |
|||
}, func(ctx context.Context, op string, d *cache.Item, n cache.Provider) error { |
|||
d.Key.Separator += "_" |
|||
|
|||
return n(ctx, op, d) |
|||
}), |
|||
cache.WithHandleGet(func(ctx context.Context, op string, d *cache.Item, n cache.Provider) error { |
|||
d.Key.Separator = "----" |
|||
|
|||
return n(ctx, op, d) |
|||
}), |
|||
) |
|||
|
|||
var user test.User |
|||
|
|||
require.Nil(t, c.Set(ctx, 2, test.NewUser(2), cache.WithTTL(time.Minute))) |
|||
require.Nil(t, c.Get(ctx, 2, &user)) |
|||
} |
|||
|
|||
func TestCacheWith(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
ctx := context.Background() |
|||
pro := test.NewProviderMock(t, |
|||
test.WithGet(func(t *testing.T) func(ctx context.Context, item *cache.Item) error { |
|||
t.Helper() |
|||
|
|||
return func(ctx context.Context, d *cache.Item) error { |
|||
switch d.Key.Key.(int) { |
|||
case 1: |
|||
require.Equal(t, "ns:1", d.Key.String()) |
|||
case 2: |
|||
require.Equal(t, "new_ns_2", d.Key.String()) |
|||
default: |
|||
t.Errorf("key %v no allowed", d.Key.Key) |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
}), |
|||
test.WithSet(func(t *testing.T) func(ctx context.Context, item *cache.Item) error { |
|||
t.Helper() |
|||
|
|||
return func(ctx context.Context, d *cache.Item) error { |
|||
switch d.Key.Key.(int) { |
|||
case 1: |
|||
require.Equal(t, time.Hour, d.TTL) |
|||
case 2: |
|||
require.Equal(t, time.Minute, d.TTL) |
|||
default: |
|||
t.Errorf("key %v no allowed", d.Key.Key) |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
}), |
|||
) |
|||
|
|||
var ( |
|||
cntSetCache2, cntGetCache1 int32 |
|||
val1, val2 string |
|||
) |
|||
|
|||
cache1 := cache.New(pro, |
|||
cache.WithHandleGet(func(ctx context.Context, operation string, item *cache.Item, next cache.Provider) error { |
|||
atomic.AddInt32(&cntGetCache1, 1) |
|||
|
|||
return next(ctx, operation, item) |
|||
}), |
|||
cache.WithDataOption( |
|||
cache.WithNamespace("ns", ":"), |
|||
cache.WithTTL(time.Hour), |
|||
), |
|||
) |
|||
cache2 := cache1.With( |
|||
cache.WithHandleSet(func(ctx context.Context, operation string, item *cache.Item, next cache.Provider) error { |
|||
atomic.AddInt32(&cntSetCache2, 1) |
|||
|
|||
return next(ctx, operation, item) |
|||
}), |
|||
cache.WithDataOption( |
|||
cache.WithNamespace("new_ns", "_"), |
|||
cache.WithTTL(time.Minute), |
|||
), |
|||
) |
|||
|
|||
require.NoError(t, cache1.Get(ctx, 1, &val1)) |
|||
require.NoError(t, cache2.Get(ctx, 2, &val2)) |
|||
|
|||
require.NoError(t, cache1.Set(ctx, 1, val1)) |
|||
require.NoError(t, cache2.Set(ctx, 2, val2)) |
|||
|
|||
require.Equal(t, int32(1), cntSetCache2) |
|||
require.Equal(t, int32(2), cntGetCache1) |
|||
} |
@ -0,0 +1,13 @@ |
|||
version: '3' |
|||
|
|||
services: |
|||
redis: |
|||
container_name: 4devs_cache_redis |
|||
image: redis:latest |
|||
ports: |
|||
- "127.0.0.1:6379:6379" |
|||
memcached: |
|||
container_name: 4devs_cache_memcached |
|||
image: memcached:latest |
|||
ports: |
|||
- "11211:11211" |
@ -0,0 +1,33 @@ |
|||
package cache |
|||
|
|||
import ( |
|||
"errors" |
|||
"fmt" |
|||
) |
|||
|
|||
// Cached errors.
|
|||
var ( |
|||
ErrCacheMiss = errors.New("cache miss") |
|||
ErrCacheExpired = errors.New("cache expired") |
|||
ErrSourceNotValid = errors.New("source is not valid") |
|||
ErrKeyNotValid = errors.New("key is not valid") |
|||
ErrTargetNil = errors.New("target is nil") |
|||
ErrOperationNotAllwed = errors.New("operation not allowed") |
|||
) |
|||
|
|||
var _ error = NewErrorTarget(nil) |
|||
|
|||
// NewErrorTarget creates new target error.
|
|||
func NewErrorTarget(target interface{}) ErrorTarget { |
|||
return ErrorTarget{target: target} |
|||
} |
|||
|
|||
// ErrorTarget errs target is not a settable.
|
|||
type ErrorTarget struct { |
|||
target interface{} |
|||
} |
|||
|
|||
// ErrorTarget errors.
|
|||
func (e ErrorTarget) Error() string { |
|||
return fmt.Sprintf("target is not a settable %T", e.target) |
|||
} |
@ -0,0 +1,15 @@ |
|||
module gitoa.ru/go-4devs/cache |
|||
|
|||
go 1.15 |
|||
|
|||
require ( |
|||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b |
|||
github.com/cockroachdb/pebble v0.0.0-20200916123908-284ba0668391 |
|||
github.com/dgraph-io/ristretto v0.0.3 |
|||
github.com/gomodule/redigo v1.8.2 |
|||
github.com/hashicorp/golang-lru v0.5.4 |
|||
github.com/mailru/easyjson v0.7.6 |
|||
github.com/prometheus/client_golang v1.7.1 |
|||
github.com/stretchr/testify v1.6.1 |
|||
gitoa.ru/go-4devs/encoding v0.0.3 |
|||
) |
@ -0,0 +1,185 @@ |
|||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= |
|||
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/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= |
|||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= |
|||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= |
|||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= |
|||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= |
|||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= |
|||
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= |
|||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= |
|||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= |
|||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= |
|||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 h1:JLaf/iINcLyjwbtTsCJjc6rtlASgHeIJPrB6QmwURnA= |
|||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= |
|||
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/cockroachdb/errors v1.2.4 h1:Lap807SXTH5tri2TivECb/4abUkMZC9zRoLarvcKDqs= |
|||
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= |
|||
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= |
|||
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= |
|||
github.com/cockroachdb/pebble v0.0.0-20200916123908-284ba0668391 h1:iCTxMsf8E/rUVDw+tqgOHnYBJiXg4dU9psCv4jjI9n0= |
|||
github.com/cockroachdb/pebble v0.0.0-20200916123908-284ba0668391/go.mod h1:hU7vhtrqonEphNF+xt8/lHdaBprxmV1h8BOGrd9XwmQ= |
|||
github.com/cockroachdb/redact v0.0.0-20200622112456-cd282804bbd3 h1:2+dpIJzYMSbLi0587YXpi8tOJT52qCOI/1I0UNThc/I= |
|||
github.com/cockroachdb/redact v0.0.0-20200622112456-cd282804bbd3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= |
|||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= |
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|||
github.com/dgraph-io/ristretto v0.0.3 h1:jh22xisGBjrEVnRZ1DVTpBVQm0Xndu8sMl0CWDzSIBI= |
|||
github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= |
|||
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/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= |
|||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= |
|||
github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= |
|||
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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= |
|||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= |
|||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= |
|||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= |
|||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
|||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
|||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
|||
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.2-0.20190904063534-ff6b7dc882cf h1:gFVkHXmVAhEbxZVDln5V9GKrLaluNoFHDbrZwAWZgws= |
|||
github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= |
|||
github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= |
|||
github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= |
|||
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= |
|||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= |
|||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= |
|||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= |
|||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= |
|||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= |
|||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= |
|||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= |
|||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= |
|||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= |
|||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= |
|||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= |
|||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= |
|||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= |
|||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= |
|||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= |
|||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= |
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= |
|||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= |
|||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= |
|||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= |
|||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= |
|||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= |
|||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= |
|||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= |
|||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= |
|||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= |
|||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
|||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
|||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= |
|||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
|||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= |
|||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= |
|||
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= |
|||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= |
|||
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.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= |
|||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= |
|||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= |
|||
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= |
|||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= |
|||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= |
|||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= |
|||
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= |
|||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= |
|||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= |
|||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= |
|||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= |
|||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= |
|||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= |
|||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= |
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
|||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= |
|||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |
|||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= |
|||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= |
|||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= |
|||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
|||
gitoa.ru/go-4devs/encoding v0.0.3 h1:Rqjs0lsnco5PfMZ4iP3+TFYd/dHbG8FqMXxmbgTdfq4= |
|||
gitoa.ru/go-4devs/encoding v0.0.3/go.mod h1:TPHAyATVNvRFt4Z+4beRhg9+E/VpXxwpniZfzkH9258= |
|||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= |
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
|||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
|||
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/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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
|||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
|||
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/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= |
|||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
|||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
|||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= |
|||
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/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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= |
|||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|||
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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
|||
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86 h1:OfFoIUYv/me30yv7XlMy4F9RJw8DEm8WQ6QG1Ph4bH0= |
|||
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
@ -0,0 +1,147 @@ |
|||
package cache |
|||
|
|||
import ( |
|||
"encoding/json" |
|||
"fmt" |
|||
"strconv" |
|||
"time" |
|||
|
|||
"gitoa.ru/go-4devs/encoding" |
|||
) |
|||
|
|||
var _ fmt.Stringer = (*Key)(nil) |
|||
|
|||
// Option ffor the configuration item.
|
|||
type Option func(*Item) |
|||
|
|||
// WithNamespace sets prefix and separator.
|
|||
func WithNamespace(prefix, sep string) Option { |
|||
return func(d *Item) { |
|||
d.Key.Prefix = prefix |
|||
d.Key.Separator = sep |
|||
} |
|||
} |
|||
|
|||
// WithTTL sets ttl.
|
|||
func WithTTL(ttl time.Duration) Option { |
|||
return func(d *Item) { |
|||
d.TTL = ttl |
|||
} |
|||
} |
|||
|
|||
// WithMarshal sets marshal and unmarshal.
|
|||
func WithMarshal(unmarshal encoding.Unmarshal, marshal encoding.Marshal) Option { |
|||
return func(d *Item) { |
|||
d.unmarshal = unmarshal |
|||
d.marshal = marshal |
|||
} |
|||
} |
|||
|
|||
// NewItem creates and configure new item.
|
|||
func NewItem(key, value interface{}, opts ...Option) *Item { |
|||
item := &Item{ |
|||
Key: Key{ |
|||
Key: key, |
|||
Prefix: "", |
|||
Separator: "", |
|||
}, |
|||
Value: value, |
|||
TTL: 0, |
|||
unmarshal: json.Unmarshal, |
|||
marshal: json.Marshal, |
|||
} |
|||
|
|||
for _, opt := range opts { |
|||
opt(item) |
|||
} |
|||
|
|||
return item |
|||
} |
|||
|
|||
// Item to pass to the provider.
|
|||
type Item struct { |
|||
Key Key |
|||
Value interface{} |
|||
TTL time.Duration |
|||
unmarshal encoding.Unmarshal |
|||
marshal encoding.Marshal |
|||
} |
|||
|
|||
func (i *Item) With(key, val interface{}, opts ...Option) *Item { |
|||
return NewItem(key, val, append(i.Options(), opts...)...) |
|||
} |
|||
|
|||
// IsExpired checks expired item.
|
|||
func (i *Item) IsExpired() bool { |
|||
return i.TTL < 0 |
|||
} |
|||
|
|||
func (i *Item) Marshal() ([]byte, error) { |
|||
return i.marshal(i.Value) |
|||
} |
|||
|
|||
func (i *Item) Unmarshal(data []byte) error { |
|||
return i.unmarshal(data, i.Value) |
|||
} |
|||
|
|||
// Options gets item options.
|
|||
func (i *Item) Options() []Option { |
|||
opts := []Option{WithTTL(i.TTL), WithMarshal(i.unmarshal, i.marshal)} |
|||
|
|||
if i.Key.Prefix != "" { |
|||
opts = append(opts, WithNamespace(i.Key.Prefix, i.Key.Separator)) |
|||
} |
|||
|
|||
return opts |
|||
} |
|||
|
|||
// TTLInSecond to set the ttl in seconds.
|
|||
func (i *Item) TTLInSecond(in int64) { |
|||
i.TTL = time.Second * time.Duration(in) |
|||
} |
|||
|
|||
// Expired get the time when the ttl is outdated.
|
|||
func (i *Item) Expired() time.Time { |
|||
return time.Now().Add(i.TTL) |
|||
} |
|||
|
|||
// Key with prefix and separator.
|
|||
type Key struct { |
|||
Key interface{} |
|||
Prefix string |
|||
Separator string |
|||
} |
|||
|
|||
func (k Key) Value() interface{} { |
|||
if v, ok := k.Key.(interface{ Value() interface{} }); ok { |
|||
return v.Value() |
|||
} |
|||
|
|||
return k.Key |
|||
} |
|||
|
|||
// String returns a formatted key.
|
|||
func (k Key) String() string { |
|||
if k.Prefix != "" { |
|||
return fmt.Sprint(k.Prefix, k.Separator, k.Key) |
|||
} |
|||
|
|||
switch v := k.Key.(type) { |
|||
case int: |
|||
return strconv.Itoa(v) |
|||
case int32: |
|||
return strconv.FormatInt(int64(v), 10) |
|||
case int64: |
|||
return strconv.FormatInt(v, 10) |
|||
case uint: |
|||
return strconv.FormatUint(uint64(v), 10) |
|||
case uint32: |
|||
return strconv.FormatUint(uint64(v), 10) |
|||
case uint64: |
|||
return strconv.FormatUint(v, 10) |
|||
case string: |
|||
return v |
|||
default: |
|||
return fmt.Sprint(v) |
|||
} |
|||
} |
@ -0,0 +1,56 @@ |
|||
package item |
|||
|
|||
import ( |
|||
"fmt" |
|||
"time" |
|||
|
|||
"gitoa.ru/go-4devs/cache" |
|||
) |
|||
|
|||
//go:generate easyjson
|
|||
|
|||
//easyjson:json
|
|||
type expiredByte struct { |
|||
Data []byte `json:"d"` |
|||
Expired time.Time `json:"e"` |
|||
} |
|||
|
|||
func MarshalExpired(item *cache.Item) ([]byte, error) { |
|||
var ( |
|||
e expiredByte |
|||
err error |
|||
) |
|||
|
|||
e.Data, err = item.Marshal() |
|||
if err != nil { |
|||
return nil, fmt.Errorf("failed marshal expired: %w", err) |
|||
} |
|||
|
|||
if item.TTL > 0 { |
|||
e.Expired = item.Expired() |
|||
} |
|||
|
|||
return e.MarshalJSON() |
|||
} |
|||
|
|||
func UnmarshalExpired(item *cache.Item, d []byte) error { |
|||
var e expiredByte |
|||
|
|||
if err := e.UnmarshalJSON(d); err != nil { |
|||
return err |
|||
} |
|||
|
|||
if !e.Expired.IsZero() { |
|||
item.TTL = time.Until(e.Expired) |
|||
} |
|||
|
|||
if item.IsExpired() { |
|||
return cache.ErrCacheExpired |
|||
} |
|||
|
|||
if err := item.Unmarshal(e.Data); err != nil { |
|||
return fmt.Errorf("failed unmarshal expired: %w", err) |
|||
} |
|||
|
|||
return nil |
|||
} |
@ -0,0 +1,99 @@ |
|||
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
|
|||
|
|||
package item |
|||
|
|||
import ( |
|||
json "encoding/json" |
|||
easyjson "github.com/mailru/easyjson" |
|||
jlexer "github.com/mailru/easyjson/jlexer" |
|||
jwriter "github.com/mailru/easyjson/jwriter" |
|||
) |
|||
|
|||
// suppress unused package warning
|
|||
var ( |
|||
_ *json.RawMessage |
|||
_ *jlexer.Lexer |
|||
_ *jwriter.Writer |
|||
_ easyjson.Marshaler |
|||
) |
|||
|
|||
func easyjsonB8950805DecodeGitoaRuGo4devsCacheItem(in *jlexer.Lexer, out *expiredByte) { |
|||
isTopLevel := in.IsStart() |
|||
if in.IsNull() { |
|||
if isTopLevel { |
|||
in.Consumed() |
|||
} |
|||
in.Skip() |
|||
return |
|||
} |
|||
in.Delim('{') |
|||
for !in.IsDelim('}') { |
|||
key := in.UnsafeFieldName(false) |
|||
in.WantColon() |
|||
if in.IsNull() { |
|||
in.Skip() |
|||
in.WantComma() |
|||
continue |
|||
} |
|||
switch key { |
|||
case "d": |
|||
if in.IsNull() { |
|||
in.Skip() |
|||
out.Data = nil |
|||
} else { |
|||
out.Data = in.Bytes() |
|||
} |
|||
case "e": |
|||
if data := in.Raw(); in.Ok() { |
|||
in.AddError((out.Expired).UnmarshalJSON(data)) |
|||
} |
|||
default: |
|||
in.SkipRecursive() |
|||
} |
|||
in.WantComma() |
|||
} |
|||
in.Delim('}') |
|||
if isTopLevel { |
|||
in.Consumed() |
|||
} |
|||
} |
|||
func easyjsonB8950805EncodeGitoaRuGo4devsCacheItem(out *jwriter.Writer, in expiredByte) { |
|||
out.RawByte('{') |
|||
first := true |
|||
_ = first |
|||
{ |
|||
const prefix string = ",\"d\":" |
|||
out.RawString(prefix[1:]) |
|||
out.Base64Bytes(in.Data) |
|||
} |
|||
{ |
|||
const prefix string = ",\"e\":" |
|||
out.RawString(prefix) |
|||
out.Raw((in.Expired).MarshalJSON()) |
|||
} |
|||
out.RawByte('}') |
|||
} |
|||
|
|||
// MarshalJSON supports json.Marshaler interface
|
|||
func (v expiredByte) MarshalJSON() ([]byte, error) { |
|||
w := jwriter.Writer{} |
|||
easyjsonB8950805EncodeGitoaRuGo4devsCacheItem(&w, v) |
|||
return w.Buffer.BuildBytes(), w.Error |
|||
} |
|||
|
|||
// MarshalEasyJSON supports easyjson.Marshaler interface
|
|||
func (v expiredByte) MarshalEasyJSON(w *jwriter.Writer) { |
|||
easyjsonB8950805EncodeGitoaRuGo4devsCacheItem(w, v) |
|||
} |
|||
|
|||
// UnmarshalJSON supports json.Unmarshaler interface
|
|||
func (v *expiredByte) UnmarshalJSON(data []byte) error { |
|||
r := jlexer.Lexer{Data: data} |
|||
easyjsonB8950805DecodeGitoaRuGo4devsCacheItem(&r, v) |
|||
return r.Error() |
|||
} |
|||
|
|||
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
|
|||
func (v *expiredByte) UnmarshalEasyJSON(l *jlexer.Lexer) { |
|||
easyjsonB8950805DecodeGitoaRuGo4devsCacheItem(l, v) |
|||
} |
@ -0,0 +1,134 @@ |
|||
package mw |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"sync" |
|||
|
|||
"gitoa.ru/go-4devs/cache" |
|||
) |
|||
|
|||
type Fallback func(ctx context.Context, key, value interface{}) error |
|||
|
|||
type Getter func(ctx context.Context, key interface{}) (interface{}, error) |
|||
|
|||
// HandleByErr checks if cache return err.
|
|||
func HandleByErr(_ *cache.Item, err error) bool { |
|||
return err != nil |
|||
} |
|||
|
|||
// LockFallback locks run fallback by item key.
|
|||
func LockFallback(fallback Fallback) Fallback { |
|||
var mu sync.Mutex |
|||
|
|||
type entry struct { |
|||
item interface{} |
|||
err error |
|||
} |
|||
|
|||
keys := make(map[interface{}]chan entry) |
|||
|
|||
return func(ctx context.Context, key, value interface{}) error { |
|||
mu.Lock() |
|||
if _, ok := keys[key]; !ok { |
|||
keys[key] = make(chan entry, 1) |
|||
mu.Unlock() |
|||
|
|||
err := fallback(ctx, key, value) |
|||
keys[key] <- entry{ |
|||
item: value, |
|||
err: err, |
|||
} |
|||
|
|||
defer func() { |
|||
close(keys[key]) |
|||
delete(keys, key) |
|||
}() |
|||
|
|||
return err |
|||
} |
|||
mu.Unlock() |
|||
|
|||
entry := <-keys[key] |
|||
if entry.err != nil { |
|||
return entry.err |
|||
} |
|||
|
|||
if err := cache.TypeAssert(entry.item, value); err != nil { |
|||
return fmt.Errorf("%w: assert value", err) |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
} |
|||
|
|||
// WithFallback sets fallback when cache handle success and set result in cache.
|
|||
func WithFallback(fallback Fallback, isHandleFallback func(*cache.Item, error) bool) cache.Configure { |
|||
return cache.WithHandleGet(func(ctx context.Context, op string, item *cache.Item, next cache.Provider) error { |
|||
err := next(ctx, op, item) |
|||
if isHandleFallback(item, err) { |
|||
if ferr := fallback(ctx, item.Key.Key, item.Value); ferr != nil { |
|||
return ferr |
|||
} |
|||
|
|||
return next(ctx, cache.OperationSet, item) |
|||
} |
|||
|
|||
return err |
|||
}) |
|||
} |
|||
|
|||
// WithLockGetter sets values from getter when cache handle success and set result in cache.
|
|||
func WithLockGetter(getter Getter, isHandle func(*cache.Item, error) bool) cache.Configure { |
|||
var mu sync.Mutex |
|||
|
|||
type entry struct { |
|||
value interface{} |
|||
err error |
|||
} |
|||
|
|||
keys := make(map[cache.Key]chan entry) |
|||
|
|||
return cache.WithHandleGet(func(ctx context.Context, op string, item *cache.Item, next cache.Provider) error { |
|||
if err := next(ctx, op, item); !isHandle(item, err) { |
|||
return err |
|||
} |
|||
|
|||
mu.Lock() |
|||
if _, ok := keys[item.Key]; !ok { |
|||
keys[item.Key] = make(chan entry, 1) |
|||
mu.Unlock() |
|||
value, gerr := getter(ctx, item.Key.Value()) |
|||
keys[item.Key] <- entry{ |
|||
value: value, |
|||
err: gerr, |
|||
} |
|||
|
|||
defer func() { |
|||
close(keys[item.Key]) |
|||
delete(keys, item.Key) |
|||
}() |
|||
if gerr != nil { |
|||
return gerr |
|||
} |
|||
|
|||
if err := cache.TypeAssert(value, item.Value); err != nil { |
|||
return fmt.Errorf("lock failed assert type: %w", err) |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
mu.Unlock() |
|||
|
|||
entry := <-keys[item.Key] |
|||
if entry.err != nil { |
|||
return entry.err |
|||
} |
|||
|
|||
if err := cache.TypeAssert(entry.value, item.Value); err != nil { |
|||
return fmt.Errorf("lock failed assert type: %w", err) |
|||
} |
|||
|
|||
return next(ctx, cache.OperationSet, item) |
|||
}) |
|||
} |
@ -0,0 +1,221 @@ |
|||
package mw_test |
|||
|
|||
import ( |
|||
"context" |
|||
"errors" |
|||
"fmt" |
|||
"sync" |
|||
"sync/atomic" |
|||
"testing" |
|||
"time" |
|||
|
|||
"github.com/stretchr/testify/assert" |
|||
"github.com/stretchr/testify/require" |
|||
"gitoa.ru/go-4devs/cache" |
|||
"gitoa.ru/go-4devs/cache/mw" |
|||
"gitoa.ru/go-4devs/cache/test" |
|||
) |
|||
|
|||
var ( |
|||
errFallback = errors.New("fallback error") |
|||
errKey = errors.New("unexpected key") |
|||
) |
|||
|
|||
func TestWithFallback(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
ctx := context.Background() |
|||
key1 := "fallback:key1" |
|||
key2 := "fb:key2" |
|||
|
|||
prov := test.NewProviderMock(t, |
|||
test.WithGet(cacheGetMiss), |
|||
test.WithSet(cacheSetMiss(map[interface{}]test.User{ |
|||
key1: test.NewUser(1), |
|||
key2: test.NewUser(2), |
|||
})), |
|||
) |
|||
c := cache.New(prov, mw.WithFallback( |
|||
func(ctx context.Context, key, value interface{}) error { |
|||
switch key.(string) { |
|||
case key1: |
|||
*value.(*test.User) = test.NewUser(1) |
|||
case key2: |
|||
*value.(*test.User) = test.NewUser(2) |
|||
default: |
|||
t.Errorf("unexpected key: %s", key) |
|||
} |
|||
|
|||
return nil |
|||
}, |
|||
mw.HandleByErr, |
|||
)) |
|||
|
|||
var user test.User |
|||
|
|||
require.Nil(t, c.Get(ctx, key1, &user)) |
|||
require.Equal(t, test.NewUser(1), user) |
|||
|
|||
require.Nil(t, c.Get(ctx, key2, &user, cache.WithNamespace("namespace", ":"))) |
|||
require.Equal(t, test.NewUser(2), user) |
|||
} |
|||
|
|||
func TestLockFallback(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
ctx := context.Background() |
|||
|
|||
var ( |
|||
val1, val2, val3 string |
|||
wg sync.WaitGroup |
|||
cnt int32 |
|||
) |
|||
|
|||
fallback := mw.LockFallback(func(ctx context.Context, key, value interface{}) error { |
|||
time.Sleep(time.Second) |
|||
atomic.AddInt32(&cnt, 1) |
|||
*value.(*string) = fmt.Sprintf("value:%v", cnt) |
|||
|
|||
return nil |
|||
}) |
|||
|
|||
wg.Add(2) |
|||
|
|||
go func() { |
|||
assert.Nil(t, fallback(ctx, 1, &val1)) |
|||
wg.Done() |
|||
}() |
|||
go func() { |
|||
assert.Nil(t, fallback(ctx, 1, &val2)) |
|||
wg.Done() |
|||
}() |
|||
wg.Wait() |
|||
|
|||
require.Equal(t, "value:1", val1) |
|||
require.Equal(t, "value:1", val2) |
|||
|
|||
assert.Nil(t, fallback(ctx, 1, &val3)) |
|||
require.Equal(t, "value:2", val3) |
|||
} |
|||
|
|||
func TestLockFallback_Error(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
ctx := context.Background() |
|||
|
|||
var ( |
|||
val1, val2, val3 string |
|||
wg sync.WaitGroup |
|||
cnt int32 |
|||
) |
|||
|
|||
fallback := mw.LockFallback(func(ctx context.Context, key, value interface{}) error { |
|||
time.Sleep(time.Second) |
|||
atomic.AddInt32(&cnt, 1) |
|||
|
|||
return fmt.Errorf("%w:%v", errFallback, cnt) |
|||
}) |
|||
|
|||
wg.Add(2) |
|||
|
|||
go func() { |
|||
assert.EqualError(t, fallback(ctx, 1, &val1), "fallback error:1") |
|||
wg.Done() |
|||
}() |
|||
go func() { |
|||
assert.EqualError(t, fallback(ctx, 1, &val2), "fallback error:1") |
|||
wg.Done() |
|||
}() |
|||
wg.Wait() |
|||
|
|||
require.Empty(t, val1) |
|||
require.Empty(t, val2) |
|||
|
|||
assert.EqualError(t, fallback(ctx, 1, val3), "fallback error:2") |
|||
require.Empty(t, val3) |
|||
} |
|||
|
|||
func TestWithLockGetter(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
ctx := context.Background() |
|||
key1 := "getter:key1" |
|||
|
|||
var cnt int32 |
|||
|
|||
prov := test.NewProviderMock(t, |
|||
test.WithGet(cacheGetMiss), |
|||
test.WithSet(cacheSetMiss( |
|||
map[interface{}]test.User{ |
|||
key1: test.NewUser(1), |
|||
}, |
|||
)), |
|||
) |
|||
c := cache.New(prov, |
|||
cache.WithDataOption( |
|||
cache.WithNamespace("gn", ":"), |
|||
), |
|||
mw.WithLockGetter( |
|||
func(ctx context.Context, key interface{}) (interface{}, error) { |
|||
atomic.AddInt32(&cnt, 1) |
|||
time.Sleep(time.Second / 2) |
|||
switch key.(string) { |
|||
case key1: |
|||
return test.NewUser(1), nil |
|||
case "key2": |
|||
return test.NewUser(2), nil |
|||
} |
|||
|
|||
return nil, fmt.Errorf("%w: key '%v'", errKey, key) |
|||
}, |
|||
mw.HandleByErr, |
|||
)) |
|||
|
|||
var ( |
|||
user1, user2 test.User |
|||
wg sync.WaitGroup |
|||
) |
|||
|
|||
wg.Add(2) |
|||
|
|||
go func() { |
|||
require.Nil(t, c.Get(ctx, key1, &user1)) |
|||
wg.Done() |
|||
}() |
|||
go func() { |
|||
require.Nil(t, c.Get(ctx, key1, &user2)) |
|||
wg.Done() |
|||
}() |
|||
|
|||
wg.Wait() |
|||
|
|||
require.Equal(t, test.NewUser(1), user1) |
|||
require.Equal(t, test.NewUser(1), user2) |
|||
require.Equal(t, int32(1), cnt) |
|||
} |
|||
|
|||
func cacheGetMiss(t *testing.T) func(ctx context.Context, item *cache.Item) error { |
|||
t.Helper() |
|||
|
|||
return func(ctx context.Context, item *cache.Item) error { |
|||
return cache.ErrCacheMiss |
|||
} |
|||
} |
|||
|
|||
func cacheSetMiss(items map[interface{}]test.User) func(t *testing.T) func(ctx context.Context, item *cache.Item) error { |
|||
return func(t *testing.T) func(ctx context.Context, item *cache.Item) error { |
|||
t.Helper() |
|||
|
|||
return func(ctx context.Context, item *cache.Item) error { |
|||
if value, ok := items[item.Key.Key]; ok { |
|||
require.Equal(t, &value, item.Value) |
|||
|
|||
return nil |
|||
} |
|||
|
|||
t.Errorf("unexpected key %v", item.Key.String()) |
|||
|
|||
return nil |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,78 @@ |
|||
package mw |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"sync" |
|||
"time" |
|||
|
|||
"gitoa.ru/go-4devs/cache" |
|||
) |
|||
|
|||
type key struct { |
|||
key interface{} |
|||
ctxPrefix string |
|||
} |
|||
|
|||
func (k key) Value() interface{} { |
|||
return k.key |
|||
} |
|||
|
|||
func (k key) String() string { |
|||
return fmt.Sprint(k.ctxPrefix, k.key) |
|||
} |
|||
|
|||
// WithClearByContext clear cache if context done.
|
|||
func WithClearByContext(ctxKey interface{}) cache.Configure { |
|||
operation := func(ctx context.Context, op string, item *cache.Item, next cache.Provider) error { |
|||
ctxPrefix, ok := ctx.Value(ctxKey).(string) |
|||
if !ok { |
|||
return fmt.Errorf("%w: must be unique ctx key", cache.ErrKeyNotValid) |
|||
} |
|||
|
|||
k := item.Key.Key |
|||
item.Key.Key = key{ |
|||
key: k, |
|||
ctxPrefix: ctxPrefix, |
|||
} |
|||
|
|||
return next(ctx, op, item) |
|||
} |
|||
|
|||
return cache.WithMiddleware( |
|||
func(ctx context.Context, op string, item *cache.Item, next cache.Provider) error { |
|||
if op == cache.OperationSet { |
|||
go func(ctx context.Context, item *cache.Item) { |
|||
<-ctx.Done() |
|||
_ = next(ctx, cache.OperationDelete, item) |
|||
}(ctx, item) |
|||
} |
|||
|
|||
return operation(ctx, op, item, next) |
|||
}) |
|||
} |
|||
|
|||
// WithClearByTTL clear cache by key after ttl.
|
|||
func WithClearByTTL() cache.Configure { |
|||
keys := make(map[cache.Key]*time.Timer) |
|||
mu := sync.Mutex{} |
|||
|
|||
return cache.WithHandleSet(func(ctx context.Context, op string, item *cache.Item, next cache.Provider) error { |
|||
if item.TTL > 0 { |
|||
go func() { |
|||
mu.Lock() |
|||
defer mu.Unlock() |
|||
if t, ok := keys[item.Key]; ok { |
|||
t.Reset(item.TTL) |
|||
} else { |
|||
keys[item.Key] = time.AfterFunc(item.TTL, func() { |
|||
_ = next(ctx, cache.OperationDelete, item) |
|||
delete(keys, item.Key) |
|||
}) |
|||
} |
|||
}() |
|||
} |
|||
|
|||
return next(ctx, op, item) |
|||
}) |
|||
} |
@ -0,0 +1,68 @@ |
|||
package mw_test |
|||
|
|||
import ( |
|||
"context" |
|||
"testing" |
|||
"time" |
|||
|
|||
"github.com/stretchr/testify/require" |
|||
"gitoa.ru/go-4devs/cache" |
|||
"gitoa.ru/go-4devs/cache/mw" |
|||
"gitoa.ru/go-4devs/cache/provider/memory" |
|||
) |
|||
|
|||
func TestWithClearByTTL(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
ctx := context.Background() |
|||
gcMap := cache.New(memory.NewMap(), mw.WithClearByTTL()) |
|||
cacheMap := cache.New(memory.NewMap()) |
|||
|
|||
var ( |
|||
value string |
|||
err error |
|||
) |
|||
|
|||
require.NoError(t, gcMap.Set(ctx, "keys", "value", cache.WithTTL(time.Second/3))) |
|||
require.NoError(t, cacheMap.Set(ctx, "keys", "value", cache.WithTTL(time.Second/3))) |
|||
time.Sleep(time.Second) |
|||
|
|||
err = gcMap.Get(ctx, "keys", &value) |
|||
require.EqualError(t, err, cache.ErrCacheMiss.Error()+": map") |
|||
|
|||
err = cacheMap.Get(ctx, "keys", &value) |
|||
require.EqualError(t, err, cache.ErrCacheExpired.Error()+": map") |
|||
|
|||
require.NoError(t, gcMap.Set(ctx, "keys", "value", cache.WithTTL(time.Second/2))) |
|||
time.AfterFunc(time.Second/3, func() { |
|||
require.NoError(t, gcMap.Set(ctx, "keys", "value", cache.WithTTL(time.Second))) |
|||
}) |
|||
time.Sleep(time.Second / 2) |
|||
require.NoError(t, gcMap.Get(ctx, "keys", &value)) |
|||
require.Equal(t, value, "value") |
|||
} |
|||
|
|||
func TestWithClearByContext(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
type ctxKey int |
|||
|
|||
var ( |
|||
requestID ctxKey = 1 |
|||
data string |
|||
) |
|||
|
|||
ctx1, cancel1 := context.WithCancel(context.WithValue(context.Background(), requestID, "request1")) |
|||
ctx2, cancel2 := context.WithCancel(context.WithValue(context.Background(), requestID, "request2")) |
|||
|
|||
cacheMap := cache.New(memory.NewMap(), mw.WithClearByContext(requestID)) |
|||
|
|||
require.NoError(t, cacheMap.Set(ctx1, "key", "value")) |
|||
require.EqualError(t, cacheMap.Get(ctx2, "key", &data), "cache miss: map") |
|||
require.NoError(t, cacheMap.Get(ctx1, "key", &data)) |
|||
cancel1() |
|||
|
|||
time.Sleep(time.Millisecond) |
|||
require.EqualError(t, cacheMap.Get(ctx1, "key", &data), "cache miss: map") |
|||
cancel2() |
|||
} |
@ -0,0 +1,60 @@ |
|||
package mw |
|||
|
|||
import ( |
|||
"context" |
|||
"errors" |
|||
"time" |
|||
|
|||
"gitoa.ru/go-4devs/cache" |
|||
) |
|||
|
|||
// Metrics interface for middleware.
|
|||
type Metrics interface { |
|||
Hit(label string) |
|||
Miss(label string) |
|||
Expired(label string) |
|||
Err(label string, operation string) |
|||
Observe(label string, operation string, start time.Time) |
|||
} |
|||
|
|||
// LabelName sets static name.
|
|||
func LabelName(name string) func(ctx context.Context, item *cache.Item) string { |
|||
return func(_ context.Context, _ *cache.Item) string { |
|||
return name |
|||
} |
|||
} |
|||
|
|||
// LabelPreficKey gets lebale by item prefix.
|
|||
func LabelPreficKey(ctx context.Context, item *cache.Item) string { |
|||
return item.Key.Prefix |
|||
} |
|||
|
|||
// WithMetrics cache middleware metrics.
|
|||
func WithMetrics(m Metrics, labelCallback func(ctx context.Context, item *cache.Item) string) cache.Configure { |
|||
return cache.WithMiddleware( |
|||
func(ctx context.Context, op string, item *cache.Item, next cache.Provider) error { |
|||
label := labelCallback(ctx, item) |
|||
start := time.Now() |
|||
err := next(ctx, op, item) |
|||
m.Observe(label, op, start) |
|||
if err != nil { |
|||
switch { |
|||
case errors.Is(err, cache.ErrCacheMiss): |
|||
m.Miss(label) |
|||
case errors.Is(err, cache.ErrCacheExpired): |
|||
m.Expired(label) |
|||
default: |
|||
m.Err(label, op) |
|||
} |
|||
|
|||
return err |
|||
} |
|||
|
|||
if op == cache.OperationGet { |
|||
m.Hit(label) |
|||
} |
|||
|
|||
return nil |
|||
}, |
|||
) |
|||
} |
@ -0,0 +1,93 @@ |
|||
package prometheus |
|||
|
|||
import ( |
|||
"time" |
|||
|
|||
metrics "github.com/prometheus/client_golang/prometheus" |
|||
"gitoa.ru/go-4devs/cache/mw" |
|||
) |
|||
|
|||
const ( |
|||
labelSet = "label" |
|||
labelOperation = "operation" |
|||
) |
|||
|
|||
//nolint: gochecknoglobals
|
|||
var ( |
|||
hitCount = metrics.NewCounterVec( |
|||
metrics.CounterOpts{ |
|||
Name: "cache_hit_total", |
|||
Help: "Counter of hits cache.", |
|||
}, |
|||
[]string{labelSet}, |
|||
) |
|||
missCount = metrics.NewCounterVec( |
|||
metrics.CounterOpts{ |
|||
Name: "cache_miss_total", |
|||
Help: "Counter of misses cache.", |
|||
}, |
|||
[]string{labelSet}, |
|||
) |
|||
expiredCount = metrics.NewCounterVec( |
|||
metrics.CounterOpts{ |
|||
Name: "cache_expired_total", |
|||
Help: "Counter of expired items in cache.", |
|||
}, |
|||
[]string{labelSet}, |
|||
) |
|||
errorsCount = metrics.NewCounterVec( |
|||
metrics.CounterOpts{ |
|||
Name: "cache_errors_total", |
|||
Help: "Counter of errors in cache.", |
|||
}, |
|||
[]string{labelSet, labelOperation}, |
|||
) |
|||
responseTime = metrics.NewHistogramVec( |
|||
metrics.HistogramOpts{ |
|||
Name: "cache_request_duration_seconds", |
|||
Help: "Histogram of RT for the request cache (seconds).", |
|||
}, |
|||
[]string{labelSet, labelOperation}, |
|||
) |
|||
) |
|||
|
|||
//nolint: gochecknoinits
|
|||
func init() { |
|||
metrics.MustRegister( |
|||
hitCount, |
|||
missCount, |
|||
expiredCount, |
|||
errorsCount, |
|||
responseTime, |
|||
) |
|||
} |
|||
|
|||
var _ mw.Metrics = Metrics{} |
|||
|
|||
// Metrics prometeus.
|
|||
type Metrics struct{} |
|||
|
|||
// Miss inc miss error cache.
|
|||
func (m Metrics) Miss(label string) { |
|||
missCount.WithLabelValues(label).Inc() |
|||
} |
|||
|
|||
// Hit increment hit cache.
|
|||
func (m Metrics) Hit(label string) { |
|||
hitCount.WithLabelValues(label).Inc() |
|||
} |
|||
|
|||
// Expired increment error expired.
|
|||
func (m Metrics) Expired(label string) { |
|||
expiredCount.WithLabelValues(label).Inc() |
|||
} |
|||
|
|||
// Err increment base undefined error.
|
|||
func (m Metrics) Err(label string, operation string) { |
|||
errorsCount.WithLabelValues(label, operation).Inc() |
|||
} |
|||
|
|||
// Observe time from start.
|
|||
func (m Metrics) Observe(label string, operation string, start time.Time) { |
|||
responseTime.WithLabelValues(label, operation).Observe(float64(time.Since(start)) / float64(time.Second)) |
|||
} |
@ -0,0 +1,21 @@ |
|||
package cache |
|||
|
|||
import "context" |
|||
|
|||
// available operation.
|
|||
const ( |
|||
OperationGet = "get" |
|||
OperationSet = "set" |
|||
OperationDelete = "delete" |
|||
) |
|||
|
|||
// OperationProvider creating a provider based on available operations.
|
|||
func OperationProvider(prov map[string]func(ctx context.Context, item *Item) error) Provider { |
|||
return func(ctx context.Context, operation string, item *Item) error { |
|||
if method, ok := prov[operation]; ok { |
|||
return method(ctx, item) |
|||
} |
|||
|
|||
return ErrOperationNotAllwed |
|||
} |
|||
} |
@ -0,0 +1,50 @@ |
|||
package cache |
|||
|
|||
import ( |
|||
"context" |
|||
) |
|||
|
|||
// Provider for different types of cache, in memory, lru, redis.
|
|||
type Provider func(ctx context.Context, operation string, item *Item) error |
|||
|
|||
// Handle middleware before/after provider.
|
|||
type Handle func(ctx context.Context, operation string, item *Item, next Provider) error |
|||
|
|||
// ChainHandle chain handle middleware.
|
|||
func ChainHandle(handle ...Handle) Handle { |
|||
if n := len(handle); n > 1 { |
|||
lastI := n - 1 |
|||
|
|||
return func(ctx context.Context, operation string, item *Item, next Provider) error { |
|||
var ( |
|||
chainHandler func(context.Context, string, *Item) error |
|||
curI int |
|||
) |
|||
|
|||
chainHandler = func(currentCtx context.Context, currentOperation string, currentData *Item) error { |
|||
if curI == lastI { |
|||
return next(currentCtx, currentOperation, currentData) |
|||
} |
|||
curI++ |
|||
err := handle[curI](currentCtx, currentOperation, currentData, chainHandler) |
|||
curI-- |
|||
|
|||
return err |
|||
} |
|||
|
|||
return handle[0](ctx, operation, item, chainHandler) |
|||
} |
|||
} |
|||
|
|||
return handle[0] |
|||
} |
|||
|
|||
func chain(init Provider, handleFunc ...Handle) Provider { |
|||
if len(handleFunc) > 0 { |
|||
return func(ctx context.Context, operation string, item *Item) error { |
|||
return ChainHandle(handleFunc...)(ctx, operation, item, init) |
|||
} |
|||
} |
|||
|
|||
return init |
|||
} |
@ -0,0 +1,194 @@ |
|||
package provider_test |
|||
|
|||
import ( |
|||
"context" |
|||
"crypto/rand" |
|||
"fmt" |
|||
"math" |
|||
"math/big" |
|||
"testing" |
|||
"time" |
|||
|
|||
glru "github.com/hashicorp/golang-lru" |
|||
"github.com/stretchr/testify/require" |
|||
"gitoa.ru/go-4devs/cache" |
|||
"gitoa.ru/go-4devs/cache/provider/lru" |
|||
"gitoa.ru/go-4devs/cache/provider/memcache" |
|||
"gitoa.ru/go-4devs/cache/provider/memory" |
|||
"gitoa.ru/go-4devs/cache/provider/pebble" |
|||
"gitoa.ru/go-4devs/cache/provider/redis" |
|||
"gitoa.ru/go-4devs/cache/provider/ristretto" |
|||
"gitoa.ru/go-4devs/cache/test" |
|||
) |
|||
|
|||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" |
|||
|
|||
type provider struct { |
|||
name string |
|||
prov cache.Provider |
|||
} |
|||
|
|||
func providers() []provider { |
|||
client, _ := glru.New(10000) |
|||
db, cl := test.PebbleDB() |
|||
|
|||
defer cl() |
|||
|
|||
return []provider{ |
|||
{"encoding", memory.NewEncoding()}, |
|||
{"map", memory.NewMap()}, |
|||
{"shard", memory.NewMapShard()}, |
|||
{"lru", lru.New(client)}, |
|||
{"ristretto", ristretto.New(test.RistrettoClient())}, |
|||
{"memcache", memcache.New(test.MemcacheClient())}, |
|||
{"redis", redis.New(test.RedisClient())}, |
|||
{"pebble", pebble.New(db)}, |
|||
} |
|||
} |
|||
|
|||
func randStringBytes(n int64) string { |
|||
b := make([]byte, n) |
|||
|
|||
for i := range b { |
|||
b[i] = letterBytes[randInt64(int64(len(letterBytes)))] |
|||
} |
|||
|
|||
return string(b) |
|||
} |
|||
|
|||
func randInt64(max int64) int64 { |
|||
m := big.NewInt(max) |
|||
nBig, _ := rand.Int(rand.Reader, m) |
|||
|
|||
return nBig.Int64() |
|||
} |
|||
|
|||
func BenchmarkCacheGetRandomKeyString(b *testing.B) { |
|||
ctx := context.Background() |
|||
keysLen := 10000 |
|||
|
|||
for _, p := range providers() { |
|||
prov := p.prov |
|||
items := make([]*cache.Item, keysLen) |
|||
|
|||
for i := 0; i < keysLen; i++ { |
|||
var val string |
|||
|
|||
key := randStringBytes(55) |
|||
items[i] = cache.NewItem(key, &val) |
|||
require.Nil(b, prov(ctx, cache.OperationSet, cache.NewItem(key, "value: "+p.name, cache.WithTTL(time.Minute)))) |
|||
} |
|||
|
|||
b.Run(p.name, func(b *testing.B) { |
|||
for i := 0; i < b.N; i++ { |
|||
_ = prov(ctx, cache.OperationGet, items[i%keysLen]) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
func BenchmarkCacheGetRandomKeyInt(b *testing.B) { |
|||
ctx := context.Background() |
|||
keysLen := 10000 |
|||
|
|||
for _, p := range providers() { |
|||
prov := p.prov |
|||
items := make([]*cache.Item, keysLen) |
|||
|
|||
for i := 0; i < keysLen; i++ { |
|||
var val int64 |
|||
|
|||
key := randInt64(math.MaxInt64) |
|||
|
|||
items[i] = cache.NewItem(key, &val) |
|||
require.Nil(b, prov(ctx, cache.OperationSet, cache.NewItem(key, randInt64(math.MaxInt64), cache.WithTTL(time.Minute)))) |
|||
} |
|||
|
|||
b.Run(p.name, func(b *testing.B) { |
|||
for i := 0; i < b.N; i++ { |
|||
_ = prov(ctx, cache.OperationGet, items[i%keysLen]) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
func BenchmarkCacheGetStruct(b *testing.B) { |
|||
ctx := context.Background() |
|||
|
|||
type testStruct struct { |
|||
Key string |
|||
val string |
|||
} |
|||
|
|||
var val testStruct |
|||
item := cache.NewItem("key", &val) |
|||
|
|||
for _, p := range providers() { |
|||
prov := p.prov |
|||
require.Nil(b, prov(ctx, cache.OperationSet, cache.NewItem("key", testStruct{Key: "key", val: ""}, cache.WithTTL(time.Minute)))) |
|||
|
|||
b.Run(p.name, func(b *testing.B) { |
|||
for i := 0; i < b.N; i++ { |
|||
_ = prov(ctx, cache.OperationGet, item) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
func BenchmarkCacheSetStruct(b *testing.B) { |
|||
ctx := context.Background() |
|||
|
|||
type testStruct struct { |
|||
Key string |
|||
Val int |
|||
} |
|||
|
|||
for _, p := range providers() { |
|||
prov := p.prov |
|||
b.Run(p.name, func(b *testing.B) { |
|||
for i := 0; i < b.N; i++ { |
|||
item := cache.NewItem(i, testStruct{"k", i}, cache.WithTTL(time.Hour)) |
|||
_ = prov(ctx, cache.OperationSet, item) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
func BenchmarkCacheGetParallel(b *testing.B) { |
|||
ctx := context.Background() |
|||
|
|||
for _, p := range providers() { |
|||
prov := p.prov |
|||
key := fmt.Sprintf("key_%s", p.name) |
|||
val := fmt.Sprintf("value_%s", p.name) |
|||
item := cache.NewItem(key, &val, cache.WithTTL(time.Minute)) |
|||
require.Nil(b, prov(ctx, cache.OperationSet, item)) |
|||
|
|||
b.Run(p.name, func(b *testing.B) { |
|||
b.RunParallel(func(pb *testing.PB) { |
|||
for pb.Next() { |
|||
_ = prov(ctx, cache.OperationGet, item) |
|||
} |
|||
}) |
|||
}) |
|||
} |
|||
} |
|||
|
|||
func BenchmarkCacheSetParallel(b *testing.B) { |
|||
ctx := context.Background() |
|||
|
|||
for _, p := range providers() { |
|||
prov := p.prov |
|||
key := fmt.Sprintf("key: %v", prov) |
|||
val := fmt.Sprintf("value: %v", prov) |
|||
|
|||
b.Run(p.name, func(b *testing.B) { |
|||
b.RunParallel(func(pb *testing.PB) { |
|||
for pb.Next() { |
|||
item := cache.NewItem(key, val, cache.WithTTL(time.Hour)) |
|||
_ = prov(ctx, cache.OperationSet, item) |
|||
} |
|||
}) |
|||
}) |
|||
} |
|||
} |
@ -0,0 +1,66 @@ |
|||
package lru |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"time" |
|||
|
|||
lru "github.com/hashicorp/golang-lru" |
|||
"gitoa.ru/go-4devs/cache" |
|||
) |
|||
|
|||
// New create new lru cache provider.
|
|||
func New(client *lru.Cache) cache.Provider { |
|||
return func(ctx context.Context, operation string, item *cache.Item) error { |
|||
switch operation { |
|||
case cache.OperationGet: |
|||
val, ok := client.Get(item.Key) |
|||
if !ok { |
|||
return wrapErr(cache.ErrCacheMiss) |
|||
} |
|||
|
|||
it, _ := val.(expired) |
|||
|
|||
if !it.ex.IsZero() { |
|||
item.TTL = time.Until(it.ex) |
|||
} |
|||
|
|||
if item.IsExpired() { |
|||
return wrapErr(cache.ErrCacheExpired) |
|||
} |
|||
|
|||
return wrapErr(cache.TypeAssert(it.value, item.Value)) |
|||
case cache.OperationSet: |
|||
it := expired{ |
|||
value: item.Value, |
|||
ex: time.Time{}, |
|||
} |
|||
if item.TTL > 0 { |
|||
it.ex = item.Expired() |
|||
} |
|||
|
|||
_ = client.Add(item.Key, it) |
|||
|
|||
return nil |
|||
case cache.OperationDelete: |
|||
_ = client.Remove(item.Key) |
|||
|
|||
return nil |
|||
} |
|||
|
|||
return wrapErr(cache.ErrOperationNotAllwed) |
|||
} |
|||
} |
|||
|
|||
type expired struct { |
|||
ex time.Time |
|||
value interface{} |
|||
} |
|||
|
|||
func wrapErr(err error) error { |
|||
if err != nil { |
|||
return fmt.Errorf("%w: lru", err) |
|||
} |
|||
|
|||
return nil |
|||
} |
@ -0,0 +1,18 @@ |
|||
package lru_test |
|||
|
|||
import ( |
|||
"testing" |
|||
|
|||
glru "github.com/hashicorp/golang-lru" |
|||
"github.com/stretchr/testify/require" |
|||
"gitoa.ru/go-4devs/cache/provider/lru" |
|||
"gitoa.ru/go-4devs/cache/test" |
|||
) |
|||
|
|||
func TestEncoding(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
client, err := glru.New(10) |
|||
require.Nil(t, err) |
|||
test.RunSute(t, lru.New(client)) |
|||
} |
@ -0,0 +1,52 @@ |
|||
package memcache |
|||
|
|||
import ( |
|||
"context" |
|||
"errors" |
|||
"fmt" |
|||
|
|||
"github.com/bradfitz/gomemcache/memcache" |
|||
"gitoa.ru/go-4devs/cache" |
|||
) |
|||
|
|||
// New memcache provider.
|
|||
func New(client *memcache.Client) cache.Provider { |
|||
return func(ctx context.Context, operation string, item *cache.Item) error { |
|||
key := item.Key.String() |
|||
|
|||
switch operation { |
|||
case cache.OperationGet: |
|||
ci, err := client.Get(item.Key.String()) |
|||
|
|||
switch { |
|||
case errors.Is(err, memcache.ErrCacheMiss): |
|||
return wrapErr(cache.ErrCacheMiss) |
|||
case errors.Is(err, memcache.ErrMalformedKey): |
|||
return wrapErr(cache.ErrKeyNotValid) |
|||
case err != nil: |
|||
return wrapErr(err) |
|||
} |
|||
|
|||
return wrapErr(item.Unmarshal(ci.Value)) |
|||
case cache.OperationSet: |
|||
data, err := item.Marshal() |
|||
if err != nil { |
|||
return wrapErr(err) |
|||
} |
|||
|
|||
return wrapErr(client.Set(&memcache.Item{Key: key, Flags: 0, Value: data, Expiration: int32(item.TTL.Seconds())})) |
|||
case cache.OperationDelete: |
|||
return wrapErr(client.Delete(key)) |
|||
} |
|||
|
|||
return wrapErr(cache.ErrOperationNotAllwed) |
|||
} |
|||
} |
|||
|
|||
func wrapErr(err error) error { |
|||
if err != nil { |
|||
return fmt.Errorf("%w: memcache", err) |
|||
} |
|||
|
|||
return nil |
|||
} |
@ -0,0 +1,14 @@ |
|||
package memcache_test |
|||
|
|||
import ( |
|||
"testing" |
|||
|
|||
"gitoa.ru/go-4devs/cache" |
|||
"gitoa.ru/go-4devs/cache/provider/memcache" |
|||
"gitoa.ru/go-4devs/cache/test" |
|||
) |
|||
|
|||
func TestProvider(t *testing.T) { |
|||
t.Parallel() |
|||
test.RunSute(t, memcache.New(test.MemcacheClient()), test.WithExpire(cache.ErrCacheMiss)) |
|||
} |
@ -0,0 +1,93 @@ |
|||
package memory |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"sync" |
|||
"time" |
|||
|
|||
"gitoa.ru/go-4devs/cache" |
|||
) |
|||
|
|||
// NewEncoding create new provider.
|
|||
func NewEncoding() cache.Provider { |
|||
items := make(map[cache.Key]encodedEntry) |
|||
mu := sync.RWMutex{} |
|||
|
|||
return func(ctx context.Context, operation string, item *cache.Item) error { |
|||
switch operation { |
|||
case cache.OperationSet: |
|||
i, err := newEncodedEntry(item) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
mu.Lock() |
|||
items[item.Key] = i |
|||
mu.Unlock() |
|||
|
|||
return nil |
|||
case cache.OperationDelete: |
|||
mu.Lock() |
|||
delete(items, item.Key) |
|||
mu.Unlock() |
|||
|
|||
return nil |
|||
case cache.OperationGet: |
|||
mu.RLock() |
|||
i, ok := items[item.Key] |
|||
mu.RUnlock() |
|||
|
|||
if !ok { |
|||
return wrapErr(cache.ErrCacheMiss) |
|||
} |
|||
|
|||
return resolveEncodedEntry(i, item) |
|||
} |
|||
|
|||
return wrapErr(cache.ErrOperationNotAllwed) |
|||
} |
|||
} |
|||
|
|||
type encodedEntry struct { |
|||
data []byte |
|||
expired time.Time |
|||
} |
|||
|
|||
func wrapErr(err error) error { |
|||
if err != nil { |
|||
return fmt.Errorf("%w: encoding", err) |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func newEncodedEntry(item *cache.Item) (encodedEntry, error) { |
|||
var ( |
|||
e encodedEntry |
|||
err error |
|||
) |
|||
|
|||
e.data, err = item.Marshal() |
|||
if err != nil { |
|||
return e, wrapErr(err) |
|||
} |
|||
|
|||
if item.TTL > 0 { |
|||
e.expired = item.Expired() |
|||
} |
|||
|
|||
return e, nil |
|||
} |
|||
|
|||
func resolveEncodedEntry(e encodedEntry, item *cache.Item) error { |
|||
if !e.expired.IsZero() { |
|||
item.TTL = time.Until(e.expired) |
|||
} |
|||
|
|||
if item.IsExpired() { |
|||
return wrapErr(cache.ErrCacheExpired) |
|||
} |
|||
|
|||
return wrapErr(item.Unmarshal(e.data)) |
|||
} |
@ -0,0 +1,13 @@ |
|||
package memory_test |
|||
|
|||
import ( |
|||
"testing" |
|||
|
|||
"gitoa.ru/go-4devs/cache/provider/memory" |
|||
"gitoa.ru/go-4devs/cache/test" |
|||
) |
|||
|
|||
func TestEncoding(t *testing.T) { |
|||
t.Parallel() |
|||
test.RunSute(t, memory.NewEncoding()) |
|||
} |
@ -0,0 +1,155 @@ |
|||
package memory |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"hash/crc64" |
|||
"sync" |
|||
"time" |
|||
|
|||
"gitoa.ru/go-4devs/cache" |
|||
) |
|||
|
|||
const defaultShards = 255 |
|||
|
|||
// NewMap creates new map cache.
|
|||
func NewMap() cache.Provider { |
|||
m := sync.Map{} |
|||
|
|||
return func(ctx context.Context, op string, item *cache.Item) error { |
|||
switch op { |
|||
case cache.OperationDelete: |
|||
m.Delete(item.Key) |
|||
|
|||
return nil |
|||
case cache.OperationSet: |
|||
m.Store(item.Key, newEntry(item)) |
|||
|
|||
return nil |
|||
case cache.OperationGet: |
|||
e, ok := m.Load(item.Key) |
|||
if !ok { |
|||
return fmt.Errorf("%w: map", cache.ErrCacheMiss) |
|||
} |
|||
|
|||
return resolveEntry(e.(entry), item) |
|||
} |
|||
|
|||
return fmt.Errorf("%w: map", cache.ErrOperationNotAllwed) |
|||
} |
|||
} |
|||
|
|||
func resolveEntry(e entry, item *cache.Item) error { |
|||
if !e.expired.IsZero() { |
|||
item.TTL = time.Until(e.expired) |
|||
} |
|||
|
|||
if item.IsExpired() { |
|||
return fmt.Errorf("%w: map", cache.ErrCacheExpired) |
|||
} |
|||
|
|||
if err := cache.TypeAssert(e.data, item.Value); err != nil { |
|||
return fmt.Errorf("%w: map", err) |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func newEntry(item *cache.Item) entry { |
|||
e := entry{data: item.Value} |
|||
|
|||
if item.TTL > 0 { |
|||
e.expired = item.Expired() |
|||
} |
|||
|
|||
return e |
|||
} |
|||
|
|||
type entry struct { |
|||
data interface{} |
|||
expired time.Time |
|||
} |
|||
|
|||
type settings struct { |
|||
numShards uint64 |
|||
hashString func(in cache.Key) uint64 |
|||
} |
|||
type Option func(*settings) |
|||
|
|||
func WithNumShards(num uint64) Option { |
|||
return func(s *settings) { |
|||
s.numShards = num |
|||
} |
|||
} |
|||
|
|||
func WithHashKey(f func(in cache.Key) uint64) Option { |
|||
return func(s *settings) { |
|||
s.hashString = f |
|||
} |
|||
} |
|||
|
|||
//nolint: gochecknoglobals
|
|||
var table = crc64.MakeTable(crc64.ISO) |
|||
|
|||
func hashString(in cache.Key) uint64 { |
|||
switch k := in.Key.(type) { |
|||
case int64: |
|||
return uint64(k) |
|||
case int32: |
|||
return uint64(k) |
|||
case int: |
|||
return uint64(k) |
|||
case uint64: |
|||
return k |
|||
case uint32: |
|||
return uint64(k) |
|||
case uint: |
|||
return uint64(k) |
|||
default: |
|||
return crc64.Checksum([]byte(in.String()), table) |
|||
} |
|||
} |
|||
|
|||
func NewMapShard(opts ...Option) cache.Provider { |
|||
s := settings{ |
|||
numShards: defaultShards, |
|||
hashString: hashString, |
|||
} |
|||
|
|||
for _, opt := range opts { |
|||
opt(&s) |
|||
} |
|||
|
|||
items := make([]*sync.Map, s.numShards) |
|||
for i := range items { |
|||
items[i] = &sync.Map{} |
|||
} |
|||
|
|||
return func(ctx context.Context, operation string, item *cache.Item) error { |
|||
idx := s.hashString(item.Key) |
|||
|
|||
switch operation { |
|||
case cache.OperationDelete: |
|||
items[idx%s.numShards].Delete(item.Key) |
|||
|
|||
return nil |
|||
case cache.OperationSet: |
|||
items[idx%s.numShards].Store(item.Key, newEntry(item)) |
|||
|
|||
return nil |
|||
case cache.OperationGet: |
|||
e, ok := items[idx%s.numShards].Load(item.Key) |
|||
if !ok { |
|||
return wrapShardErr(cache.ErrCacheMiss) |
|||
} |
|||
|
|||
return resolveEntry(e.(entry), item) |
|||
} |
|||
|
|||
return wrapShardErr(cache.ErrOperationNotAllwed) |
|||
} |
|||
} |
|||
|
|||
func wrapShardErr(err error) error { |
|||
return fmt.Errorf("%w: memory shards", err) |
|||
} |
@ -0,0 +1,18 @@ |
|||
package memory_test |
|||
|
|||
import ( |
|||
"testing" |
|||
|
|||
"gitoa.ru/go-4devs/cache/provider/memory" |
|||
"gitoa.ru/go-4devs/cache/test" |
|||
) |
|||
|
|||
func TestMap(t *testing.T) { |
|||
t.Parallel() |
|||
test.RunSute(t, memory.NewMap()) |
|||
} |
|||
|
|||
func TestMapShard(t *testing.T) { |
|||
t.Parallel() |
|||
test.RunSute(t, memory.NewMapShard()) |
|||
} |
@ -0,0 +1,22 @@ |
|||
package ns |
|||
|
|||
import ( |
|||
"context" |
|||
"errors" |
|||
|
|||
"gitoa.ru/go-4devs/cache" |
|||
) |
|||
|
|||
var ErrProviderNotFound = errors.New("provider not found") |
|||
|
|||
func New(providers map[string]cache.Provider) cache.Provider { |
|||
return func(ctx context.Context, operation string, item *cache.Item) error { |
|||
if prov, ok := providers[item.Key.Prefix]; ok { |
|||
item.Key.Prefix = "" |
|||
|
|||
return prov(ctx, operation, item) |
|||
} |
|||
|
|||
return ErrProviderNotFound |
|||
} |
|||
} |
@ -0,0 +1,54 @@ |
|||
package pebble |
|||
|
|||
import ( |
|||
"context" |
|||
"errors" |
|||
"fmt" |
|||
|
|||
"github.com/cockroachdb/pebble" |
|||
"gitoa.ru/go-4devs/cache" |
|||
"gitoa.ru/go-4devs/cache/item" |
|||
) |
|||
|
|||
func New(db *pebble.DB) cache.Provider { |
|||
return func(ctx context.Context, operation string, i *cache.Item) error { |
|||
key := []byte(i.Key.String()) |
|||
|
|||
switch operation { |
|||
case cache.OperationGet: |
|||
val, cl, err := db.Get([]byte(i.Key.String())) |
|||
if err != nil { |
|||
if errors.Is(err, pebble.ErrNotFound) { |
|||
return wrapErr(cache.ErrCacheMiss) |
|||
} |
|||
|
|||
return wrapErr(err) |
|||
} |
|||
|
|||
defer func() { |
|||
_ = cl.Close() |
|||
}() |
|||
|
|||
return wrapErr(item.UnmarshalExpired(i, val)) |
|||
case cache.OperationSet: |
|||
b, err := item.MarshalExpired(i) |
|||
if err != nil { |
|||
return wrapErr(err) |
|||
} |
|||
|
|||
return wrapErr(db.Set(key, b, pebble.Sync)) |
|||
case cache.OperationDelete: |
|||
return wrapErr(db.Delete(key, pebble.Sync)) |
|||
} |
|||
|
|||
return wrapErr(cache.ErrOperationNotAllwed) |
|||
} |
|||
} |
|||
|
|||
func wrapErr(err error) error { |
|||
if err != nil { |
|||
return fmt.Errorf("%w: pebble", err) |
|||
} |
|||
|
|||
return nil |
|||
} |
@ -0,0 +1,17 @@ |
|||
package pebble_test |
|||
|
|||
import ( |
|||
"testing" |
|||
|
|||
"gitoa.ru/go-4devs/cache/provider/pebble" |
|||
"gitoa.ru/go-4devs/cache/test" |
|||
) |
|||
|
|||
func TestPebble(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
db, cl := test.PebbleDB() |
|||
defer cl() |
|||
|
|||
test.RunSute(t, pebble.New(db)) |
|||
} |
@ -0,0 +1,110 @@ |
|||
package redis |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
|
|||
"gitoa.ru/go-4devs/cache" |
|||
) |
|||
|
|||
type Conn interface { |
|||
Do(commandName string, args ...interface{}) (reply interface{}, err error) |
|||
Send(commandName string, args ...interface{}) error |
|||
Flush() error |
|||
Close() error |
|||
} |
|||
|
|||
// New creates new provider.
|
|||
func New(pool func(context.Context) (Conn, error)) cache.Provider { |
|||
return func(ctx context.Context, operation string, item *cache.Item) error { |
|||
conn, err := pool(ctx) |
|||
if err != nil { |
|||
return wrapErr(err) |
|||
} |
|||
defer conn.Close() |
|||
|
|||
key := item.Key.String() |
|||
|
|||
switch operation { |
|||
case cache.OperationGet: |
|||
data, ttl, err := get(conn, key) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
item.TTLInSecond(ttl) |
|||
|
|||
return wrapErr(item.Unmarshal(data)) |
|||
case cache.OperationSet: |
|||
data, err := item.Marshal() |
|||
if err != nil { |
|||
return wrapErr(err) |
|||
} |
|||
|
|||
return set(conn, key, data, int(item.TTL.Seconds())) |
|||
case cache.OperationDelete: |
|||
return del(conn, key) |
|||
} |
|||
|
|||
return wrapErr(cache.ErrOperationNotAllwed) |
|||
} |
|||
} |
|||
|
|||
func get(conn Conn, key string) ([]byte, int64, error) { |
|||
data, err := conn.Do("GET", key) |
|||
if err != nil { |
|||
return nil, 0, wrapErr(err) |
|||
} |
|||
|
|||
if data == nil { |
|||
return nil, 0, wrapErr(cache.ErrCacheMiss) |
|||
} |
|||
|
|||
v, ok := data.([]byte) |
|||
if !ok { |
|||
return nil, 0, wrapErr(cache.ErrSourceNotValid) |
|||
} |
|||
|
|||
expire, err := conn.Do("TTL", key) |
|||
if err != nil { |
|||
return v, 0, wrapErr(err) |
|||
} |
|||
|
|||
ex, _ := expire.(int64) |
|||
|
|||
return v, ex, nil |
|||
} |
|||
|
|||
func set(conn Conn, key string, data []byte, ttl int) error { |
|||
if err := conn.Send("SET", key, data); err != nil { |
|||
return wrapErr(err) |
|||
} |
|||
|
|||
if ttl > 0 { |
|||
if err := conn.Send("EXPIRE", key, ttl); err != nil { |
|||
return wrapErr(err) |
|||
} |
|||
} |
|||
|
|||
if err := conn.Flush(); err != nil { |
|||
return fmt.Errorf("failed flush then set %s by %w", key, conn.Flush()) |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func del(conn Conn, key string) error { |
|||
if _, err := conn.Do("DEL", key); err != nil { |
|||
return wrapErr(err) |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func wrapErr(err error) error { |
|||
if err != nil { |
|||
return fmt.Errorf("%w: redis pool", err) |
|||
} |
|||
|
|||
return nil |
|||
} |
@ -0,0 +1,14 @@ |
|||
package redis_test |
|||
|
|||
import ( |
|||
"testing" |
|||
|
|||
"gitoa.ru/go-4devs/cache" |
|||
"gitoa.ru/go-4devs/cache/provider/redis" |
|||
"gitoa.ru/go-4devs/cache/test" |
|||
) |
|||
|
|||
func TestRedisPool(t *testing.T) { |
|||
t.Parallel() |
|||
test.RunSute(t, redis.New(test.RedisClient()), test.WithExpire(cache.ErrCacheMiss)) |
|||
} |
@ -0,0 +1,20 @@ |
|||
package redis |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
|
|||
"github.com/gomodule/redigo/redis" |
|||
) |
|||
|
|||
// NewPool creates redigo pool.
|
|||
func NewPool(pool *redis.Pool) func(context.Context) (Conn, error) { |
|||
return func(ctx context.Context) (Conn, error) { |
|||
conn, err := pool.GetContext(ctx) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("failed get connect: %w", err) |
|||
} |
|||
|
|||
return conn, nil |
|||
} |
|||
} |
@ -0,0 +1,69 @@ |
|||
package ristretto |
|||
|
|||
import ( |
|||
"context" |
|||
"errors" |
|||
"fmt" |
|||
|
|||
"github.com/dgraph-io/ristretto" |
|||
"gitoa.ru/go-4devs/cache" |
|||
) |
|||
|
|||
var ErrSetValue = errors.New("failed set value") |
|||
|
|||
type Option func(*setting) |
|||
|
|||
func WithCost(cost int64) Option { |
|||
return func(s *setting) { |
|||
s.cost = cost |
|||
} |
|||
} |
|||
|
|||
type setting struct { |
|||
cost int64 |
|||
} |
|||
|
|||
func New(retto *ristretto.Cache, opts ...Option) cache.Provider { |
|||
s := setting{ |
|||
cost: 1, |
|||
} |
|||
|
|||
for _, opt := range opts { |
|||
opt(&s) |
|||
} |
|||
|
|||
return func(ctx context.Context, operation string, item *cache.Item) error { |
|||
var key interface{} |
|||
if item.Key.Prefix != "" { |
|||
key = item.Key.String() |
|||
} else { |
|||
key = item.Key.Key |
|||
} |
|||
|
|||
switch operation { |
|||
case cache.OperationGet: |
|||
res, ok := retto.Get(key) |
|||
if !ok { |
|||
return fmt.Errorf("%w: ristretto", cache.ErrCacheMiss) |
|||
} |
|||
|
|||
if err := cache.TypeAssert(res, item.Value); err != nil { |
|||
return fmt.Errorf("failed assert type: %w", err) |
|||
} |
|||
|
|||
return nil |
|||
case cache.OperationDelete: |
|||
retto.Del(key) |
|||
|
|||
return nil |
|||
case cache.OperationSet: |
|||
if ok := retto.SetWithTTL(key, item.Value, s.cost, item.TTL); !ok { |
|||
return ErrSetValue |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
return cache.ErrOperationNotAllwed |
|||
} |
|||
} |
@ -0,0 +1,26 @@ |
|||
package ristretto_test |
|||
|
|||
import ( |
|||
"testing" |
|||
"time" |
|||
|
|||
"github.com/dgraph-io/ristretto" |
|||
"github.com/stretchr/testify/require" |
|||
"gitoa.ru/go-4devs/cache" |
|||
provider "gitoa.ru/go-4devs/cache/provider/ristretto" |
|||
"gitoa.ru/go-4devs/cache/test" |
|||
) |
|||
|
|||
func TestRistretto(t *testing.T) { |
|||
t.Parallel() |
|||
|
|||
retto, err := ristretto.NewCache(&ristretto.Config{ |
|||
NumCounters: 1e7, // number of keys to track frequency of (10M).
|
|||
MaxCost: 1 << 30, // maximum cost of cache (1GB).
|
|||
BufferItems: 64, // number of keys per Get buffer.
|
|||
}) |
|||
require.Nil(t, err) |
|||
test.RunSute(t, provider.New(retto), test.WithWaitGet(func() { |
|||
time.Sleep(10 * time.Millisecond) |
|||
}), test.WithExpire(cache.ErrCacheMiss)) |
|||
} |
@ -0,0 +1,84 @@ |
|||
package test |
|||
|
|||
import ( |
|||
"context" |
|||
"os" |
|||
"path/filepath" |
|||
"runtime" |
|||
"time" |
|||
|
|||
gom "github.com/bradfitz/gomemcache/memcache" |
|||
"github.com/cockroachdb/pebble" |
|||
"github.com/dgraph-io/ristretto" |
|||
redigo "github.com/gomodule/redigo/redis" |
|||
"gitoa.ru/go-4devs/cache/provider/redis" |
|||
) |
|||
|
|||
// RedisClient created redis client.
|
|||
func RedisClient() func(ctx context.Context) (redis.Conn, error) { |
|||
host, ok := os.LookupEnv("FDEVS_CACHE_REDIS_HOST") |
|||
if !ok { |
|||
host = ":6379" |
|||
} |
|||
|
|||
client := &redigo.Pool{ |
|||
DialContext: func(ctx context.Context) (redigo.Conn, error) { |
|||
return redigo.DialContext(ctx, "tcp", host) |
|||
}, |
|||
} |
|||
|
|||
return redis.NewPool(client) |
|||
} |
|||
|
|||
// MemcacheClient created memcached client.
|
|||
func MemcacheClient() *gom.Client { |
|||
host, ok := os.LookupEnv("FDEVS_CACHE_MEMCACHE_HOST") |
|||
if !ok { |
|||
host = "localhost:11211" |
|||
} |
|||
|
|||
return gom.New(host) |
|||
} |
|||
|
|||
// RistrettoClient creates ristretto client.
|
|||
func RistrettoClient() *ristretto.Cache { |
|||
cache, _ := ristretto.NewCache(&ristretto.Config{ |
|||
NumCounters: 1e7, // number of keys to track frequency of (10M).
|
|||
MaxCost: 1 << 30, // maximum cost of cache (1GB).
|
|||
BufferItems: 64, // number of keys per Get buffer.
|
|||
}) |
|||
|
|||
return cache |
|||
} |
|||
|
|||
// PebbleDB creates pebble DB.
|
|||
func PebbleDB() (*pebble.DB, func()) { |
|||
path := "demo.test" |
|||
if _, filename, _, ok := runtime.Caller(0); ok { |
|||
path = filepath.Dir(filename) + "/" + path |
|||
} |
|||
|
|||
db, _ := pebble.Open(path, &pebble.Options{}) |
|||
|
|||
return db, func() { |
|||
os.RemoveAll(path) |
|||
} |
|||
} |
|||
|
|||
// User tested user data.
|
|||
type User struct { |
|||
ID int |
|||
Name string |
|||
UpdateAt time.Time |
|||
CreatedAt time.Time |
|||
} |
|||
|
|||
// NewUser create mocks data user.
|
|||
func NewUser(id int) User { |
|||
return User{ |
|||
ID: id, |
|||
Name: "andrey", |
|||
UpdateAt: time.Date(2020, 2, 1, 1, 2, 3, 4, time.UTC), |
|||
CreatedAt: time.Date(1999, 2, 1, 1, 2, 3, 4, time.UTC), |
|||
} |
|||
} |
@ -0,0 +1,57 @@ |
|||
package test |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"testing" |
|||
|
|||
"gitoa.ru/go-4devs/cache" |
|||
) |
|||
|
|||
var _ cache.Provider = NewProviderMock(&testing.T{}) |
|||
|
|||
// OptionMock configure mock.
|
|||
type OptionMock func(*ProviderMock) |
|||
|
|||
// WithDelete sets delete method.
|
|||
func WithDelete(f func(t *testing.T) func(ctx context.Context, item *cache.Item) error) OptionMock { |
|||
return func(pm *ProviderMock) { pm.operations[cache.OperationDelete] = f } |
|||
} |
|||
|
|||
// WithGet sets get method.
|
|||
func WithGet(f func(t *testing.T) func(ctx context.Context, item *cache.Item) error) OptionMock { |
|||
return func(pm *ProviderMock) { pm.operations[cache.OperationGet] = f } |
|||
} |
|||
|
|||
// WithSet sets set method.
|
|||
func WithSet(f func(t *testing.T) func(ctx context.Context, item *cache.Item) error) OptionMock { |
|||
return func(pm *ProviderMock) { pm.operations[cache.OperationSet] = f } |
|||
} |
|||
|
|||
// NewProviderMock create new mock provider.
|
|||
func NewProviderMock(t *testing.T, opts ...OptionMock) cache.Provider { |
|||
t.Helper() |
|||
|
|||
pm := &ProviderMock{ |
|||
t: t, |
|||
operations: make(map[string]func(t *testing.T) func(ctx context.Context, item *cache.Item) error, 3), |
|||
} |
|||
|
|||
for _, o := range opts { |
|||
o(pm) |
|||
} |
|||
|
|||
return func(ctx context.Context, operation string, item *cache.Item) error { |
|||
if m, ok := pm.operations[operation]; ok { |
|||
return m(pm.t)(ctx, item) |
|||
} |
|||
|
|||
return fmt.Errorf("%w: %s", cache.ErrOperationNotAllwed, operation) |
|||
} |
|||
} |
|||
|
|||
// ProviderMock mock.
|
|||
type ProviderMock struct { |
|||
t *testing.T |
|||
operations map[string]func(t *testing.T) func(ctx context.Context, item *cache.Item) error |
|||
} |
@ -0,0 +1,133 @@ |
|||
package test |
|||
|
|||
import ( |
|||
"context" |
|||
"errors" |
|||
"testing" |
|||
"time" |
|||
|
|||
"github.com/stretchr/testify/require" |
|||
"github.com/stretchr/testify/suite" |
|||
"gitoa.ru/go-4devs/cache" |
|||
) |
|||
|
|||
const ( |
|||
expire = time.Second |
|||
waitExpire = expire * 2 |
|||
) |
|||
|
|||
// Option configure sute.
|
|||
type Option func(*ProviderSuite) |
|||
|
|||
// WithExpire sets expired errors.
|
|||
func WithExpire(err error) Option { |
|||
return func(ps *ProviderSuite) { ps.expire = err } |
|||
} |
|||
|
|||
func WithWaitGet(f func()) Option { |
|||
return func(ps *ProviderSuite) { ps.waitGet = f } |
|||
} |
|||
|
|||
// RunSute run test by provider.
|
|||
func RunSute(t *testing.T, provider cache.Provider, opts ...Option) { |
|||
t.Helper() |
|||
|
|||
cs := &ProviderSuite{ |
|||
provider: provider, |
|||
expire: cache.ErrCacheExpired, |
|||
waitGet: func() {}, |
|||
} |
|||
|
|||
for _, o := range opts { |
|||
o(cs) |
|||
} |
|||
|
|||
suite.Run(t, cs) |
|||
} |
|||
|
|||
// ProviderSuite for testing providers.
|
|||
type ProviderSuite struct { |
|||
provider cache.Provider |
|||
expire error |
|||
waitGet func() |
|||
suite.Suite |
|||
} |
|||
|
|||
// TestGet tested get.
|
|||
func (s *ProviderSuite) TestGet() { |
|||
s.T().Parallel() |
|||
|
|||
ctx := context.Background() |
|||
|
|||
var val string |
|||
|
|||
require.Nil(s.T(), s.provider(ctx, cache.OperationSet, cache.NewItem("get", "some value"))) |
|||
s.waitGet() |
|||
require.Nil(s.T(), s.provider(ctx, cache.OperationGet, cache.NewItem("get", &val))) |
|||
require.Equal(s.T(), "some value", val) |
|||
|
|||
var user User |
|||
|
|||
cachedUser := NewUser(1) |
|||
|
|||
require.Nil(s.T(), s.provider(ctx, cache.OperationSet, cache.NewItem("get_user", cachedUser))) |
|||
s.waitGet() |
|||
require.Nil(s.T(), s.provider(ctx, cache.OperationGet, cache.NewItem("get_user", &user))) |
|||
require.Equal(s.T(), cachedUser, user) |
|||
} |
|||
|
|||
// TestCacheMiss tested cache miss error.
|
|||
func (s *ProviderSuite) TestCacheMiss() { |
|||
s.T().Parallel() |
|||
|
|||
ctx := context.Background() |
|||
|
|||
require.True(s.T(), |
|||
errors.Is(s.provider(ctx, cache.OperationGet, cache.NewItem("cache_miss", nil)), cache.ErrCacheMiss), |
|||
"failed expect errors", |
|||
) |
|||
} |
|||
|
|||
// TestExpired tested error expired.
|
|||
func (s *ProviderSuite) TestExpired() { |
|||
s.T().Parallel() |
|||
|
|||
ctx := context.Background() |
|||
|
|||
var val string |
|||
|
|||
require.Nil(s.T(), s.provider(ctx, cache.OperationSet, cache.NewItem("expired", "some value", cache.WithTTL(expire)))) |
|||
time.Sleep(waitExpire) |
|||
|
|||
err := s.provider(ctx, cache.OperationGet, cache.NewItem("expired", nil)) |
|||
require.Truef(s.T(), errors.Is(err, s.expire), "failed expired error got:%s", err) |
|||
require.Equal(s.T(), "", val) |
|||
} |
|||
|
|||
// TestTTL tested set ttl.
|
|||
func (s *ProviderSuite) TestTTL() { |
|||
s.T().Parallel() |
|||
|
|||
ctx := context.Background() |
|||
|
|||
var val string |
|||
|
|||
require.Nil(s.T(), s.provider(ctx, cache.OperationSet, cache.NewItem("ttl", "some ttl value", cache.WithTTL(time.Hour)))) |
|||
s.waitGet() |
|||
require.Nil(s.T(), s.provider(ctx, cache.OperationGet, cache.NewItem("ttl", &val))) |
|||
require.Equal(s.T(), "some ttl value", val) |
|||
} |
|||
|
|||
// TestDelete tested delete method.
|
|||
func (s *ProviderSuite) TestDelete() { |
|||
s.T().Parallel() |
|||
|
|||
ctx := context.Background() |
|||
|
|||
require.Nil(s.T(), s.provider(ctx, cache.OperationSet, cache.NewItem("delete:key", "some delete value"))) |
|||
require.Nil(s.T(), s.provider(ctx, cache.OperationDelete, cache.NewItem("delete:key", nil))) |
|||
require.True(s.T(), |
|||
errors.Is(s.provider(ctx, cache.OperationGet, cache.NewItem("cache_miss", nil)), cache.ErrCacheMiss), |
|||
"failed delete errors", |
|||
) |
|||
} |
@ -0,0 +1,99 @@ |
|||
package cache |
|||
|
|||
import ( |
|||
"reflect" |
|||
"time" |
|||
) |
|||
|
|||
// TypeAssert assert source to target.
|
|||
func TypeAssert(source, target interface{}) (err error) { |
|||
if source == nil { |
|||
return nil |
|||
} |
|||
|
|||
if directTypeAssert(source, target) { |
|||
return nil |
|||
} |
|||
|
|||
v := reflect.ValueOf(target) |
|||
if !v.IsValid() { |
|||
return ErrTargetNil |
|||
} |
|||
|
|||
if v.Kind() != reflect.Ptr { |
|||
return NewErrorTarget(target) |
|||
} |
|||
|
|||
v = v.Elem() |
|||
|
|||
if !v.IsValid() { |
|||
return NewErrorTarget(target) |
|||
} |
|||
|
|||
s := reflect.ValueOf(source) |
|||
if !s.IsValid() { |
|||
return ErrSourceNotValid |
|||
} |
|||
|
|||
s = deReference(s) |
|||
v.Set(s) |
|||
|
|||
return nil |
|||
} |
|||
|
|||
// nolint: cyclop,gocyclo
|
|||
func directTypeAssert(source, target interface{}) bool { |
|||
var ok bool |
|||
switch v := target.(type) { |
|||
case *string: |
|||
*v, ok = source.(string) |
|||
case *[]byte: |
|||
*v, ok = source.([]byte) |
|||
case *int: |
|||
*v, ok = source.(int) |
|||
case *int8: |
|||
*v, ok = source.(int8) |
|||
case *int16: |
|||
*v, ok = source.(int16) |
|||
case *int32: |
|||
*v, ok = source.(int32) |
|||
case *int64: |
|||
*v, ok = source.(int64) |
|||
case *uint: |
|||
*v, ok = source.(uint) |
|||
case *uint8: |
|||
*v, ok = source.(uint8) |
|||
case *uint16: |
|||
*v, ok = source.(uint16) |
|||
case *uint32: |
|||
*v, ok = source.(uint32) |
|||
case *uint64: |
|||
*v, ok = source.(uint64) |
|||
case *bool: |
|||
*v, ok = source.(bool) |
|||
case *float32: |
|||
*v, ok = source.(float32) |
|||
case *float64: |
|||
*v, ok = source.(float64) |
|||
case *time.Duration: |
|||
*v, ok = source.(time.Duration) |
|||
case *time.Time: |
|||
*v, ok = source.(time.Time) |
|||
case *[]string: |
|||
*v, ok = source.([]string) |
|||
case *map[string]string: |
|||
*v, ok = source.(map[string]string) |
|||
case *map[string]interface{}: |
|||
*v, ok = source.(map[string]interface{}) |
|||
} |
|||
|
|||
return ok |
|||
} |
|||
|
|||
func deReference(v reflect.Value) reflect.Value { |
|||
if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && !v.IsNil() { |
|||
return v.Elem() |
|||
} |
|||
|
|||
return v |
|||
} |
Loading…
Reference in new issue