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  }