mirror of
https://github.com/hanzoai/pubsub.git
synced 2026-10-30 15:06:26 +00:00
Also fixes the configuration file to remove deprecated options and/or to use new format. Finally, run `goimports` on `jetstream_sourcing_scaling_test.go` to satisfy the linter. Signed-off-by: Neil Twigg <[email protected]>
284 lines
8.1 KiB
Go
284 lines
8.1 KiB
Go
// Copyright 2024 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.
|
|
|
|
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
)
|
|
|
|
var serverConfig1 = `
|
|
server_name: server1
|
|
listen: 127.0.0.1:4222
|
|
http: 8222
|
|
|
|
prof_port = 18222
|
|
|
|
jetstream: {max_mem_store: 256MB, max_file_store: 2GB, store_dir: '%s'}
|
|
|
|
cluster {
|
|
name: my_cluster
|
|
listen: 127.0.0.1:4248
|
|
routes: [nats://127.0.0.1:4249,nats://127.0.0.1:4250]
|
|
}
|
|
|
|
accounts { $SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }}
|
|
`
|
|
|
|
var serverConfig2 = `
|
|
server_name: server2
|
|
listen: 127.0.0.1:5222
|
|
http: 8223
|
|
|
|
prof_port = 18223
|
|
|
|
jetstream: {max_mem_store: 256MB, max_file_store: 2GB, store_dir: '%s'}
|
|
|
|
cluster {
|
|
name: my_cluster
|
|
listen: 127.0.0.1:4249
|
|
routes: [nats://127.0.0.1:4248,nats://127.0.0.1:4250]
|
|
}
|
|
|
|
accounts { $SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }}
|
|
`
|
|
|
|
var serverConfig3 = `
|
|
server_name: server3
|
|
listen: 127.0.0.1:6222
|
|
http: 8224
|
|
|
|
prof_port = 18224
|
|
|
|
jetstream: {max_mem_store: 256MB, max_file_store: 2GB, store_dir: '%s'}
|
|
|
|
cluster {
|
|
name: my_cluster
|
|
listen: 127.0.0.1:4250
|
|
routes: [nats://127.0.0.1:4248,nats://127.0.0.1:4249]
|
|
}
|
|
|
|
accounts { $SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }}
|
|
`
|
|
|
|
var connectURL = "nats://127.0.0.1:4222,nats://127.0.0.1:4223,nats://127.0.0.1:4224"
|
|
|
|
func createMyLocalCluster(t *testing.T) *cluster {
|
|
c := &cluster{servers: make([]*Server, 0, 3), opts: make([]*Options, 0, 3), name: "C3"}
|
|
|
|
storeDir1 := t.TempDir()
|
|
s1, o := RunServerWithConfig(createConfFile(t, []byte(fmt.Sprintf(serverConfig1, storeDir1))))
|
|
c.servers = append(c.servers, s1)
|
|
c.opts = append(c.opts, o)
|
|
|
|
storeDir2 := t.TempDir()
|
|
s2, o := RunServerWithConfig(createConfFile(t, []byte(fmt.Sprintf(serverConfig2, storeDir2))))
|
|
c.servers = append(c.servers, s2)
|
|
c.opts = append(c.opts, o)
|
|
|
|
storeDir3 := t.TempDir()
|
|
s3, o := RunServerWithConfig(createConfFile(t, []byte(fmt.Sprintf(serverConfig3, storeDir3))))
|
|
c.servers = append(c.servers, s3)
|
|
c.opts = append(c.opts, o)
|
|
|
|
c.t = t
|
|
|
|
// Wait til we are formed and have a leader.
|
|
c.checkClusterFormed()
|
|
c.waitOnClusterReady()
|
|
|
|
return c
|
|
}
|
|
|
|
// This test is being skipped by CI as it takes too long to run and is meant to test the scalability of sourcing
|
|
// rather than being a unit test.
|
|
func TestStreamSourcingScalingSourcingManyBenchmark(t *testing.T) {
|
|
t.Skip()
|
|
|
|
var numSourced = 1000
|
|
var numMsgPerSource = 10_000
|
|
var batchSize = 200
|
|
var retries int
|
|
|
|
var err error
|
|
|
|
c := createMyLocalCluster(t)
|
|
defer c.shutdown()
|
|
|
|
nc, err := nats.Connect(connectURL)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create client: %v", err)
|
|
}
|
|
js, err := nc.JetStream(nats.MaxWait(10 * time.Second))
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error getting JetStream context: %v", err)
|
|
}
|
|
defer nc.Close()
|
|
|
|
// create n streams to source from
|
|
for i := 0; i < numSourced; i++ {
|
|
_, err := js.AddStream(&nats.StreamConfig{
|
|
Name: fmt.Sprintf("sourced-%d", i),
|
|
Subjects: []string{strconv.Itoa(i)},
|
|
Retention: nats.LimitsPolicy,
|
|
Storage: nats.FileStorage,
|
|
Discard: nats.DiscardOld,
|
|
Replicas: 1,
|
|
AllowDirect: true,
|
|
MirrorDirect: false,
|
|
DiscardNewPerSubject: false,
|
|
})
|
|
require_NoError(t, err)
|
|
}
|
|
|
|
fmt.Printf("Streams created\n")
|
|
|
|
// publish n messages for each sourced stream
|
|
for j := 0; j < numMsgPerSource; j++ {
|
|
start := time.Now()
|
|
var pafs = make([]nats.PubAckFuture, numSourced)
|
|
for i := 0; i < numSourced; i++ {
|
|
var err error
|
|
|
|
for {
|
|
pafs[i], err = js.PublishAsync(strconv.Itoa(i), []byte(strconv.Itoa(j)))
|
|
if err != nil {
|
|
fmt.Printf("Error async publishing: %v, retrying\n", err)
|
|
retries++
|
|
time.Sleep(10 * time.Millisecond)
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
if i != 0 && i%batchSize == 0 {
|
|
<-js.PublishAsyncComplete()
|
|
}
|
|
|
|
}
|
|
|
|
<-js.PublishAsyncComplete()
|
|
|
|
for i := 0; i < numSourced; i++ {
|
|
select {
|
|
case <-pafs[i].Ok():
|
|
case psae := <-pafs[i].Err():
|
|
fmt.Printf("Error on PubAckFuture: %v, retrying sync...\n", psae)
|
|
retries++
|
|
_, err = js.Publish(strconv.Itoa(i), []byte(strconv.Itoa(j)))
|
|
require_NoError(t, err)
|
|
}
|
|
}
|
|
|
|
end := time.Now()
|
|
if j%1000 == 0 {
|
|
fmt.Printf("Published round %d, avg pub latency %v\n", j, end.Sub(start)/time.Duration(numSourced))
|
|
}
|
|
}
|
|
|
|
fmt.Printf("Messages published\n")
|
|
|
|
// create the StreamSources
|
|
streamSources := make([]*nats.StreamSource, numSourced)
|
|
|
|
for i := 0; i < numSourced; i++ {
|
|
streamSources[i] = &nats.StreamSource{Name: fmt.Sprintf("sourced-%d", i), FilterSubject: strconv.Itoa(i)}
|
|
}
|
|
|
|
// create a stream that sources from them
|
|
_, err = js.AddStream(&nats.StreamConfig{
|
|
Name: "sourcing",
|
|
Subjects: []string{"foo"},
|
|
Sources: streamSources,
|
|
Retention: nats.LimitsPolicy,
|
|
Storage: nats.FileStorage,
|
|
Discard: nats.DiscardOld,
|
|
Replicas: 3,
|
|
AllowDirect: true,
|
|
MirrorDirect: false,
|
|
DiscardNewPerSubject: false,
|
|
})
|
|
require_NoError(t, err)
|
|
c.waitOnStreamLeader(globalAccountName, "sourcing")
|
|
sl := c.streamLeader(globalAccountName, "sourcing")
|
|
fmt.Printf("Sourcing stream created leader is *** %s ***\n", sl.Name())
|
|
|
|
expectedSeq := make([]int, numSourced)
|
|
|
|
start := time.Now()
|
|
|
|
var lastMsgs uint64
|
|
mset, err := sl.GlobalAccount().lookupStream("sourcing")
|
|
require_NoError(t, err)
|
|
|
|
checkFor(t, 5*time.Minute, 1000*time.Millisecond, func() error {
|
|
mset.mu.RLock()
|
|
var state StreamState
|
|
mset.store.FastState(&state)
|
|
mset.mu.RUnlock()
|
|
|
|
if state.Msgs == uint64(numMsgPerSource*numSourced) {
|
|
fmt.Printf("👍 Test passed: expected %d messages, got %d and took %v\n", uint64(numMsgPerSource*numSourced), state.Msgs, time.Since(start))
|
|
return nil
|
|
} else if state.Msgs < uint64(numMsgPerSource*numSourced) {
|
|
fmt.Printf("Current Rate %d per second - Received %d\n", state.Msgs-lastMsgs, state.Msgs)
|
|
lastMsgs = state.Msgs
|
|
return fmt.Errorf("Expected %d messages, got %d", uint64(numMsgPerSource*numSourced), state.Msgs)
|
|
} else {
|
|
fmt.Printf("Too many messages! expected %d (retries=%d), got %d\n", uint64(numMsgPerSource*numSourced), retries, state.Msgs)
|
|
return fmt.Errorf("Too many messages: expected %d (retries=%d), got %d", uint64(numMsgPerSource*numSourced), retries, state.Msgs)
|
|
}
|
|
})
|
|
|
|
// Check that all the messages sourced in the stream are correct
|
|
// Note: expects to see exactly increasing matching sequence numbers, so could theoretically fail if some messages
|
|
// get recorded 'out of order' (according to the payload value) because asynchronous JS publication is used to
|
|
// publish the messages.
|
|
// However, that should not happen if the publish 'batch size' is not more than the number of streams being sourced.
|
|
|
|
// create a consumer on sourcing
|
|
_, err = js.AddConsumer("sourcing", &nats.ConsumerConfig{AckPolicy: nats.AckExplicitPolicy})
|
|
require_NoError(t, err)
|
|
syncSub, err := js.SubscribeSync("", nats.BindStream("sourcing"))
|
|
require_NoError(t, err)
|
|
|
|
start = time.Now()
|
|
|
|
print("Checking the messages\n")
|
|
for i := 0; i < numSourced*numMsgPerSource; i++ {
|
|
msg, err := syncSub.NextMsg(30 * time.Second)
|
|
require_NoError(t, err)
|
|
sId, err := strconv.Atoi(msg.Subject)
|
|
require_NoError(t, err)
|
|
seq, err := strconv.Atoi(string(msg.Data))
|
|
require_NoError(t, err)
|
|
if expectedSeq[sId] == seq {
|
|
expectedSeq[sId]++
|
|
} else {
|
|
t.Fatalf("Expected seq number %d got %d for source %d\n", expectedSeq[sId], seq, sId)
|
|
}
|
|
msg.Ack()
|
|
if i%100_000 == 0 {
|
|
now := time.Now()
|
|
fmt.Printf("[%v] Checked %d messages: %f msgs/sec \n", now, i, 100_000/now.Sub(start).Seconds())
|
|
start = now
|
|
}
|
|
}
|
|
print("👍 Done. \n")
|
|
}
|