Files
nats-server/server/memstore_test.go
Derek CollisonandNeil Twigg ff463bb382 Introduce a NumPendingMulti that uses a sublist for efficient selection. (#6089)
Consumers with many filtered subjects and large streams could suffer
from slow creation times due to NumPending() being called for all
filtered subjects.

This improvement is similar to the LoadNextMsgMulti for consumers with
large number of filtered subjects.

Also fixed a bug in normal NumPending for memstore.

Signed-off-by: Derek Collison <[email protected]>

---------

Signed-off-by: Derek Collison <[email protected]>
2024-11-12 14:44:56 +00:00

1112 lines
28 KiB
Go

// Copyright 2019-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.
//go:build !skip_store_tests
// +build !skip_store_tests
package server
import (
"bytes"
"errors"
"fmt"
"math/rand"
"reflect"
"testing"
"time"
"github.com/nats-io/nuid"
)
func TestMemStoreBasics(t *testing.T) {
ms, err := newMemStore(&StreamConfig{Storage: MemoryStorage})
require_NoError(t, err)
defer ms.Stop()
subj, msg := "foo", []byte("Hello World")
now := time.Now().UnixNano()
if seq, ts, err := ms.StoreMsg(subj, nil, msg); err != nil {
t.Fatalf("Error storing msg: %v", err)
} else if seq != 1 {
t.Fatalf("Expected sequence to be 1, got %d", seq)
} else if ts < now || ts > now+int64(time.Millisecond) {
t.Fatalf("Expected timestamp to be current, got %v", ts-now)
}
state := ms.State()
if state.Msgs != 1 {
t.Fatalf("Expected 1 msg, got %d", state.Msgs)
}
expectedSize := memStoreMsgSize(subj, nil, msg)
if state.Bytes != expectedSize {
t.Fatalf("Expected %d bytes, got %d", expectedSize, state.Bytes)
}
sm, err := ms.LoadMsg(1, nil)
if err != nil {
t.Fatalf("Unexpected error looking up msg: %v", err)
}
if sm.subj != subj {
t.Fatalf("Subjects don't match, original %q vs %q", subj, sm.subj)
}
if !bytes.Equal(sm.msg, msg) {
t.Fatalf("Msgs don't match, original %q vs %q", msg, sm.msg)
}
}
func TestMemStoreMsgLimit(t *testing.T) {
ms, err := newMemStore(&StreamConfig{Storage: MemoryStorage, MaxMsgs: 10})
require_NoError(t, err)
defer ms.Stop()
subj, msg := "foo", []byte("Hello World")
for i := 0; i < 10; i++ {
ms.StoreMsg(subj, nil, msg)
}
state := ms.State()
if state.Msgs != 10 {
t.Fatalf("Expected %d msgs, got %d", 10, state.Msgs)
}
if _, _, err := ms.StoreMsg(subj, nil, msg); err != nil {
t.Fatalf("Error storing msg: %v", err)
}
state = ms.State()
if state.Msgs != 10 {
t.Fatalf("Expected %d msgs, got %d", 10, state.Msgs)
}
if state.LastSeq != 11 {
t.Fatalf("Expected the last sequence to be 11 now, but got %d", state.LastSeq)
}
if state.FirstSeq != 2 {
t.Fatalf("Expected the first sequence to be 2 now, but got %d", state.FirstSeq)
}
// Make sure we can not lookup seq 1.
if _, err := ms.LoadMsg(1, nil); err == nil {
t.Fatalf("Expected error looking up seq 1 but got none")
}
}
func TestMemStoreBytesLimit(t *testing.T) {
subj, msg := "foo", make([]byte, 512)
storedMsgSize := memStoreMsgSize(subj, nil, msg)
toStore := uint64(1024)
maxBytes := storedMsgSize * toStore
ms, err := newMemStore(&StreamConfig{Storage: MemoryStorage, MaxBytes: int64(maxBytes)})
require_NoError(t, err)
defer ms.Stop()
for i := uint64(0); i < toStore; i++ {
ms.StoreMsg(subj, nil, msg)
}
state := ms.State()
if state.Msgs != toStore {
t.Fatalf("Expected %d msgs, got %d", toStore, state.Msgs)
}
if state.Bytes != storedMsgSize*toStore {
t.Fatalf("Expected bytes to be %d, got %d", storedMsgSize*toStore, state.Bytes)
}
// Now send 10 more and check that bytes limit enforced.
for i := 0; i < 10; i++ {
if _, _, err := ms.StoreMsg(subj, nil, msg); err != nil {
t.Fatalf("Error storing msg: %v", err)
}
}
state = ms.State()
if state.Msgs != toStore {
t.Fatalf("Expected %d msgs, got %d", toStore, state.Msgs)
}
if state.Bytes != storedMsgSize*toStore {
t.Fatalf("Expected bytes to be %d, got %d", storedMsgSize*toStore, state.Bytes)
}
if state.FirstSeq != 11 {
t.Fatalf("Expected first sequence to be 11, got %d", state.FirstSeq)
}
if state.LastSeq != toStore+10 {
t.Fatalf("Expected last sequence to be %d, got %d", toStore+10, state.LastSeq)
}
}
// https://github.com/nats-io/nats-server/issues/4771
func TestMemStoreBytesLimitWithDiscardNew(t *testing.T) {
subj, msg := "tiny", make([]byte, 7)
storedMsgSize := memStoreMsgSize(subj, nil, msg)
toStore := uint64(3)
maxBytes := 100
ms, err := newMemStore(&StreamConfig{Storage: MemoryStorage, MaxBytes: int64(maxBytes), Discard: DiscardNew})
require_NoError(t, err)
defer ms.Stop()
// Now send 10 messages and check that bytes limit enforced.
for i := 0; i < 10; i++ {
_, _, err := ms.StoreMsg(subj, nil, msg)
if i < int(toStore) {
if err != nil {
t.Fatalf("Error storing msg: %v", err)
}
} else if !errors.Is(err, ErrMaxBytes) {
t.Fatalf("Storing msg should result in: %v", ErrMaxBytes)
}
}
state := ms.State()
if state.Msgs != toStore {
t.Fatalf("Expected %d msgs, got %d", toStore, state.Msgs)
}
if state.Bytes != storedMsgSize*toStore {
t.Fatalf("Expected bytes to be %d, got %d", storedMsgSize*toStore, state.Bytes)
}
}
func TestMemStoreAgeLimit(t *testing.T) {
maxAge := 10 * time.Millisecond
ms, err := newMemStore(&StreamConfig{Storage: MemoryStorage, MaxAge: maxAge})
require_NoError(t, err)
defer ms.Stop()
// Store some messages. Does not really matter how many.
subj, msg := "foo", []byte("Hello World")
toStore := 100
for i := 0; i < toStore; i++ {
ms.StoreMsg(subj, nil, msg)
}
state := ms.State()
if state.Msgs != uint64(toStore) {
t.Fatalf("Expected %d msgs, got %d", toStore, state.Msgs)
}
checkExpired := func(t *testing.T) {
t.Helper()
checkFor(t, time.Second, maxAge, func() error {
state = ms.State()
if state.Msgs != 0 {
return fmt.Errorf("Expected no msgs, got %d", state.Msgs)
}
if state.Bytes != 0 {
return fmt.Errorf("Expected no bytes, got %d", state.Bytes)
}
return nil
})
}
// Let them expire
checkExpired(t)
// Now add some more and make sure that timer will fire again.
for i := 0; i < toStore; i++ {
ms.StoreMsg(subj, nil, msg)
}
state = ms.State()
if state.Msgs != uint64(toStore) {
t.Fatalf("Expected %d msgs, got %d", toStore, state.Msgs)
}
checkExpired(t)
}
func TestMemStoreTimeStamps(t *testing.T) {
ms, err := newMemStore(&StreamConfig{Storage: MemoryStorage})
require_NoError(t, err)
defer ms.Stop()
last := time.Now().UnixNano()
subj, msg := "foo", []byte("Hello World")
for i := 0; i < 10; i++ {
time.Sleep(5 * time.Microsecond)
ms.StoreMsg(subj, nil, msg)
}
var smv StoreMsg
for seq := uint64(1); seq <= 10; seq++ {
sm, err := ms.LoadMsg(seq, &smv)
if err != nil {
t.Fatalf("Unexpected error looking up msg: %v", err)
}
// These should be different
if sm.ts <= last {
t.Fatalf("Expected different timestamps, got %v", sm.ts)
}
last = sm.ts
}
}
func TestMemStorePurge(t *testing.T) {
ms, err := newMemStore(&StreamConfig{Storage: MemoryStorage})
require_NoError(t, err)
defer ms.Stop()
subj, msg := "foo", []byte("Hello World")
for i := 0; i < 10; i++ {
ms.StoreMsg(subj, nil, msg)
}
if state := ms.State(); state.Msgs != 10 {
t.Fatalf("Expected 10 msgs, got %d", state.Msgs)
}
ms.Purge()
if state := ms.State(); state.Msgs != 0 {
t.Fatalf("Expected no msgs, got %d", state.Msgs)
}
}
func TestMemStoreCompact(t *testing.T) {
ms, err := newMemStore(&StreamConfig{Storage: MemoryStorage})
require_NoError(t, err)
defer ms.Stop()
subj, msg := "foo", []byte("Hello World")
for i := 0; i < 10; i++ {
ms.StoreMsg(subj, nil, msg)
}
if state := ms.State(); state.Msgs != 10 {
t.Fatalf("Expected 10 msgs, got %d", state.Msgs)
}
n, err := ms.Compact(6)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if n != 5 {
t.Fatalf("Expected to have purged 5 msgs, got %d", n)
}
state := ms.State()
if state.Msgs != 5 {
t.Fatalf("Expected 5 msgs, got %d", state.Msgs)
}
if state.FirstSeq != 6 {
t.Fatalf("Expected first seq of 6, got %d", state.FirstSeq)
}
// Now test that compact will also reset first if seq > last
n, err = ms.Compact(100)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if n != 5 {
t.Fatalf("Expected to have purged 5 msgs, got %d", n)
}
if state = ms.State(); state.FirstSeq != 100 {
t.Fatalf("Expected first seq of 100, got %d", state.FirstSeq)
}
}
func TestMemStoreEraseMsg(t *testing.T) {
ms, err := newMemStore(&StreamConfig{Storage: MemoryStorage})
require_NoError(t, err)
defer ms.Stop()
subj, msg := "foo", []byte("Hello World")
ms.StoreMsg(subj, nil, msg)
sm, err := ms.LoadMsg(1, nil)
if err != nil {
t.Fatalf("Unexpected error looking up msg: %v", err)
}
if !bytes.Equal(msg, sm.msg) {
t.Fatalf("Expected same msg, got %q vs %q", sm.msg, msg)
}
if removed, _ := ms.EraseMsg(1); !removed {
t.Fatalf("Expected erase msg to return success")
}
}
func TestMemStoreMsgHeaders(t *testing.T) {
ms, err := newMemStore(&StreamConfig{Storage: MemoryStorage})
require_NoError(t, err)
defer ms.Stop()
subj, hdr, msg := "foo", []byte("name:derek"), []byte("Hello World")
if sz := int(memStoreMsgSize(subj, hdr, msg)); sz != (len(subj) + len(hdr) + len(msg) + 16) {
t.Fatalf("Wrong size for stored msg with header")
}
ms.StoreMsg(subj, hdr, msg)
sm, err := ms.LoadMsg(1, nil)
if err != nil {
t.Fatalf("Unexpected error looking up msg: %v", err)
}
if !bytes.Equal(msg, sm.msg) {
t.Fatalf("Expected same msg, got %q vs %q", sm.msg, msg)
}
if !bytes.Equal(hdr, sm.hdr) {
t.Fatalf("Expected same hdr, got %q vs %q", sm.hdr, hdr)
}
if removed, _ := ms.EraseMsg(1); !removed {
t.Fatalf("Expected erase msg to return success")
}
}
func TestMemStoreStreamStateDeleted(t *testing.T) {
ms, err := newMemStore(&StreamConfig{Storage: MemoryStorage})
require_NoError(t, err)
defer ms.Stop()
subj, toStore := "foo", uint64(10)
for i := uint64(1); i <= toStore; i++ {
msg := []byte(fmt.Sprintf("[%08d] Hello World!", i))
if _, _, err := ms.StoreMsg(subj, nil, msg); err != nil {
t.Fatalf("Error storing msg: %v", err)
}
}
state := ms.State()
if len(state.Deleted) != 0 {
t.Fatalf("Expected deleted to be empty")
}
// Now remove some interior messages.
var expected []uint64
for seq := uint64(2); seq < toStore; seq += 2 {
ms.RemoveMsg(seq)
expected = append(expected, seq)
}
state = ms.State()
if !reflect.DeepEqual(state.Deleted, expected) {
t.Fatalf("Expected deleted to be %+v, got %+v\n", expected, state.Deleted)
}
// Now fill the gap by deleting 1 and 3
ms.RemoveMsg(1)
ms.RemoveMsg(3)
expected = expected[2:]
state = ms.State()
if !reflect.DeepEqual(state.Deleted, expected) {
t.Fatalf("Expected deleted to be %+v, got %+v\n", expected, state.Deleted)
}
if state.FirstSeq != 5 {
t.Fatalf("Expected first seq to be 5, got %d", state.FirstSeq)
}
ms.Purge()
if state = ms.State(); len(state.Deleted) != 0 {
t.Fatalf("Expected no deleted after purge, got %+v\n", state.Deleted)
}
}
func TestMemStoreStreamTruncate(t *testing.T) {
ms, err := newMemStore(&StreamConfig{Storage: MemoryStorage})
require_NoError(t, err)
defer ms.Stop()
tseq := uint64(50)
subj, toStore := "foo", uint64(100)
for i := uint64(1); i < tseq; i++ {
_, _, err := ms.StoreMsg(subj, nil, []byte("ok"))
require_NoError(t, err)
}
subj = "bar"
for i := tseq; i <= toStore; i++ {
_, _, err := ms.StoreMsg(subj, nil, []byte("ok"))
require_NoError(t, err)
}
if state := ms.State(); state.Msgs != toStore {
t.Fatalf("Expected %d msgs, got %d", toStore, state.Msgs)
}
// Check that sequence has to be interior.
if err := ms.Truncate(toStore + 1); err != ErrInvalidSequence {
t.Fatalf("Expected err of '%v', got '%v'", ErrInvalidSequence, err)
}
if err := ms.Truncate(tseq); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if state := ms.State(); state.Msgs != tseq {
t.Fatalf("Expected %d msgs, got %d", tseq, state.Msgs)
}
// Now make sure we report properly if we have some deleted interior messages.
ms.RemoveMsg(10)
ms.RemoveMsg(20)
ms.RemoveMsg(30)
ms.RemoveMsg(40)
tseq = uint64(25)
if err := ms.Truncate(tseq); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
state := ms.State()
if state.Msgs != tseq-2 {
t.Fatalf("Expected %d msgs, got %d", tseq-2, state.Msgs)
}
if state.NumSubjects != 1 {
t.Fatalf("Expected only 1 subject, got %d", state.NumSubjects)
}
expected := []uint64{10, 20}
if !reflect.DeepEqual(state.Deleted, expected) {
t.Fatalf("Expected deleted to be %+v, got %+v\n", expected, state.Deleted)
}
}
func TestMemStorePurgeExWithSubject(t *testing.T) {
ms, err := newMemStore(&StreamConfig{Storage: MemoryStorage})
require_NoError(t, err)
defer ms.Stop()
for i := 0; i < 100; i++ {
_, _, err = ms.StoreMsg("foo", nil, nil)
require_NoError(t, err)
}
// This should purge all.
ms.PurgeEx("foo", 1, 0)
require_True(t, ms.State().Msgs == 0)
}
func TestMemStoreUpdateMaxMsgsPerSubject(t *testing.T) {
cfg := &StreamConfig{
Name: "TEST",
Storage: MemoryStorage,
Subjects: []string{"foo"},
MaxMsgsPer: 10,
}
ms, err := newMemStore(cfg)
require_NoError(t, err)
defer ms.Stop()
// Make sure this is honored on an update.
cfg.MaxMsgsPer = 50
err = ms.UpdateConfig(cfg)
require_NoError(t, err)
numStored := 22
for i := 0; i < numStored; i++ {
_, _, err = ms.StoreMsg("foo", nil, nil)
require_NoError(t, err)
}
ss := ms.SubjectsState("foo")["foo"]
if ss.Msgs != uint64(numStored) {
t.Fatalf("Expected to have %d stored, got %d", numStored, ss.Msgs)
}
// Now make sure we trunk if setting to lower value.
cfg.MaxMsgsPer = 10
err = ms.UpdateConfig(cfg)
require_NoError(t, err)
ss = ms.SubjectsState("foo")["foo"]
if ss.Msgs != 10 {
t.Fatalf("Expected to have %d stored, got %d", 10, ss.Msgs)
}
}
func TestMemStoreStreamTruncateReset(t *testing.T) {
cfg := &StreamConfig{
Name: "TEST",
Storage: MemoryStorage,
Subjects: []string{"foo"},
}
ms, err := newMemStore(cfg)
require_NoError(t, err)
defer ms.Stop()
subj, msg := "foo", []byte("Hello World")
for i := 0; i < 1000; i++ {
_, _, err := ms.StoreMsg(subj, nil, msg)
require_NoError(t, err)
}
// Reset everything
require_NoError(t, ms.Truncate(0))
state := ms.State()
require_True(t, state.Msgs == 0)
require_True(t, state.Bytes == 0)
require_True(t, state.FirstSeq == 0)
require_True(t, state.LastSeq == 0)
require_True(t, state.NumSubjects == 0)
require_True(t, state.NumDeleted == 0)
for i := 0; i < 1000; i++ {
_, _, err := ms.StoreMsg(subj, nil, msg)
require_NoError(t, err)
}
state = ms.State()
require_True(t, state.Msgs == 1000)
require_True(t, state.Bytes == 30000)
require_True(t, state.FirstSeq == 1)
require_True(t, state.LastSeq == 1000)
require_True(t, state.NumSubjects == 1)
require_True(t, state.NumDeleted == 0)
}
func TestMemStoreStreamCompactMultiBlockSubjectInfo(t *testing.T) {
cfg := &StreamConfig{
Name: "TEST",
Storage: MemoryStorage,
Subjects: []string{"foo.*"},
}
ms, err := newMemStore(cfg)
require_NoError(t, err)
defer ms.Stop()
for i := 0; i < 1000; i++ {
subj := fmt.Sprintf("foo.%d", i)
_, _, err := ms.StoreMsg(subj, nil, []byte("Hello World"))
require_NoError(t, err)
}
// Compact such that we know we throw blocks away from the beginning.
deleted, err := ms.Compact(501)
require_NoError(t, err)
require_True(t, deleted == 500)
// Make sure we adjusted for subjects etc.
state := ms.State()
require_True(t, state.NumSubjects == 500)
}
func TestMemStoreSubjectsTotals(t *testing.T) {
cfg := &StreamConfig{
Name: "TEST",
Storage: MemoryStorage,
Subjects: []string{"*.*"},
}
ms, err := newMemStore(cfg)
require_NoError(t, err)
defer ms.Stop()
fmap := make(map[int]int)
bmap := make(map[int]int)
var m map[int]int
var ft string
for i := 0; i < 10_000; i++ {
// Flip coin for prefix
if rand.Intn(2) == 0 {
ft, m = "foo", fmap
} else {
ft, m = "bar", bmap
}
dt := rand.Intn(100)
subj := fmt.Sprintf("%s.%d", ft, dt)
m[dt]++
_, _, err := ms.StoreMsg(subj, nil, []byte("Hello World"))
require_NoError(t, err)
}
// Now test SubjectsTotal
for dt, total := range fmap {
subj := fmt.Sprintf("foo.%d", dt)
m := ms.SubjectsTotals(subj)
if m[subj] != uint64(total) {
t.Fatalf("Expected %q to have %d total, got %d", subj, total, m[subj])
}
}
// Check fmap.
if st := ms.SubjectsTotals("foo.*"); len(st) != len(fmap) {
t.Fatalf("Expected %d subjects for %q, got %d", len(fmap), "foo.*", len(st))
} else {
expected := 0
for _, n := range fmap {
expected += n
}
received := uint64(0)
for _, n := range st {
received += n
}
if received != uint64(expected) {
t.Fatalf("Expected %d total but got %d", expected, received)
}
}
// Check bmap.
if st := ms.SubjectsTotals("bar.*"); len(st) != len(bmap) {
t.Fatalf("Expected %d subjects for %q, got %d", len(bmap), "bar.*", len(st))
} else {
expected := 0
for _, n := range bmap {
expected += n
}
received := uint64(0)
for _, n := range st {
received += n
}
if received != uint64(expected) {
t.Fatalf("Expected %d total but got %d", expected, received)
}
}
// All with pwc match.
if st, expected := ms.SubjectsTotals("*.*"), len(bmap)+len(fmap); len(st) != expected {
t.Fatalf("Expected %d subjects for %q, got %d", expected, "*.*", len(st))
}
}
func TestMemStoreNumPending(t *testing.T) {
cfg := &StreamConfig{
Name: "TEST",
Storage: MemoryStorage,
Subjects: []string{"*.*.*.*"},
}
ms, err := newMemStore(cfg)
require_NoError(t, err)
defer ms.Stop()
tokens := []string{"foo", "bar", "baz"}
genSubj := func() string {
return fmt.Sprintf("%s.%s.%s.%s",
tokens[rand.Intn(len(tokens))],
tokens[rand.Intn(len(tokens))],
tokens[rand.Intn(len(tokens))],
tokens[rand.Intn(len(tokens))],
)
}
for i := 0; i < 50_000; i++ {
subj := genSubj()
_, _, err := ms.StoreMsg(subj, nil, []byte("Hello World"))
require_NoError(t, err)
}
state := ms.State()
// Scan one by one for sanity check against other calculations.
sanityCheck := func(sseq uint64, filter string) SimpleState {
t.Helper()
var ss SimpleState
var smv StoreMsg
// For here we know 0 is invalid, set to 1.
if sseq == 0 {
sseq = 1
}
for seq := sseq; seq <= state.LastSeq; seq++ {
sm, err := ms.LoadMsg(seq, &smv)
if err != nil {
t.Logf("Encountered error %v loading sequence: %d", err, seq)
continue
}
if subjectIsSubsetMatch(sm.subj, filter) {
ss.Msgs++
ss.Last = seq
if ss.First == 0 || seq < ss.First {
ss.First = seq
}
}
}
return ss
}
check := func(sseq uint64, filter string) {
t.Helper()
np, lvs := ms.NumPending(sseq, filter, false)
ss := ms.FilteredState(sseq, filter)
sss := sanityCheck(sseq, filter)
if lvs != state.LastSeq {
t.Fatalf("Expected NumPending to return valid through last of %d but got %d", state.LastSeq, lvs)
}
if ss.Msgs != np {
t.Fatalf("NumPending of %d did not match ss.Msgs of %d", np, ss.Msgs)
}
if ss != sss {
t.Fatalf("Failed sanity check, expected %+v got %+v", sss, ss)
}
}
sanityCheckLastOnly := func(sseq uint64, filter string) SimpleState {
t.Helper()
var ss SimpleState
var smv StoreMsg
// For here we know 0 is invalid, set to 1.
if sseq == 0 {
sseq = 1
}
seen := make(map[string]bool)
for seq := state.LastSeq; seq >= sseq; seq-- {
sm, err := ms.LoadMsg(seq, &smv)
if err != nil {
t.Logf("Encountered error %v loading sequence: %d", err, seq)
continue
}
if !seen[sm.subj] && subjectIsSubsetMatch(sm.subj, filter) {
ss.Msgs++
if ss.Last == 0 {
ss.Last = seq
}
if ss.First == 0 || seq < ss.First {
ss.First = seq
}
seen[sm.subj] = true
}
}
return ss
}
checkLastOnly := func(sseq uint64, filter string) {
t.Helper()
np, lvs := ms.NumPending(sseq, filter, true)
ss := sanityCheckLastOnly(sseq, filter)
if lvs != state.LastSeq {
t.Fatalf("Expected NumPending to return valid through last of %d but got %d", state.LastSeq, lvs)
}
if ss.Msgs != np {
t.Fatalf("NumPending of %d did not match ss.Msgs of %d", np, ss.Msgs)
}
}
startSeqs := []uint64{0, 1, 2, 200, 444, 555, 2222, 8888, 12_345, 28_222, 33_456, 44_400, 49_999}
checkSubs := []string{"foo.>", "*.bar.>", "foo.bar.*.baz", "*.bar.>", "*.foo.bar.*", "foo.foo.bar.baz"}
for _, filter := range checkSubs {
for _, start := range startSeqs {
check(start, filter)
checkLastOnly(start, filter)
}
}
}
func TestMemStoreInitialFirstSeq(t *testing.T) {
cfg := &StreamConfig{
Name: "zzz",
Storage: MemoryStorage,
FirstSeq: 1000,
}
ms, err := newMemStore(cfg)
require_NoError(t, err)
defer ms.Stop()
seq, _, err := ms.StoreMsg("A", nil, []byte("OK"))
require_NoError(t, err)
if seq != 1000 {
t.Fatalf("Message should have been sequence 1000 but was %d", seq)
}
seq, _, err = ms.StoreMsg("B", nil, []byte("OK"))
require_NoError(t, err)
if seq != 1001 {
t.Fatalf("Message should have been sequence 1001 but was %d", seq)
}
var state StreamState
ms.FastState(&state)
switch {
case state.Msgs != 2:
t.Fatalf("Expected 2 messages, got %d", state.Msgs)
case state.FirstSeq != 1000:
t.Fatalf("Expected first seq 1000, got %d", state.FirstSeq)
case state.LastSeq != 1001:
t.Fatalf("Expected last seq 1001, got %d", state.LastSeq)
}
}
func TestMemStoreDeleteBlocks(t *testing.T) {
cfg := &StreamConfig{
Name: "zzz",
Subjects: []string{"*"},
Storage: MemoryStorage,
}
ms, err := newMemStore(cfg)
require_NoError(t, err)
defer ms.Stop()
// Put in 10_000 msgs.
total := 10_000
for i := 0; i < total; i++ {
_, _, err := ms.StoreMsg("A", nil, []byte("OK"))
require_NoError(t, err)
}
// Now pick 5k random sequences.
delete := 5000
deleteMap := make(map[int]struct{}, delete)
for len(deleteMap) < delete {
deleteMap[rand.Intn(total)+1] = struct{}{}
}
// Now remove?
for seq := range deleteMap {
ms.RemoveMsg(uint64(seq))
}
var state StreamState
ms.FastState(&state)
// For now we just track via one dmap.
ms.mu.RLock()
dmap := ms.dmap.Clone()
ms.mu.RUnlock()
require_True(t, dmap.Size() == state.NumDeleted)
}
// https://github.com/nats-io/nats-server/issues/4850
func TestMemStoreGetSeqFromTimeWithLastDeleted(t *testing.T) {
cfg := &StreamConfig{
Name: "zzz",
Subjects: []string{"*"},
Storage: MemoryStorage,
}
ms, err := newMemStore(cfg)
require_NoError(t, err)
// Put in 1000 msgs.
total := 1000
var st time.Time
for i := 1; i <= total; i++ {
_, _, err := ms.StoreMsg("A", nil, []byte("OK"))
require_NoError(t, err)
if i == total/2 {
time.Sleep(100 * time.Millisecond)
st = time.Now()
}
}
// Delete last 100
for seq := total - 100; seq <= total; seq++ {
ms.RemoveMsg(uint64(seq))
}
// Make sure this does not panic with last sequence no longer accessible.
seq := ms.GetSeqFromTime(st)
// Make sure we get the right value.
require_Equal(t, seq, 501)
}
func TestMemStoreSkipMsgs(t *testing.T) {
cfg := &StreamConfig{
Name: "zzz",
Subjects: []string{"*"},
Storage: MemoryStorage,
}
ms, err := newMemStore(cfg)
require_NoError(t, err)
// Test on empty FS first.
// Make sure wrong starting sequence fails.
err = ms.SkipMsgs(10, 100)
require_Error(t, err, ErrSequenceMismatch)
err = ms.SkipMsgs(1, 100)
require_NoError(t, err)
state := ms.State()
require_Equal(t, state.FirstSeq, 101)
require_Equal(t, state.LastSeq, 100)
// Now add alot.
err = ms.SkipMsgs(101, 100_000)
require_NoError(t, err)
state = ms.State()
require_Equal(t, state.FirstSeq, 100_101)
require_Equal(t, state.LastSeq, 100_100)
// Now add in a message, and then skip to check dmap.
ms, err = newMemStore(cfg)
require_NoError(t, err)
ms.StoreMsg("foo", nil, nil)
err = ms.SkipMsgs(2, 10)
require_NoError(t, err)
state = ms.State()
require_Equal(t, state.FirstSeq, 1)
require_Equal(t, state.LastSeq, 11)
require_Equal(t, state.Msgs, 1)
require_Equal(t, state.NumDeleted, 10)
require_Equal(t, len(state.Deleted), 10)
// Check Fast State too.
state.Deleted = nil
ms.FastState(&state)
require_Equal(t, state.FirstSeq, 1)
require_Equal(t, state.LastSeq, 11)
require_Equal(t, state.Msgs, 1)
require_Equal(t, state.NumDeleted, 10)
}
// Bug would cause PurgeEx to fail if it encountered a deleted msg at sequence to delete up to.
func TestMemStorePurgeExWithDeletedMsgs(t *testing.T) {
cfg := &StreamConfig{
Name: "zzz",
Subjects: []string{"foo"},
Storage: MemoryStorage,
}
ms, err := newMemStore(cfg)
require_NoError(t, err)
defer ms.Stop()
msg := []byte("abc")
for i := 1; i <= 10; i++ {
ms.StoreMsg("foo", nil, msg)
}
ms.RemoveMsg(2)
ms.RemoveMsg(9) // This was the bug
n, err := ms.PurgeEx(_EMPTY_, 9, 0)
require_NoError(t, err)
require_Equal(t, n, 7)
var state StreamState
ms.FastState(&state)
require_Equal(t, state.FirstSeq, 10)
require_Equal(t, state.LastSeq, 10)
require_Equal(t, state.Msgs, 1)
}
// When all messages are deleted we should have a state of first = last + 1.
func TestMemStoreDeleteAllFirstSequenceCheck(t *testing.T) {
cfg := &StreamConfig{
Name: "zzz",
Subjects: []string{"foo"},
Storage: MemoryStorage,
}
ms, err := newMemStore(cfg)
require_NoError(t, err)
defer ms.Stop()
msg := []byte("abc")
for i := 1; i <= 10; i++ {
ms.StoreMsg("foo", nil, msg)
}
for seq := uint64(1); seq <= 10; seq++ {
ms.RemoveMsg(seq)
}
var state StreamState
ms.FastState(&state)
require_Equal(t, state.FirstSeq, 11)
require_Equal(t, state.LastSeq, 10)
require_Equal(t, state.Msgs, 0)
}
func TestMemStoreNumPendingMulti(t *testing.T) {
cfg := &StreamConfig{
Name: "zzz",
Subjects: []string{"ev.*"},
Storage: MemoryStorage,
}
ms, err := newMemStore(cfg)
require_NoError(t, err)
defer ms.Stop()
totalMsgs := 100_000
totalSubjects := 10_000
numFiltered := 5000
startSeq := uint64(5_000 + rand.Intn(90_000))
subjects := make([]string, 0, totalSubjects)
for i := 0; i < totalSubjects; i++ {
subjects = append(subjects, fmt.Sprintf("ev.%s", nuid.Next()))
}
// Put in 100k msgs with random subjects.
msg := bytes.Repeat([]byte("ZZZ"), 333)
for i := 0; i < totalMsgs; i++ {
_, _, err = ms.StoreMsg(subjects[rand.Intn(totalSubjects)], nil, msg)
require_NoError(t, err)
}
// Now we want to do a calculate NumPendingMulti.
filters := NewSublistNoCache()
for filters.Count() < uint32(numFiltered) {
filter := subjects[rand.Intn(totalSubjects)]
if !filters.HasInterest(filter) {
filters.Insert(&subscription{subject: []byte(filter)})
}
}
// Use new function.
total, _ := ms.NumPendingMulti(startSeq, filters, false)
// Check our results.
var checkTotal uint64
var smv StoreMsg
for seq := startSeq; seq <= uint64(totalMsgs); seq++ {
sm, err := ms.LoadMsg(seq, &smv)
require_NoError(t, err)
if filters.HasInterest(sm.subj) {
checkTotal++
}
}
require_Equal(t, total, checkTotal)
}
func TestMemStoreNumPendingBug(t *testing.T) {
cfg := &StreamConfig{
Name: "zzz",
Subjects: []string{"foo.*"},
Storage: MemoryStorage,
}
ms, err := newMemStore(cfg)
require_NoError(t, err)
defer ms.Stop()
// 12 msgs total
for _, subj := range []string{"foo.foo", "foo.bar", "foo.baz", "foo.zzz"} {
ms.StoreMsg("foo.aaa", nil, nil)
ms.StoreMsg(subj, nil, nil)
ms.StoreMsg(subj, nil, nil)
}
total, _ := ms.NumPending(4, "foo.*", false)
var checkTotal uint64
var smv StoreMsg
for seq := 4; seq <= 12; seq++ {
sm, err := ms.LoadMsg(uint64(seq), &smv)
require_NoError(t, err)
if subjectIsSubsetMatch(sm.subj, "foo.*") {
checkTotal++
}
}
require_Equal(t, total, checkTotal)
}
///////////////////////////////////////////////////////////////////////////
// Benchmarks
///////////////////////////////////////////////////////////////////////////
func Benchmark_MemStoreNumPendingWithLargeInteriorDeletesScan(b *testing.B) {
cfg := &StreamConfig{
Name: "zzz",
Subjects: []string{"foo.*.*"},
Storage: MemoryStorage,
}
ms, err := newMemStore(cfg)
require_NoError(b, err)
defer ms.Stop()
msg := []byte("abc")
ms.StoreMsg("foo.bar.baz", nil, msg)
for i := 1; i <= 1_000_000; i++ {
ms.SkipMsg()
}
ms.StoreMsg("foo.bar.baz", nil, msg)
b.ResetTimer()
for i := 0; i < b.N; i++ {
total, _ := ms.NumPending(600_000, "foo.*.baz", false)
if total != 1 {
b.Fatalf("Expected total of 2 got %d", total)
}
}
}
func Benchmark_MemStoreNumPendingWithLargeInteriorDeletesExclude(b *testing.B) {
cfg := &StreamConfig{
Name: "zzz",
Subjects: []string{"foo.*.*"},
Storage: MemoryStorage,
}
ms, err := newMemStore(cfg)
require_NoError(b, err)
defer ms.Stop()
msg := []byte("abc")
ms.StoreMsg("foo.bar.baz", nil, msg)
for i := 1; i <= 1_000_000; i++ {
ms.SkipMsg()
}
ms.StoreMsg("foo.bar.baz", nil, msg)
b.ResetTimer()
for i := 0; i < b.N; i++ {
total, _ := ms.NumPending(400_000, "foo.*.baz", false)
if total != 1 {
b.Fatalf("Expected total of 2 got %d", total)
}
}
}