github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/tests/integration/topic_stress_test.go (about) 1 //go:build integration 2 // +build integration 3 4 package integration 5 6 import ( 7 "context" 8 "errors" 9 "math/rand" 10 "runtime" 11 "strconv" 12 "strings" 13 "sync" 14 "sync/atomic" 15 "testing" 16 "time" 17 18 "github.com/stretchr/testify/require" 19 "golang.org/x/sync/errgroup" 20 21 "github.com/ydb-platform/ydb-go-sdk/v3" 22 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 23 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" 24 "github.com/ydb-platform/ydb-go-sdk/v3/topic/topicoptions" 25 "github.com/ydb-platform/ydb-go-sdk/v3/topic/topicreader" 26 "github.com/ydb-platform/ydb-go-sdk/v3/topic/topictypes" 27 "github.com/ydb-platform/ydb-go-sdk/v3/topic/topicwriter" 28 ) 29 30 func TestReadersWritersStress(t *testing.T) { 31 ctx := xtest.Context(t) 32 db := connect(t) 33 34 topicPrefix := db.Name() + "/stress-topic-" 35 consumerName := commonConsumerName 36 37 writeTime := time.Second * 10 38 topicCount := runtime.GOMAXPROCS(0) 39 if topicCount > 10 { 40 topicCount = 10 41 } 42 t.Log("topic count: ", topicCount) 43 44 topicPartitions := 3 45 writersPerTopic := topicPartitions * 2 46 readersPerTopic := 2 47 48 var topics []string 49 for i := 0; i < topicCount; i++ { 50 topicPath := topicPrefix + strconv.Itoa(i) 51 _ = db.Topic().Drop(ctx, topicPath) 52 err := db.Topic().Create(ctx, topicPath, 53 topicoptions.CreateWithMinActivePartitions(int64(topicPartitions)), 54 topicoptions.CreateWithConsumer(topictypes.Consumer{Name: consumerName}), 55 ) 56 require.NoError(t, err) 57 topics = append(topics, topicPath) 58 } 59 60 errGrp, grpCtx := errgroup.WithContext(ctx) 61 for _, topicOuter := range topics { 62 topicInner := topicOuter 63 errGrp.Go(func() error { 64 return stressTestInATopic( 65 grpCtx, 66 t, 67 db, 68 writeTime, 69 topicInner, 70 consumerName, 71 writersPerTopic, 72 readersPerTopic, 73 ) 74 }) 75 } 76 require.NoError(t, errGrp.Wait()) 77 } 78 79 func stressTestInATopic( 80 ctx context.Context, 81 t testing.TB, 82 db *ydb.Driver, 83 testTime time.Duration, 84 topicPath string, 85 consumerName string, 86 topicWriters, topicReaders int, 87 ) error { 88 maxMessagesInBatch := 5 89 var mStatus sync.Mutex 90 writeStatusWriterSeqno := map[string]int64{} 91 readStatusWriterMaxSeqNo := map[string]int64{} 92 93 var stopWrite atomic.Bool 94 95 writeToTopic := func(ctx context.Context, producerID string, wg *sync.WaitGroup) (resErr error) { 96 var writer *topicwriter.Writer 97 98 defer func() { 99 closeErr := writer.Close(context.Background()) 100 101 if resErr == nil && closeErr != nil { 102 resErr = closeErr 103 } 104 105 wg.Done() 106 }() 107 108 writer, err := db.Topic().StartWriter(topicPath, 109 topicoptions.WithProducerID(producerID), 110 topicoptions.WithSyncWrite(true), 111 topicoptions.WithWriterSetAutoSeqNo(false), 112 ) 113 if err != nil { 114 return xerrors.WithStackTrace(err) 115 } 116 117 seqNo := int64(0) 118 for !stopWrite.Load() { 119 messageCount := rand.Intn(maxMessagesInBatch) + 1 //nolint:gosec 120 var messages []topicwriter.Message 121 for i := 0; i < messageCount; i++ { 122 seqNo++ 123 message := topicwriter.Message{ 124 SeqNo: seqNo, 125 Data: strings.NewReader(strconv.FormatInt(seqNo, 10) + "-content"), 126 } 127 messages = append(messages, message) 128 } 129 err = writer.Write(ctx, messages...) 130 if err != nil { 131 return err 132 } 133 mStatus.Lock() 134 writeStatusWriterSeqno[producerID] = seqNo 135 mStatus.Unlock() 136 } 137 return nil 138 } 139 140 readFromTopic := func(ctx context.Context, wg *sync.WaitGroup) (resErr error) { 141 var reader *topicreader.Reader 142 defer func() { 143 closeErr := reader.Close(context.Background()) 144 145 if resErr == nil && closeErr != nil { 146 resErr = closeErr 147 } 148 149 if ctx.Err() != nil && errors.Is(resErr, context.Canceled) { 150 resErr = nil 151 } 152 153 wg.Done() 154 }() 155 156 reader, err := db.Topic().StartReader(consumerName, topicoptions.ReadTopic(topicPath)) 157 if err != nil { 158 return err 159 } 160 161 for { 162 mess, err := reader.ReadMessage(ctx) 163 if err != nil { 164 return err 165 } 166 167 // store max readed seqno for every producer id 168 mStatus.Lock() 169 oldSeq := readStatusWriterMaxSeqNo[mess.ProducerID] 170 if mess.SeqNo > oldSeq { 171 readStatusWriterMaxSeqNo[mess.ProducerID] = mess.SeqNo 172 } 173 mStatus.Unlock() 174 175 err = reader.Commit(ctx, mess) 176 if err != nil { 177 return err 178 } 179 } 180 } 181 182 var writersWG sync.WaitGroup 183 writersErrors := make(chan error, topicWriters) 184 for i := 0; i < topicWriters; i++ { 185 producerID := "producer-" + strconv.Itoa(i) 186 writersWG.Add(1) 187 go func() { 188 writersErrors <- writeToTopic(ctx, producerID, &writersWG) 189 }() 190 } 191 192 var readersWG sync.WaitGroup 193 readersError := make(chan error, topicReaders) 194 readCtx, stopReader := context.WithCancel(ctx) 195 defer stopReader() 196 197 for i := 0; i < topicReaders; i++ { 198 readersWG.Add(1) 199 go func() { 200 readersError <- readFromTopic(readCtx, &readersWG) 201 }() 202 } 203 204 time.Sleep(testTime) 205 stopWrite.Store(true) 206 207 xtest.WaitGroup(t, &writersWG) 208 209 for i := 0; i < topicWriters; i++ { 210 err := <-writersErrors 211 if err != nil { 212 return err 213 } 214 } 215 216 xtest.SpinWaitProgressWithTimeout(t, time.Minute, func() (progressValue interface{}, finished bool) { 217 time.Sleep(time.Millisecond) 218 needReadMessages := int64(0) 219 mStatus.Lock() 220 for producerID, writtenSeqNo := range writeStatusWriterSeqno { 221 readedSeqNo := readStatusWriterMaxSeqNo[producerID] 222 needReadMessages += writtenSeqNo - readedSeqNo 223 } 224 mStatus.Unlock() 225 return needReadMessages, needReadMessages == 0 226 }) 227 228 stopReader() 229 xtest.WaitGroup(t, &readersWG) 230 231 for i := 0; i < topicReaders; i++ { 232 err := <-readersError 233 if err != nil { 234 return err 235 } 236 } 237 238 //nolint:gocritic 239 //// check about all messages are committed 240 // https://github.com/ydb-platform/ydb-go-sdk/issues/531 241 //reader, err := db.Topic().StartReader(consumerName, topicoptions.ReadTopic(topicPath)) 242 //if err != nil { 243 // return err 244 //} 245 //readWithTimeout, readCancel := context.WithTimeout(ctx, time.Second/10) 246 //defer readCancel() 247 //_, err = reader.ReadMessage(readWithTimeout) 248 //t.Log("err: ", err) 249 //require.ErrorIs(t, err, context.DeadlineExceeded) 250 251 return nil 252 }