Tech News
← Back to articles

Redis is fast – I'll cache in Postgres

read original related products more articles

There are books & many articles online, like this one arguing for using Postgres for everything. I thought I’d take a look at one use case - using Postgres instead of Redis for caching. I work with APIs quite a bit, so I’d build a super simple HTTP server that responds with data from that cache. I’d start from Redis as this is something I frequently encounter at work, switch it out to Postgres using unlogged tables and see if there’s a difference.

The setup#

I’ll run the experiment on my homelab’s k8s cluster. The idea is to run Postgres or Redis on one node, limiting it to 2CPUs via k8s limits, as well as 8GiB of memory. On another node, I’ll run the web server itself and then spin a pod for the benchmark executed via k6 on the third.

Both postgres and redis are used with the out of the box settings for the following images:

Postgres - postgres:17.6

Redis - redis:8.2

I wrote a simple webserver, with 2 endpoints, a cache and a “Session” struct which we’ll store in the cache:

var ErrCacheMiss = errors . New ( "cache miss" ) type Cache interface { Get ( ctx context . Context , key string ) ( string , error ) Set ( ctx context . Context , key string , value string ) error } type Session struct { ID string } func serveHTTP ( c Cache ) { http . HandleFunc ( "/get" , getHandler ( c )) http . HandleFunc ( "/set" , setHandler ( c )) port := os . Getenv ( "PORT" ) if port == "" { port = "8080" } fmt . Println ( "Server starting on http://0.0.0.0:" + port ) server := & http . Server { Addr : "0.0.0.0:" + port } go func () { if err := server . ListenAndServe (); err != nil && err != http . ErrServerClosed { fmt . Println ( "Error starting server:" , err ) } }() quit := make( chan os . Signal , 1 ) signal . Notify ( quit , os . Interrupt ) <- quit fmt . Println ( "Shutting down server..." ) if err := server . Close (); err != nil { fmt . Println ( "Error shutting down server:" , err ) } }

For redis, I’ve implemented the cache using github.com/redis/go-redis/v9 as follows:

type RedisCache struct { client * redis . Client } func NewRedisCache () * RedisCache { redisURL := os . Getenv ( "REDIS_URL" ) if redisURL == "" { redisURL = "localhost:6379" } fmt . Println ( "Connecting to Redis at" , redisURL ) client := redis . NewClient ( & redis . Options { Addr : redisURL , Password : "" , DB : 0 , }) return & RedisCache { client : client , } } func ( r * RedisCache ) Get ( ctx context . Context , key string ) ( string , error ) { val , err := r . client . Get ( ctx , key ). Result () if err == redis . Nil { return "" , ErrCacheMiss } if err != nil { return "" , err } return val , nil } func ( r * RedisCache ) Set ( ctx context . Context , key string , value string ) error { return r . client . Set ( ctx , key , value , 0 ). Err () }

... continue reading