This is an automated email from the ASF dual-hosted git repository. hanahmily pushed a commit to branch feat/segmeta in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git
commit 86d4d3dd1c07a0c4a699bb2139379ef8295336b5 Author: Hongtao Gao <[email protected]> AuthorDate: Tue Apr 7 00:55:38 2026 +0000 feat(storage): write JSON metadata with endTime on segment creation --- banyand/internal/storage/segment.go | 10 ++++- banyand/internal/storage/segment_test.go | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/banyand/internal/storage/segment.go b/banyand/internal/storage/segment.go index f9ad6a122..4de7ab50b 100644 --- a/banyand/internal/storage/segment.go +++ b/banyand/internal/storage/segment.go @@ -19,6 +19,7 @@ package storage import ( "context" + "encoding/json" "fmt" "io/fs" "path" @@ -539,7 +540,14 @@ func (sc *segmentController[T, O]) create(start time.Time) (*segment[T, O], erro } segPath := path.Join(sc.location, fmt.Sprintf(segTemplate, sc.format(start))) sc.lfs.MkdirPanicIfExist(segPath, DirPerm) - data := []byte(currentVersion) + meta := segmentMeta{ + Version: currentVersion, + EndTime: end.Format(time.RFC3339Nano), + } + data, marshalErr := json.Marshal(meta) + if marshalErr != nil { + logger.Panicf("cannot marshal segment metadata: %s", marshalErr) + } metadataPath := filepath.Join(segPath, metadataFilename) lf, err := sc.lfs.CreateLockFile(metadataPath, FilePerm) if err != nil { diff --git a/banyand/internal/storage/segment_test.go b/banyand/internal/storage/segment_test.go index 7bcc1063b..85f28c259 100644 --- a/banyand/internal/storage/segment_test.go +++ b/banyand/internal/storage/segment_test.go @@ -641,3 +641,69 @@ func TestDeleteExpiredSegmentsWithClosedSegments(t *testing.T) { "Remaining segment %d should be from the expected date", i) } } + +func TestCreateSegmentWritesJSONMetadata(t *testing.T) { + tempDir, cleanup := setupTestEnvironment(t) + defer cleanup() + + ctx := context.Background() + l := logger.GetLogger("test-segment-metadata") + ctx = context.WithValue(ctx, logger.ContextKey, l) + ctx = common.SetPosition(ctx, func(_ common.Position) common.Position { + return common.Position{ + Database: "test-db", + Stage: "test-stage", + } + }) + + opts := TSDBOpts[mockTSTable, mockTSTableOpener]{ + TSTableCreator: func(_ fs.FileSystem, _ string, _ common.Position, _ *logger.Logger, + _ timestamp.TimeRange, _ mockTSTableOpener, _ any, + ) (mockTSTable, error) { + return mockTSTable{ID: common.ShardID(0)}, nil + }, + ShardNum: 1, + SegmentInterval: IntervalRule{Unit: DAY, Num: 1}, + TTL: IntervalRule{Unit: DAY, Num: 7}, + SeriesIndexFlushTimeoutSeconds: 10, + SeriesIndexCacheMaxBytes: 1024 * 1024, + } + + serviceCache := NewServiceCache().(*serviceCache) + sc := newSegmentController[mockTSTable, mockTSTableOpener]( + ctx, + tempDir, + l, + opts, + nil, + nil, + 5*time.Minute, + fs.NewLocalFileSystemWithLoggerAndLimit(logger.GetLogger("storage"), opts.MemoryLimit), + serviceCache, + group, + ) + + now := time.Now().UTC() + startTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC) + + seg, createErr := sc.create(startTime) + require.NoError(t, createErr) + require.NotNil(t, seg) + + // Read metadata from disk and verify it's JSON with endTime + suffix := startTime.Format(dayFormat) + metadataPath := filepath.Join(tempDir, fmt.Sprintf("seg-%s", suffix), metadataFilename) + rawMeta, readErr := os.ReadFile(metadataPath) + require.NoError(t, readErr) + + meta, parseErr := readSegmentMeta(rawMeta) + require.NoError(t, parseErr) + assert.Equal(t, currentVersion, meta.Version) + assert.NotEmpty(t, meta.EndTime, "endTime should be persisted in metadata") + + // Verify the endTime matches the segment's End + expectedEnd := startTime.Add(24 * time.Hour) + assert.Equal(t, expectedEnd.Format(time.RFC3339Nano), meta.EndTime) + + seg.DecRef() +}
