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