Skip to content

Commit 7f71e92

Browse files
committedMay 29, 2024·
Add in-memory cache for rwp server
1 parent e022f93 commit 7f71e92

File tree

6 files changed

+168
-31
lines changed

6 files changed

+168
-31
lines changed
 

‎cmd/rwp/cmd/serve/api.go

+38-31
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/gorilla/mux"
1717
httprange "github.com/gotd/contrib/http_range"
1818
"github.com/pkg/errors"
19+
"github.com/readium/go-toolkit/cmd/rwp/cmd/serve/cache"
1920
"github.com/readium/go-toolkit/pkg/asset"
2021
"github.com/readium/go-toolkit/pkg/manifest"
2122
"github.com/readium/go-toolkit/pkg/pub"
@@ -53,43 +54,49 @@ func (s *Server) getPublication(filename string) (*pub.Publication, error) {
5354
return nil, err
5455
}
5556

56-
// TODO: cache open publications
57-
5857
cp := filepath.Clean(string(fpath))
59-
pub, err := streamer.New(streamer.Config{
60-
InferA11yMetadata: s.config.InferA11yMetadata,
61-
}).Open(asset.File(filepath.Join(s.config.BaseDirectory, cp)), "")
62-
if err != nil {
63-
return nil, errors.Wrap(err, "failed opening "+cp)
64-
}
58+
dat, ok := s.lfu.Get(cp)
59+
if !ok {
60+
pub, err := streamer.New(streamer.Config{
61+
InferA11yMetadata: s.config.InferA11yMetadata,
62+
}).Open(asset.File(filepath.Join(s.config.BaseDirectory, cp)), "")
63+
if err != nil {
64+
return nil, errors.Wrap(err, "failed opening "+cp)
65+
}
6566

66-
// TODO: Remove this after we make links relative in the go-toolkit
67-
for i, link := range pub.Manifest.Links {
68-
pub.Manifest.Links[i] = makeRelative(link)
69-
}
70-
for i, link := range pub.Manifest.Resources {
71-
pub.Manifest.Resources[i] = makeRelative(link)
72-
}
73-
for i, link := range pub.Manifest.ReadingOrder {
74-
pub.Manifest.ReadingOrder[i] = makeRelative(link)
75-
}
76-
for i, link := range pub.Manifest.TableOfContents {
77-
pub.Manifest.TableOfContents[i] = makeRelative(link)
78-
}
79-
var makeCollectionRelative func(mp manifest.PublicationCollectionMap)
80-
makeCollectionRelative = func(mp manifest.PublicationCollectionMap) {
81-
for i := range mp {
82-
for j := range mp[i] {
83-
for k := range mp[i][j].Links {
84-
mp[i][j].Links[k] = makeRelative(mp[i][j].Links[k])
67+
// TODO: Remove this after we make links relative in the go-toolkit
68+
for i, link := range pub.Manifest.Links {
69+
pub.Manifest.Links[i] = makeRelative(link)
70+
}
71+
for i, link := range pub.Manifest.Resources {
72+
pub.Manifest.Resources[i] = makeRelative(link)
73+
}
74+
for i, link := range pub.Manifest.ReadingOrder {
75+
pub.Manifest.ReadingOrder[i] = makeRelative(link)
76+
}
77+
for i, link := range pub.Manifest.TableOfContents {
78+
pub.Manifest.TableOfContents[i] = makeRelative(link)
79+
}
80+
var makeCollectionRelative func(mp manifest.PublicationCollectionMap)
81+
makeCollectionRelative = func(mp manifest.PublicationCollectionMap) {
82+
for i := range mp {
83+
for j := range mp[i] {
84+
for k := range mp[i][j].Links {
85+
mp[i][j].Links[k] = makeRelative(mp[i][j].Links[k])
86+
}
87+
makeCollectionRelative(mp[i][j].Subcollections)
8588
}
86-
makeCollectionRelative(mp[i][j].Subcollections)
8789
}
8890
}
89-
}
90-
makeCollectionRelative(pub.Manifest.Subcollections)
91+
makeCollectionRelative(pub.Manifest.Subcollections)
9192

92-
return pub, nil
93+
// Cache the publication
94+
encPub := &cache.CachedPublication{Publication: pub}
95+
s.lfu.Set(cp, encPub)
96+
97+
return encPub.Publication, nil
98+
}
99+
return dat.(*cache.CachedPublication).Publication, nil
93100
}
94101

95102
func (s *Server) getManifest(w http.ResponseWriter, req *http.Request) {

‎cmd/rwp/cmd/serve/cache/local.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package cache
2+
3+
// Originally from https://github.com/go-redis/cache/blob/v8.4.3/local.go
4+
// Modified to store interface{} instead of []byte
5+
6+
import (
7+
"sync"
8+
"time"
9+
10+
"github.com/vmihailenco/go-tinylfu"
11+
"golang.org/x/exp/rand"
12+
)
13+
14+
type Evictable interface {
15+
OnEvict()
16+
}
17+
18+
type LocalCache interface {
19+
Set(key string, data Evictable)
20+
Get(key string) (Evictable, bool)
21+
Del(key string)
22+
}
23+
24+
type TinyLFU struct {
25+
mu sync.Mutex
26+
rand *rand.Rand
27+
lfu *tinylfu.T
28+
ttl time.Duration
29+
offset time.Duration
30+
}
31+
32+
var _ LocalCache = (*TinyLFU)(nil)
33+
34+
func NewTinyLFU(size int, ttl time.Duration) *TinyLFU {
35+
const maxOffset = 10 * time.Second
36+
37+
offset := ttl / 10
38+
if offset > maxOffset {
39+
offset = maxOffset
40+
}
41+
42+
return &TinyLFU{
43+
rand: rand.New(rand.NewSource(uint64(time.Now().UnixNano()))),
44+
lfu: tinylfu.New(size, 100000),
45+
ttl: ttl,
46+
offset: offset,
47+
}
48+
}
49+
50+
func (c *TinyLFU) UseRandomizedTTL(offset time.Duration) {
51+
c.offset = offset
52+
}
53+
54+
func (c *TinyLFU) Set(key string, b Evictable) {
55+
c.mu.Lock()
56+
defer c.mu.Unlock()
57+
58+
ttl := c.ttl
59+
if c.offset > 0 {
60+
ttl += time.Duration(c.rand.Int63n(int64(c.offset)))
61+
}
62+
63+
c.lfu.Set(&tinylfu.Item{
64+
Key: key,
65+
Value: b,
66+
ExpireAt: time.Now().Add(ttl),
67+
OnEvict: func() {
68+
b.OnEvict()
69+
},
70+
})
71+
}
72+
73+
func (c *TinyLFU) Get(key string) (Evictable, bool) {
74+
c.mu.Lock()
75+
defer c.mu.Unlock()
76+
77+
val, ok := c.lfu.Get(key)
78+
if !ok {
79+
return nil, false
80+
}
81+
82+
return val.(Evictable), true
83+
}
84+
85+
func (c *TinyLFU) Del(key string) {
86+
c.mu.Lock()
87+
defer c.mu.Unlock()
88+
89+
c.lfu.Del(key)
90+
}

‎cmd/rwp/cmd/serve/cache/pubcache.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package cache
2+
3+
import (
4+
"github.com/readium/go-toolkit/pkg/pub"
5+
)
6+
7+
// CachedPublication implements Evictable
8+
type CachedPublication struct {
9+
*pub.Publication
10+
}
11+
12+
func EncapsulatePublication(pub *pub.Publication) *CachedPublication {
13+
cp := &CachedPublication{pub}
14+
return cp
15+
}
16+
17+
func (cp *CachedPublication) OnEvict() {
18+
// Cleanup
19+
if cp.Publication != nil {
20+
cp.Publication.Close()
21+
}
22+
}

‎cmd/rwp/cmd/serve/server.go

+8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package serve
22

33
import (
4+
"time"
5+
46
"github.com/gorilla/mux"
7+
"github.com/readium/go-toolkit/cmd/rwp/cmd/serve/cache"
58
"github.com/readium/go-toolkit/pkg/streamer"
69
)
710

@@ -15,10 +18,15 @@ type ServerConfig struct {
1518
type Server struct {
1619
config ServerConfig
1720
router *mux.Router
21+
lfu *cache.TinyLFU
1822
}
1923

24+
const MaxCachedPublicationAmount = 10
25+
const MaxCachedPublicationTTL = time.Second * time.Duration(600)
26+
2027
func NewServer(config ServerConfig) *Server {
2128
return &Server{
2229
config: config,
30+
lfu: cache.NewTinyLFU(MaxCachedPublicationAmount, MaxCachedPublicationTTL),
2331
}
2432
}

‎go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,17 @@ require (
2121
github.com/stretchr/testify v1.9.0
2222
github.com/trimmer-io/go-xmp v1.0.0
2323
github.com/urfave/negroni v1.0.0
24+
github.com/vmihailenco/go-tinylfu v0.2.2
2425
github.com/zeebo/xxh3 v1.0.2
26+
golang.org/x/exp v0.0.0-20240529005216-23cca8864a10
2527
golang.org/x/net v0.23.0
2628
golang.org/x/text v0.14.0
2729
)
2830

2931
require (
3032
github.com/andybalholm/brotli v1.0.5 // indirect
3133
github.com/antchfx/xpath v1.2.1 // indirect
34+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
3235
github.com/davecgh/go-spew v1.1.1 // indirect
3336
github.com/fsnotify/fsnotify v1.4.9 // indirect
3437
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect

‎go.sum

+7
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
5656
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
5757
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
5858
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
59+
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
60+
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
61+
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5962
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
6063
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
6164
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -292,6 +295,8 @@ github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
292295
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
293296
github.com/valyala/gozstd v1.20.1 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g=
294297
github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
298+
github.com/vmihailenco/go-tinylfu v0.2.2 h1:H1eiG6HM36iniK6+21n9LLpzx1G9R3DJa2UjUjbynsI=
299+
github.com/vmihailenco/go-tinylfu v0.2.2/go.mod h1:CutYi2Q9puTxfcolkliPq4npPuofg9N9t8JVrjzwa3Q=
295300
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
296301
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
297302
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -333,6 +338,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
333338
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
334339
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
335340
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
341+
golang.org/x/exp v0.0.0-20240529005216-23cca8864a10 h1:vpzMC/iZhYFAjJzHU0Cfuq+w1vLLsF2vLkDrPjzKYck=
342+
golang.org/x/exp v0.0.0-20240529005216-23cca8864a10/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
336343
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
337344
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
338345
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=

0 commit comments

Comments
 (0)
Please sign in to comment.