본문으로 건너뛰기

Refresh Storm 방지 — 동시 Auto-Refresh 상한

개요

인증 토큰을 자동 갱신하는 시스템에서 토큰 만료 직전 다수의 요청이 동시에 들어오면 각 요청이 독립적으로 refresh를 시도해 refresh storm이 발생한다. 동시 실행 상한(semaphore) 또는 singleflight 패턴으로 이를 막는다.

왜 문제인가

  • 만료 직전 N개의 API 요청 → N개의 refresh 호출
  • 인증 서버에 수십~수백 개의 refresh가 한꺼번에 쏟아짐
  • Rate limit 초과, 불필요한 토큰 재발급, 레이스 컨디션

방법 1 — Semaphore로 동시 실행 수 제한

const maxConcurrentRefresh = 16

var sem = make(chan struct{}, maxConcurrentRefresh)

func refreshToken(ctx context.Context) error {
select {
case sem <- struct{}{}:
defer func() { <-sem }()
case <-ctx.Done():
return ctx.Err()
}
return doRefresh(ctx)
}

버퍼드 채널을 세마포로 활용해 최대 16개까지만 동시 refresh. 상한값은 인증 서버의 rate limit을 보고 결정.

방법 2 — singleflight로 중복 완전 제거

import "golang.org/x/sync/singleflight"

var group singleflight.Group

func refreshToken(ctx context.Context) (string, error) {
v, err, _ := group.Do("refresh", func() (any, error) {
return doRefresh(ctx)
})
if err != nil {
return "", err
}
return v.(string), nil
}

동일 키에 대해 진행 중인 호출이 있으면 새 호출자는 결과를 공유한다. 중복 refresh 0건 보장.

선택 기준

상황선택
동시 실행을 "적당히" 제한하고 싶다Semaphore
같은 토큰에 대한 중복 refresh를 완전히 제거singleflight
실패 시 재시도 타이밍을 분산Exponential Backoff + Jitter (보완)