Files
wehub-resource-sync 498b235461
Build and test / Build and test AMD64 Ubuntu 22.04 (push) Failing after 0s
Publish Builder / amazonlinux2023 (push) Failing after 1s
Build and test / UT for Go (push) Has been skipped
Publish KRTE Images / KRTE (push) Failing after 1s
Build and test / Integration Test (push) Has been skipped
Build and test / Upload Code Coverage (push) Has been skipped
Publish Builder / rockylinux9 (push) Failing after 1s
Publish Builder / ubuntu22.04 (push) Failing after 0s
Publish Builder / ubuntu24.04 (push) Failing after 0s
Publish Gpu Builder / publish-gpu-builder (push) Failing after 1s
Publish Test Images / PyTest (push) Failing after 0s
Build and test / UT for Cpp (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:17 +08:00

518 lines
17 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 compaction
import (
"context"
"path/filepath"
"testing"
"github.com/apache/arrow/go/v17/arrow"
"github.com/apache/arrow/go/v17/arrow/array"
"github.com/apache/arrow/go/v17/arrow/memory"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/milvus-io/milvus-proto/go-api/v3/schemapb"
"github.com/milvus-io/milvus/internal/storage"
"github.com/milvus-io/milvus/internal/storagecommon"
"github.com/milvus-io/milvus/internal/storagev2/packed"
"github.com/milvus-io/milvus/pkg/v3/proto/datapb"
"github.com/milvus-io/milvus/pkg/v3/proto/indexpb"
"github.com/milvus-io/milvus/pkg/v3/util/paramtable"
"github.com/milvus-io/milvus/pkg/v3/util/typeutil"
)
func TestCommonSuite(t *testing.T) {
suite.Run(t, new(CommonSuite))
}
type CommonSuite struct {
suite.Suite
}
func (s *CommonSuite) SetupSuite() {
paramtable.Init()
}
func (s *CommonSuite) TestComposeDeleteFromDeltalogs() {
ctx := context.Background()
tests := []struct {
name string
pkType schemapb.DataType
setupDeltalogs func() (map[string][]byte, []*datapb.FieldBinlog)
expectedCount int
verifyResults func(pk2Ts map[any]typeutil.Timestamp)
}{
{
name: "Int64 PK - single deltalog",
pkType: schemapb.DataType_Int64,
setupDeltalogs: func() (map[string][]byte, []*datapb.FieldBinlog) {
blob := s.createTestDeltaLog(schemapb.DataType_Int64, []int64{1, 2, 3, 4, 5}, []int64{1000, 1001, 1002, 1003, 1004})
deltalogs := []*datapb.FieldBinlog{
{
FieldID: 100,
Binlogs: []*datapb.Binlog{{LogPath: "/test/deltalog.bin"}},
},
}
return map[string][]byte{"/test/deltalog.bin": blob.Value}, deltalogs
},
expectedCount: 5,
verifyResults: func(pk2Ts map[any]typeutil.Timestamp) {
s.Equal(typeutil.Timestamp(1000), pk2Ts[int64(1)])
s.Equal(typeutil.Timestamp(1001), pk2Ts[int64(2)])
s.Equal(typeutil.Timestamp(1002), pk2Ts[int64(3)])
s.Equal(typeutil.Timestamp(1003), pk2Ts[int64(4)])
s.Equal(typeutil.Timestamp(1004), pk2Ts[int64(5)])
},
},
{
name: "VarChar PK - single deltalog",
pkType: schemapb.DataType_VarChar,
setupDeltalogs: func() (map[string][]byte, []*datapb.FieldBinlog) {
blob := s.createTestDeltaLog(schemapb.DataType_VarChar, []string{"pk_1", "pk_2", "pk_3"}, []int64{2000, 2001, 2002})
deltalogs := []*datapb.FieldBinlog{
{
FieldID: 100,
Binlogs: []*datapb.Binlog{{LogPath: "/test/deltalog.bin"}},
},
}
return map[string][]byte{"/test/deltalog.bin": blob.Value}, deltalogs
},
expectedCount: 3,
verifyResults: func(pk2Ts map[any]typeutil.Timestamp) {
s.Equal(typeutil.Timestamp(2000), pk2Ts["pk_1"])
s.Equal(typeutil.Timestamp(2001), pk2Ts["pk_2"])
s.Equal(typeutil.Timestamp(2002), pk2Ts["pk_3"])
},
},
{
name: "Multiple deltalogs without duplicates",
pkType: schemapb.DataType_Int64,
setupDeltalogs: func() (map[string][]byte, []*datapb.FieldBinlog) {
blob1 := s.createTestDeltaLog(schemapb.DataType_Int64, []int64{1, 2, 3}, []int64{1000, 1001, 1002})
blob2 := s.createTestDeltaLog(schemapb.DataType_Int64, []int64{4, 5, 6}, []int64{2000, 2001, 2002})
deltalogs := []*datapb.FieldBinlog{
{
FieldID: 100,
Binlogs: []*datapb.Binlog{{LogPath: "/test/deltalog1.bin"}},
},
{
FieldID: 100,
Binlogs: []*datapb.Binlog{{LogPath: "/test/deltalog2.bin"}},
},
}
return map[string][]byte{
"/test/deltalog1.bin": blob1.Value,
"/test/deltalog2.bin": blob2.Value,
}, deltalogs
},
expectedCount: 6,
verifyResults: func(pk2Ts map[any]typeutil.Timestamp) {
s.Equal(typeutil.Timestamp(1000), pk2Ts[int64(1)])
s.Equal(typeutil.Timestamp(2000), pk2Ts[int64(4)])
},
},
{
name: "Duplicate PKs - keep newer timestamps",
pkType: schemapb.DataType_Int64,
setupDeltalogs: func() (map[string][]byte, []*datapb.FieldBinlog) {
blob1 := s.createTestDeltaLog(schemapb.DataType_Int64, []int64{1, 2, 3}, []int64{1000, 1001, 1002})
blob2 := s.createTestDeltaLog(schemapb.DataType_Int64, []int64{1, 2, 3}, []int64{2000, 2001, 2002})
deltalogs := []*datapb.FieldBinlog{
{
FieldID: 100,
Binlogs: []*datapb.Binlog{{LogPath: "/test/deltalog1.bin"}},
},
{
FieldID: 100,
Binlogs: []*datapb.Binlog{{LogPath: "/test/deltalog2.bin"}},
},
}
return map[string][]byte{
"/test/deltalog1.bin": blob1.Value,
"/test/deltalog2.bin": blob2.Value,
}, deltalogs
},
expectedCount: 3,
verifyResults: func(pk2Ts map[any]typeutil.Timestamp) {
s.Equal(typeutil.Timestamp(2000), pk2Ts[int64(1)])
s.Equal(typeutil.Timestamp(2001), pk2Ts[int64(2)])
s.Equal(typeutil.Timestamp(2002), pk2Ts[int64(3)])
},
},
{
name: "Duplicate PKs - skip older timestamps",
pkType: schemapb.DataType_Int64,
setupDeltalogs: func() (map[string][]byte, []*datapb.FieldBinlog) {
blob1 := s.createTestDeltaLog(schemapb.DataType_Int64, []int64{1, 2, 3}, []int64{2000, 2001, 2002})
blob2 := s.createTestDeltaLog(schemapb.DataType_Int64, []int64{1, 2, 3}, []int64{1000, 1001, 1002})
deltalogs := []*datapb.FieldBinlog{
{
FieldID: 100,
Binlogs: []*datapb.Binlog{{LogPath: "/test/deltalog1.bin"}},
},
{
FieldID: 100,
Binlogs: []*datapb.Binlog{{LogPath: "/test/deltalog2.bin"}},
},
}
return map[string][]byte{
"/test/deltalog1.bin": blob1.Value,
"/test/deltalog2.bin": blob2.Value,
}, deltalogs
},
expectedCount: 3,
verifyResults: func(pk2Ts map[any]typeutil.Timestamp) {
s.Equal(typeutil.Timestamp(2000), pk2Ts[int64(1)])
s.Equal(typeutil.Timestamp(2001), pk2Ts[int64(2)])
s.Equal(typeutil.Timestamp(2002), pk2Ts[int64(3)])
},
},
{
name: "Empty deltalogs",
pkType: schemapb.DataType_Int64,
setupDeltalogs: func() (map[string][]byte, []*datapb.FieldBinlog) {
return map[string][]byte{}, []*datapb.FieldBinlog{}
},
expectedCount: 0,
verifyResults: func(pk2Ts map[any]typeutil.Timestamp) {},
},
}
for _, tt := range tests {
s.Run(tt.name, func() {
blobs, deltalogs := tt.setupDeltalogs()
var options []storage.RwOption
if len(blobs) > 0 {
options = []storage.RwOption{
storage.WithVersion(storage.StorageV1),
storage.WithDownloader(func(ctx context.Context, paths []string) ([][]byte, error) {
result := make([][]byte, len(paths))
for i, path := range paths {
result[i] = blobs[path]
}
return result, nil
}),
}
}
pk2Ts, err := ComposeDeleteFromDeltalogsV1(ctx, tt.pkType, deltalogs, options...)
s.NoError(err)
s.NotNil(pk2Ts)
s.Equal(tt.expectedCount, len(pk2Ts))
if tt.verifyResults != nil {
tt.verifyResults(pk2Ts)
}
})
}
}
// Helper functions to create test deltalog data
// createTestDeltaLog creates a deltalog blob with the given PKs and timestamps.
// For Int64 PKs, pass []int64; for VarChar PKs, pass []string.
func (s *CommonSuite) createTestDeltaLog(pkType schemapb.DataType, pks any, tss []int64) *storage.Blob {
var record storage.Record
switch pkType {
case schemapb.DataType_Int64:
int64Pks := pks.([]int64)
require.Equal(s.T(), len(int64Pks), len(tss), "pks and tss must have same length")
record = s.createTestRecord(pkType, int64Pks, nil, tss)
case schemapb.DataType_VarChar:
stringPks := pks.([]string)
require.Equal(s.T(), len(stringPks), len(tss), "pks and tss must have same length")
record = s.createTestRecord(pkType, nil, stringPks, tss)
default:
s.FailNow("unsupported pk type")
return nil
}
defer record.Release()
blob := &storage.Blob{}
path := "/test/deltalog.bin"
writer, err := storage.NewLegacyDeltalogWriter(1, 1, 1, 0, pkType,
func(ctx context.Context, kvs map[string][]byte) error {
blob.Value = kvs[path]
blob.Key = path
return nil
}, path)
require.NoError(s.T(), err)
err = writer.Write(record)
require.NoError(s.T(), err)
err = writer.Close()
require.NoError(s.T(), err)
return blob
}
func (s *CommonSuite) createTestRecord(pkType schemapb.DataType, int64Pks []int64, stringPks []string, tss []int64) storage.Record {
allocator := memory.DefaultAllocator
var pkArray arrow.Array
var numRows int
switch pkType {
case schemapb.DataType_Int64:
builder := array.NewInt64Builder(allocator)
defer builder.Release()
for _, pk := range int64Pks {
builder.Append(pk)
}
pkArray = builder.NewArray()
numRows = len(int64Pks)
case schemapb.DataType_VarChar:
builder := array.NewStringBuilder(allocator)
defer builder.Release()
for _, pk := range stringPks {
builder.Append(pk)
}
pkArray = builder.NewArray()
numRows = len(stringPks)
default:
s.FailNow("unsupported pk type")
}
require.Equal(s.T(), numRows, len(tss), "number of pks and tss must match")
// Create timestamp array
tsBuilder := array.NewInt64Builder(allocator)
defer tsBuilder.Release()
for _, ts := range tss {
tsBuilder.Append(ts)
}
tsArray := tsBuilder.NewArray()
// Create arrow schema
var pkFieldType arrow.DataType
if pkType == schemapb.DataType_Int64 {
pkFieldType = arrow.PrimitiveTypes.Int64
} else {
pkFieldType = arrow.BinaryTypes.String
}
pkArrowField := arrow.Field{Name: "pk", Type: pkFieldType, Nullable: false}
tsField := arrow.Field{Name: "ts", Type: arrow.PrimitiveTypes.Int64, Nullable: false}
schema := arrow.NewSchema([]arrow.Field{pkArrowField, tsField}, nil)
record := array.NewRecord(schema, []arrow.Array{pkArray, tsArray}, int64(numRows))
field2Col := map[int64]int{
0: 0, // pk column
1: 1, // ts column
}
return storage.NewSimpleArrowRecord(record, field2Col)
}
// writeV2DeltaLog writes a V2 deltalog parquet file at the given path.
// path should follow production pattern: filepath.Join(rootPath, "delta_log/collID/partID/segID/logID")
func (s *CommonSuite) writeV2DeltaLog(
ctx context.Context,
pkType schemapb.DataType,
pks any,
tss []int64,
path string,
storageConfig *indexpb.StorageConfig,
) {
t := s.T()
var record storage.Record
switch pkType {
case schemapb.DataType_Int64:
int64Pks := pks.([]int64)
record = s.createTestRecord(pkType, int64Pks, nil, tss)
case schemapb.DataType_VarChar:
stringPks := pks.([]string)
record = s.createTestRecord(pkType, nil, stringPks, tss)
default:
s.FailNow("unsupported pk type")
}
defer record.Release()
writer, err := storage.NewDeltalogWriter(ctx, 1, 2, 3, 101, pkType, path,
storage.WithVersion(storage.StorageV2),
storage.WithStorageConfig(storageConfig))
require.NoError(t, err)
err = writer.Write(record)
require.NoError(t, err)
err = writer.Close()
require.NoError(t, err)
}
// createBaseManifest creates a base manifest via FFIPackedWriter for V2 deltalog tests.
// basePath should follow production pattern: filepath.Join(rootPath, "insert_log/collID/partID/segID")
func (s *CommonSuite) createBaseManifest(basePath string, storageConfig *indexpb.StorageConfig) string {
t := s.T()
arrowSchema := arrow.NewSchema([]arrow.Field{
{
Name: "pk",
Type: arrow.PrimitiveTypes.Int64,
Nullable: false,
Metadata: arrow.NewMetadata([]string{packed.ArrowFieldIdMetadataKey}, []string{"100"}),
},
{
Name: "vector",
Type: &arrow.FixedSizeBinaryType{ByteWidth: 16},
Nullable: false,
Metadata: arrow.NewMetadata([]string{packed.ArrowFieldIdMetadataKey}, []string{"101"}),
},
}, nil)
columnGroups := []storagecommon.ColumnGroup{
{Columns: []int{0, 1}, GroupID: storagecommon.DefaultShortColumnGroupID},
}
pw, err := packed.NewFFIPackedWriter(basePath, arrowSchema, columnGroups, storageConfig, nil)
require.NoError(t, err)
// Write minimal data to create a valid manifest
b := array.NewRecordBuilder(memory.DefaultAllocator, arrowSchema)
defer b.Release()
b.Field(0).(*array.Int64Builder).Append(1)
vectorBytes := make([]byte, 16)
b.Field(1).(*array.FixedSizeBinaryBuilder).Append(vectorBytes)
rec := b.NewRecord()
defer rec.Release()
err = pw.WriteRecordBatch(rec)
require.NoError(t, err)
out, err := pw.Close()
require.NoError(t, err)
defer out.Destroy()
manifestPath, err := packed.CommitManifestUpdates(basePath, packed.ManifestEarliest, storageConfig,
&packed.ManifestUpdates{NewFiles: out})
require.NoError(t, err)
return manifestPath
}
func (s *CommonSuite) TestComposeDeleteFromDeltalogsV2() {
ctx := context.Background()
pt := paramtable.Get()
pt.Save(pt.CommonCfg.StorageType.Key, "local")
dir := s.T().TempDir()
pt.Save(pt.LocalStorageCfg.Path.Key, dir)
s.T().Cleanup(func() {
pt.Reset(pt.CommonCfg.StorageType.Key)
pt.Reset(pt.LocalStorageCfg.Path.Key)
})
storageConfig := &indexpb.StorageConfig{
RootPath: dir,
StorageType: "local",
}
// Options that match production callers: downloader (for V1 validate) + storageConfig (for V2 ops)
options := []storage.RwOption{
storage.WithDownloader(func(ctx context.Context, paths []string) ([][]byte, error) {
return nil, nil
}),
storage.WithStorageConfig(storageConfig),
}
s.Run("V2 manifest - Int64 PK", func() {
// Use basePath matching production pattern: path.Join(rootPath, "insert_log", collID, partID, segID)
basePath := filepath.Join(dir, "insert_log/1/2/3_int64")
manifestPath := s.createBaseManifest(basePath, storageConfig)
// deltaPath matching production: path.Join(rootPath, "delta_log", collID, partID, segID, logID)
deltaPath := filepath.Join(dir, "delta_log/1/2/3/101")
s.writeV2DeltaLog(ctx, schemapb.DataType_Int64, []int64{10, 20, 30}, []int64{1000, 1001, 1002}, deltaPath, storageConfig)
newManifest, err := packed.AddDeltaLogsToManifest(manifestPath, storageConfig, []packed.DeltaLogEntry{
{Path: deltaPath, NumEntries: 3},
})
s.Require().NoError(err)
segment := &datapb.CompactionSegmentBinlogs{Manifest: newManifest}
pk2Ts, err := ComposeDeleteFromDeltalogs(ctx, schemapb.DataType_Int64, segment, options...)
s.NoError(err)
s.Equal(3, len(pk2Ts))
s.Equal(typeutil.Timestamp(1000), pk2Ts[int64(10)])
s.Equal(typeutil.Timestamp(1001), pk2Ts[int64(20)])
s.Equal(typeutil.Timestamp(1002), pk2Ts[int64(30)])
})
s.Run("V2 manifest - VarChar PK", func() {
basePath := filepath.Join(dir, "insert_log/1/2/3_varchar")
manifestPath := s.createBaseManifest(basePath, storageConfig)
deltaPath := filepath.Join(dir, "delta_log/1/2/3/102")
s.writeV2DeltaLog(ctx, schemapb.DataType_VarChar, []string{"pk_a", "pk_b", "pk_c"}, []int64{2000, 2001, 2002}, deltaPath, storageConfig)
newManifest, err := packed.AddDeltaLogsToManifest(manifestPath, storageConfig, []packed.DeltaLogEntry{
{Path: deltaPath, NumEntries: 3},
})
s.Require().NoError(err)
segment := &datapb.CompactionSegmentBinlogs{Manifest: newManifest}
pk2Ts, err := ComposeDeleteFromDeltalogs(ctx, schemapb.DataType_VarChar, segment, options...)
s.NoError(err)
s.Equal(3, len(pk2Ts))
s.Equal(typeutil.Timestamp(2000), pk2Ts["pk_a"])
s.Equal(typeutil.Timestamp(2001), pk2Ts["pk_b"])
s.Equal(typeutil.Timestamp(2002), pk2Ts["pk_c"])
})
s.Run("V2 manifest - multiple deltalogs", func() {
basePath := filepath.Join(dir, "insert_log/1/2/3_multi")
manifestPath := s.createBaseManifest(basePath, storageConfig)
deltaPath1 := filepath.Join(dir, "delta_log/1/2/3/201")
deltaPath2 := filepath.Join(dir, "delta_log/1/2/3/202")
s.writeV2DeltaLog(ctx, schemapb.DataType_Int64, []int64{100, 200}, []int64{3000, 3001}, deltaPath1, storageConfig)
s.writeV2DeltaLog(ctx, schemapb.DataType_Int64, []int64{300, 400}, []int64{3002, 3003}, deltaPath2, storageConfig)
newManifest, err := packed.AddDeltaLogsToManifest(manifestPath, storageConfig, []packed.DeltaLogEntry{
{Path: deltaPath1, NumEntries: 2},
{Path: deltaPath2, NumEntries: 2},
})
s.Require().NoError(err)
segment := &datapb.CompactionSegmentBinlogs{Manifest: newManifest}
pk2Ts, err := ComposeDeleteFromDeltalogs(ctx, schemapb.DataType_Int64, segment, options...)
s.NoError(err)
s.Equal(4, len(pk2Ts))
s.Equal(typeutil.Timestamp(3000), pk2Ts[int64(100)])
s.Equal(typeutil.Timestamp(3001), pk2Ts[int64(200)])
s.Equal(typeutil.Timestamp(3002), pk2Ts[int64(300)])
s.Equal(typeutil.Timestamp(3003), pk2Ts[int64(400)])
})
s.Run("V2 manifest - no deltalogs", func() {
basePath := filepath.Join(dir, "insert_log/1/2/3_empty")
manifestPath := s.createBaseManifest(basePath, storageConfig)
segment := &datapb.CompactionSegmentBinlogs{Manifest: manifestPath}
_, err := ComposeDeleteFromDeltalogs(ctx, schemapb.DataType_Int64, segment, options...)
s.NoError(err)
})
}