From 5a06209389538d3f6ce438ecdcaa19b1a73f95a3 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Fri, 30 Jul 2021 13:13:51 +0200 Subject: [PATCH] Redis (#62) --- Makefile | 2 +- README.md | 56 +++++++---- go.mod | 5 +- go.sum | 31 +++++- prefix_benchmark_test.go | 113 +++++++--------------- prefix_test.go | 1 + redis.go | 146 +++++++++++++++++++++++++++++ testing_test.go | 197 +++++++++++++++++++++++++++++++++++++-- 8 files changed, 438 insertions(+), 113 deletions(-) create mode 100644 redis.go diff --git a/Makefile b/Makefile index 2a6ac7f..96cb716 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CGO_ENABLED := $(or ${CGO_ENABLED},0) GO := go GO111MODULE := on PG_VERSION := $(or ${PG_VERSION},13-alpine) -COCKROACH_VERSION := $(or ${COCKROACH_VERSION},v21.1.3) +COCKROACH_VERSION := $(or ${COCKROACH_VERSION},v21.1.5) .EXPORT_ALL_VARIABLES: diff --git a/README.md b/README.md index 1bca7f1..cf56d74 100644 --- a/README.md +++ b/README.md @@ -72,24 +72,44 @@ func main() { } ``` +## Supported Databases + +| | Postgres | CockroachDB | Redis | KeyDB | Memory | +|------------------------------|----------|-------------|---------|---------|------------| +| Production ready | Y | Y | Y | Y | N | +| geo redundant setup possible | N | Y | N | Y | N | +| AcquireIP/sec | ~100/s | ~60/s | ~1400/s | ~1400/s | >200.000/s | +| AcquireChildPrefix/sec | ~40/s | ~35/s | ~1000/s | ~1000/s | >100.000/s | + +Test were run on a Intel(R) Core(TM) i5-6600 CPU @ 3.30GHz + ## Performance ```bash -BenchmarkNewPrefix/Memory-4 422596 2712 ns/op 1536 B/op 20 allocs/op -BenchmarkNewPrefix/Postgres-4 127 8257821 ns/op 5587 B/op 126 allocs/op -BenchmarkNewPrefix/Cockroach-4 18 78498926 ns/op 5869 B/op 128 allocs/op -BenchmarkAcquireIP/Memory-4 299738 3941 ns/op 2360 B/op 42 allocs/op -BenchmarkAcquireIP/Postgres-4 88 13501419 ns/op 10740 B/op 257 allocs/op -BenchmarkAcquireIP/Cockroach-4 14 79709070 ns/op 11253 B/op 265 allocs/op -BenchmarkAcquireChildPrefix/8/14-4 153535 7995 ns/op 4496 B/op 69 allocs/op -BenchmarkAcquireChildPrefix/8/16-4 151178 8000 ns/op 4496 B/op 69 allocs/op -BenchmarkAcquireChildPrefix/8/20-4 152636 7760 ns/op 4496 B/op 69 allocs/op -BenchmarkAcquireChildPrefix/8/22-4 154134 7793 ns/op 4496 B/op 69 allocs/op -BenchmarkAcquireChildPrefix/8/24-4 153325 7759 ns/op 4496 B/op 69 allocs/op -BenchmarkAcquireChildPrefix/16/18-4 147488 8186 ns/op 4496 B/op 69 allocs/op -BenchmarkAcquireChildPrefix/16/20-4 154622 7802 ns/op 4496 B/op 69 allocs/op -BenchmarkAcquireChildPrefix/16/22-4 154148 8034 ns/op 4496 B/op 69 allocs/op -BenchmarkAcquireChildPrefix/16/24-4 138088 8748 ns/op 4496 B/op 69 allocs/op -BenchmarkAcquireChildPrefix/16/26-4 125978 8104 ns/op 4496 B/op 69 allocs/op -BenchmarkPrefixOverlapping-4 3400266 342 ns/op 0 B/op 0 allocs/op -``` \ No newline at end of file +BenchmarkNewPrefix/Memory-4 464994 2675 ns/op 1728 B/op 27 allocs/op +BenchmarkNewPrefix/Postgres-4 126 11775448 ns/op 6259 B/op 144 allocs/op +BenchmarkNewPrefix/Cockroach-4 100 25558820 ns/op 6250 B/op 144 allocs/op +BenchmarkNewPrefix/Redis-4 3854 308122 ns/op 3930 B/op 78 allocs/op +BenchmarkNewPrefix/KeyDB-4 3907 307655 ns/op 3930 B/op 78 allocs/op +BenchmarkAcquireIP/Memory-4 229524 4508 ns/op 2680 B/op 56 allocs/op +BenchmarkAcquireIP/Postgres-4 98 14918027 ns/op 10684 B/op 263 allocs/op +BenchmarkAcquireIP/Cockroach-4 51 19688920 ns/op 10728 B/op 264 allocs/op +BenchmarkAcquireIP/Redis-4 1734 695545 ns/op 12113 B/op 268 allocs/op +BenchmarkAcquireIP/KeyDB-4 1476 751854 ns/op 12110 B/op 268 allocs/op +BenchmarkAcquireChildPrefix/Memory-4 128704 8453 ns/op 5201 B/op 94 allocs/op +BenchmarkAcquireChildPrefix/Postgres-4 70 21220704 ns/op 15663 B/op 378 allocs/op +BenchmarkAcquireChildPrefix/Cockroach-4 32 37638608 ns/op 15774 B/op 381 allocs/op +BenchmarkAcquireChildPrefix/Redis-4 1280 925054 ns/op 16016 B/op 349 allocs/op +BenchmarkAcquireChildPrefix/KeyDB-4 1143 953056 ns/op 16018 B/op 349 allocs/op +BenchmarkPrefixOverlapping-4 4306106 274.4 ns/op 0 B/op 0 allocs/op +``` + +## Testing individual Backends + +It is possible to test a individual backend only to speed up development roundtrip. + +`backend` can be one of `Memory`, `Postgres`, `Cockroach` and `Redis`. + +```bash +BACKEND=backend make test +``` diff --git a/go.mod b/go.mod index bfe84e5..d0a9634 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,12 @@ go 1.16 require ( github.com/avast/retry-go/v3 v3.1.1 + github.com/go-redis/redis/v8 v8.11.1 github.com/jmoiron/sqlx v1.3.4 github.com/lib/pq v1.10.2 github.com/stretchr/testify v1.7.0 github.com/testcontainers/testcontainers-go v0.11.1 - golang.org/x/net v0.0.0-20210716203947-853a461950ff // indirect + golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - inet.af/netaddr v0.0.0-20210718074554-06ca8145d722 + inet.af/netaddr v0.0.0-20210721214506-ce7a8ad02cc1 ) diff --git a/go.sum b/go.sum index e33d2f8..311a3e4 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,7 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3k github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -188,6 +189,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= @@ -220,6 +223,7 @@ github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= @@ -244,6 +248,8 @@ github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis/v8 v8.11.1 h1:Aqf/1y2eVfE9zrySM++/efzwv3mkLH7n/T96//gbo94= +github.com/go-redis/redis/v8 v8.11.1/go.mod h1:DLomh7y2e3ggQXQLd1YgmvIfecPJoFl7WU5SOQ/r06M= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -296,8 +302,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -407,6 +414,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -414,10 +423,16 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= +github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= +github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -627,12 +642,14 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210716203947-853a461950ff h1:j2EK/QoxYNBsXI4R7fQkkRUk8y6wnOBI+6hgPdP/6Ds= -golang.org/x/net v0.0.0-20210716203947-853a461950ff/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -671,6 +688,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -704,6 +722,7 @@ golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -760,6 +779,7 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -850,6 +870,7 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -873,8 +894,8 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -inet.af/netaddr v0.0.0-20210718074554-06ca8145d722 h1:Qws2rZnQudC58cIagVucPQDLmMi3kAXgxscsgD0v6DU= -inet.af/netaddr v0.0.0-20210718074554-06ca8145d722/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls= +inet.af/netaddr v0.0.0-20210721214506-ce7a8ad02cc1 h1:mxmfTV6kjXTlFqqFETnG9FQZzNFc6AKunZVAgQ3b7WA= +inet.af/netaddr v0.0.0-20210721214506-ce7a8ad02cc1/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= diff --git a/prefix_benchmark_test.go b/prefix_benchmark_test.go index 8a191fb..9bde8ab 100644 --- a/prefix_benchmark_test.go +++ b/prefix_benchmark_test.go @@ -6,94 +6,48 @@ import ( ) func BenchmarkNewPrefix(b *testing.B) { - _, pg, err := startPostgres() - if err != nil { - panic(err) - } - defer pg.db.Close() - pgipam := NewWithStorage(pg) - _, cock, err := startCockroach() - if err != nil { - panic(err) - } - defer cock.db.Close() - cockipam := NewWithStorage(cock) - benchmarks := []struct { - name string - ipam Ipamer - }{ - {name: "Memory", ipam: New()}, - {name: "Postgres", ipam: pgipam}, - {name: "Cockroach", ipam: cockipam}, - } - for _, bm := range benchmarks { - test := bm - b.Run(test.name, func(b *testing.B) { - for n := 0; n < b.N; n++ { - p, err := test.ipam.NewPrefix("192.168.0.0/24") - if err != nil { - panic(err) - } - if p == nil { - panic("Prefix nil") - } - _, err = test.ipam.DeletePrefix(p.Cidr) - if err != nil { - panic(err) - } + benchWithBackends(b, func(b *testing.B, ipam *ipamer) { + for n := 0; n < b.N; n++ { + p, err := ipam.NewPrefix("192.168.0.0/24") + if err != nil { + panic(err) } - }) - } + if p == nil { + panic("Prefix nil") + } + _, err = ipam.DeletePrefix(p.Cidr) + if err != nil { + panic(err) + } + } + }) } func BenchmarkAcquireIP(b *testing.B) { - _, pg, err := startPostgres() - if err != nil { - panic(err) - } - defer pg.db.Close() - pgipam := NewWithStorage(pg) - _, cr, err := startCockroach() - if err != nil { - panic(err) - } - defer cr.db.Close() - cockipam := NewWithStorage(cr) - benchmarks := []struct { - name string - ipam Ipamer - cidr string - }{ - {name: "Memory", ipam: New(), cidr: "11.0.0.0/24"}, - {name: "Postgres", ipam: pgipam, cidr: "10.0.0.0/16"}, - {name: "Cockroach", ipam: cockipam, cidr: "10.0.0.0/16"}, - } - for _, bm := range benchmarks { - test := bm - b.Run(test.name, func(b *testing.B) { - p, err := test.ipam.NewPrefix(test.cidr) + testCidr := "10.0.0.0/16" + benchWithBackends(b, func(b *testing.B, ipam *ipamer) { + p, err := ipam.NewPrefix(testCidr) + if err != nil { + panic(err) + } + for n := 0; n < b.N; n++ { + ip, err := ipam.AcquireIP(p.Cidr) if err != nil { panic(err) } - for n := 0; n < b.N; n++ { - ip, err := test.ipam.AcquireIP(p.Cidr) - if err != nil { - panic(err) - } - if ip == nil { - panic("IP nil") - } - p, err = test.ipam.ReleaseIP(ip) - if err != nil { - panic(err) - } + if ip == nil { + panic("IP nil") } - _, err = test.ipam.DeletePrefix(test.cidr) + p, err = ipam.ReleaseIP(ip) if err != nil { - b.Fatalf("error deleting prefix:%v", err) + panic(err) } - }) - } + } + _, err = ipam.DeletePrefix(testCidr) + if err != nil { + b.Fatalf("error deleting prefix:%v", err) + } + }) } func BenchmarkAcquireChildPrefix(b *testing.B) { @@ -115,8 +69,7 @@ func BenchmarkAcquireChildPrefix(b *testing.B) { } for _, bm := range benchmarks { test := bm - b.Run(test.name, func(b *testing.B) { - ipam := New() + benchWithBackends(b, func(b *testing.B, ipam *ipamer) { p, err := ipam.NewPrefix(fmt.Sprintf("192.168.0.0/%d", test.parentLength)) if err != nil { panic(err) diff --git a/prefix_test.go b/prefix_test.go index 4e87438..55440a7 100644 --- a/prefix_test.go +++ b/prefix_test.go @@ -141,6 +141,7 @@ func TestIpamer_AcquireIP(t *testing.T) { if err != nil { t.Errorf("Could not create prefix: %v", err) } + t.Logf("Prefix:%#v", p) for _, ipString := range test.fields.existingips { p.ips[ipString] = true } diff --git a/redis.go b/redis.go new file mode 100644 index 0000000..14e31c5 --- /dev/null +++ b/redis.go @@ -0,0 +1,146 @@ +package ipam + +import ( + "context" + "errors" + "fmt" + "sync" + + redigo "github.com/go-redis/redis/v8" +) + +var ctx = context.Background() + +type redis struct { + rdb *redigo.Client + lock sync.RWMutex +} + +// NewRedis create a redis storage for ipam +func NewRedis(ip, port string) Storage { + return newRedis(ip, port) +} + +func newRedis(ip, port string) *redis { + rdb := redigo.NewClient(&redigo.Options{ + Addr: fmt.Sprintf("%s:%s", ip, port), + Password: "", // no password set + DB: 0, // use default DB + }) + + return &redis{ + rdb: rdb, + lock: sync.RWMutex{}, + } +} + +func (r *redis) CreatePrefix(prefix Prefix) (Prefix, error) { + r.lock.Lock() + defer r.lock.Unlock() + + existing, err := r.rdb.Exists(ctx, prefix.Cidr).Result() + if err != nil { + return Prefix{}, fmt.Errorf("unable to read existing prefix:%v, error:%w", prefix, err) + } + if existing != 0 { + return Prefix{}, fmt.Errorf("prefix:%v already exists", prefix) + } + pfx, err := prefix.toJSON() + if err != nil { + return Prefix{}, err + } + err = r.rdb.Set(ctx, prefix.Cidr, pfx, 0).Err() + return prefix, err +} +func (r *redis) ReadPrefix(prefix string) (Prefix, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + result, err := r.rdb.Get(ctx, prefix).Result() + if err != nil { + return Prefix{}, fmt.Errorf("unable to read existing prefix:%v, error:%w", prefix, err) + } + return fromJSON([]byte(result)) +} +func (r *redis) ReadAllPrefixes() ([]Prefix, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + pfxs, err := r.rdb.Keys(ctx, "*").Result() + if err != nil { + return nil, fmt.Errorf("unable to get all prefix cidrs:%w", err) + } + + result := []Prefix{} + for _, pfx := range pfxs { + v, err := r.rdb.Get(ctx, pfx).Bytes() + if err != nil { + return nil, err + } + pfx, err := fromJSON(v) + if err != nil { + return nil, err + } + result = append(result, pfx) + } + return result, nil +} +func (r *redis) ReadAllPrefixCidrs() ([]string, error) { + r.lock.RLock() + defer r.lock.RUnlock() + pfxs, err := r.rdb.Keys(ctx, "*").Result() + if err != nil { + return nil, fmt.Errorf("unable to get all prefix cidrs:%w", err) + } + return pfxs, nil +} +func (r *redis) UpdatePrefix(prefix Prefix) (Prefix, error) { + r.lock.Lock() + defer r.lock.Unlock() + + oldVersion := prefix.version + prefix.version = oldVersion + 1 + pn, err := prefix.toJSON() + if err != nil { + return Prefix{}, err + } + + txf := func(tx *redigo.Tx) error { + // Get current value or zero. + p, err := tx.Get(ctx, prefix.Cidr).Result() + if err != nil && !errors.Is(err, redigo.Nil) { + return err + } + oldPrefix, err := fromJSON([]byte(p)) + if err != nil { + return err + } + // Actual operation (local in optimistic lock). + if oldPrefix.version != oldVersion { + return fmt.Errorf("%w: unable to update prefix:%s", ErrOptimisticLockError, prefix.Cidr) + } + + // Operation is committed only if the watched keys remain unchanged. + _, err = tx.TxPipelined(ctx, func(pipe redigo.Pipeliner) error { + pipe.Set(ctx, prefix.Cidr, pn, 0) + return nil + }) + return err + } + err = r.rdb.Watch(ctx, txf, prefix.Cidr) + if err != nil { + return Prefix{}, err + } + + return prefix, nil +} +func (r *redis) DeletePrefix(prefix Prefix) (Prefix, error) { + r.lock.Lock() + defer r.lock.Unlock() + + _, err := r.rdb.Del(ctx, prefix.Cidr).Result() + if err != nil { + return *prefix.deepCopy(), err + } + return *prefix.deepCopy(), nil +} diff --git a/testing_test.go b/testing_test.go index 9fd1bb8..94be0b7 100644 --- a/testing_test.go +++ b/testing_test.go @@ -15,25 +15,47 @@ import ( var ( pgOnce sync.Once - crOnce sync.Once pgContainer testcontainers.Container - crContainer testcontainers.Container pgVersion string + crOnce sync.Once + crContainer testcontainers.Container cockroachVersion string + redisOnce sync.Once + redisContainer testcontainers.Container + redisVersion string + keyDBVersion string + keyDBContainer testcontainers.Container + + backend string ) -func init() { +func TestMain(m *testing.M) { + // call flag.Parse() here if TestMain uses flags pgVersion = os.Getenv("PG_VERSION") if pgVersion == "" { pgVersion = "13" } cockroachVersion = os.Getenv("COCKROACH_VERSION") if cockroachVersion == "" { - cockroachVersion = "v21.1.3" + cockroachVersion = "v21.1.5" + } + redisVersion = os.Getenv("REDIS_VERSION") + if redisVersion == "" { + redisVersion = "6-alpine" + } + keyDBVersion = os.Getenv("KEYDB_VERSION") + if keyDBVersion == "" { + keyDBVersion = "alpine_x86_64_v6.0.18" + } + backend = os.Getenv("BACKEND") + if backend == "" { + fmt.Printf("Using postgres:%s cockroach:%s redis:%s keydb:%s\n", pgVersion, cockroachVersion, redisVersion, keyDBVersion) + } else { + fmt.Printf("only test %s\n", backend) } - fmt.Printf("Using postgres:%s cockroach:%s\n", pgVersion, cockroachVersion) // prevent testcontainer logging mangle test and benchmark output log.SetOutput(ioutil.Discard) + os.Exit(m.Run()) } func startPostgres() (container testcontainers.Container, dn *sql, err error) { @@ -109,6 +131,71 @@ func startCockroach() (container testcontainers.Container, dn *sql, err error) { return crContainer, db, err } +func startRedis() (container testcontainers.Container, s *redis, err error) { + ctx := context.Background() + redisOnce.Do(func() { + var err error + req := testcontainers.ContainerRequest{ + Image: "redis:" + redisVersion, + ExposedPorts: []string{"6379/tcp"}, + WaitingFor: wait.ForAll( + wait.ForLog("Ready to accept connections"), + wait.ForListeningPort("6379/tcp"), + ), + } + redisContainer, err = testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + if err != nil { + panic(err.Error()) + } + }) + ip, err := redisContainer.Host(ctx) + if err != nil { + return redisContainer, nil, err + } + port, err := redisContainer.MappedPort(ctx, "6379") + if err != nil { + return redisContainer, nil, err + } + db := newRedis(ip, port.Port()) + + return redisContainer, db, nil +} +func startKeyDB() (container testcontainers.Container, s *redis, err error) { + ctx := context.Background() + redisOnce.Do(func() { + var err error + req := testcontainers.ContainerRequest{ + Image: "eqalpha/keydb" + keyDBVersion, + ExposedPorts: []string{"6379/tcp"}, + WaitingFor: wait.ForAll( + wait.ForLog("Server initialized"), + wait.ForListeningPort("6379/tcp"), + ), + } + keyDBContainer, err = testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + if err != nil { + panic(err.Error()) + } + }) + ip, err := redisContainer.Host(ctx) + if err != nil { + return redisContainer, nil, err + } + port, err := redisContainer.MappedPort(ctx, "6379") + if err != nil { + return redisContainer, nil, err + } + db := newRedis(ip, port.Port()) + + return redisContainer, db, nil +} + // func stopDB(c testcontainers.Container) error { // ctx := context.Background() // return c.Terminate(ctx) @@ -129,6 +216,12 @@ type extendedSQL struct { c testcontainers.Container } +// extendedSQL extended sql interface +type kvStorage struct { + *redis + c testcontainers.Container +} + func newPostgresWithCleanup() (*extendedSQL, error) { c, s, err := startPostgres() if err != nil { @@ -155,6 +248,32 @@ func newCockroachWithCleanup() (*extendedSQL, error) { return ext, nil } +func newRedisWithCleanup() (*kvStorage, error) { + c, r, err := startRedis() + if err != nil { + return nil, err + } + + kv := &kvStorage{ + redis: r, + c: c, + } + + return kv, nil +} +func newKeyDBWithCleanup() (*kvStorage, error) { + c, r, err := startKeyDB() + if err != nil { + return nil, err + } + + kv := &kvStorage{ + redis: r, + c: c, + } + + return kv, nil +} // cleanup database before test func (e *extendedSQL) cleanup() error { @@ -166,6 +285,15 @@ func (e *extendedSQL) cleanup() error { return tx.Commit() } +// cleanup database before test +func (kv *kvStorage) cleanup() error { + _, err := kv.redis.rdb.FlushAll(context.Background()).Result() + if err != nil { + return err + } + return nil +} + // cleanup database before test func (sql *sql) cleanup() error { tx := sql.db.MustBegin() @@ -176,11 +304,38 @@ func (sql *sql) cleanup() error { return tx.Commit() } +type benchMethod func(b *testing.B, ipam *ipamer) + +func benchWithBackends(b *testing.B, fn benchMethod) { + for _, storageProvider := range storageProviders() { + if backend != "" && backend != storageProvider.name { + continue + } + storage := storageProvider.provide() + + if tp, ok := storage.(cleanable); ok { + err := tp.cleanup() + if err != nil { + b.Errorf("error cleaning up, %v", err) + } + } + + ipamer := &ipamer{storage: storage} + testName := storageProvider.name + + b.Run(testName, func(b *testing.B) { + fn(b, ipamer) + }) + } +} + type testMethod func(t *testing.T, ipam *ipamer) func testWithBackends(t *testing.T, fn testMethod) { for _, storageProvider := range storageProviders() { - + if backend != "" && backend != storageProvider.name { + continue + } storage := storageProvider.provide() if tp, ok := storage.(cleanable); ok { @@ -203,7 +358,9 @@ type sqlTestMethod func(t *testing.T, sql *sql) func testWithSQLBackends(t *testing.T, fn sqlTestMethod) { for _, storageProvider := range storageProviders() { - + if backend != "" && backend != storageProvider.name { + continue + } sqlstorage := storageProvider.providesql() if sqlstorage == nil { continue @@ -277,5 +434,31 @@ func storageProviders() []storageProvider { return storage.sql }, }, + { + name: "Redis", + provide: func() Storage { + s, err := newRedisWithCleanup() + if err != nil { + panic(fmt.Sprintf("unable to start redis:%s", err)) + } + return s + }, + providesql: func() *sql { + return nil + }, + }, + { + name: "KeyDB", + provide: func() Storage { + s, err := newKeyDBWithCleanup() + if err != nil { + panic(fmt.Sprintf("unable to start keydb:%s", err)) + } + return s + }, + providesql: func() *sql { + return nil + }, + }, } }