milvus/internal/datanode/importv2/memory_allocator_test.go
yihao.dai 9cbd194c6b
fix: Prevent import from generating small binlogs (#43132)
- Introduce dynamic buffer sizing to avoid generating small binlogs
during import
- Refactor import slot calculation based on CPU and memory constraints
- Implement dynamic pool sizing for sync manager and import tasks
according to CPU core count

issue: https://github.com/milvus-io/milvus/issues/43131

---------

Signed-off-by: bigsheeper <yihao.dai@zilliz.com>
2025-07-07 21:32:47 +08:00

158 lines
5.3 KiB
Go

// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 importv2
import (
"testing"
"github.com/stretchr/testify/assert"
)
// TestMemoryAllocatorBasicOperations tests basic memory allocation and release operations
func TestMemoryAllocatorBasicOperations(t *testing.T) {
// Create memory allocator with 1GB system memory
ma := NewMemoryAllocator(1024 * 1024 * 1024)
// Test initial state
assert.Equal(t, int64(0), ma.(*memoryAllocator).usedMemory)
// Test memory allocation for task 1
success := ma.TryAllocate(1, 50*1024*1024) // 50MB for task 1
assert.True(t, success)
assert.Equal(t, int64(50*1024*1024), ma.(*memoryAllocator).usedMemory)
// Test memory allocation for task 2
success = ma.TryAllocate(2, 50*1024*1024) // 50MB for task 2
assert.True(t, success)
assert.Equal(t, int64(100*1024*1024), ma.(*memoryAllocator).usedMemory)
// Test memory release for task 1
ma.Release(1, 50*1024*1024)
assert.Equal(t, int64(50*1024*1024), ma.(*memoryAllocator).usedMemory)
// Test memory release for task 2
ma.Release(2, 50*1024*1024)
assert.Equal(t, int64(0), ma.(*memoryAllocator).usedMemory)
}
// TestMemoryAllocatorMemoryLimit tests memory limit enforcement
func TestMemoryAllocatorMemoryLimit(t *testing.T) {
// Create memory allocator with 1GB system memory
ma := NewMemoryAllocator(1024 * 1024 * 1024)
// Get the memory limit based on system memory and configuration percentage
memoryLimit := ma.(*memoryAllocator).systemTotalMemory
// Use a reasonable test size that should be within limits
testSize := memoryLimit / 10 // Use 10% of available memory
// Allocate memory up to the limit
success := ma.TryAllocate(1, testSize)
assert.True(t, success)
assert.Equal(t, testSize, ma.(*memoryAllocator).usedMemory)
// Try to allocate more memory than available (should fail)
success = ma.TryAllocate(2, memoryLimit)
assert.False(t, success)
assert.Equal(t, testSize, ma.(*memoryAllocator).usedMemory) // Should remain unchanged
// Release the allocated memory
ma.Release(1, testSize)
assert.Equal(t, int64(0), ma.(*memoryAllocator).usedMemory)
}
// TestMemoryAllocatorConcurrentAccess tests concurrent memory allocation and release
func TestMemoryAllocatorConcurrentAccess(t *testing.T) {
// Create memory allocator with 1GB system memory
ma := NewMemoryAllocator(1024 * 1024 * 1024)
// Test concurrent memory requests
done := make(chan bool, 10)
for i := 0; i < 10; i++ {
taskID := int64(i + 1)
go func() {
success := ma.TryAllocate(taskID, 50*1024*1024) // 50MB each
if success {
ma.Release(taskID, 50*1024*1024)
}
done <- true
}()
}
// Wait for all goroutines to complete
for i := 0; i < 10; i++ {
<-done
}
// Verify final state - should be 0 or the sum of remaining allocations
// depending on memory limit and concurrent execution
finalMemory := ma.(*memoryAllocator).usedMemory
assert.GreaterOrEqual(t, finalMemory, int64(0))
}
// TestMemoryAllocatorNegativeRelease tests handling of negative memory release
func TestMemoryAllocatorNegativeRelease(t *testing.T) {
// Create memory allocator with 1GB system memory
ma := NewMemoryAllocator(1024 * 1024 * 1024)
// Allocate some memory
success := ma.TryAllocate(1, 100*1024*1024) // 100MB
assert.True(t, success)
assert.Equal(t, int64(100*1024*1024), ma.(*memoryAllocator).usedMemory)
// Release more than allocated (should not go negative)
ma.Release(1, 200*1024*1024) // 200MB
assert.Equal(t, int64(0), ma.(*memoryAllocator).usedMemory) // Should be reset to 0
}
// TestMemoryAllocatorMultipleTasks tests memory management for multiple tasks
func TestMemoryAllocatorMultipleTasks(t *testing.T) {
// Create memory allocator with 1GB system memory
ma := NewMemoryAllocator(1024 * 1024 * 1024)
// Allocate memory for multiple tasks with smaller sizes
taskIDs := []int64{1, 2, 3, 4, 5}
sizes := []int64{20, 30, 25, 15, 35} // Total: 125MB
for i, taskID := range taskIDs {
success := ma.TryAllocate(taskID, sizes[i]*1024*1024)
assert.True(t, success)
}
// Verify total used memory
expectedTotal := int64(0)
for _, size := range sizes {
expectedTotal += size * 1024 * 1024
}
assert.Equal(t, expectedTotal, ma.(*memoryAllocator).usedMemory)
// Release memory for specific tasks
ma.Release(2, 30*1024*1024) // Release task 2
ma.Release(4, 15*1024*1024) // Release task 4
// Verify updated memory usage
expectedTotal = (20 + 25 + 35) * 1024 * 1024 // 80MB
assert.Equal(t, expectedTotal, ma.(*memoryAllocator).usedMemory)
// Release remaining tasks
ma.Release(1, 20*1024*1024)
ma.Release(3, 25*1024*1024)
ma.Release(5, 35*1024*1024)
// Verify final state
assert.Equal(t, int64(0), ma.(*memoryAllocator).usedMemory)
}