get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/server/jetstream_meta_benchmark_test.go (about)

     1  // Copyright 2024 The NATS Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  //go:build !skip_js_tests && !skip_js_cluster_tests && !skip_js_cluster_tests_2
    15  // +build !skip_js_tests,!skip_js_cluster_tests,!skip_js_cluster_tests_2
    16  
    17  package server
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  	"sync/atomic"
    23  	"testing"
    24  
    25  	"github.com/nats-io/nats.go"
    26  )
    27  
    28  func BenchmarkJetStreamCreate(b *testing.B) {
    29  
    30  	const (
    31  		verbose        = false
    32  		resourcePrefix = "S"
    33  		concurrency    = 12
    34  	)
    35  
    36  	// Types of resource that this benchmark creates
    37  	type ResourceType string
    38  	const (
    39  		Stream      ResourceType = "Stream"
    40  		KVBucket    ResourceType = "KVBucket"
    41  		ObjectStore ResourceType = "ObjStore"
    42  	)
    43  
    44  	resourceTypeCases := []ResourceType{
    45  		Stream,
    46  		KVBucket,
    47  		ObjectStore,
    48  	}
    49  
    50  	benchmarksCases := []struct {
    51  		clusterSize int
    52  		replicas    int
    53  		storage     nats.StorageType
    54  	}{
    55  		{1, 1, nats.MemoryStorage},
    56  		{3, 3, nats.MemoryStorage},
    57  		{3, 3, nats.FileStorage},
    58  	}
    59  
    60  	for _, bc := range benchmarksCases {
    61  		bName := fmt.Sprintf(
    62  			"N=%d,R=%d,storage=%s,C=%d",
    63  			bc.clusterSize,
    64  			bc.replicas,
    65  			bc.storage.String(),
    66  			concurrency,
    67  		)
    68  
    69  		b.Run(
    70  			bName,
    71  			func(b *testing.B) {
    72  				for _, rt := range resourceTypeCases {
    73  					//for _, bc := range benchmarksCases {
    74  					rName := fmt.Sprintf("resource=%s", rt)
    75  					b.Run(
    76  						rName,
    77  						func(b *testing.B) {
    78  
    79  							if verbose {
    80  								b.Logf(
    81  									"Creating %d %s resources in cluster with %d nodes, R=%d, %s storage",
    82  									b.N,
    83  									string(rt),
    84  									bc.clusterSize,
    85  									bc.replicas,
    86  									bc.storage,
    87  								)
    88  							}
    89  
    90  							// Setup server or cluster
    91  							_, leaderServer, shutdown, nc, _ := startJSClusterAndConnect(b, bc.clusterSize)
    92  							defer shutdown()
    93  							defer nc.Close()
    94  
    95  							// All clients connect to cluster (meta) leader for lower variability
    96  							connectURL := leaderServer.ClientURL()
    97  
    98  							// Wait for all clients and main routine to be ready
    99  							wgReady := sync.WaitGroup{}
   100  							wgReady.Add(concurrency + 1)
   101  							// Wait for all routines to complete
   102  							wgComplete := sync.WaitGroup{}
   103  							wgComplete.Add(concurrency)
   104  
   105  							// Number of operations (divided amongst clients)
   106  							opsLeft := atomic.Int64{}
   107  							opsLeft.Store(int64(b.N))
   108  							totalErrors := atomic.Int64{}
   109  
   110  							// Pre-create connections and JS contexts
   111  							for i := 1; i <= concurrency; i++ {
   112  								nc, js := jsClientConnectURL(b, connectURL)
   113  								defer nc.Close()
   114  								go func(clientId int, nc *nats.Conn, js nats.JetStreamContext) {
   115  									defer wgComplete.Done()
   116  
   117  									// Config struct (reused and modified in place for each call)
   118  									streamConfig := nats.StreamConfig{
   119  										Name:     "?",
   120  										Storage:  bc.storage,
   121  										Replicas: bc.replicas,
   122  									}
   123  									kvConfig := nats.KeyValueConfig{
   124  										Bucket:   "?",
   125  										Storage:  bc.storage,
   126  										Replicas: bc.replicas,
   127  									}
   128  									objConfig := nats.ObjectStoreConfig{
   129  										Bucket:   "?",
   130  										Storage:  bc.storage,
   131  										Replicas: bc.replicas,
   132  									}
   133  
   134  									// Block until everyone is ready
   135  									wgReady.Done()
   136  									wgReady.Wait()
   137  
   138  									errCount := int64(0)
   139  									defer func() {
   140  										// Roll up error count on completion
   141  										totalErrors.Add(errCount)
   142  									}()
   143  
   144  									// Track per-client opCount (just for logging/debugging)
   145  									opCount := 0
   146  									for opsLeft.Add(-1) >= 0 {
   147  										var err error
   148  										// Create unique resource name
   149  										resourceName := fmt.Sprintf("%s_%d_%d", resourcePrefix, clientId, opCount)
   150  										switch rt {
   151  										case Stream:
   152  											streamConfig.Name = resourceName
   153  											_, err = js.AddStream(&streamConfig)
   154  										case KVBucket:
   155  											kvConfig.Bucket = resourceName
   156  											_, err = js.CreateKeyValue(&kvConfig)
   157  										case ObjectStore:
   158  											objConfig.Bucket = resourceName
   159  											_, err = js.CreateObjectStore(&objConfig)
   160  										}
   161  										opCount += 1
   162  										if err != nil {
   163  											b.Logf("Error creating %s (%s): %s", rt, resourceName, err)
   164  											errCount += 1
   165  										}
   166  									}
   167  
   168  									if verbose {
   169  										b.Logf("Client %d completed %d operations", clientId, opCount)
   170  									}
   171  
   172  								}(i, nc, js)
   173  							}
   174  
   175  							// Wait for all clients to be ready
   176  							wgReady.Done()
   177  							wgReady.Wait()
   178  
   179  							// Start benchmark clock
   180  							b.ResetTimer()
   181  
   182  							wgComplete.Wait()
   183  							b.StopTimer()
   184  
   185  							b.ReportMetric(float64(100*(totalErrors.Load()))/float64(b.N), "%error")
   186  						},
   187  					)
   188  				}
   189  			},
   190  		)
   191  	}
   192  }
   193  
   194  func BenchmarkJetStreamCreateConsumers(b *testing.B) {
   195  
   196  	const (
   197  		verbose        = false
   198  		streamName     = "S"
   199  		consumerPrefix = "C"
   200  		concurrency    = 12
   201  	)
   202  
   203  	benchmarksCases := []struct {
   204  		clusterSize      int
   205  		consumerReplicas int
   206  		consumerStorage  nats.StorageType
   207  	}{
   208  		{1, 1, nats.MemoryStorage},
   209  		{3, 3, nats.MemoryStorage},
   210  		{3, 3, nats.FileStorage},
   211  	}
   212  
   213  	type ConsumerType string
   214  	const (
   215  		Ephemeral ConsumerType = "Ephemeral"
   216  		Durable   ConsumerType = "Durable"
   217  	)
   218  
   219  	consumerTypeCases := []ConsumerType{
   220  		Ephemeral,
   221  		Durable,
   222  	}
   223  
   224  	for _, bc := range benchmarksCases {
   225  
   226  		bName := fmt.Sprintf(
   227  			"N=%d,R=%d,storage=%s,C=%d",
   228  			bc.clusterSize,
   229  			bc.consumerReplicas,
   230  			bc.consumerStorage.String(),
   231  			concurrency,
   232  		)
   233  
   234  		b.Run(
   235  			bName,
   236  			func(b *testing.B) {
   237  
   238  				for _, ct := range consumerTypeCases {
   239  
   240  					cName := fmt.Sprintf("Consumer=%s", ct)
   241  
   242  					b.Run(
   243  						cName,
   244  						func(b *testing.B) {
   245  							if verbose {
   246  								b.Logf(
   247  									"Creating %d consumers in cluster with %d nodes, R=%d, %s storage",
   248  									b.N,
   249  									bc.clusterSize,
   250  									bc.consumerReplicas,
   251  									bc.consumerStorage,
   252  								)
   253  							}
   254  
   255  							// Setup server or cluster
   256  							_, leaderServer, shutdown, nc, js := startJSClusterAndConnect(b, bc.clusterSize)
   257  							defer shutdown()
   258  							defer nc.Close()
   259  
   260  							// All clients connect to cluster (meta) leader for lower variability
   261  							connectURL := leaderServer.ClientURL()
   262  
   263  							// Create stream
   264  							streamConfig := nats.StreamConfig{
   265  								Name:     streamName,
   266  								Storage:  nats.FileStorage,
   267  								Replicas: bc.clusterSize,
   268  							}
   269  
   270  							_, err := js.AddStream(&streamConfig)
   271  							if err != nil {
   272  								b.Fatalf("Failed to create stream: %s", err)
   273  							}
   274  
   275  							// Wait for all clients and main routine to be ready
   276  							wgReady := sync.WaitGroup{}
   277  							wgReady.Add(concurrency + 1)
   278  							// Wait for all routines to complete
   279  							wgComplete := sync.WaitGroup{}
   280  							wgComplete.Add(concurrency)
   281  
   282  							// Number of operations (divided amongst clients)
   283  							opsLeft := atomic.Int64{}
   284  							opsLeft.Store(int64(b.N))
   285  							// Total number of errors
   286  							totalErrors := atomic.Int64{}
   287  
   288  							// Pre-create connections and JS contexts
   289  							for i := 1; i <= concurrency; i++ {
   290  								nc, js := jsClientConnectURL(b, connectURL)
   291  								defer nc.Close()
   292  
   293  								go func(clientId int, nc *nats.Conn, js nats.JetStreamContext) {
   294  									defer wgComplete.Done()
   295  
   296  									// Config struct (reused and modified in place for each call)
   297  									cfg := nats.ConsumerConfig{
   298  										Durable:       "",
   299  										Name:          "",
   300  										Replicas:      bc.consumerReplicas,
   301  										MemoryStorage: bc.consumerStorage == nats.MemoryStorage,
   302  									}
   303  
   304  									// Block until everyone is ready
   305  									wgReady.Done()
   306  									wgReady.Wait()
   307  
   308  									errCount := int64(0)
   309  									opCount := 0
   310  									for opsLeft.Add(-1) >= 0 {
   311  										var err error
   312  										// Set unique consumer name
   313  										cfg.Name = fmt.Sprintf("%s_%d_%d", consumerPrefix, clientId, opCount)
   314  										if ct == Durable {
   315  											cfg.Durable = cfg.Name
   316  										}
   317  										_, err = js.AddConsumer(streamName, &cfg)
   318  										if err != nil {
   319  											b.Logf("Failed to add consumer: %s", err)
   320  											errCount += 1
   321  										}
   322  										opCount += 1
   323  									}
   324  
   325  									if verbose {
   326  										b.Logf("Client %d completed %d operations", clientId, opCount)
   327  									}
   328  
   329  									totalErrors.Add(errCount)
   330  
   331  								}(i, nc, js)
   332  							}
   333  
   334  							// Wait for all clients to be ready
   335  							wgReady.Done()
   336  							wgReady.Wait()
   337  
   338  							// Start benchmark clock
   339  							b.ResetTimer()
   340  
   341  							wgComplete.Wait()
   342  							b.StopTimer()
   343  
   344  							b.ReportMetric(float64(100*(totalErrors.Load()))/float64(b.N), "%error")
   345  						},
   346  					)
   347  				}
   348  			},
   349  		)
   350  	}
   351  }