Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 779c1a1

Browse files
committedOct 25, 2024
perf: improve performance search for keys we already found
misc: update readme i Please enter the commit message for your changes. Lines starting
1 parent a11979d commit 779c1a1

File tree

8 files changed

+80
-50
lines changed

8 files changed

+80
-50
lines changed
 

‎README.md

+7-9
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,16 @@ It does not support all the features of unkey-cache yet.
1111
- [x] Memory Store
1212
- [x] Redis Store
1313
- [x] Libsql Store
14+
The following Table is needed:
1415

1516
```sql
16-
The following Table is needed:
17-
CREATE TABLE cache (
17+
CREATE TABLE cache
18+
(
1819
key TEXT PRIMARY KEY,
19-
fresh_until TIMESTAMP,
20-
stale_until TIMESTAMP,
21-
value TEXT
22-
);
23-
24-
CREATE INDEX idx_fresh_until ON cache (fresh_until);
25-
CREATE INDEX idx_stale_until ON cache (stale_until);
20+
fresh_until INTEGER,
21+
stale_until INTEGER,
22+
value TEXT
23+
);
2624
```
2725

2826
Todo:

‎example/example.go

+26-9
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ func init() {
6868
})
6969

7070
dbName := "local.db"
71-
primaryUrl := "libsql://YOUR_DB.turso.io"
72-
authToken := "YOUR_AUTH_TOKEN"
71+
primaryUrl := os.Getenv("TURSO_URL")
72+
authToken := os.Getenv("TURSO_TOKEN")
7373

7474
dir, err := os.MkdirTemp("", "libsql-*")
7575
if err != nil {
@@ -79,7 +79,9 @@ func init() {
7979

8080
dbPath := filepath.Join(dir, dbName)
8181

82-
connector, err := libsql.NewEmbeddedReplicaConnector(dbPath, primaryUrl,
82+
connector, err := libsql.NewEmbeddedReplicaConnector(
83+
dbPath,
84+
primaryUrl,
8385
libsql.WithAuthToken(authToken),
8486
)
8587

@@ -95,7 +97,7 @@ func init() {
9597
TableName: "cache",
9698
})
9799

98-
encryption := middleware.WithEncryption("YOUR_SECRET")
100+
encryption := middleware.WithEncryption(os.Getenv("ENCRYPTION_KEY"))
99101
libSqlEncrypted := encryption.Wrap(libsql)
100102

101103
c := Cache{
@@ -156,10 +158,15 @@ func main() {
156158
Description: "This is a test post",
157159
}
158160

159-
service.cache.String.Set(ctx, "hallo", "welt", nil)
160-
stringValue, found, err := service.cache.String.Get(ctx, "hallo")
161+
stringValue, err := service.cache.String.Swr(ctx, "hallo", func(string) (*string, error) {
162+
str := "welt"
163+
return &str, nil
164+
})
165+
if err != nil {
166+
log.Fatal(err)
167+
}
161168

162-
log.Printf("stringValue has value: %+v found %+v err %+v", *stringValue, found, err)
169+
log.Printf("stringValue has value: %+v err %+v", *stringValue, err)
163170
if err := service.cache.EncryptedStruct.Set(ctx, "p1", p, nil); err != nil {
164171
log.Printf("error: %+v", err)
165172
}
@@ -334,10 +341,20 @@ func main() {
334341

335342
start := time.Now()
336343
// get them all
337-
manyStrings, err := service.cache.User.GetMany(ctx, keys)
344+
manyStrings, err := service.cache.String.GetMany(ctx, keys)
338345
if err != nil {
339346
log.Fatal(err)
340347
}
341348

342-
log.Printf("manyStrings: %d took %s", len(manyStrings), time.Since(start))
349+
foundKeys := 0
350+
notFound := 0
351+
for _, m := range manyStrings {
352+
if m.Found {
353+
foundKeys++
354+
} else {
355+
notFound++
356+
}
357+
}
358+
359+
log.Printf("manyStrings: %d took %s and found %d and not found %d", len(manyStrings), time.Since(start), foundKeys, notFound)
343360
}

‎middleware.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ package cache
33
// StoreMiddleware represents a middleware for wrapping a Store
44
type StoreMiddleware interface {
55
// Wrap takes a Store and returns a new Store with added functionality
6-
Wrap(store Store) Store
6+
Wrap(Store) Store
77
}

‎middleware/encryption/encryption.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"encoding/base64"
99
"fmt"
1010
"io"
11+
"log"
1112
"reflect"
1213
"strings"
1314

@@ -186,18 +187,21 @@ func (e *EncryptedStore) Decrypt(encryptedValue *EncryptedValue) (string, error)
186187
func FromBase64Key(base64EncodedKey string) cache.StoreMiddleware {
187188
key, err := base64.StdEncoding.DecodeString(base64EncodedKey)
188189
if err != nil {
189-
panic(err)
190+
log.Printf("error: %+v", err)
191+
return nil
190192
}
191193

192194
// Verify key length for AES-256
193195
if len(key) != 32 {
194-
panic(err)
196+
log.Printf("error: %+v", err)
197+
return nil
195198
}
196199

197200
// Create AES cipher to verify the key
198201
_, err = aes.NewCipher(key)
199202
if err != nil {
200-
panic(err)
203+
log.Printf("error: %+v", err)
204+
return nil
201205
}
202206

203207
// Compute SHA-256 hash of the key

‎pkg/util/util.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package util
2+
3+
func MapToString(m map[string]struct{}) []string {
4+
ret := make([]string, 0)
5+
for k := range m {
6+
ret = append(ret, k)
7+
}
8+
return ret
9+
}

‎store.go

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
type Store interface {
88
Name() string
99

10+
CreateCacheKey(namespace types.TNamespace, key string) string
1011
Get(namespace types.TNamespace, key string, T any) (value types.TValue, found bool, err error)
1112
GetMany(namespace types.TNamespace, keys []string, T any) ([]types.TValue, error)
1213
Set(namespace types.TNamespace, key string, value types.TValue) error

‎store/libsql/libsql.go

+19-12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"reflect"
77
"strings"
88

9+
"github.com/Southclaws/fault"
10+
"github.com/Southclaws/fault/fmsg"
911
"github.com/steamsets/go-cache/pkg/types"
1012
)
1113

@@ -36,12 +38,16 @@ func (l *LibsqlStore) Name() string {
3638
return l.name
3739
}
3840

39-
func (l *LibsqlStore) CreateCacheKey(namespace string, key string) string {
41+
func (l *LibsqlStore) CreateCacheKey(namespace types.TNamespace, key string) string {
4042
return string(namespace) + "::" + key
4143
}
4244

45+
func (l *LibsqlStore) UndoCacheKey(namespace types.TNamespace, key string) string {
46+
return strings.TrimPrefix(key, string(namespace)+"::")
47+
}
48+
4349
func (l *LibsqlStore) Get(ns types.TNamespace, key string, T any) (value types.TValue, found bool, err error) {
44-
cacheKey := l.CreateCacheKey(string(ns), key)
50+
cacheKey := l.CreateCacheKey(ns, key)
4551
val := types.TValue{Found: false, Key: cacheKey}
4652
raw := make([]byte, 0)
4753

@@ -60,6 +66,7 @@ func (l *LibsqlStore) Get(ns types.TNamespace, key string, T any) (value types.T
6066
return value, false, err
6167
}
6268

69+
val.Key = l.UndoCacheKey(ns, val.Key)
6370
val.Found = true
6471
val.Value = v.Value
6572

@@ -74,35 +81,35 @@ func (l *LibsqlStore) GetMany(ns types.TNamespace, keys []string, T any) ([]type
7481

7582
keysToGet := make([]interface{}, 0)
7683
for _, key := range keys {
77-
keysToGet = append(keysToGet, l.CreateCacheKey(string(ns), key))
84+
keysToGet = append(keysToGet, l.CreateCacheKey(ns, key))
7885
}
7986

8087
rows, err := l.config.DB.Query("SELECT key, fresh_until, stale_until, value FROM "+l.config.TableName+" WHERE key IN ("+strings.Join(placeHolders, ",")+")", keysToGet...)
8188
if err != nil {
82-
return nil, err
89+
return nil, fault.Wrap(err, fmsg.With("failed to exec query"))
8390
}
8491

8592
defer rows.Close()
8693

8794
values := make([]types.TValue, 0)
8895

8996
for rows.Next() {
90-
var val types.TValue
97+
val := types.TValue{}
9198
raw := make([]byte, 0)
9299

93100
if err := rows.Scan(&val.Key, &val.FreshUntil, &val.StaleUntil, &raw); err != nil {
94-
return nil, err
101+
return nil, fault.Wrap(err, fmsg.With("failed to scan row"))
95102
}
96103

97104
localT := reflect.New(reflect.TypeOf(T).Elem()).Interface()
98-
99-
v, err := types.SetTIntoTValue(raw, localT)
105+
v, err := types.SetTIntoValue(raw, localT)
100106
if err != nil {
101107
return nil, err
102108
}
103109

110+
val.Key = l.UndoCacheKey(ns, val.Key)
104111
val.Found = true
105-
val.Value = v
112+
val.Value = v.Value
106113
values = append(values, val)
107114
}
108115

@@ -121,7 +128,7 @@ func (l *LibsqlStore) Set(ns types.TNamespace, key string, value types.TValue) e
121128

122129
_, err = l.config.DB.Exec(
123130
"INSERT OR REPLACE INTO "+l.config.TableName+" (key, fresh_until, stale_until, value) VALUES(?, ?, ?, ?)",
124-
l.CreateCacheKey(string(ns), key),
131+
l.CreateCacheKey(ns, key),
125132
value.FreshUntil,
126133
value.StaleUntil,
127134
string(b),
@@ -151,7 +158,7 @@ func (l *LibsqlStore) SetMany(ns types.TNamespace, values []types.TValue, opts *
151158
}
152159

153160
sql = sql + "(?, ?, ?, ?),"
154-
params = append(params, l.CreateCacheKey(string(ns), v.Key), v.FreshUntil, v.StaleUntil, string(b))
161+
params = append(params, l.CreateCacheKey(ns, v.Key), v.FreshUntil, v.StaleUntil, string(b))
155162
}
156163

157164
sql = sql[:len(sql)-1]
@@ -174,7 +181,7 @@ func (l *LibsqlStore) Remove(ns types.TNamespace, key []string) error {
174181
keysToDelete := make([]string, 0)
175182

176183
for _, key := range key {
177-
keysToDelete = append(keysToDelete, l.CreateCacheKey(string(ns), key))
184+
keysToDelete = append(keysToDelete, l.CreateCacheKey(ns, key))
178185
}
179186

180187
_, err := l.config.DB.Exec("DELETE FROM "+l.config.TableName+" WHERE key IN ("+strings.Join(placeHolders, ",")+")", keysToDelete)

‎tiered.go

+10-16
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ package cache
33
import (
44
"context"
55
"errors"
6-
"slices"
76
"strings"
87
"time"
98

109
"github.com/Southclaws/fault"
1110
"github.com/Southclaws/fault/fmsg"
1211
"github.com/steamsets/go-cache/pkg/telemetry"
1312
"github.com/steamsets/go-cache/pkg/types"
13+
"github.com/steamsets/go-cache/pkg/util"
1414
)
1515

1616
type tieredCache[T any] struct {
@@ -115,22 +115,24 @@ func (t tieredCache[T]) GetMany(ctx context.Context, ns types.TNamespace, keys [
115115
return nil, errors.New("no stores found")
116116
}
117117

118+
keysToGet := make(map[string]struct{}, 0)
118119
valuesToSet := make([]types.TValue, 0)
119120
foundValues := make(map[string]types.TValue)
120121

121122
// We need to check for all keys in all stores
122123
// If we can't find one key in a store we need to check the lower stores for all values except
123124
// the ones that we already found
124125

125-
// Hack, not sure why but I need to copy the whole array in order to not modify the original
126-
keysToFind := make([]string, len(keys))
127-
copy(keysToFind, keys)
126+
for _, key := range keys {
127+
keysToGet[key] = struct{}{}
128+
}
128129

129130
for _, store := range t.stores {
130131
// we already found all keys
131-
if len(keysToFind) == 0 {
132+
if len(keysToGet) == 0 {
132133
break
133134
}
135+
keysToFind := util.MapToString(keysToGet)
134136

135137
var result T
136138
ctx, span := telemetry.NewSpan(ctx, store.Name()+".get-many")
@@ -139,8 +141,8 @@ func (t tieredCache[T]) GetMany(ctx context.Context, ns types.TNamespace, keys [
139141
telemetry.AttributeKV{Key: "keys", Value: keysToFind},
140142
telemetry.AttributeKV{Key: "namespace", Value: string(ns)},
141143
)
142-
values, err := store.GetMany(t.ns, keysToFind, &result)
143144

145+
values, err := store.GetMany(t.ns, keysToFind, &result)
144146
if err != nil {
145147
telemetry.RecordError(span, err)
146148
return nil, fault.Wrap(err, fmsg.With(store.Name()+" failed to get keys: "+strings.Join(keys, ",")))
@@ -150,15 +152,8 @@ func (t tieredCache[T]) GetMany(ctx context.Context, ns types.TNamespace, keys [
150152
if v.Found {
151153
// Since we found it set it to the lower stores
152154
valuesToSet = append(valuesToSet, v)
153-
154155
// But we should not look for it again
155-
for i, k := range keysToFind {
156-
if k == v.Key {
157-
keysToFind = slices.Delete(keysToFind, i, i+1)
158-
break
159-
}
160-
}
161-
156+
delete(keysToGet, v.Key)
162157
foundValues[v.Key] = v
163158
}
164159
}
@@ -172,7 +167,7 @@ func (t tieredCache[T]) GetMany(ctx context.Context, ns types.TNamespace, keys [
172167
_, span := telemetry.NewSpan(ctx, lowerStore.Name()+".set-many")
173168
defer span.End()
174169
telemetry.WithAttributes(span,
175-
telemetry.AttributeKV{Key: "keys", Value: keysToFind},
170+
telemetry.AttributeKV{Key: "keys_amount", Value: len(keysToGet)},
176171
telemetry.AttributeKV{Key: "namespace", Value: string(ns)},
177172
)
178173
if err := lowerStore.SetMany(t.ns, valuesToSet, nil); err != nil {
@@ -187,7 +182,6 @@ func (t tieredCache[T]) GetMany(ctx context.Context, ns types.TNamespace, keys [
187182
}
188183

189184
valuesToReturn := make([]types.TValue, 0)
190-
191185
// Now we need to map all the values which we did find or didn't
192186
for _, key := range keys {
193187
if v, ok := foundValues[key]; !ok {

0 commit comments

Comments
 (0)
Please sign in to comment.