You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							208 lines
						
					
					
						
							5.8 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							208 lines
						
					
					
						
							5.8 KiB
						
					
					
				| package integration | |
| 
 | |
| import ( | |
| 	"fmt" | |
| 	"sync" | |
| 	"sync/atomic" | |
| 	"testing" | |
| 	"time" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/glog" | |
| ) | |
| 
 | |
| // TestResumeMillionRecords_Fixed - Fixed version with better concurrency handling | |
| func TestResumeMillionRecords_Fixed(t *testing.T) { | |
| 	const ( | |
| 		totalRecords  = 1000000 | |
| 		numPartitions = int32(8) | |
| 		numProducers  = 4 | |
| 		brokerAddr    = "localhost:17777" | |
| 		batchSize     = 100 // Process in smaller batches to avoid overwhelming | |
| 	) | |
| 
 | |
| 	// Create direct broker client | |
| 	client, err := NewDirectBrokerClient(brokerAddr) | |
| 	if err != nil { | |
| 		t.Fatalf("Failed to create direct broker client: %v", err) | |
| 	} | |
| 	defer client.Close() | |
| 
 | |
| 	topicName := fmt.Sprintf("resume-million-test-%d", time.Now().Unix()) | |
| 
 | |
| 	// Create topic | |
| 	glog.Infof("Creating topic %s with %d partitions for RESUMED test", topicName, numPartitions) | |
| 	err = client.ConfigureTopic(topicName, numPartitions) | |
| 	if err != nil { | |
| 		t.Fatalf("Failed to configure topic: %v", err) | |
| 	} | |
| 
 | |
| 	// Performance tracking | |
| 	var totalProduced int64 | |
| 	var totalErrors int64 | |
| 	startTime := time.Now() | |
| 
 | |
| 	// Progress tracking | |
| 	ticker := time.NewTicker(5 * time.Second) // More frequent updates | |
| 	defer ticker.Stop() | |
| 
 | |
| 	go func() { | |
| 		for range ticker.C { | |
| 			produced := atomic.LoadInt64(&totalProduced) | |
| 			errors := atomic.LoadInt64(&totalErrors) | |
| 			elapsed := time.Since(startTime) | |
| 			rate := float64(produced) / elapsed.Seconds() | |
| 			progressPercent := float64(produced) / float64(totalRecords) * 100 | |
| 
 | |
| 			glog.Infof("PROGRESS: %d/%d records (%.1f%%), rate: %.0f records/sec, errors: %d", | |
| 				produced, totalRecords, progressPercent, rate, errors) | |
| 
 | |
| 			if produced >= totalRecords { | |
| 				return | |
| 			} | |
| 		} | |
| 	}() | |
| 
 | |
| 	// Fixed producer function with better error handling | |
| 	producer := func(producerID int, recordsPerProducer int) error { | |
| 		defer glog.Infof("Producer %d FINISHED", producerID) | |
| 
 | |
| 		// Create dedicated clients per producer to avoid contention | |
| 		producerClient, err := NewDirectBrokerClient(brokerAddr) | |
| 		if err != nil { | |
| 			return fmt.Errorf("producer %d failed to create client: %v", producerID, err) | |
| 		} | |
| 		defer producerClient.Close() | |
| 
 | |
| 		successCount := 0 | |
| 		for i := 0; i < recordsPerProducer; i++ { | |
| 			recordID := producerID*recordsPerProducer + i | |
| 
 | |
| 			// Generate test record | |
| 			testRecord := GenerateMockTestRecord(recordID) | |
| 			key, value := SerializeMockTestRecord(testRecord) | |
| 
 | |
| 			partition := int32(testRecord.UserID % int64(numPartitions)) | |
| 
 | |
| 			// Produce with retry logic | |
| 			maxRetries := 3 | |
| 			var lastErr error | |
| 			success := false | |
| 
 | |
| 			for retry := 0; retry < maxRetries; retry++ { | |
| 				err := producerClient.PublishRecord(topicName, partition, key, value) | |
| 				if err == nil { | |
| 					success = true | |
| 					break | |
| 				} | |
| 				lastErr = err | |
| 				time.Sleep(time.Duration(retry+1) * 100 * time.Millisecond) // Exponential backoff | |
| 			} | |
| 
 | |
| 			if success { | |
| 				atomic.AddInt64(&totalProduced, 1) | |
| 				successCount++ | |
| 			} else { | |
| 				atomic.AddInt64(&totalErrors, 1) | |
| 				if atomic.LoadInt64(&totalErrors) < 10 { | |
| 					glog.Errorf("Producer %d failed record %d after retries: %v", producerID, recordID, lastErr) | |
| 				} | |
| 			} | |
| 
 | |
| 			// Batch progress logging | |
| 			if successCount > 0 && successCount%10000 == 0 { | |
| 				glog.Infof("Producer %d: %d/%d records completed", producerID, successCount, recordsPerProducer) | |
| 			} | |
| 
 | |
| 			// Small delay to prevent overwhelming the broker | |
| 			if i > 0 && i%batchSize == 0 { | |
| 				time.Sleep(10 * time.Millisecond) | |
| 			} | |
| 		} | |
| 
 | |
| 		glog.Infof("Producer %d completed: %d successful, %d errors", | |
| 			producerID, successCount, recordsPerProducer-successCount) | |
| 		return nil | |
| 	} | |
| 
 | |
| 	// Start concurrent producers | |
| 	glog.Infof("Starting FIXED %d producers for %d records total", numProducers, totalRecords) | |
| 
 | |
| 	var wg sync.WaitGroup | |
| 	recordsPerProducer := totalRecords / numProducers | |
| 
 | |
| 	for i := 0; i < numProducers; i++ { | |
| 		wg.Add(1) | |
| 		go func(producerID int) { | |
| 			defer wg.Done() | |
| 			if err := producer(producerID, recordsPerProducer); err != nil { | |
| 				glog.Errorf("Producer %d FAILED: %v", producerID, err) | |
| 			} | |
| 		}(i) | |
| 	} | |
| 
 | |
| 	// Wait for completion with timeout | |
| 	done := make(chan bool) | |
| 	go func() { | |
| 		wg.Wait() | |
| 		done <- true | |
| 	}() | |
| 
 | |
| 	select { | |
| 	case <-done: | |
| 		glog.Infof("All producers completed normally") | |
| 	case <-time.After(30 * time.Minute): // 30-minute timeout | |
| 		glog.Errorf("Test timed out after 30 minutes") | |
| 		t.Errorf("Test timed out") | |
| 		return | |
| 	} | |
| 
 | |
| 	produceTime := time.Since(startTime) | |
| 	finalProduced := atomic.LoadInt64(&totalProduced) | |
| 	finalErrors := atomic.LoadInt64(&totalErrors) | |
| 
 | |
| 	// Performance results | |
| 	throughputPerSec := float64(finalProduced) / produceTime.Seconds() | |
| 	dataVolumeMB := float64(finalProduced) * 300 / (1024 * 1024) | |
| 	throughputMBPerSec := dataVolumeMB / produceTime.Seconds() | |
| 	successRate := float64(finalProduced) / float64(totalRecords) * 100 | |
| 
 | |
| 	glog.Infof("\n"+ | |
| 		"=== FINAL MILLION RECORD TEST RESULTS ===\n"+ | |
| 		"==========================================\n"+ | |
| 		"Records produced: %d / %d\n"+ | |
| 		"Production time: %v\n"+ | |
| 		"Average throughput: %.0f records/sec\n"+ | |
| 		"Data volume: %.1f MB\n"+ | |
| 		"Bandwidth: %.1f MB/sec\n"+ | |
| 		"Errors: %d (%.2f%%)\n"+ | |
| 		"Success rate: %.1f%%\n"+ | |
| 		"Partitions used: %d\n"+ | |
| 		"Concurrent producers: %d\n", | |
| 		finalProduced, totalRecords, | |
| 		produceTime, | |
| 		throughputPerSec, | |
| 		dataVolumeMB, | |
| 		throughputMBPerSec, | |
| 		finalErrors, | |
| 		float64(finalErrors)/float64(totalRecords)*100, | |
| 		successRate, | |
| 		numPartitions, | |
| 		numProducers, | |
| 	) | |
| 
 | |
| 	// Test assertions | |
| 	if finalProduced < int64(totalRecords*0.95) { // Allow 5% tolerance | |
| 		t.Errorf("Too few records produced: %d < %d (95%% of target)", finalProduced, int64(float64(totalRecords)*0.95)) | |
| 	} | |
| 
 | |
| 	if finalErrors > int64(totalRecords*0.05) { // Error rate should be < 5% | |
| 		t.Errorf("Too many errors: %d > %d (5%% of target)", finalErrors, int64(float64(totalRecords)*0.05)) | |
| 	} | |
| 
 | |
| 	if throughputPerSec < 100 { | |
| 		t.Errorf("Throughput too low: %.0f records/sec (expected > 100)", throughputPerSec) | |
| 	} | |
| 
 | |
| 	glog.Infof("🏆 MILLION RECORD KAFKA INTEGRATION TEST COMPLETED SUCCESSFULLY!") | |
| } | |
| 
 |