Caching in Go: How to Speed Up Your Application 100x Without Losing Your Mind

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why is your Go application slow? You've probably forgotten about caching



You wrote the perfect Go application. You're proud of your goroutines, channels, and fast compiler. But when the load reaches 1000 RPS, everything starts to creak. The database crashes, the API responds in 5 seconds, users leave for competitors.



Sound familiar? Welcome to the world where caching in Go is essential. In this article, we'll break down how to turn your tortoise-like application into a cheetah. No fluff, with real examples and ready-made solutions.



By the way, my name is Python, and this is PythonLib. Yes, I know we're talking about Go. But good code knows no language boundaries, and we write useful articles about everything. Let's go!



What is caching and why do you need it in Go?



Data caching is storing frequently requested data in fast storage (RAM, Redis, files) so you don't have to hit the slow source (database, external API, file system) every time.



Go is a language for high-load systems. But even in Go, you can write slow code if you don't use caching. Imagine: you query PostgreSQL for a list of countries 1000 times per second. Instead of 1000 database queries, you can make just 1, and serve the data from in-memory cache Go the other 999 times.



Result: response time drops from 50 ms to 0.5 ms. A 100x difference. That's not magic, it's good architecture.



When is caching mandatory?

  • High load (1000+ requests per second)
  • Slow external APIs (payment gateways, weather, exchange rates)
  • Heavy computations (reports, analytics)
  • Frequently requested reference data (city lists, product categories)


Caching strategies: choosing the right one



To avoid turning caching into an anti-pattern, you need to choose the right caching strategy. Here are the main options that work in Go:



1. Cache-Aside (Lazy Caching)

The most popular pattern. The application first checks the cache. If the data is not there (cache miss), it goes to the database, saves the result to the cache, and returns it to the client.



func GetUser(cache *redis.Client, db *sql.DB, userID int) (*User, error) {    // Try to get from cache    val, err := cache.Get(ctx, "user:"+strconv.Itoa(userID)).Result()    if err == nil {        user := &User{}        json.Unmarshal([]byte(val), user)        return user, nil    }

// Cache miss — go to DB user, err := db.Query("SELECT * FROM users WHERE id = ?", userID) if err != nil { return nil, err }

// Save to cache for 1 hour data, _ := json.Marshal(user) cache.Set(ctx, "user:"+strconv.Itoa(userID), data, time.Hour)

return user, nil}


2. Write-Through

Data is written to both the cache and the database simultaneously. Pro: the cache is always up-to-date. Con: writes are slightly slower.



3. TTL (Time To Live)

The simplest way of cache invalidation. Data lives for a fixed time, then is automatically deleted.



// Cache for 5 minutescache.Set(ctx, "key", value, 5*time.Minute)


Implementing an in-memory cache in Go from scratch



Sometimes you don't want to set up Redis for just a couple of keys. For such cases, in-memory cache Go works great. Let's write a simple but functional version:



package cache

import ( "sync" "time")

type Item struct { Value interface{} Expiration int64}

type Cache struct { items map[string]Item mu sync.RWMutex}

func New() *Cache { c := &Cache{ items: make(map[string]Item), } // Start a goroutine to clean up expired entries go c.cleanup() return c}

func (c *Cache) Set(key string, value interface{}, ttl time.Duration) { c.mu.Lock() defer c.mu.Un

Blogs

Book Recommendations