github.com/rudderlabs/rudder-go-kit@v0.30.0/throttling/benchmark_test.go (about) 1 package throttling 2 3 import ( 4 "context" 5 "strconv" 6 "testing" 7 "time" 8 9 "github.com/go-redis/redis/v8" 10 "github.com/ory/dockertest/v3" 11 "github.com/stretchr/testify/require" 12 "github.com/throttled/throttled/v2" 13 "github.com/throttled/throttled/v2/store/memstore" 14 15 "github.com/rudderlabs/rudder-go-kit/cachettl" 16 "github.com/rudderlabs/rudder-go-kit/testhelper/rand" 17 ) 18 19 /* 20 goos: linux, goarch: amd64 21 cpu: 12th Gen Intel(R) Core(TM) i9-12900K 22 BenchmarkLimiters/gcra_redis-24 58465 20173 ns/op 23 BenchmarkLimiters/sorted_sets_redis-24 60723 19385 ns/op 24 BenchmarkLimiters/gcra-24 9005494 129.9 ns/op 25 */ 26 func BenchmarkLimiters(b *testing.B) { 27 pool, err := dockertest.NewPool("") 28 require.NoError(b, err) 29 30 var ( 31 rate int64 = 10 32 window int64 = 1 33 ctx = context.Background() 34 rc = bootstrapRedis(ctx, b, pool) 35 limiters = map[string]*Limiter{ 36 "gcra": newLimiter(b, WithInMemoryGCRA(0)), 37 "gcra redis": newLimiter(b, WithRedisGCRA(rc, 0)), 38 "sorted sets redis": newLimiter(b, WithRedisSortedSet(rc)), 39 } 40 ) 41 42 for name, l := range limiters { 43 l := l 44 b.Run(name, func(b *testing.B) { 45 key := rand.UniqueString(10) 46 47 b.ResetTimer() 48 for i := 0; i < b.N; i++ { 49 _, _, _ = l.Allow(ctx, 1, rate, window, key) 50 } 51 }) 52 } 53 } 54 55 /* 56 goos: linux, goarch: amd64 57 cpu: 12th Gen Intel(R) Core(TM) i9-12900K 58 BenchmarkRedisSortedSetRemover/sortedSetRedisReturn-24 74870 14740 ns/op 59 */ 60 func BenchmarkRedisSortedSetRemover(b *testing.B) { 61 ctx, cancel := context.WithCancel(context.Background()) 62 defer cancel() 63 64 pool, err := dockertest.NewPool("") 65 require.NoError(b, err) 66 67 prepare := func(b *testing.B) (*redis.Client, string, []*redis.Z) { 68 rc := bootstrapRedis(ctx, b, pool) 69 70 key := rand.UniqueString(10) 71 members := make([]*redis.Z, b.N*3) 72 for i := range members { 73 members[i] = &redis.Z{ 74 Score: float64(i), 75 Member: strconv.Itoa(i), 76 } 77 } 78 _, err := rc.ZAdd(ctx, key, members...).Result() 79 require.NoError(b, err) 80 81 count, err := rc.ZCard(ctx, key).Result() 82 require.NoError(b, err) 83 require.EqualValues(b, b.N*3, count) 84 85 return rc, key, members 86 } 87 88 b.Run("sortedSetRedisReturn", func(b *testing.B) { 89 rc, key, members := prepare(b) 90 rem := func(members ...string) *sortedSetRedisReturn { 91 return &sortedSetRedisReturn{ 92 key: key, 93 remover: rc, 94 members: members, 95 } 96 } 97 98 b.ResetTimer() 99 for i, j := 0, 0; i < b.N; i, j = i+1, j+3 { 100 err = rem( // check error only once at the end to avoid altering benchmark results 101 members[j].Member.(string), 102 members[j+1].Member.(string), 103 members[j+2].Member.(string), 104 ).Return(ctx) 105 } 106 107 require.NoError(b, err) 108 109 b.StopTimer() 110 count, err := rc.ZCard(ctx, key).Result() 111 require.NoError(b, err) 112 require.EqualValues(b, 0, count) 113 }) 114 } 115 116 func BenchmarkInMemoryGCRA(b *testing.B) { 117 var ( 118 rate = 10 119 period = 100 * time.Millisecond 120 burst = rate 121 ) 122 b.Run("one unlimited store per throttler", func(b *testing.B) { 123 b.Run("single key", func(b *testing.B) { 124 var ( 125 key = "key" 126 ctx = context.Background() 127 cache = cachettl.New[string, *throttled.GCRARateLimiterCtx]() 128 ) 129 130 b.ResetTimer() 131 for i := 0; i < b.N; i++ { 132 rl := cache.Get(key) 133 if rl == nil { 134 store, _ := memstore.NewCtx(0) 135 rl, _ = throttled.NewGCRARateLimiterCtx(store, throttled.RateQuota{ 136 MaxRate: throttled.PerDuration(rate, period), 137 MaxBurst: burst, 138 }) 139 rl.SetMaxCASAttemptsLimit(defaultMaxCASAttemptsLimit) 140 cache.Put(key, rl, period) 141 } 142 _, _, _ = rl.RateLimitCtx(ctx, key, 1) 143 } 144 }) 145 b.Run("multiple keys", func(b *testing.B) { 146 var ( 147 div = 10 148 ctx = context.Background() 149 keys = make([]string, b.N) 150 cache = cachettl.New[string, *throttled.GCRARateLimiterCtx]() 151 ) 152 for i := 0; i < b.N; i++ { 153 keys[i] = rand.UniqueString(10) 154 } 155 156 b.ResetTimer() 157 for i := 0; i < b.N; i++ { 158 key := keys[i/div] // don't always use a different key 159 rl := cache.Get(key) 160 if rl == nil { 161 store, _ := memstore.NewCtx(0) 162 rl, _ = throttled.NewGCRARateLimiterCtx(store, throttled.RateQuota{ 163 MaxRate: throttled.PerDuration(rate, period), 164 MaxBurst: burst, 165 }) 166 rl.SetMaxCASAttemptsLimit(defaultMaxCASAttemptsLimit) 167 cache.Put(key, rl, period) 168 } 169 _, _, _ = rl.RateLimitCtx(ctx, key, 1) 170 } 171 }) 172 }) 173 b.Run("one unlimited store for all throttlers", func(b *testing.B) { 174 b.Run("single key", func(b *testing.B) { 175 var ( 176 key = "key" 177 ctx = context.Background() 178 store, _ = memstore.NewCtx(0) 179 cache = cachettl.New[string, *throttled.GCRARateLimiterCtx]() 180 ) 181 182 b.ResetTimer() 183 for i := 0; i < b.N; i++ { 184 rl := cache.Get(key) 185 if rl == nil { 186 rl, _ = throttled.NewGCRARateLimiterCtx(store, throttled.RateQuota{ 187 MaxRate: throttled.PerDuration(rate, period), 188 MaxBurst: burst, 189 }) 190 rl.SetMaxCASAttemptsLimit(defaultMaxCASAttemptsLimit) 191 cache.Put(key, rl, period) 192 } 193 _, _, _ = rl.RateLimitCtx(ctx, key, 1) 194 } 195 }) 196 b.Run("multiple keys", func(b *testing.B) { 197 var ( 198 div = 10 199 ctx = context.Background() 200 store, _ = memstore.NewCtx(0) 201 keys = make([]string, b.N) 202 cache = cachettl.New[string, *throttled.GCRARateLimiterCtx]() 203 ) 204 for i := 0; i < b.N; i++ { 205 keys[i] = rand.UniqueString(10) 206 } 207 208 b.ResetTimer() 209 for i := 0; i < b.N; i++ { 210 key := keys[i/div] // don't always use a different key 211 rl := cache.Get(key) 212 if rl == nil { 213 rl, _ = throttled.NewGCRARateLimiterCtx(store, throttled.RateQuota{ 214 MaxRate: throttled.PerDuration(rate, period), 215 MaxBurst: burst, 216 }) 217 rl.SetMaxCASAttemptsLimit(defaultMaxCASAttemptsLimit) 218 cache.Put(key, rl, period) 219 } 220 _, _, _ = rl.RateLimitCtx(ctx, key, 1) 221 } 222 }) 223 }) 224 b.Run("one single key store per throttler", func(b *testing.B) { 225 b.Run("single key", func(b *testing.B) { 226 var ( 227 key = "key" 228 ctx = context.Background() 229 cache = cachettl.New[string, *throttled.GCRARateLimiterCtx]() 230 ) 231 232 b.ResetTimer() 233 for i := 0; i < b.N; i++ { 234 rl := cache.Get(key) 235 if rl == nil { 236 store, _ := memstore.NewCtx(1) 237 rl, _ = throttled.NewGCRARateLimiterCtx(store, throttled.RateQuota{ 238 MaxRate: throttled.PerDuration(rate, period), 239 MaxBurst: burst, 240 }) 241 rl.SetMaxCASAttemptsLimit(defaultMaxCASAttemptsLimit) 242 cache.Put(key, rl, period) 243 } 244 _, _, _ = rl.RateLimitCtx(ctx, key, 1) 245 } 246 }) 247 b.Run("multiple keys", func(b *testing.B) { 248 var ( 249 div = 10 250 ctx = context.Background() 251 keys = make([]string, b.N) 252 cache = cachettl.New[string, *throttled.GCRARateLimiterCtx]() 253 ) 254 for i := 0; i < b.N; i++ { 255 keys[i] = rand.UniqueString(10) 256 } 257 258 b.ResetTimer() 259 for i := 0; i < b.N; i++ { 260 key := keys[i/div] 261 rl := cache.Get(key) 262 if rl == nil { 263 store, _ := memstore.NewCtx(1) 264 rl, _ = throttled.NewGCRARateLimiterCtx(store, throttled.RateQuota{ 265 MaxRate: throttled.PerDuration(rate, period), 266 MaxBurst: burst, 267 }) 268 rl.SetMaxCASAttemptsLimit(defaultMaxCASAttemptsLimit) 269 cache.Put(key, rl, period) 270 } 271 _, _, _ = rl.RateLimitCtx(ctx, key, 1) 272 } 273 }) 274 }) 275 b.Run("custom store per throttler", func(b *testing.B) { 276 b.Run("single key", func(b *testing.B) { 277 var ( 278 key = "key" 279 ctx = context.Background() 280 cache = cachettl.New[string, *throttled.GCRARateLimiterCtx]() 281 ) 282 283 b.ResetTimer() 284 for i := 0; i < b.N; i++ { 285 rl := cache.Get(key) 286 if rl == nil { 287 rl, _ = throttled.NewGCRARateLimiterCtx(newGCRAMemStore(), throttled.RateQuota{ 288 MaxRate: throttled.PerDuration(rate, period), 289 MaxBurst: burst, 290 }) 291 rl.SetMaxCASAttemptsLimit(defaultMaxCASAttemptsLimit) 292 cache.Put(key, rl, period) 293 } 294 _, _, _ = rl.RateLimitCtx(ctx, key, 1) 295 } 296 }) 297 b.Run("multiple keys", func(b *testing.B) { 298 var ( 299 div = 10 300 ctx = context.Background() 301 keys = make([]string, b.N) 302 cache = cachettl.New[string, *throttled.GCRARateLimiterCtx]() 303 ) 304 for i := 0; i < b.N; i++ { 305 keys[i] = rand.UniqueString(10) 306 } 307 308 b.ResetTimer() 309 for i := 0; i < b.N; i++ { 310 key := keys[i/div] // don't always use a different key 311 rl := cache.Get(key) 312 if rl == nil { 313 rl, _ = throttled.NewGCRARateLimiterCtx(newGCRAMemStore(), throttled.RateQuota{ 314 MaxRate: throttled.PerDuration(rate, period), 315 MaxBurst: burst, 316 }) 317 rl.SetMaxCASAttemptsLimit(defaultMaxCASAttemptsLimit) 318 cache.Put(key, rl, period) 319 } 320 _, _, _ = rl.RateLimitCtx(ctx, key, 1) 321 } 322 }) 323 }) 324 }