Sablecache
A read-through cache proxy that speaks the Postgres wire protocol.
- Go
- Postgres
Sablecache sits between an application and Postgres, speaking the wire protocol on both sides. To the application it is a database; to the database it is a client.
The problem
A dashboard was running the same six aggregate queries on every page load. They were cheap individually and ruinous in aggregate. Adding a cache to the application meant touching four services in three languages.
The approach
Because it speaks the wire protocol, no application code changes. It parses each query, hashes the normalised form plus its parameters, and serves from an in-memory LRU when the entry is fresh.
func (c *Cache) Key(q string, args [][]byte) string {
h := fnv.New64a()
h.Write([]byte(normalise(q)))
for _, a := range args {
h.Write(a)
}
return strconv.FormatUint(h.Sum64(), 36)
}
Invalidation is the hard half. Sablecache subscribes to logical replication and drops any cached entry whose query touched a table that changed. Coarse, but correct, and it means nobody has to remember to invalidate anything.
Honest limits
- Only
SELECTis cached. Anything in a transaction passes straight through. - Table-level invalidation means a write-heavy table gets no benefit at all.