PhilipAng
← All projects

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 SELECT is cached. Anything in a transaction passes straight through.
  • Table-level invalidation means a write-heavy table gets no benefit at all.