Browse Source

first commit

master
andrey1s 3 years ago
commit
7da0cd57ce
  1. 23
      .drone.yml
  2. 17
      .gitignore
  3. 43
      .golangci.yml
  4. 19
      LICENSE
  5. 378
      README.md
  6. 67
      bench_test.go
  7. 121
      cache.go
  8. 226
      cache_example_test.go
  9. 208
      cache_test.go
  10. 13
      docker-compose.yml
  11. 33
      error.go
  12. 15
      go.mod
  13. 185
      go.sum
  14. 147
      item.go
  15. 56
      item/expired.go
  16. 99
      item/expired_easyjson.go
  17. 134
      mw/fallback.go
  18. 221
      mw/fallback_test.go
  19. 78
      mw/gc.go
  20. 68
      mw/gc_test.go
  21. 60
      mw/metrics.go
  22. 93
      mw/prometheus/metrics.go
  23. 21
      operation.go
  24. 50
      provider.go
  25. 194
      provider/bench_provider_test.go
  26. 66
      provider/lru/provider.go
  27. 18
      provider/lru/provider_test.go
  28. 52
      provider/memcache/provider.go
  29. 14
      provider/memcache/provider_test.go
  30. 93
      provider/memory/encoding.go
  31. 13
      provider/memory/encoding_test.go
  32. 155
      provider/memory/map.go
  33. 18
      provider/memory/map_test.go
  34. 22
      provider/ns/provider.go
  35. 54
      provider/pebble/provider.go
  36. 17
      provider/pebble/provider_test.go
  37. 110
      provider/redis/pool.go
  38. 14
      provider/redis/pool_test.go
  39. 20
      provider/redis/redigo.go
  40. 69
      provider/ristretto/provider.go
  41. 26
      provider/ristretto/provider_test.go
  42. 84
      test/helpers.go
  43. 57
      test/provider.go
  44. 133
      test/sute.go
  45. 99
      type_assert.go

23
.drone.yml

@ -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

17
.gitignore

@ -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/

43
.golangci.yml

@ -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

19
LICENSE

@ -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.

378
README.md

@ -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
```

67
bench_test.go

@ -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)
}
})
}
}

121
cache.go

@ -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...)
}

226
cache_example_test.go

@ -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()
}

208
cache_test.go

@ -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)
}

13
docker-compose.yml

@ -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"

33
error.go

@ -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)
}

15
go.mod

@ -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
)

185
go.sum

@ -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=

147
item.go

@ -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)
}
}

56
item/expired.go

@ -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
}

99
item/expired_easyjson.go

@ -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)
}

134
mw/fallback.go

@ -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)
})
}

221
mw/fallback_test.go

@ -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
}
}
}

78
mw/gc.go

@ -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)
})
}

68
mw/gc_test.go

@ -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()
}

60
mw/metrics.go

@ -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
},
)
}

93
mw/prometheus/metrics.go

@ -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))
}

21
operation.go

@ -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
}
}

50
provider.go

@ -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
}

194
provider/bench_provider_test.go

@ -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)
}
})
})
}
}

66
provider/lru/provider.go

@ -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
}

18
provider/lru/provider_test.go

@ -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))
}

52
provider/memcache/provider.go

@ -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
}

14
provider/memcache/provider_test.go

@ -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))
}

93
provider/memory/encoding.go

@ -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))
}

13
provider/memory/encoding_test.go

@ -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())
}

155
provider/memory/map.go

@ -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)
}

18
provider/memory/map_test.go

@ -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())
}

22
provider/ns/provider.go

@ -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
}
}

54
provider/pebble/provider.go

@ -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
}

17
provider/pebble/provider_test.go

@ -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))
}

110
provider/redis/pool.go

@ -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
}

14
provider/redis/pool_test.go

@ -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))
}

20
provider/redis/redigo.go

@ -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
}
}

69
provider/ristretto/provider.go

@ -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
}
}

26
provider/ristretto/provider_test.go

@ -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))
}

84
test/helpers.go

@ -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),
}
}

57
test/provider.go

@ -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
}

133
test/sute.go

@ -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",
)
}

99
type_assert.go

@ -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…
Cancel
Save