mirror of
https://github.com/hanzoai/pubsub.git
synced 2026-10-30 15:06:26 +00:00
351 lines
8.7 KiB
Go
351 lines
8.7 KiB
Go
// Copyright 2024-2025 The NATS Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//go:build !skip_js_tests && !skip_js_cluster_tests && !skip_js_cluster_tests_2
|
|
|
|
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
)
|
|
|
|
func BenchmarkJetStreamCreate(b *testing.B) {
|
|
|
|
const (
|
|
verbose = false
|
|
resourcePrefix = "S"
|
|
concurrency = 12
|
|
)
|
|
|
|
// Types of resource that this benchmark creates
|
|
type ResourceType string
|
|
const (
|
|
Stream ResourceType = "Stream"
|
|
KVBucket ResourceType = "KVBucket"
|
|
ObjectStore ResourceType = "ObjStore"
|
|
)
|
|
|
|
resourceTypeCases := []ResourceType{
|
|
Stream,
|
|
KVBucket,
|
|
ObjectStore,
|
|
}
|
|
|
|
benchmarksCases := []struct {
|
|
clusterSize int
|
|
replicas int
|
|
storage nats.StorageType
|
|
}{
|
|
{1, 1, nats.MemoryStorage},
|
|
{3, 3, nats.MemoryStorage},
|
|
{3, 3, nats.FileStorage},
|
|
}
|
|
|
|
for _, bc := range benchmarksCases {
|
|
bName := fmt.Sprintf(
|
|
"N=%d,R=%d,storage=%s,C=%d",
|
|
bc.clusterSize,
|
|
bc.replicas,
|
|
bc.storage.String(),
|
|
concurrency,
|
|
)
|
|
|
|
b.Run(
|
|
bName,
|
|
func(b *testing.B) {
|
|
for _, rt := range resourceTypeCases {
|
|
//for _, bc := range benchmarksCases {
|
|
rName := fmt.Sprintf("resource=%s", rt)
|
|
b.Run(
|
|
rName,
|
|
func(b *testing.B) {
|
|
|
|
if verbose {
|
|
b.Logf(
|
|
"Creating %d %s resources in cluster with %d nodes, R=%d, %s storage",
|
|
b.N,
|
|
string(rt),
|
|
bc.clusterSize,
|
|
bc.replicas,
|
|
bc.storage,
|
|
)
|
|
}
|
|
|
|
// Setup server or cluster
|
|
_, leaderServer, shutdown, nc, _ := startJSClusterAndConnect(b, bc.clusterSize)
|
|
defer shutdown()
|
|
defer nc.Close()
|
|
|
|
// All clients connect to cluster (meta) leader for lower variability
|
|
connectURL := leaderServer.ClientURL()
|
|
|
|
// Wait for all clients and main routine to be ready
|
|
wgReady := sync.WaitGroup{}
|
|
wgReady.Add(concurrency + 1)
|
|
// Wait for all routines to complete
|
|
wgComplete := sync.WaitGroup{}
|
|
wgComplete.Add(concurrency)
|
|
|
|
// Number of operations (divided amongst clients)
|
|
opsLeft := atomic.Int64{}
|
|
opsLeft.Store(int64(b.N))
|
|
totalErrors := atomic.Int64{}
|
|
|
|
// Pre-create connections and JS contexts
|
|
for i := 1; i <= concurrency; i++ {
|
|
nc, js := jsClientConnectURL(b, connectURL)
|
|
defer nc.Close()
|
|
go func(clientId int, nc *nats.Conn, js nats.JetStreamContext) {
|
|
defer wgComplete.Done()
|
|
|
|
// Config struct (reused and modified in place for each call)
|
|
streamConfig := nats.StreamConfig{
|
|
Name: "?",
|
|
Storage: bc.storage,
|
|
Replicas: bc.replicas,
|
|
}
|
|
kvConfig := nats.KeyValueConfig{
|
|
Bucket: "?",
|
|
Storage: bc.storage,
|
|
Replicas: bc.replicas,
|
|
}
|
|
objConfig := nats.ObjectStoreConfig{
|
|
Bucket: "?",
|
|
Storage: bc.storage,
|
|
Replicas: bc.replicas,
|
|
}
|
|
|
|
// Block until everyone is ready
|
|
wgReady.Done()
|
|
wgReady.Wait()
|
|
|
|
errCount := int64(0)
|
|
defer func() {
|
|
// Roll up error count on completion
|
|
totalErrors.Add(errCount)
|
|
}()
|
|
|
|
// Track per-client opCount (just for logging/debugging)
|
|
opCount := 0
|
|
for opsLeft.Add(-1) >= 0 {
|
|
var err error
|
|
// Create unique resource name
|
|
resourceName := fmt.Sprintf("%s_%d_%d", resourcePrefix, clientId, opCount)
|
|
switch rt {
|
|
case Stream:
|
|
streamConfig.Name = resourceName
|
|
_, err = js.AddStream(&streamConfig)
|
|
case KVBucket:
|
|
kvConfig.Bucket = resourceName
|
|
_, err = js.CreateKeyValue(&kvConfig)
|
|
case ObjectStore:
|
|
objConfig.Bucket = resourceName
|
|
_, err = js.CreateObjectStore(&objConfig)
|
|
}
|
|
opCount += 1
|
|
if err != nil {
|
|
b.Logf("Error creating %s (%s): %s", rt, resourceName, err)
|
|
errCount += 1
|
|
}
|
|
}
|
|
|
|
if verbose {
|
|
b.Logf("Client %d completed %d operations", clientId, opCount)
|
|
}
|
|
|
|
}(i, nc, js)
|
|
}
|
|
|
|
// Wait for all clients to be ready
|
|
wgReady.Done()
|
|
wgReady.Wait()
|
|
|
|
// Start benchmark clock
|
|
b.ResetTimer()
|
|
|
|
wgComplete.Wait()
|
|
b.StopTimer()
|
|
|
|
b.ReportMetric(float64(100*(totalErrors.Load()))/float64(b.N), "%error")
|
|
},
|
|
)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
func BenchmarkJetStreamCreateConsumers(b *testing.B) {
|
|
|
|
const (
|
|
verbose = false
|
|
streamName = "S"
|
|
consumerPrefix = "C"
|
|
concurrency = 12
|
|
)
|
|
|
|
benchmarksCases := []struct {
|
|
clusterSize int
|
|
consumerReplicas int
|
|
consumerStorage nats.StorageType
|
|
}{
|
|
{1, 1, nats.MemoryStorage},
|
|
{3, 3, nats.MemoryStorage},
|
|
{3, 3, nats.FileStorage},
|
|
}
|
|
|
|
type ConsumerType string
|
|
const (
|
|
Ephemeral ConsumerType = "Ephemeral"
|
|
Durable ConsumerType = "Durable"
|
|
)
|
|
|
|
consumerTypeCases := []ConsumerType{
|
|
Ephemeral,
|
|
Durable,
|
|
}
|
|
|
|
for _, bc := range benchmarksCases {
|
|
|
|
bName := fmt.Sprintf(
|
|
"N=%d,R=%d,storage=%s,C=%d",
|
|
bc.clusterSize,
|
|
bc.consumerReplicas,
|
|
bc.consumerStorage.String(),
|
|
concurrency,
|
|
)
|
|
|
|
b.Run(
|
|
bName,
|
|
func(b *testing.B) {
|
|
|
|
for _, ct := range consumerTypeCases {
|
|
|
|
cName := fmt.Sprintf("Consumer=%s", ct)
|
|
|
|
b.Run(
|
|
cName,
|
|
func(b *testing.B) {
|
|
if verbose {
|
|
b.Logf(
|
|
"Creating %d consumers in cluster with %d nodes, R=%d, %s storage",
|
|
b.N,
|
|
bc.clusterSize,
|
|
bc.consumerReplicas,
|
|
bc.consumerStorage,
|
|
)
|
|
}
|
|
|
|
// Setup server or cluster
|
|
_, leaderServer, shutdown, nc, js := startJSClusterAndConnect(b, bc.clusterSize)
|
|
defer shutdown()
|
|
defer nc.Close()
|
|
|
|
// All clients connect to cluster (meta) leader for lower variability
|
|
connectURL := leaderServer.ClientURL()
|
|
|
|
// Create stream
|
|
streamConfig := nats.StreamConfig{
|
|
Name: streamName,
|
|
Storage: nats.FileStorage,
|
|
Replicas: bc.clusterSize,
|
|
}
|
|
|
|
_, err := js.AddStream(&streamConfig)
|
|
if err != nil {
|
|
b.Fatalf("Failed to create stream: %s", err)
|
|
}
|
|
|
|
// Wait for all clients and main routine to be ready
|
|
wgReady := sync.WaitGroup{}
|
|
wgReady.Add(concurrency + 1)
|
|
// Wait for all routines to complete
|
|
wgComplete := sync.WaitGroup{}
|
|
wgComplete.Add(concurrency)
|
|
|
|
// Number of operations (divided amongst clients)
|
|
opsLeft := atomic.Int64{}
|
|
opsLeft.Store(int64(b.N))
|
|
// Total number of errors
|
|
totalErrors := atomic.Int64{}
|
|
|
|
// Pre-create connections and JS contexts
|
|
for i := 1; i <= concurrency; i++ {
|
|
nc, js := jsClientConnectURL(b, connectURL)
|
|
defer nc.Close()
|
|
|
|
go func(clientId int, nc *nats.Conn, js nats.JetStreamContext) {
|
|
defer wgComplete.Done()
|
|
|
|
// Config struct (reused and modified in place for each call)
|
|
cfg := nats.ConsumerConfig{
|
|
Durable: "",
|
|
Name: "",
|
|
Replicas: bc.consumerReplicas,
|
|
MemoryStorage: bc.consumerStorage == nats.MemoryStorage,
|
|
}
|
|
|
|
// Block until everyone is ready
|
|
wgReady.Done()
|
|
wgReady.Wait()
|
|
|
|
errCount := int64(0)
|
|
opCount := 0
|
|
for opsLeft.Add(-1) >= 0 {
|
|
var err error
|
|
// Set unique consumer name
|
|
cfg.Name = fmt.Sprintf("%s_%d_%d", consumerPrefix, clientId, opCount)
|
|
if ct == Durable {
|
|
cfg.Durable = cfg.Name
|
|
}
|
|
_, err = js.AddConsumer(streamName, &cfg)
|
|
if err != nil {
|
|
b.Logf("Failed to add consumer: %s", err)
|
|
errCount += 1
|
|
}
|
|
opCount += 1
|
|
}
|
|
|
|
if verbose {
|
|
b.Logf("Client %d completed %d operations", clientId, opCount)
|
|
}
|
|
|
|
totalErrors.Add(errCount)
|
|
|
|
}(i, nc, js)
|
|
}
|
|
|
|
// Wait for all clients to be ready
|
|
wgReady.Done()
|
|
wgReady.Wait()
|
|
|
|
// Start benchmark clock
|
|
b.ResetTimer()
|
|
|
|
wgComplete.Wait()
|
|
b.StopTimer()
|
|
|
|
b.ReportMetric(float64(100*(totalErrors.Load()))/float64(b.N), "%error")
|
|
},
|
|
)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|