ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

Go

Golang - Rate Limit

mokhs 2023. 9. 16. 18:01

์ด ๊ธ€์—์„œ๋Š”

์ด ๊ธ€์—์„œ๋Š” golang์„ ํ™œ์šฉํ•œ rate limit์— ๋Œ€ํ•ด์„œ ๋‹ค๋ฃน๋‹ˆ๋‹ค.
์ตœ๊ทผ์— ํšŒ์‚ฌ ์—…๋ฌด์—์„œ tcp 100์œผ๋กœ ๊ณ ์ •ํ•˜๊ณ  ์œ ์ € ์ •๋ณด๋ฅผ ๋“ฑ๋กํ•˜๋Š” batch job์„ ๊ฐœ๋ฐœํ–ˆ์—ˆ๋Š”๋ฐ,
golang์—์„œ ์ œ๊ณตํ•˜๋Š” rate limit ํŒจํ‚ค์ง€๋ฅผ ํ™œ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๊ด€๋ จํ•ด์„œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด๋ณด๋ ค๊ณ  ํ•ด์š”!
์ด ๊ธ€์„ ์ฝ๋Š” ์‚ฌ๋žŒ๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ง€์‹์„ ์–ป์–ด๊ฐ€๊ธธ ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค

  • ์ฒ˜๋ฆฌ์œจ ์ œํ•œ(rate limit)์ด๋ž€?
  • golang rate package

 

์ฃผ์˜*

์ œ๊ฐ€ ๊ฒฝํ—˜ํ•œ ์ผ€์ด์Šค๋Š” ๋‹จ์ผ batch job์„ kubernetes cronjob์œผ๋กœ ๋™์ž‘์‹œํ‚จ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค.
๋ถ„์‚ฐ ์‹œ์Šคํ…œ์—์„œ์˜ rate limiter๋Š” ๋˜ ๋‹ค๋ฅธ ๊ณ ๋ฏผ๊ฑฐ๋ฆฌ๋“ค์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.
๊ทธ ๊ณ ๋ฏผ๊ฑฐ๋ฆฌ์— ๋Œ€ํ•œ ์ด์•ผ๊ธฐ๋Š” ๊ธ€ ํ›„๋ฐ˜๋ถ€์— ๋‹ค๋ฃน๋‹ˆ๋‹ค.

 

์ฒ˜๋ฆฌ์œจ ์ œํ•œ(rate limit)์ด๋ž€?

rate limit์€ ๋ง ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ๋Ÿ‰ ์ œ์–ด๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋„คํŠธ์›Œํฌ ์‹œ์Šคํ…œ์—์„œ ์ฒ˜๋ฆฌ์œจ(rate)์„ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•ด ํ™œ์šฉํ•ด์š”.
์ด๋ ‡๊ฒŒ ๋งํ•˜๋ฉด ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์œผ๋‹ˆ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐ„๋‹จํ•œ ์‚ฌ๋ก€๋ฅผ ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  • Dos(Denial of Service) ๊ณต๊ฒฉ์— ์˜ํ•œ ์ž์› ๊ณ ๊ฐˆ(resource starvation) ๋ฐฉ์ง€
  • ๋น„์šฉ ์ ˆ๊ฐ
    • third-part API ์‚ฌ์šฉ๋Ÿ‰์— ๋”ฐ๋ผ ๋น„์šฉ์„ ์ง€๋ถˆํ•ด์•ผํ•˜๋Š”๊ฒฝ์šฐ ์ ˆ์•ฝ ๊ฐ€๋Šฅ
  • ์„œ๋ฒ„ ๊ณผ๋ถ€ํ•˜ ๋ฐฉ์ง€
    • ๋ด‡์—์„œ ์˜ค๋Š” ํŠธ๋ž˜ํ”ฝ์ด๋‚˜ ์‚ฌ์šฉ์ž์˜ ์ž˜๋ชป๋œ ์ด์šฉ ํŒจํ„ธ์œผ๋กœ ์œ ๋ฐœ๋œ ํŠธ๋ž˜ํ”ฝ์„ ์ฐจ๋‹จ
  • ์™ธ๋ถ€ ์„œ๋น„์Šค๋กœ๋ถ€ํ„ฐ์˜ ํŠธ๋ž˜ํ”ฝ ์ œ์–ด ์š”์ฒญ
    • ํ˜‘๋ ฅ์‚ฌ) ์ €ํฌ ์„œ๋น„์Šค์— tps 100 ์ •๋„๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด์ฃผ์„ธ์š”.
      -> ์‹ค์ œ๋กœ ์ œ๊ฐ€ ๊ฒช์—ˆ๋˜ ์ƒํ™ฉ์ด ์ด ์ผ€์ด์Šค!

์ด ์™ธ์—๋„ ๋‹ค์–‘ํ•œ ์‚ฌ๋ก€๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฒ˜๋ฆฌ์œจ ์ œํ•œ ์•Œ๊ณ ๋ฆฌ์ฆ˜?

rate limit์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐ์—๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์ด ์žˆ๊ณ  ๋˜ํ•œ ์•„๋ž˜์™€ ๊ฐ™์ด ๋‹ค์–‘ํ•œ ์ฒ˜๋ฆฌ์œจ ์ œํ•œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

  • ํ† ํฐ ๋ฒ„ํ‚ท ์•Œ๊ณ ๋ฆฌ์ฆ˜(token bucket)
  • ๋ˆ„์ถœ ๋ฒ„ํ‚ท ์•Œ๊ณ ๋ฆฌ์ฆ˜(leaky bucket)
  • ๊ณ ์ • ์œˆ๋„์šฐ ์นด์šดํ„ฐ(fixed window counter)
  • ์ด๋™ ์œˆ๋„์šฐ ๋กœ๊ทธ(sliding window log)
  • ์ด๋™ ์œˆ๋„์šฐ ์นด์šดํ„ฐ(sliding window counter)

์ด ๊ธ€์—์„œ๋Š” ์œ„ ๋งŽ์€ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ค‘ ํ† ํฐ ๋ฒ„ํ‚ท ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ํ™œ์šฉํ•œ ์ผ€์ด์Šค๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค!

ํ† ํฐ ๋ฒ„ํ‚ท ์•Œ๊ณ ๋ฆฌ์ฆ˜

ํ† ํฐ ๋ฒ„ํ‚ท ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ ์ด๋ฆ„์—์„œ๋„ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด ํ† ํฐ(token), ๋ฒ„ํ‚ท(bucket) ์ด ๋‘๊ฐ€์ง€ ์š”์†Œ๋ฅผ ํ™œ์šฉํ•ด ๊ตฌํ˜„ํ•˜๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜์ž…๋‹ˆ๋‹ค.

ํ† ํฐ ๋ฒ„ํ‚ท ์•Œ๊ณ ๋ฆฌ์ฆ˜์˜ ํฐ ๋ฒ”์œ„์—์„œ์˜ ํ”Œ๋กœ์šฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค

  1. ์ผ์ • ์‹œ๊ฐ„(r)๋งˆ๋‹ค ํ† ํฐ์ด ์ƒ์„ฑ๋˜๋Š” ๋ฒ„ํ‚ท(๋ฐ”๊ตฌ๋‹ˆ)์ด ์กด์žฌํ•˜๊ณ 
  2. ์š”์ฒญ์ด ์˜ฌ๋•Œ ๋ฒ„ํ‚ท์—์„œ ํ† ํฐ์„ ํ•˜๋‚˜ ๊บผ๋‚ด์„œ ์†Œ๋ชจํ•˜๋ฉฐ (์ด ๋ฒ„ํ‚ท์€ ์ผ์ • ์‚ฌ์ด์ฆˆ(b)๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค)
  3. ๋ฒ„ํ‚ท์— ํ† ํฐ์ด 0๊ฐœ๊ฐ€ ๋˜๋ฉด ๊ฐ€์ ธ๊ฐˆ ํ† ํฐ์ด ์—†์œผ๋ฏ€๋กœ ์š”์ฒญ์„ drop (์ดํ›„ ํ† ํฐ์ด ์ฑ„์›Œ์งˆ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์•ผํ•จ)

https://www.researchgate.net/figure/Diagram-representing-token-bucket-algorithm_fig1_351452059

 

ํ† ํฐ ๋ฒ„ํ‚ท ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ํ™œ์šฉํ•ด rate limit์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ์ง€์‹์„ ๊ฐ„๋žตํ•˜๊ฒŒ ์•Œ์•„๋ดค์œผ๋‹ˆ ์ด์ œ ์‹ค์ œ ์‚ฌ๋ก€๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค

Golang rate package๋ฅผ ํ™œ์šฉํ•œ rate limit

์•„๋ž˜ ์ฝ”๋“œ๋Š” golang golang.org/x/time/rate ํŒจํ‚ค์ง€๋ฅผ ํ™œ์šฉํ•ด ์ดˆ๋‹น 100 ๊ฑด์˜ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋„๋ก rate limit์„ ๊ตฌํ˜„ํ•œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.
์ง€๊ธˆ์€ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์ง€๋งŒ ์•„๋ž˜ ์ฝ”๋“œ์—์„œ ํ™œ์šฉ๋œ ์ง€์‹๋“ค์„ ์ด ๊ธ€์—์„œ ์•Œ์•„๋ณด์ฃ !

package main

import (
	"context"
	"fmt"
	"golang.org/x/time/rate"
	"time"
)

func main() {
	ctx := context.Background()

	// rate limiter ์ƒ์„ฑ
	// r(rate tokens per seconds): 100
	// b(bucket size): 100
	rateLimiter := rate.NewLimiter(100, 100)

	// ๋ฐ˜๋ณต ๋™์ž‘ํ•  ๋Œ€์ƒ์œผ๋กœ ๋‹จ์ˆœํ•˜๊ฒŒ size 1000์˜ ๋ฐฐ์—ด ์ƒ์„ฑ
	values := make([]int64, 1000, 1000)
	for i := range values {
		values[i] = int64(1 + i)
	}

	now := time.Now()

	for _, v := range values {
		if err := rateLimiter.Wait(ctx); err != nil {
			fmt.Println(err)
		}

		fmt.Println(v)
	}

	since := time.Since(now)
    	// 9.007893291s
	fmt.Println(since)
}

 

golang.org/x/time/rate ํŒจํ‚ค์ง€

golang.org/x/time/rate ํŒจํ‚ค์ง€์—์„œ๋„ rate limit์„ ํ† ํฐ ๋ฒ„ํ‚ท ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

https://pkg.go.dev/golang.org/x/time/rate

์‹ค์ œ๋กœ ์•ˆ์ชฝ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋Ÿฌ ๋“ค์–ด๊ฐ€๋ฉด wait()ํ•จ์ˆ˜ ์•ˆ์— ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

๋ถ„์‚ฐ ์‹œ์Šคํ…œ์—์„œ์˜ ์ฒ˜๋ฆฌ์œจ ์ œํ•œ ์žฅ์น˜?

๋ถ„์‚ฐ ์‹œ์Šคํ…œ์—์„œ ์ฒ˜๋ฆฌ์œจ ์ œํ•œ ์žฅ์น˜๋Š” ๋˜ ๋‹ค๋ฅธ ๊ณ ๋ฏผ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋ถ„์‚ฐ ์‹œ์Šคํ…œํ•˜๋ฉด ๋– ์˜ค๋ฅด๋Š” ๋Œ€ํ‘œ์ ์ธ ๋™์‹œ์„ฑ ๋ฌธ์ œ๋“ค์„ ํ•ด๊ฒฐํ•ด์•ผํ•˜์ฃ .
๋Œ€ํ‘œ์ ์ธ ๊ณ ๋ฏผ๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ๊ฒฝ์Ÿ ์กฐ๊ฑด(race condition)
    • DB์—์„œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ง€๋งŒ ์—ฌ๋Ÿฌ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋Š” ์ƒํ™ฉ์—์„œ ๊ฒฝ์Ÿ ์กฐ๊ฑด์€ ํ•ญ์ƒ ๊ณ ๋ฏผ์ž…๋‹ˆ๋‹ค.
      ๊ฒฝ์Ÿ ์กฐ๊ฑด์€ lock, Lua script ๊ทธ๋ฆฌ๊ณ  redis์˜ sorted set์„ ํ™œ์šฉํ•ด์„œ ํ•ด๊ฒฐํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • redis๋Š” single thread๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ณด๋‹ค ์‰ฝ๊ฒŒ ํ™œ์šฉํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • lock์„ ๊ฒฐ ๊ฒฝ์šฐ ๊ด€๋ฆฌ ํฌ์ธํŠธ๊ฐ€ ๋Š˜์–ด๋‚˜๋Š” ๋ถ€์ž‘์šฉ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋ฌผ๋ก  ๋น„์šฉ์„ ์ ์ ˆํ•˜๊ฒŒ ๊ณ ๋ฏผํ•ด์„œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ๋™๊ธฐํ™” ์ด์Šˆ
    • ํŠธ๋ž˜ํ”ฝ์ด ๋Š˜์–ด๋‚˜๋ฉด ํ•œ ๋Œ€์˜ ์ฒ˜๋ฆฌ์œจ ์ œํ•œ ์žฅ์น˜ ์„œ๋ฒ„๋กœ๋Š” ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      ์ฒ˜๋ฆฌ์œจ ์ œํ•œ ์žฅ์น˜๋„ ์Šค์ผ€์ผ๋งํ•ด์•ผํ•˜๋Š” ์‹œ์ ์ด ์˜ค๋ฉด ๊ฐ ์ฒ˜๋ฆฌ์œจ ์ œํ•œ ์žฅ์น˜ ๊ฐ„์˜ ๋™๊ธฐํ™” ๋ฌธ์ œ๋„ ๊ณ ๋ฏผํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.
    • ์ด ๊ฒฝ์šฐ sticky sessionํ˜น์€ redis์™€ ๊ฐ™์€ ๋ณ„๋„์˜ ์˜์†์„ฑ layer๋ฅผ ํ™œ์šฉํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. redis cluster์— ์œ„์ž„ํ•  ์ˆ˜๋„ ์žˆ์ฃ 

 

๊ฒฐ๊ตญ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„

๊ฒฐ๊ตญ์€ ๋ชจ๋“  ๊ธฐ์ˆ  ์„ ํƒ์€ ๊ฐ ์ƒํ™ฉ์— ๋งž๊ฒŒ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๋ฅผ ์ž˜ ๊ณ ๋ คํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

์ €์˜ ๊ฒฝ์šฐ์—๋Š” ํ•˜๋‚˜์˜ ๋ฐฐ์น˜์žก ํŒŒ์ดํ”„๋ผ์ธ์—์„œ ๋กœ์ง์„ ๋™์ž‘์‹œ์ผฐ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ„๋‹จํ•˜๊ฒŒ application level์—์„œ rate limit์„ ํ™œ์šฉํ•ด๋ณผ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
์—ฌ๋Ÿฌ๋ถ„๋“ค์˜ ์‚ฌ๋ก€์—์„œ๋Š” ์–ด๋–ค ์ธํ”„๋ผ๋ฅผ ํ™œ์šฉํ•˜๋‚˜์š”? ์ •๋ง rate limit์ด ํ•„์š”ํ•œ ์š”๊ตฌ์‚ฌํ•ญ์ธ๊ฐ€์š”? ๋‹ค๋ฅด๊ฒŒ ํ‘ธ๋Š” ๋ฐฉ๋ฒ•์€ ์—†์„๊นŒ์š”? 

๋ฐ”๋ผ๋ณด๋Š” ์‹œ๊ฐ์„ ์ „ํ™˜ํ•˜์—ฌ ์–ด๋ ค์šด ๋ฌธ์ œ๋ฅผ ๊ฐ„๋‹จํ•œ ๋ฌธ์ œ๋กœ ์น˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ์ž‘์€ ๋ฌธ์ œ๋กœ ๋‚˜๋ˆ ์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ’€์–ด๋‚ด๋Š” ๊ฒƒ๋„ ์—”์ง€๋‹ˆ์–ด์˜ ์ค‘์š”ํ•œ ์—ญ๋Ÿ‰์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ถ”๊ฐ€๋กœ https://github.com/uber-go/ratelimit ๋ผ๋Š” ์šฐ๋ฒ„์—์„œ rate limit์„ ์œ„ํ•ด ๋งŒ๋“  ํŒจํ‚ค์ง€๋„ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.
์ด ํŒจํ‚ค์ง€๋Š” ๊ฐ„๋‹จํ•œ API์™€ ๋น„๊ต์  ์ ์€ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
์˜ค๋Š˜ ์†Œ๊ฐœํ•œ rate ํŒจํ‚ค์ง€๋Š” ์ข€ ๋” ๋””ํ…Œ์ผํ•˜๊ฒŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๊ณ  ๋ฐ˜๋Œ€๋กœ ๊ฐ„๋‹จํ•œ ์š”๊ตฌ์‚ฌํ•ญ์ด๋ผ๋ฉด ํ•ด๋‹น ํŒจํ‚ค์ง€๋ฅผ ํ™œ์šฉํ•ด๋ด๋„ ์ข‹์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค!

 

Action item

์šฐ๋ฆฌ ๋ชจ๋‘ ํ•ญ์ƒ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๋ฅผ ์ž˜ ๊ณ ๋ คํ•ด์„œ ํ˜„์‹ค์ ์œผ๋กœ ์„ ํƒํ•˜๋Š” ์—”์ง€๋‹ˆ์–ด๋ง์„ ํ•˜๊ธฐ ์œ„ํ•ด ๊นจ์–ด์žˆ์œผ๋ ค ๋…ธ๋ ฅํ•˜๊ธฐ

๋ฐ˜์‘ํ˜•