-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmongo_cache.go
232 lines (196 loc) · 5.49 KB
/
mongo_cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package cache
import (
"errors"
"fmt"
"sync"
"time"
mgo "gopkg.in/mgo.v2"
)
const (
defaultExpireDuration = time.Minute
defaultCollectionName = "jCache"
defaultGCInterval = time.Minute
indexExpireAt = "expireAt"
)
var (
ErrNotFound = errors.New("not found")
)
// MongoCache holds the cache values that will be stored in mongoDB
type MongoCache struct {
// mongeSession specifies the mongoDB connection
mongeSession *mgo.Session
// CollectionName speficies the optional collection name for mongoDB
// if CollectionName is not set, then default value will be set
CollectionName string
// ttl is a duration for a cache key to expire
TTL time.Duration
// GCInterval specifies the time duration for garbage collector time interval
GCInterval time.Duration
// GCStart starts the garbage collector and deletes the
// expired keys from mongo with given time interval
GCStart bool
// gcTicker controls gc intervals
gcTicker *time.Ticker
// done controls sweeping goroutine lifetime
done chan struct{}
// Mutex is used for handling the concurrent
// read/write requests for cache
sync.RWMutex
}
// Option sets the options specified.
type Option func(*MongoCache)
// NewMongoCacheWithTTL creates a caching layer backed by mongo. TTL's are
// managed either by a background cleaner or document is removed on the Get
// operation. Mongo TTL indexes are not utilized since there can be multiple
// systems using the same collection with different TTL values.
//
// The responsibility of stopping the GC process belongs to the user.
//
// Session is not closed while stopping the GC.
//
// This self-referential function satisfy you to avoid passing
// nil value to the function as parameter
// e.g (usage) :
// configure with defaults, just call;
// NewMongoCacheWithTTL(session)
//
// configure ttl duration with;
// NewMongoCacheWithTTL(session, func(m *MongoCache) {
// m.TTL = 2 * time.Minute
// })
// or
// NewMongoCacheWithTTL(session, SetTTL(time.Minute * 2))
//
// configure collection name with;
// NewMongoCacheWithTTL(session, func(m *MongoCache) {
// m.CollectionName = "MongoCacheCollectionName"
// })
func NewMongoCacheWithTTL(session *mgo.Session, configs ...Option) *MongoCache {
if session == nil {
panic("session must be set")
}
mc := &MongoCache{
mongeSession: session,
TTL: defaultExpireDuration,
CollectionName: defaultCollectionName,
GCInterval: defaultGCInterval,
GCStart: false,
}
for _, configFunc := range configs {
configFunc(mc)
}
if mc.GCStart {
mc.StartGC(mc.GCInterval)
}
return mc
}
// MustEnsureIndexExpireAt ensures the expireAt index
// usage:
// NewMongoCacheWithTTL(mongoSession, MustEnsureIndexExpireAt())
func MustEnsureIndexExpireAt() Option {
return func(m *MongoCache) {
if err := m.EnsureIndex(); err != nil {
panic(fmt.Sprintf("index must ensure %q", err))
}
}
}
// StartGC enables the garbage collector in MongoCache struct
// usage:
// NewMongoCacheWithTTL(mongoSession, StartGC())
func StartGC() Option {
return func(m *MongoCache) {
m.GCStart = true
}
}
// SetTTL sets the ttl duration in MongoCache as option
// usage:
// NewMongoCacheWithTTL(mongoSession, SetTTL(time*Minute))
func SetTTL(duration time.Duration) Option {
return func(m *MongoCache) {
m.TTL = duration
}
}
// SetGCInterval sets the garbage collector interval in MongoCache struct as option
// usage:
// NewMongoCacheWithTTL(mongoSession, SetGCInterval(time*Minute))
func SetGCInterval(duration time.Duration) Option {
return func(m *MongoCache) {
m.GCInterval = duration
}
}
// SetCollectionName sets the collection name for mongoDB in MongoCache struct as option
// usage:
// NewMongoCacheWithTTL(mongoSession, SetCollectionName("mongoCollName"))
func SetCollectionName(collName string) Option {
return func(m *MongoCache) {
m.CollectionName = collName
}
}
// Get returns a value of a given key if it exists
func (m *MongoCache) Get(key string) (interface{}, error) {
data, err := m.get(key)
if err == mgo.ErrNotFound {
return nil, ErrNotFound
}
if err != nil {
return nil, err
}
return data.Value, nil
}
// Set will persist a value to the cache or override existing one with the new
// one
func (m *MongoCache) Set(key string, value interface{}) error {
return m.set(key, m.TTL, value)
}
// SetEx will persist a value to the cache or override existing one with the new
// one with ttl duration
func (m *MongoCache) SetEx(key string, duration time.Duration, value interface{}) error {
return m.set(key, duration, value)
}
// Delete deletes a given key if exists
func (m *MongoCache) Delete(key string) error {
return m.delete(key)
}
// EnsureIndex ensures the index with expireAt key
func (m *MongoCache) EnsureIndex() error {
query := func(c *mgo.Collection) error {
return c.EnsureIndexKey(indexExpireAt)
}
return m.run(m.CollectionName, query)
}
// StartGC starts the garbage collector with given time interval The
// expired data will be checked & deleted with given interval time
func (m *MongoCache) StartGC(gcInterval time.Duration) {
if gcInterval <= 0 {
return
}
ticker := time.NewTicker(gcInterval)
done := make(chan struct{})
m.Lock()
m.gcTicker = ticker
m.done = done
m.Unlock()
go func() {
for {
select {
case <-ticker.C:
m.Lock()
m.deleteExpiredKeys()
m.Unlock()
case <-done:
return
}
}
}()
}
// StopGC stops sweeping goroutine.
func (m *MongoCache) StopGC() {
if m.gcTicker != nil {
m.Lock()
m.gcTicker.Stop()
m.gcTicker = nil
close(m.done)
m.done = nil
m.Unlock()
}
}