Skip to content

Commit e8b962c

Browse files
authored
Merge pull request #35 from nyaruka/rcache
Add rcache module
2 parents 475fff6 + a98a1fd commit e8b962c

File tree

5 files changed

+139
-0
lines changed

5 files changed

+139
-0
lines changed

.github/workflows/ci.yml

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ jobs:
1414
- name: Install Linux packages
1515
run: sudo apt install -y --no-install-recommends pandoc
1616

17+
- name: Install Redis
18+
uses: zhulik/[email protected]
19+
with:
20+
redis version: '5'
21+
1722
- name: Install Go
1823
uses: actions/setup-go@v1
1924
with:

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ require (
55
github.com/davecgh/go-spew v1.1.1 // indirect
66
github.com/go-chi/chi v4.1.2+incompatible
77
github.com/gofrs/uuid v3.3.0+incompatible
8+
github.com/gomodule/redigo v2.0.0+incompatible
89
github.com/nyaruka/phonenumbers v1.0.58
910
github.com/pkg/errors v0.9.1
1011
github.com/shopspring/decimal v1.2.0

go.sum

+5
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
55
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
66
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
77
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
8+
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
89
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
910
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
1011
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
1112
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
1213
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
14+
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
15+
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
1316
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
1417
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
1518
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@@ -27,6 +30,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
2730
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
2831
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
2932
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
33+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
3034
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
3135
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
3236
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
@@ -35,6 +39,7 @@ golang.org/x/net v0.0.0-20200925080053-05aa5d4ee321 h1:lleNcKRbcaC8MqgLwghIkzZ2J
3539
golang.org/x/net v0.0.0-20200925080053-05aa5d4ee321/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
3640
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
3741
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
42+
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
3843
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
3944
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
4045
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

rcache/rcache.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package rcache
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/gomodule/redigo/redis"
8+
"github.com/pkg/errors"
9+
)
10+
11+
const (
12+
keyPattern = "%s:%s"
13+
datePattern = "2006_01_02"
14+
secondsInDay = 60 * 60 * 24
15+
)
16+
17+
var hasTask = redis.NewScript(3,
18+
`-- KEYS: [TodayKey, YesterdayKey, Key]
19+
local value = redis.call("hget", KEYS[1], KEYS[3])
20+
if (value ~= nil) then
21+
return value
22+
end
23+
return redis.call("hget", KEYS[2], KEYS[3])
24+
`)
25+
26+
// Get returns the cached value for key in the passed in group or nil
27+
func Get(rc redis.Conn, group string, key string) (string, error) {
28+
todayKey := fmt.Sprintf(keyPattern, group, time.Now().UTC().Format(datePattern))
29+
yesterdayKey := fmt.Sprintf(keyPattern, group, time.Now().Add(time.Hour*-24).UTC().Format(datePattern))
30+
value, err := redis.String(hasTask.Do(rc, todayKey, yesterdayKey, key))
31+
if err != nil && err != redis.ErrNil {
32+
return "", errors.Wrapf(err, "error getting value for group: %s and key: %s", group, key)
33+
}
34+
return value, nil
35+
}
36+
37+
// Set sets the cached value for key for the passed in group. It will be cached for at least 24 hours but no longer than 48 hours
38+
func Set(rc redis.Conn, group string, key string, value string) error {
39+
dateKey := fmt.Sprintf(keyPattern, group, time.Now().UTC().Format(datePattern))
40+
rc.Send("hset", dateKey, key, value)
41+
rc.Send("expire", dateKey, secondsInDay)
42+
_, err := rc.Do("")
43+
if err != nil {
44+
return errors.Wrapf(err, "error setting value for group: %s, key: %s, value: %s", group, key, value)
45+
}
46+
return nil
47+
}
48+
49+
// Delete removes the value with the passed in key
50+
func Delete(rc redis.Conn, group string, key string) error {
51+
todayKey := fmt.Sprintf(keyPattern, group, time.Now().UTC().Format(datePattern))
52+
yesterdayKey := fmt.Sprintf(keyPattern, group, time.Now().Add(time.Hour*-24).UTC().Format(datePattern))
53+
rc.Send("hdel", todayKey, key)
54+
rc.Send("hdel", yesterdayKey, key)
55+
_, err := rc.Do("")
56+
if err != nil {
57+
return errors.Wrapf(err, "error deleting value for group: %s and key: %s", group, key)
58+
}
59+
return nil
60+
}
61+
62+
// Clear removes all values for the passed in group
63+
func Clear(rc redis.Conn, group string) error {
64+
todayKey := fmt.Sprintf(keyPattern, group, time.Now().UTC().Format(datePattern))
65+
yesterdayKey := fmt.Sprintf(keyPattern, group, time.Now().Add(time.Hour*-24).UTC().Format(datePattern))
66+
rc.Send("del", todayKey)
67+
rc.Send("del", yesterdayKey)
68+
_, err := rc.Do("")
69+
return err
70+
}

rcache/rcache_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package rcache
2+
3+
import (
4+
"testing"
5+
6+
"github.com/gomodule/redigo/redis"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestMarker(t *testing.T) {
11+
tcs := []struct {
12+
Action string
13+
Group string
14+
Key string
15+
Value string
16+
}{
17+
{"clear", "wa", "", ""},
18+
{"clear", "tel", "", ""},
19+
{"get", "wa", "foo", ""},
20+
{"set", "wa", "foo", "bar"},
21+
{"get", "wa", "foo", "bar"},
22+
{"get", "tel", "foo", ""},
23+
{"set", "tel", "foo", "baz"},
24+
{"get", "tel", "foo", "baz"},
25+
{"delete", "wa", "foo", ""},
26+
{"get", "wa", "foo", ""},
27+
{"get", "tel", "foo", "baz"},
28+
{"clear", "tel", "", ""},
29+
{"get", "tel", "foo", ""},
30+
}
31+
32+
rc, err := redis.Dial("tcp", "localhost:6379")
33+
if err != nil {
34+
panic(err)
35+
}
36+
_, err = rc.Do("SELECT", 0)
37+
if err != nil {
38+
panic(err)
39+
}
40+
defer rc.Close()
41+
42+
for i, tc := range tcs {
43+
if tc.Action == "set" {
44+
err := Set(rc, tc.Group, tc.Key, tc.Value)
45+
assert.NoError(t, err)
46+
} else if tc.Action == "get" {
47+
value, err := Get(rc, tc.Group, tc.Key)
48+
assert.NoError(t, err)
49+
assert.Equal(t, tc.Value, value, "%d: not equal", i)
50+
} else if tc.Action == "delete" {
51+
err := Delete(rc, tc.Group, tc.Key)
52+
assert.NoError(t, err)
53+
} else if tc.Action == "clear" {
54+
err := Clear(rc, tc.Group)
55+
assert.NoError(t, err)
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)