Skip to content

Commit f6f01a8

Browse files
committed
cache: limit size (to 64GB by default)
1 parent 2636b9c commit f6f01a8

File tree

2 files changed

+45
-6
lines changed

2 files changed

+45
-6
lines changed

go/ckit/cache/cache.go

+40-5
Original file line numberDiff line numberDiff line change
@@ -33,40 +33,72 @@ package cache
3333

3434
import (
3535
"errors"
36+
"log"
37+
"os"
3638
"sync"
39+
"time"
3740

3841
"github.com/jmoiron/sqlx"
3942
"github.com/slub/labe/go/ckit/tabutils"
4043

4144
_ "github.com/mattn/go-sqlite3"
4245
)
4346

44-
var ErrCacheMiss = errors.New("cache miss")
47+
var (
48+
ErrCacheMiss = errors.New("cache miss")
49+
ErrReadOnly = errors.New("read only")
50+
DefaultMaxFileSize int64 = 1 << 36
51+
)
4552

4653
// Cache is a minimalistic cache based on sqlite. In the future, values could
4754
// be transparently compressed as well.
4855
//
4956
// TODO: set a limit on filesize, e.g. 100G; run periodic checks, whether
5057
// maximum filesize is exceeded and switch to read-only mode, if necessary
5158
type Cache struct {
52-
Path string
53-
59+
Path string
60+
MaxFileSize int64
61+
// Lock applies to both, db and readOnly.
5462
sync.Mutex
55-
db *sqlx.DB
63+
db *sqlx.DB
64+
readOnly bool
5665
}
5766

5867
func New(path string) (*Cache, error) {
5968
conn, err := sqlx.Open("sqlite3", path)
6069
if err != nil {
6170
return nil, err
6271
}
63-
c := &Cache{Path: path, db: conn}
72+
c := &Cache{Path: path, db: conn, MaxFileSize: DefaultMaxFileSize}
6473
if err := c.init(); err != nil {
6574
return nil, err
6675
}
76+
c.startSizeWatcher()
6777
return c, nil
6878
}
6979

80+
// startSizeWatcher sets up a goroutine that will watch the filesize
81+
// periodically and will switch to read-only mode, if a given size has been
82+
// exceeded.
83+
func (c *Cache) startSizeWatcher() {
84+
go func() {
85+
ticker := time.NewTicker(30 * time.Second)
86+
for t := range ticker.C {
87+
fi, err := os.Stat(c.Path)
88+
if err != nil {
89+
log.Printf("could not stat file at %s, stopping watch thread", c.Path)
90+
break
91+
}
92+
if fi.Size() > c.MaxFileSize {
93+
c.Lock()
94+
log.Printf("switching cache at %s to read-only mode at %v", c.Path, t)
95+
c.readOnly = true
96+
c.Unlock()
97+
}
98+
}
99+
}()
100+
}
101+
70102
func (c *Cache) init() error {
71103
s := `
72104
PRAGMA journal_mode = OFF;
@@ -106,6 +138,9 @@ func (c *Cache) ItemCount() (int, error) {
106138
func (c *Cache) Set(key string, value []byte) error {
107139
c.Lock()
108140
defer c.Unlock()
141+
if c.readOnly {
142+
return ErrReadOnly
143+
}
109144
s := `INSERT into map (k, v) VALUES (?, ?)`
110145
_, err := c.db.Exec(s, key, value)
111146
return err

go/ckit/server.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,11 @@ func (s *Server) handleLocalIdentifier() http.HandlerFunc {
511511
return fmt.Errorf("cache close: %w", err)
512512
}
513513
if err := s.Cache.Set(response.ID, buf.Bytes()); err != nil {
514-
return fmt.Errorf("failed to cache value for %s: %v", response.ID, err)
514+
if err == cache.ErrReadOnly {
515+
return nil
516+
} else {
517+
return fmt.Errorf("failed to cache value for %s: %v", response.ID, err)
518+
}
515519
}
516520
s.Stats.MeasureSinceWithLabels("cached", t, nil)
517521
sw.Record("cached value")

0 commit comments

Comments
 (0)