go
48 lines · 8 steps
Cache-aside reads with singleflight in Go
A read-through cache that collapses concurrent misses into a single database fetch to prevent thundering herds.
Explained by
highlit
1package cache
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "time"
8
9 "golang.org/x/sync/singleflight"
10)
11
12type UserService struct {
13 redis RedisClient
14 db *Database
15 group singleflight.Group
16 ttl time.Duration
17}
18
19func NewUserService(redis RedisClient, db *Database) *UserService {
20 return &UserService{redis: redis, db: db, ttl: 5 * time.Minute}
21}
22
23func (s *UserService) GetUser(ctx context.Context, id int64) (*User, error) {
24 key := fmt.Sprintf("user:%d", id)
25
26 if raw, err := s.redis.Get(ctx, key).Bytes(); err == nil {
27 var u User
28 if json.Unmarshal(raw, &u) == nil {
29 return &u, nil
30 }
31 }
32
33 v, err, _ := s.group.Do(key, func() (interface{}, error) {
34 user, err := s.db.FetchUser(ctx, id)
35 if err != nil {
36 return nil, err
37 }
38 if encoded, err := json.Marshal(user); err == nil {
39 s.redis.Set(ctx, key, encoded, s.ttl)
40 }
41 return user, nil
42 })
43 if err != nil {
44 return nil, err
45 }
46
47 return v.(*User), nil
48}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1The cache-aside pattern checks the fast store first and only touches the database on a miss.
- 2singleflight collapses duplicate concurrent requests for the same key into one execution, sharing the result.
- 3Populating the cache inside the deduplicated fetch ensures one write per stampede rather than many.
Related explainers
typescript
import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpHandler,
Refreshing tokens with an Angular HttpInterceptor
http-interceptor
token-refresh
rxjs
Advanced
8 steps
go
package ratelimit import ( "context"
A token bucket rate limiter in Go
rate-limiting
concurrency
goroutines
Intermediate
7 steps
python
from django.core.cache import cache from django.db.models import Count, Sum, Q from django.utils import timezone
Caching a Django dashboard aggregate query
caching
aggregation
conditional-queries
Intermediate
7 steps
go
package handlers type BookURI struct { Collection string `uri:"collection" binding:"required,alphanum"`
Validating URI params in a Gin handler
input validation
struct binding
error handling
Intermediate
7 steps
typescript
type Fetcher<T> = (key: string) => Promise<T>; export class RequestDeduplicator<T> { private inFlight = new Map<string, Promise<T>>();
Deduplicating in-flight requests in TypeScript
deduplication
promises
caching
Intermediate
7 steps
go
package httpx import ( "net/http"
A retrying HTTP transport in Go
http
middleware
retry
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/cache-aside-reads-with-singleflight-in-go-explained-go-5728/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.