Files
minio/cmd/ilm-access-tracker_test.go
T
mr javad seydi 7a060cab1e feat(ilm): relocate hot objects across server pools by GET frequency
Keep NVMe/HDD pool pairs useful without remote tiering: promote objects that
are read often, demote previously moved objects once they go idle, and leave
the feature off until operators set a two-pool topology.

Signed-off-by: mr javad seydi <seydi.birjand@gmail.com>
2026-08-15 13:48:22 +03:30

96 lines
2.7 KiB
Go

// Copyright (c) 2015-2026 MinIO, Inc.
package cmd
import (
"math"
"testing"
"time"
"github.com/minio/minio/internal/config/ilm"
)
func TestAccessEntryRollAndHits(t *testing.T) {
entry := accessEntry{Bins: []uint32{3, 2, 1}, HeadAt: 100, LastAt: 107}
entry.rollTo(120, 10, 3)
want := []uint32{0, 0, 3}
for i := range want {
if entry.Bins[i] != want[i] {
t.Fatalf("bins = %v, want %v", entry.Bins, want)
}
}
if entry.HeadAt != 120 || entry.LastAt != 107 {
t.Fatalf("head/last = %d/%d", entry.HeadAt, entry.LastAt)
}
entry = accessEntry{Bins: []uint32{10, 20, 30}, HeadAt: 120}
if got := entry.hits(20*time.Second, 10); got != 30 {
t.Fatalf("20s hits = %d, want 30", got)
}
if got := entry.hits(21*time.Second, 10); got != 60 {
t.Fatalf("21s hits = %d, want 60", got)
}
entry.rollTo(200, 10, 3)
if got := entry.total(); got != 0 {
t.Fatalf("expired total = %d, want 0", got)
}
}
func TestAccessEntryMergeSaturates(t *testing.T) {
entry := accessEntry{Bins: []uint32{math.MaxUint32 - 1}}
entry.mergeFrom(accessEntry{Bins: []uint32{10}, LastAt: 50})
if entry.Bins[0] != math.MaxUint32 || entry.LastAt != 50 {
t.Fatalf("merged entry = %+v", entry)
}
}
func TestAccessTrackerEvictsColdest(t *testing.T) {
tracker := newAccessTracker()
live := map[string]accessEntry{
"a": {Bins: []uint32{1}, HeadAt: 100},
"b": {Bins: []uint32{5}, HeadAt: 100},
"c": {Bins: []uint32{3}, HeadAt: 100},
}
tracker.evict(live, 100, 10, ilm.Config{AccessBins: 1, AccessMaxTracked: 2})
if len(live) != 2 {
t.Fatalf("len = %d, want 2", len(live))
}
if _, ok := live["a"]; ok {
t.Fatal("coldest entry was retained")
}
}
func TestAccessKeyRoundTrip(t *testing.T) {
key := accessKey("bucket", "a/b/c")
bucket, object, ok := splitAccessKey(key)
if !ok || bucket != "bucket" || object != "a/b/c" {
t.Fatalf("split %q = %q/%q/%v", key, bucket, object, ok)
}
}
func TestAccessShardFresh(t *testing.T) {
const now = int64(1000)
if !accessShardFresh(now, accessShard{UpdatedAt: 950}, 100) {
t.Fatal("fresh shard rejected")
}
if accessShardFresh(now, accessShard{UpdatedAt: 899}, 100) {
t.Fatal("stale shard accepted")
}
if accessShardFresh(now, accessShard{UpdatedAt: 1101}, 100) {
t.Fatal("far-future shard accepted")
}
if accessShardFresh(now, accessShard{}, 100) {
t.Fatal("zero timestamp accepted")
}
}
func TestAccessTrackerRestoresDemoteCandidates(t *testing.T) {
tracker := newAccessTracker()
candidate := demoteCandidate{Bucket: "bucket", Object: "object", Pool: 1}
tracker.restoreDemoteCandidates([]demoteCandidate{candidate})
got := tracker.takeDemoteCandidates()
if len(got) != 1 || got[0] != candidate {
t.Fatalf("restored candidates = %+v, want %+v", got, candidate)
}
}