Skip to content

Commit 2a4e113

Browse files
authored
Bcache now handles key expiration. (#24)
- expiration will be handled by bcache, previous version which basically do nothing with expiration - passive key deletion, data deleted when Get found that the key is expired. No GC-like mechanism at this phase, could be added later if needed - deletion delay: add delay before actually delete the key, it is used to handle temporary network connection issue, which prevent data syncing between nodes - specify ttl instead of expirationTimestamp
1 parent 7eebcef commit 2a4e113

File tree

8 files changed

+154
-131
lines changed

8 files changed

+154
-131
lines changed

README.md

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,11 @@ A Go Library to create distributed in-memory cache inside your app.
2727

2828
Only need to specify one or few nodes as bootstrap nodes, and all nodes will find each other using gossip protocol
2929

30-
2. When there is cache `set`, the event will be propagated to all of the nodes.
30+
2. When there is cache `Set` and `Delete`, the event will be propagated to all of the nodes.
3131

32-
So, all of the nodes will have synced data.
32+
So, all of the nodes will eventually have synced data.
3333

3434

35-
## Expiration
36-
37-
Although this library doesn't invalidate the keys when it reachs the expiration time,
38-
the expiration timestamp will be used in these ways:
39-
40-
(1) On `Set`:
41-
- as a way to decide which value is the newer when doing data synchronization among nodes
42-
- set timestamp expiration
43-
44-
(2) On `Get`:
45-
- the expiration timestamp could be used to check whether the key has been expired
46-
47-
(3) On `Delete`:
48-
- to decide which operation is the lastes when doing syncronization, for example:
49-
- `Delete` with timestamp 3 and `Set` with timestamp 4 -> `Set` is the latest, so the `Delete` is ignored
50-
51-
So, it is **mandatory** to set the expiration time and the delta from current time must be the same
52-
between `Set` and `Delete`.
53-
We can also use [UnixNano](https://golang.org/pkg/time/#Time.UnixNano) for better precission than `Unix`.
54-
5535

5636
## Cache filling
5737

@@ -79,7 +59,7 @@ bc, err := New(Config{
7959
if err != nil {
8060
log.Fatalf("failed to create cache: %v", err)
8161
}
82-
bc.Set("my_key", "my_val",12345)
62+
bc.Set("my_key", "my_val",86400)
8363
```
8464

8565
In server 2
@@ -94,7 +74,7 @@ bc, err := New(Config{
9474
if err != nil {
9575
log.Fatalf("failed to create cache: %v", err)
9676
}
97-
bc.Set("my_key2", "my_val2", 12345)
77+
bc.Set("my_key2", "my_val2", 86400)
9878
```
9979

10080
In server 3
@@ -109,7 +89,7 @@ bc, err := New(Config{
10989
if err != nil {
11090
log.Fatalf("failed to create cache: %v", err)
11191
}
112-
val, exp, exists := bc.Get("my_key2")
92+
val, exists := bc.Get("my_key2")
11393
```
11494

11595
### GetWithFiller example
@@ -124,12 +104,12 @@ c, err := New(Config{
124104
if err != nil {
125105
log.Fatalf("failed to create cache: %v", err)
126106
}
127-
val, exp,err := bc.GetWithFiller("my_key2",func(key string) (string, int64, error) {
107+
val, exp,err := bc.GetWithFiller("my_key2",func(key string) (string, error) {
128108
// get value from database
129109
.....
130110
//
131111
return value, 0, nil
132-
})
112+
}, 86400)
133113
```
134114

135115
## Credits

bcache.go

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"net"
66
"strconv"
7+
"time"
78

89
"github.com/weaveworks/mesh"
910
"golang.org/x/sync/singleflight"
@@ -22,10 +23,11 @@ var (
2223

2324
// Bcache represents bcache struct
2425
type Bcache struct {
25-
peer *peer
26-
router *mesh.Router
27-
logger Logger
28-
flight singleflight.Group
26+
peer *peer
27+
router *mesh.Router
28+
logger Logger
29+
flight singleflight.Group
30+
deletionDelay time.Duration
2931
}
3032

3133
// New creates new bcache from the given config
@@ -89,40 +91,45 @@ func New(cfg Config) (*Bcache, error) {
8991
router.ConnectionMaker.InitiateConnections(cfg.Peers, true)
9092

9193
return &Bcache{
92-
peer: peer,
93-
router: router,
94-
logger: logger,
94+
peer: peer,
95+
router: router,
96+
logger: logger,
97+
deletionDelay: time.Duration(cfg.DeletionDelay) * time.Second,
9598
}, nil
9699
}
97100

98-
// Set sets value for the given key.
99-
//
100-
// expiredTimestamp could be used in these way:
101-
//
102-
// - unix timestamp when this key will be expired
103-
// - as a way to decide which value is the newer when doing data synchronization among nodes
104-
func (b *Bcache) Set(key, val string, expiredTimestamp int64) {
105-
b.peer.Set(key, val, expiredTimestamp)
101+
// Set sets value for the given key with the given ttl in second.
102+
// if ttl <= 0, the key will expired instantly
103+
func (b *Bcache) Set(key, val string, ttl int) {
104+
if ttl <= 0 {
105+
b.Delete(key)
106+
return
107+
}
108+
b.set(key, val, ttl)
109+
}
110+
111+
func (b *Bcache) set(key, val string, ttl int) int64 {
112+
expired := time.Now().Add(time.Duration(ttl) * time.Second).UnixNano()
113+
b.peer.Set(key, val, expired)
114+
return expired
106115
}
107116

108117
// Get gets value for the given key.
109118
//
110-
// It returns the value, expiration timestamp, and true if the key exists
111-
func (b *Bcache) Get(key string) (string, int64, bool) {
119+
// It returns the value and true if the key exists
120+
func (b *Bcache) Get(key string) (string, bool) {
112121
return b.peer.Get(key)
113122
}
114123

115124
// Delete the given key.
116125
//
117-
// The given timestamp is used to decide which operation is the lastes when doing syncronization.
118-
//
119-
// For example: `Delete` with timestamp 3 and `Set` with timestamp 4 -> `Set` is the latest, so the `Delete` is ignored
120-
func (b *Bcache) Delete(key string, expiredTimestamp int64) {
121-
b.peer.Delete(key, expiredTimestamp)
126+
func (b *Bcache) Delete(key string) {
127+
deleteTs := time.Now().Add(b.deletionDelay).UnixNano()
128+
b.peer.Delete(key, deleteTs)
122129
}
123130

124131
// Filler defines func to be called when the given key is not exists
125-
type Filler func(key string) (val string, expired int64, err error)
132+
type Filler func(key string) (val string, err error)
126133

127134
// GetWithFiller gets value for the given key and fill the cache
128135
// if the given key is not exists.
@@ -134,27 +141,26 @@ type Filler func(key string) (val string, expired int64, err error)
134141
//
135142
//
136143
// It useful to avoid cache stampede to the underlying database
137-
func (b *Bcache) GetWithFiller(key string, filler Filler) (string, int64, error) {
144+
func (b *Bcache) GetWithFiller(key string, filler Filler, ttl int) (string, error) {
138145
if filler == nil {
139-
return "", 0, ErrNilFiller
146+
return "", ErrNilFiller
140147
}
141148

142149
// get value from cache
143-
val, exp, ok := b.Get(key)
150+
val, ok := b.Get(key)
144151
if ok {
145-
return val, exp, nil
152+
return val, nil
146153
}
147154

148155
// construct singleflight filler
149156
flightFn := func() (interface{}, error) {
150-
val, expired, err := filler(key)
157+
val, err := filler(key)
151158
if err != nil {
152159
b.logger.Errorf("filler failed: %v", err)
153160
return nil, err
154161
}
155162

156-
// set the key if filler OK
157-
b.peer.Set(key, val, expired)
163+
expired := b.set(key, val, ttl)
158164

159165
return value{
160166
value: val,
@@ -167,12 +173,12 @@ func (b *Bcache) GetWithFiller(key string, filler Filler) (string, int64, error)
167173
return flightFn()
168174
})
169175
if err != nil {
170-
return "", 0, err
176+
return "", err
171177
}
172178

173179
// return the value
174180
value := valueIf.(value)
175-
return value.value, value.expired, nil
181+
return value.value, nil
176182
}
177183

178184
// Close closes the cache, free all the resource

0 commit comments

Comments
 (0)