Skip to content

Commit ce2d087

Browse files
authored
Improve memory storage (#2162)
* improve memory storage code and performance * improve memory storage code and performance * improve memory storage code and performance * improve memory storage code and performance * improve memory storage code and performance * improve memory storage code and performance
1 parent 5316f08 commit ce2d087

File tree

9 files changed

+326
-53
lines changed

9 files changed

+326
-53
lines changed

internal/memory/memory.go

+25-24
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
// Package memory Is a slight copy of the memory storage, but far from the storage interface it can not only work with bytes
2+
// but directly store any kind of data without having to encode it each time, which gives a huge speed advantage
13
package memory
24

35
import (
46
"sync"
57
"sync/atomic"
68
"time"
9+
10+
"github.com/gofiber/fiber/v2/utils"
711
)
812

913
type Storage struct {
1014
sync.RWMutex
1115
data map[string]item // data
12-
ts uint32 // timestamp
1316
}
1417

1518
type item struct {
@@ -21,10 +24,9 @@ type item struct {
2124
func New() *Storage {
2225
store := &Storage{
2326
data: make(map[string]item),
24-
ts: uint32(time.Now().Unix()),
2527
}
28+
utils.StartTimeStampUpdater()
2629
go store.gc(1 * time.Second)
27-
go store.updater(1 * time.Second)
2830
return store
2931
}
3032

@@ -33,7 +35,7 @@ func (s *Storage) Get(key string) interface{} {
3335
s.RLock()
3436
v, ok := s.data[key]
3537
s.RUnlock()
36-
if !ok || v.e != 0 && v.e <= atomic.LoadUint32(&s.ts) {
38+
if !ok || v.e != 0 && v.e <= atomic.LoadUint32(&utils.Timestamp) {
3739
return nil
3840
}
3941
return v.v
@@ -43,7 +45,7 @@ func (s *Storage) Get(key string) interface{} {
4345
func (s *Storage) Set(key string, val interface{}, ttl time.Duration) {
4446
var exp uint32
4547
if ttl > 0 {
46-
exp = uint32(ttl.Seconds()) + atomic.LoadUint32(&s.ts)
48+
exp = uint32(ttl.Seconds()) + atomic.LoadUint32(&utils.Timestamp)
4749
}
4850
s.Lock()
4951
s.data[key] = item{exp, val}
@@ -64,28 +66,27 @@ func (s *Storage) Reset() {
6466
s.Unlock()
6567
}
6668

67-
func (s *Storage) updater(sleep time.Duration) {
68-
for {
69-
time.Sleep(sleep)
70-
atomic.StoreUint32(&s.ts, uint32(time.Now().Unix()))
71-
}
72-
}
7369
func (s *Storage) gc(sleep time.Duration) {
74-
expired := []string{}
70+
ticker := time.NewTicker(sleep)
71+
defer ticker.Stop()
72+
var expired []string
73+
7574
for {
76-
time.Sleep(sleep)
77-
expired = expired[:0]
78-
s.RLock()
79-
for key, v := range s.data {
80-
if v.e != 0 && v.e <= atomic.LoadUint32(&s.ts) {
81-
expired = append(expired, key)
75+
select {
76+
case <-ticker.C:
77+
expired = expired[:0]
78+
s.RLock()
79+
for key, v := range s.data {
80+
if v.e != 0 && v.e <= atomic.LoadUint32(&utils.Timestamp) {
81+
expired = append(expired, key)
82+
}
8283
}
84+
s.RUnlock()
85+
s.Lock()
86+
for i := range expired {
87+
delete(s.data, expired[i])
88+
}
89+
s.Unlock()
8390
}
84-
s.RUnlock()
85-
s.Lock()
86-
for i := range expired {
87-
delete(s.data, expired[i])
88-
}
89-
s.Unlock()
9091
}
9192
}

internal/memory/memory_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func Benchmark_Memory(b *testing.B) {
5858
for i := 0; i < keyLength; i++ {
5959
keys[i] = utils.UUID()
6060
}
61-
value := []string{"some", "random", "value"}
61+
value := []byte("joe")
6262

6363
ttl := 2 * time.Second
6464
b.Run("fiber_memory", func(b *testing.B) {

internal/storage/memory/config.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package memory
2+
3+
import "time"
4+
5+
// Config defines the config for storage.
6+
type Config struct {
7+
// Time before deleting expired keys
8+
//
9+
// Default is 10 * time.Second
10+
GCInterval time.Duration
11+
}
12+
13+
// ConfigDefault is the default config
14+
var ConfigDefault = Config{
15+
GCInterval: 10 * time.Second,
16+
}
17+
18+
// configDefault is a helper function to set default values
19+
func configDefault(config ...Config) Config {
20+
// Return default config if nothing provided
21+
if len(config) < 1 {
22+
return ConfigDefault
23+
}
24+
25+
// Override default config
26+
cfg := config[0]
27+
28+
// Set default values
29+
if int(cfg.GCInterval.Seconds()) <= 0 {
30+
cfg.GCInterval = ConfigDefault.GCInterval
31+
}
32+
return cfg
33+
}

internal/storage/memory/memory.go

+31-11
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
// Package memory Is a copy of the storage memory from the external storage packet as a purpose to test the behavior
2+
// in the unittests when using a storages from these packets
13
package memory
24

35
import (
46
"sync"
7+
"sync/atomic"
58
"time"
9+
10+
"github.com/gofiber/fiber/v2/utils"
611
)
712

813
// Storage interface that is implemented by storage providers
@@ -14,21 +19,25 @@ type Storage struct {
1419
}
1520

1621
type entry struct {
22+
data []byte
1723
// max value is 4294967295 -> Sun Feb 07 2106 06:28:15 GMT+0000
1824
expiry uint32
19-
data []byte
2025
}
2126

2227
// New creates a new memory storage
23-
func New() *Storage {
28+
func New(config ...Config) *Storage {
29+
// Set default config
30+
cfg := configDefault(config...)
31+
2432
// Create storage
2533
store := &Storage{
2634
db: make(map[string]entry),
27-
gcInterval: 10 * time.Second,
35+
gcInterval: cfg.GCInterval,
2836
done: make(chan struct{}),
2937
}
3038

3139
// Start garbage collector
40+
utils.StartTimeStampUpdater()
3241
go store.gc()
3342

3443
return store
@@ -42,7 +51,7 @@ func (s *Storage) Get(key string) ([]byte, error) {
4251
s.mux.RLock()
4352
v, ok := s.db[key]
4453
s.mux.RUnlock()
45-
if !ok || v.expiry != 0 && v.expiry <= uint32(time.Now().Unix()) {
54+
if !ok || v.expiry != 0 && v.expiry <= atomic.LoadUint32(&utils.Timestamp) {
4655
return nil, nil
4756
}
4857

@@ -58,11 +67,11 @@ func (s *Storage) Set(key string, val []byte, exp time.Duration) error {
5867

5968
var expire uint32
6069
if exp != 0 {
61-
expire = uint32(time.Now().Add(exp).Unix())
70+
expire = uint32(exp.Seconds()) + atomic.LoadUint32(&utils.Timestamp)
6271
}
6372

6473
s.mux.Lock()
65-
s.db[key] = entry{expire, val}
74+
s.db[key] = entry{val, expire}
6675
s.mux.Unlock()
6776
return nil
6877
}
@@ -96,20 +105,31 @@ func (s *Storage) Close() error {
96105
func (s *Storage) gc() {
97106
ticker := time.NewTicker(s.gcInterval)
98107
defer ticker.Stop()
108+
var expired []string
99109

100110
for {
101111
select {
102112
case <-s.done:
103113
return
104-
case t := <-ticker.C:
105-
now := uint32(t.Unix())
106-
s.mux.Lock()
114+
case <-ticker.C:
115+
expired = expired[:0]
116+
s.mux.RLock()
107117
for id, v := range s.db {
108-
if v.expiry != 0 && v.expiry < now {
109-
delete(s.db, id)
118+
if v.expiry != 0 && v.expiry < atomic.LoadUint32(&utils.Timestamp) {
119+
expired = append(expired, id)
110120
}
111121
}
122+
s.mux.RUnlock()
123+
s.mux.Lock()
124+
for i := range expired {
125+
delete(s.db, expired[i])
126+
}
112127
s.mux.Unlock()
113128
}
114129
}
115130
}
131+
132+
// Return database client
133+
func (s *Storage) Conn() map[string]entry {
134+
return s.db
135+
}
+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package memory
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/gofiber/fiber/v2/utils"
8+
)
9+
10+
var testStore = New()
11+
12+
func Test_Storage_Memory_Set(t *testing.T) {
13+
var (
14+
key = "john"
15+
val = []byte("doe")
16+
)
17+
18+
err := testStore.Set(key, val, 0)
19+
utils.AssertEqual(t, nil, err)
20+
}
21+
22+
func Test_Storage_Memory_Set_Override(t *testing.T) {
23+
var (
24+
key = "john"
25+
val = []byte("doe")
26+
)
27+
28+
err := testStore.Set(key, val, 0)
29+
utils.AssertEqual(t, nil, err)
30+
31+
err = testStore.Set(key, val, 0)
32+
utils.AssertEqual(t, nil, err)
33+
}
34+
35+
func Test_Storage_Memory_Get(t *testing.T) {
36+
var (
37+
key = "john"
38+
val = []byte("doe")
39+
)
40+
41+
err := testStore.Set(key, val, 0)
42+
utils.AssertEqual(t, nil, err)
43+
44+
result, err := testStore.Get(key)
45+
utils.AssertEqual(t, nil, err)
46+
utils.AssertEqual(t, val, result)
47+
}
48+
49+
func Test_Storage_Memory_Set_Expiration(t *testing.T) {
50+
var (
51+
key = "john"
52+
val = []byte("doe")
53+
exp = 1 * time.Second
54+
)
55+
56+
err := testStore.Set(key, val, exp)
57+
utils.AssertEqual(t, nil, err)
58+
59+
time.Sleep(1100 * time.Millisecond)
60+
}
61+
62+
func Test_Storage_Memory_Get_Expired(t *testing.T) {
63+
var (
64+
key = "john"
65+
)
66+
67+
result, err := testStore.Get(key)
68+
utils.AssertEqual(t, nil, err)
69+
utils.AssertEqual(t, true, len(result) == 0)
70+
}
71+
72+
func Test_Storage_Memory_Get_NotExist(t *testing.T) {
73+
74+
result, err := testStore.Get("notexist")
75+
utils.AssertEqual(t, nil, err)
76+
utils.AssertEqual(t, true, len(result) == 0)
77+
}
78+
79+
func Test_Storage_Memory_Delete(t *testing.T) {
80+
var (
81+
key = "john"
82+
val = []byte("doe")
83+
)
84+
85+
err := testStore.Set(key, val, 0)
86+
utils.AssertEqual(t, nil, err)
87+
88+
err = testStore.Delete(key)
89+
utils.AssertEqual(t, nil, err)
90+
91+
result, err := testStore.Get(key)
92+
utils.AssertEqual(t, nil, err)
93+
utils.AssertEqual(t, true, len(result) == 0)
94+
}
95+
96+
func Test_Storage_Memory_Reset(t *testing.T) {
97+
var (
98+
val = []byte("doe")
99+
)
100+
101+
err := testStore.Set("john1", val, 0)
102+
utils.AssertEqual(t, nil, err)
103+
104+
err = testStore.Set("john2", val, 0)
105+
utils.AssertEqual(t, nil, err)
106+
107+
err = testStore.Reset()
108+
utils.AssertEqual(t, nil, err)
109+
110+
result, err := testStore.Get("john1")
111+
utils.AssertEqual(t, nil, err)
112+
utils.AssertEqual(t, true, len(result) == 0)
113+
114+
result, err = testStore.Get("john2")
115+
utils.AssertEqual(t, nil, err)
116+
utils.AssertEqual(t, true, len(result) == 0)
117+
}
118+
119+
func Test_Storage_Memory_Close(t *testing.T) {
120+
utils.AssertEqual(t, nil, testStore.Close())
121+
}
122+
123+
func Test_Storage_Memory_Conn(t *testing.T) {
124+
utils.AssertEqual(t, true, testStore.Conn() != nil)
125+
}
126+
127+
// go test -v -run=^$ -bench=Benchmark_Storage_Memory -benchmem -count=4
128+
func Benchmark_Storage_Memory(b *testing.B) {
129+
keyLength := 1000
130+
keys := make([]string, keyLength)
131+
for i := 0; i < keyLength; i++ {
132+
keys[i] = utils.UUID()
133+
}
134+
value := []byte("joe")
135+
136+
ttl := 2 * time.Second
137+
b.Run("fiber_memory", func(b *testing.B) {
138+
d := New()
139+
b.ReportAllocs()
140+
b.ResetTimer()
141+
for n := 0; n < b.N; n++ {
142+
for _, key := range keys {
143+
d.Set(key, value, ttl)
144+
}
145+
for _, key := range keys {
146+
_, _ = d.Get(key)
147+
}
148+
for _, key := range keys {
149+
d.Delete(key)
150+
}
151+
}
152+
})
153+
}

0 commit comments

Comments
 (0)