๊ฐ๋ฐ์ ํ๋ค ๋ณด๋ฉด ์ฌ๋ฌ Redis ๋ช ๋ น์ด๋ฅผ ํ๋์ ํ๋ฆ์ผ๋ก ๋ฌถ์ด ์คํํด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค.
๋์ ๊ฒฝ์ฐ, Rate Limit ๊ธฐ๋ฅ์ Redis๋ก ๊ตฌํํ๋ฉด์ ์ด๋ฐ ์ํฉ์ ๋ง์ฃผํ๋ค.
1. ํ์ฌ window ๋งํผ sorted set ์๋ฅด๊ธฐ
2. sorted set์ ํฌ๊ธฐ ๊ฐ์ ธ์ค๊ธฐ
3. ํด๋น ํฌ๊ธฐ๊ฐ limit๋ณด๋ค ์๋ค๋ฉด, ํ์ฌ์ timestamp๋ฅผ sorted set์ ์ถ๊ฐํ๊ธฐ
์ด ๋ชจ๋ ๊ณผ์ ์ ๋ฐ๋์ ์์์ (atomic) ์ผ๋ก ๋ณด์ฅ๋ผ์ผ ํ๋ค.
๋ง์ฝ 2๋ฒ → 3๋ฒ ์คํ ์ฌ์ด์ ๋ค๋ฅธ ์์ฒญ์ด ๋ค์ด์ค๋ฉด, ์๋ชป๋ limit ์ฒดํฌ ๊ฒฐ๊ณผ๊ฐ ๋์ฌ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด, Redis์์๋ ์ฌ๋ฌ ๋ช ๋ น์ด๋ฅผ ํ๋์ ๋จ์๋ก ์คํํ ์ ์๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ค. ๊ทธ์ค ํ๋๊ฐ Lua Script์ด๋ค.
Lua Script๋?
Redis ๊ณต์ ๋ฌธ์์ ๋ฐ๋ฅด๋ฉด, Lua Script๋ ๋ค์๊ณผ ๊ฐ์ ํน์ง์ ๊ฐ์ง๋ค:
- ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ๋ ์๋ฒ์์ ๋ก์ง์ ์คํํ์ฌ, ๋คํธ์ํฌ ์ง์ฐ์ ์ค์ด๊ณ ๋ฆฌ์์ค๋ฅผ ์ ์ฝํ ์ ์๋ค.
- ์คํฌ๋ฆฝํธ ์ ์ฒด๊ฐ ๋ธ๋กํน ๋ฐฉ์์ผ๋ก ์คํ๋์ด, ์ค๊ฐ ๊ฐ์ ์์ด ์์ ํ ์์์ฑ์ ๋ณด์ฅํ๋ค.
https://redis.io/docs/latest/develop/interact/programmability/eval-intro/
Scripting with Lua
Executing Lua in Redis
redis.io
ํ๋ก๊ทธ๋๋ฐ ๋ ๋ฒจ Lock vs Lua Script
๊ทธ๋ ๋ค๋ฉด, ํ๋ก๊ทธ๋๋ฐ ๋ ๋ฒจ์์ Lock์ ์ก๋ ๊ฒ๊ณผ Lua Script๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์ด๋ค ์ฐจ์ด๊ฐ ์์๊น?
ํ๋ก๊ทธ๋๋ฐ ๋ ๋ฒจ์์์ Lock
- ํด๋ผ์ด์ธํธ๊ฐ ๋จผ์ SET resource_key unique_id NX PX timeout ์ผ๋ก ๋ฝ์ ํ๋
- ๋ฝ์ด ์กํ๋ฉด ์ผ๋ จ์ Redis ๋ช ๋ น์ ์์ฐจ์ ์ผ๋ก ์คํ
- ๋๋๋ฉด if redis.call("GET", key) == unique_id then DEL key end ํํ๋ก ํด์
์ฅ์ | ๋จ์ |
- ๋ถ์ฐ ๋ฝ์ ์ ์ฉํ์ฌ ์ฌ๋ฌ Redis ์ธ์คํด์ค ๊ฐ์๋ ๋ฝ ์กฐ์จ ๊ฐ๋ฅ - ๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง ํ๋ฆ ์ ์ด๊ฐ ์์ ๋ก์: ์ฆ, Redis ๋ช ๋ น์ด ๋ฟ ์๋๋ผ ๋ค๋ฅธ ๋น์ฆ๋์ค ๋ก์ง์ด ํฌํจ๋์ด๋ OK |
- ์ฝ๋ ๊ตฌํ์ด ๋ณต์กํฉ: ๋ฝ ํ๋/ํด์ ์คํจ ๋๋น, ํ์์์ ๊ด๋ฆฌ, ์ฌ์๋ ๋ฑ - ๋ฝ์ ์ก์ ๋์์๋ ๊ฐ ๋ช ๋ น์ด๋ ๊ฐ๋ณ ๋คํธ์ํฌ ์๋ณต |
Lua Script
- ์ฌ๋ฌ ๋ช ๋ น์ ํ๋์ Lua ์คํฌ๋ฆฝํธ๋ก ๋ฌถ์ด EVAL ํธ์ถ
- Redis ์๋ฒ๊ฐ ์คํฌ๋ฆฝํธ๋ฅผ ํ ๋ฉ์ด๋ฆฌ๋ก ๋จ์ผ ์ค๋ ๋์์ ์คํ
์ฅ์ | ๋จ์ |
- ์์ ํ ์์์ฑ ๋ณด์ฅ: ์ค๊ฐ์ ๋ค๋ฅธ ํด๋ผ์ด์ธํธ ๋ช
๋ น์ด ๊ฐ์
๋ถ๊ฐ - ๋คํธ์ํฌ ์๋ณต ์ ๊ฑฐ: ์คํฌ๋ฆฝํธ ๋ด๋ถ ๋ช ๋ น์ ํ๋์ ๋คํธ์ํฌ Call๋ก ์ฒ๋ฆฌ ๊ฐ๋ฅ |
- ๋จ์ผ ์ธ์คํด์ค ๋ด์์๋ง ์์์ฑ ๋ณด์ฅ: ํด๋ฌ์คํฐ ๋ชจ๋๋ผ๋ฉด ํค๊ฐ ๊ฐ์ ์ฌ๋กฏ์ ์์ด์ผ ํจ |
์ ๋ฆฌ
- ๋จ์ํ Redis ๋ช ๋ น์ด ๊ฐ์ ์์์ฑ๋ง ํ์ํ๋ค๋ฉด → Lua Script ์ถ์ฒ
- ๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง(์ธ๋ถ API ํธ์ถ, ๋ค์ํ ๋ฐ์ดํฐ ์ฐ์ฐ ๋ฑ)์ด ํฌํจ๋๋ค๋ฉด → Lock ๊ธฐ๋ฐ ์ฒ๋ฆฌ ์ถ์ฒ
Spring Boot์์ Lua Script ์ฌ์ฉํ๊ธฐ
์ด์ ์ ์๋๋ฆฌ์ค๋ฅผ Spring Boot ํ๊ฒฝ์์ ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ์ดํด๋ณด์. (์ ์ฒด ์ฝ๋๋ ์๋์์!)
Lua Script ์์ฑ
resource ๋๋ ํ ๋ฆฌ์ ํ์ํ ๋ ๋์ค ๋ช ๋ น๋ค์ ์์ฑํ .lua ํ์ผ์ ์์ฑ
-- rate_limit_script.lua
-- KEYS[1]: rate limit key
-- ARGV[1]: ํ์ฌ ํ์์คํฌํ (๋ฐ๋ฆฌ์ด)
-- ARGV[2]: ์๋์ฐ ํฌ๊ธฐ (๋ฐ๋ฆฌ์ด)
-- ARGV[3]: ํ์ฉ ํ์(limit)
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
local window_start = now - window
-- 1) ์๋์ฐ ๋ฐ ์ค๋๋ ๊ธฐ๋ก ์ญ์
redis.call("ZREMRANGEBYSCORE", key, 0, window_start)
-- 2) ํ์ฌ ์๋์ฐ ๋ด ์์ฒญ ์ ํ์ธ
local cnt = redis.call("ZCARD", key)
if cnt >= limit then
return 0
end
-- 3) ์ ์์ฒญ ๊ธฐ๋ก
redis.call("ZADD", key, now, now)
return 1
- KEYS์ ARGV๋ฅผ ํตํด ์ธ๋ถ์์ ๊ฐ์ ์ ๋ ฅ๋ฐ๋๋ค.
RedisScriptConfig
์์ ์์ฑํ lua ์คํฌ๋ฆฝํธ๋ฅผ ๊ฐ์ ธ์ RedisScript Bean ๋ฑ๋ก
@Configuration
class RedisScriptConfig {
@Bean
fun rateLimiterScript(): RedisScript<Long> = DefaultRedisScript(
// 1) Script์ ClassPath๋ฅผ ์ง์ ํ์ฌ ๊ฐ์ ธ์จ ํ ๋ฑ๋ก
ResourceScriptSource(ClassPathResource("rate_limit_script.lua")).scriptAsString,
// 2) Return Type
Long::class.java
)
}
- LuaScript์์ ๋ฆฌํดํ๋ ํ์ ์ ๋ง๊ฒ, ํ์ ์ ๋ช ์ํ๋ค.
Service ์ฌ์ฉ
Bean์ผ๋ก ๋ฑ๋กํ ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉ
@Component
class RedisRateLimiter(
private val redisTemplate: StringRedisTemplate,
private val rateLimiterScript: RedisScript<Long>
) {
companion object {
private const val ALLOW = 1L
}
fun isAllowed(key: String, timestamp: Long, windowMs: Long, limit: Int): Boolean {
// 1) Script ์คํ
val result = redisTemplate.execute(
rateLimiterScript,
listOf(key),
timestamp.toString(),
windowMs.toString(),
limit.toString()
)
return result == ALLOW
}
}
- `redisTemplate.execute` ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํ์ฌ script ์คํํ๋ค.
- ๋๋ฒ์งธ ํ๋ผ๋ฏธํฐ `keys` ์๋ ์คํฌ๋ฆฝํธ์์ ์ฌ์ฉ๋ key ๋ค์ ๋ฃ๋๋ค.
- ์ฌ์ฉ: `local key = KEYS[1]`
- ์ดํ ํ๋ผ๋ฏธํฐ๋ ์คํฌ๋ฆฝํธ์์ ์ฌ์ฉ๋ argument๋ค์ ๋์ดํ๋ค.
- ์ฌ์ฉ: `local limit = tonumber(ARGV[3])`
์ด๋ ๊ฒ ํ๋ฉด, Spring Boot ํ๋ก์ ํธ์์๋ Lua Script๋ฅผ ํตํ Redis์ ์์์ ์์ ์ฒ๋ฆฌ๋ฅผ ์ฝ๊ฒ ๊ตฌํํ ์ ์๋ค.
- ๋จ์ผ ๋ช ๋ น์ด ์๋ ๋ณตํฉ ์์ ์ด ํ์ํ ๊ฒฝ์ฐ
- ๋ ์ด์ค ์ปจ๋์ ์ด๋ ๋ฐ์ดํฐ ๊ผฌ์ ์์ด ์์ ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ณ ์ถ์ ๋
Lua Script๋ Redis์์ ๋งค์ฐ ๊ฐ๋ ฅํ ๋ฌด๊ธฐ๊ฐ ๋ ์ ์๋ค.
๐์ ์ฒด ์ฝ๋๋ ์๋์์๐
https://github.com/jeongum/ratelimit/tree/luascript
GitHub - jeongum/ratelimit
Contribute to jeongum/ratelimit development by creating an account on GitHub.
github.com