Jamie the programmer

Go에서 Throttling과 Debouncing? 요청은 줄이고 효율은 높이자! 본문

programming/golang

Go에서 Throttling과 Debouncing? 요청은 줄이고 효율은 높이자!

jamie91 2025. 3. 10. 10:55
Contents 접기
반응형

 

1. Rate Limiting이란?

  • *속도 제한(Rate Limiting)*은 리소스에 대한 접근을 일정 시간당 특정 횟수로 제한하는 메커니즘을 말한다.
    • 예: API 요청, 디스크 I/O, 네트워크 트래픽 등
  • 주요 목적
    1. 서비스의 품질 유지: 지나치게 많은 요청이 몰려와도 안정적으로 처리
    2. 악의적 공격 방어: 무차별 암호 대입 공격(Brute force)이나 Dos/DDoS 등 방지
    3. 공정성(Fairness): 특정 사용자나 특정 작업이 과도하게 자원을 사용하지 않도록 제어
    4. 시스템 부하 관리: 지나친 트래픽으로 인한 시스템 장애 예방

Go에서는 고루틴(goroutine), 채널(channel), 그리고 **티커(ticker)**를 활용하여 쉽게 Rate Limiting 기능을 구현할 수 있다.


2. Throttling & Debouncing

2.1 Throttling(쓰로틀링)

  • 연속적으로 들어오는 요청을 일정 주기로 하나씩만 처리하도록 하는 기법
  • 예: 키보드 이벤트가 연속적으로 들어올 때, 1초마다 한 번씩만 처리

2.2 Debouncing(디바운싱)

  • 연속적인 요청 중에서 마지막 요청만 처리하는 기법
  • 예: 검색창 자동완성에서 사용자가 입력을 멈췄을 때 마지막 입력만 처리

Throttling과 Debouncing은 비슷해 보이지만, 실제 적용 시점과 로직이 다르다.

참고: Throttling vs Debouncing 예시


3. 대표적인 Rate Limit 알고리즘

Rate Limiting을 구현하기 위한 알고리즘은 여러 가지가 있다. 각 알고리즘은 장단점과 사용 목적이 다르므로, 상황에 맞게 선택해야 한다.

  1. Leaky Bucket Algorithm
    • 물이 새는 양동이처럼 요청을 일정 속도로 처리
    • 일정 속도로만 빠져나가므로 버스트(순간 집중 트래픽)에 대응하기 어려울 수 있음
  2. Fixed Window Algorithm
    • 고정된 시간 단위(예: 1초, 1분)별로 카운트를 해서 요청 제한
    • 경계 시점에 요청이 몰릴 수 있는 문제(“경계 문제”) 발생 가능
  3. Sliding Window Algorithm
    • 이동하는 시간 창을 기준으로 요청 횟수를 계산
    • Fixed Window의 경계 문제를 어느 정도 해소
  4. Token Bucket Algorithm
    • 일정 속도로 토큰이 버킷에 채워지고, 요청 시 토큰을 소모
    • 버킷에 토큰이 없으면 요청을 대기하거나 거부
    • 버스트(단기간 몰리는 트래픽)를 어느 정도 허용하면서도 평균 속도를 제한

자세한 내용은 4 Rate Limit Algorithms Every Developer Should Know 문서를 참고하자.


4. Go에서 Rate Limit 구현

Go 표준 라이브러리 확장 모듈인 golang.org/x/time/rate는 내부적으로 Token Bucket Algorithm을 사용한다.

  • go.uber.org/ratelimit는 Leaky Bucket Algorithm 기반으로 Rate Limiting을 제공하는 라이브러리다.

4.1 Token Bucket 예시 (golang.org/x/time/rate)

import (
	"fmt"
	"time"

	"golang.org/x/time/rate"
)

func main() {
	// 초당 1회 요청 허용, 버스트(동시 요청) 1회
	limiter := rate.NewLimiter(1, 1)

	for i := 0; i < 5; i++ {
		// 요청이 가능해질 때까지 대기
		err := limiter.Wait(nil)
		if err != nil {
			fmt.Println("Error:", err)
		}
		fmt.Printf("Request #%d at %v\\\\n", i, time.Now())
	}
}


5. 직접 구현한 throttling 패키지 소개

5.1 패키지 개요

  • 목적: 특정 도메인/경로(API)별로 Rate Limit를 적용하고, IP나 Cloud Account 단위로도 트래픽을 제한
  • 구성 요소
    • Throttling 구조체: Rate Limit의 전반적인 설정
    • Limiter 구조체: golang.org/x/time/rate.Limiter를 내부적으로 사용
    • Url 구조체: 도메인과 경로를 관리, 경로 변수(ContainsPathVar) 지원
    • safemap.SafeMap: 동시성 안전한 맵 구조를 사용하여 여러 고루틴에서 안전하게 접근 가능

5.2 주요 설정(옵션) 메서드

  • WithLimitBaseIp(), WithLimitBaseCloudAccount(), WithLimitBaseService()
    • Rate Limit의 기준(Identifier)을 IP, Cloud Account, 혹은 Service로 설정
  • WithLimiter(...)
    • 사용할 Limiter 목록을 등록
    • 내부적으로 rate.NewLimiter(rate.Every(time.Second), 1) 형태로 Token Bucket 생성
  • WithUrl(...), WithUrlIgnore(...)
    • Rate Limit을 적용할 URL, 제외할 URL을 등록

5.3 사용 예시

func main() {
	throttling.Register(
		throttling.WithLimitBaseIp(),
		throttling.WithUrl(
			&throttling.Url{
				Domain: "api.ucloudbiz.olleh.com",
				Path:   "/d1/identity/auth/tokens",
			},
			// ... 필요한 경로 추가 ...
		),
		throttling.WithUrlIgnore(
			&throttling.Url{
				Domain: "192.168.219.158:14268",
				Path:   "/api/traces",
			},
		),
		throttling.WithLimiter(
			&throttling.Limiter{
				Identifier:  throttling.LimitBaseIP,
				RateLimiter: rate.NewLimiter(rate.Every(time.Second), 1),
			},
		),
	)
	throttling.Activate()

	// 이 이후부터는 http.DefaultTransport가 교체되어,
	// 등록된 URL로 가는 요청은 Rate Limit이 적용됨
}

5.4 실제 동작 시 문제점 및 보완

  • 1초에 1건씩 호출 설정했음에도, 실제로는 간헐적으로 1초 미만 간격으로 요청이 처리되어 429 오류가 발생할 수 있다.
    • 원인: Wait()가 정확히 1초를 보장하지 않고, OS 스케줄링이나 시간 측정 오차 등으로 인해 1초보다 조금 빠른 시점에 다음 요청을 허용할 수 있음
    • 해결책:
      1. 여유 시간을 더 두는 방식(1초 + α)
      2. 별도의 타이머 혹은 Sleep을 통해 실제 1초가 지났음을 보장
      3. 커스텀 로직으로 요청 시간 간격을 정확히 계산해서 보정

이 문제로 인해 다른 방식을 모색하거나, Rate Limit 기준 시간에 여유 버퍼를 주는 방법 등을 고민할 수 있다.


6. 결론

  • Rate Limiting은 트래픽 제어와 시스템 보호를 위해 필수적인 기능이다.
  • Go에서는 표준 라이브러리와 오픈소스 라이브러리를 통해 다양한 알고리즘을 간편히 구현할 수 있다.
  • 직접 작성한 throttling 패키지처럼, 세부 요구사항(IP·CloudAccount·Service 단위 제어 등)에 맞춰 커스텀 로직을 추가하는 것도 가능하다.
  • 다만, 실제 환경에서는 정확한 시간 측정 오차 OS 스케줄링 등의 요인으로 인해 예상치 못한 이슈(429 에러 등)가 발생할 수 있으므로, 여유 버퍼나 추가 로직을 고려해야 한다.

참고 자료

 

728x90
반응형