mirror of
https://github.com/minio/minio.git
synced 2025-01-23 04:33:15 -05:00
Periodically refresh buckets metadata from the backend disks (#16561)
fixes #16553
This commit is contained in:
parent
1141187bf2
commit
c8ffa59d28
@ -22,6 +22,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go/v2"
|
"github.com/minio/madmin-go/v2"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
@ -60,13 +61,43 @@ func activeListeners() int {
|
|||||||
return int(globalHTTPListen.Subscribers()) + int(globalTrace.Subscribers())
|
return int(globalHTTPListen.Subscribers()) + int(globalTrace.Subscribers())
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForLowHTTPReq() {
|
func waitForLowIO(maxIO int, maxWait time.Duration, currentIO func() int) {
|
||||||
var currentIO func() int
|
// No need to wait run at full speed.
|
||||||
if httpServer := newHTTPServerFn(); httpServer != nil {
|
if maxIO <= 0 {
|
||||||
currentIO = httpServer.GetRequestCount
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
globalHealConfig.Wait(currentIO, activeListeners)
|
const waitTick = 100 * time.Millisecond
|
||||||
|
|
||||||
|
tmpMaxWait := maxWait
|
||||||
|
|
||||||
|
for currentIO() >= maxIO {
|
||||||
|
if tmpMaxWait > 0 {
|
||||||
|
if tmpMaxWait < waitTick {
|
||||||
|
time.Sleep(tmpMaxWait)
|
||||||
|
} else {
|
||||||
|
time.Sleep(waitTick)
|
||||||
|
}
|
||||||
|
tmpMaxWait -= waitTick
|
||||||
|
}
|
||||||
|
if tmpMaxWait <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func currentHTTPIO() int {
|
||||||
|
httpServer := newHTTPServerFn()
|
||||||
|
if httpServer == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpServer.GetRequestCount() - activeListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForLowHTTPReq() {
|
||||||
|
maxIO, maxWait, _ := globalHealConfig.Clone()
|
||||||
|
waitForLowIO(maxIO, maxWait, currentHTTPIO)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
|
func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -40,6 +41,8 @@ import (
|
|||||||
|
|
||||||
// BucketMetadataSys captures all bucket metadata for a given cluster.
|
// BucketMetadataSys captures all bucket metadata for a given cluster.
|
||||||
type BucketMetadataSys struct {
|
type BucketMetadataSys struct {
|
||||||
|
objAPI ObjectLayer
|
||||||
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
metadataMap map[string]BucketMetadata
|
metadataMap map[string]BucketMetadata
|
||||||
}
|
}
|
||||||
@ -386,39 +389,41 @@ func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []BucketInfo, ob
|
|||||||
return errServerNotInitialized
|
return errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sys.objAPI = objAPI
|
||||||
|
|
||||||
// Load bucket metadata sys in background
|
// Load bucket metadata sys in background
|
||||||
go sys.load(ctx, buckets, objAPI)
|
go sys.init(ctx, buckets)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sys *BucketMetadataSys) loadBucketMetadata(ctx context.Context, bucket BucketInfo) error {
|
||||||
|
meta, err := loadBucketMetadata(ctx, sys.objAPI, bucket.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.Lock()
|
||||||
|
sys.metadataMap[bucket.Name] = meta
|
||||||
|
sys.Unlock()
|
||||||
|
|
||||||
|
globalEventNotifier.set(bucket, meta) // set notification targets
|
||||||
|
globalBucketTargetSys.set(bucket, meta) // set remote replication targets
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// concurrently load bucket metadata to speed up loading bucket metadata.
|
// concurrently load bucket metadata to speed up loading bucket metadata.
|
||||||
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) {
|
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo) {
|
||||||
g := errgroup.WithNErrs(len(buckets))
|
g := errgroup.WithNErrs(len(buckets))
|
||||||
for index := range buckets {
|
for index := range buckets {
|
||||||
index := index
|
index := index
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
_, _ = objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{
|
_, _ = sys.objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{
|
||||||
// Ensure heal opts for bucket metadata be deep healed all the time.
|
// Ensure heal opts for bucket metadata be deep healed all the time.
|
||||||
ScanMode: madmin.HealDeepScan,
|
ScanMode: madmin.HealDeepScan,
|
||||||
Recreate: true,
|
Recreate: true,
|
||||||
})
|
})
|
||||||
meta, err := loadBucketMetadata(ctx, objAPI, buckets[index].Name)
|
return sys.loadBucketMetadata(ctx, buckets[index])
|
||||||
if err != nil {
|
|
||||||
if !globalIsErasure && !globalIsDistErasure && errors.Is(err, errVolumeNotFound) {
|
|
||||||
meta = newBucketMetadata(buckets[index].Name)
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sys.Lock()
|
|
||||||
sys.metadataMap[buckets[index].Name] = meta
|
|
||||||
sys.Unlock()
|
|
||||||
|
|
||||||
globalEventNotifier.set(buckets[index], meta) // set notification targets
|
|
||||||
|
|
||||||
globalBucketTargetSys.set(buckets[index], meta) // set remote replication targets
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}, index)
|
}, index)
|
||||||
}
|
}
|
||||||
for _, err := range g.Wait() {
|
for _, err := range g.Wait() {
|
||||||
@ -428,17 +433,51 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context) {
|
||||||
|
const bucketMetadataRefresh = 15 * time.Minute
|
||||||
|
|
||||||
|
t := time.NewTimer(bucketMetadataRefresh)
|
||||||
|
defer t.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-t.C:
|
||||||
|
buckets, err := sys.objAPI.ListBuckets(ctx, BucketOptions{})
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i := range buckets {
|
||||||
|
err := sys.loadBucketMetadata(ctx, buckets[i])
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check if there is a spare core, wait 100ms instead
|
||||||
|
waitForLowIO(runtime.NumCPU(), 100*time.Millisecond, currentHTTPIO)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Reset(bucketMetadataRefresh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Loads bucket metadata for all buckets into BucketMetadataSys.
|
// Loads bucket metadata for all buckets into BucketMetadataSys.
|
||||||
func (sys *BucketMetadataSys) load(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) {
|
func (sys *BucketMetadataSys) init(ctx context.Context, buckets []BucketInfo) {
|
||||||
count := 100 // load 100 bucket metadata at a time.
|
count := 100 // load 100 bucket metadata at a time.
|
||||||
for {
|
for {
|
||||||
if len(buckets) < count {
|
if len(buckets) < count {
|
||||||
sys.concurrentLoad(ctx, buckets, objAPI)
|
sys.concurrentLoad(ctx, buckets)
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
sys.concurrentLoad(ctx, buckets[:count], objAPI)
|
sys.concurrentLoad(ctx, buckets[:count])
|
||||||
buckets = buckets[count:]
|
buckets = buckets[count:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if globalIsDistErasure {
|
||||||
|
go sys.refreshBucketsMetadataLoop(ctx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the state of the BucketMetadataSys.
|
// Reset the state of the BucketMetadataSys.
|
||||||
|
@ -70,39 +70,11 @@ func (opts Config) BitrotScanCycle() (d time.Duration) {
|
|||||||
return opts.cache.bitrotCycle
|
return opts.cache.bitrotCycle
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait waits for IOCount to go down or max sleep to elapse before returning.
|
// Clone safely the heal configuration
|
||||||
// usually used in healing paths to wait for specified amount of time to
|
func (opts Config) Clone() (int, time.Duration, string) {
|
||||||
// throttle healing.
|
|
||||||
func (opts Config) Wait(currentIO func() int, activeListeners func() int) {
|
|
||||||
configMutex.RLock()
|
configMutex.RLock()
|
||||||
maxIO, maxWait := opts.IOCount, opts.Sleep
|
defer configMutex.RUnlock()
|
||||||
configMutex.RUnlock()
|
return opts.IOCount, opts.Sleep, opts.Bitrot
|
||||||
|
|
||||||
// No need to wait run at full speed.
|
|
||||||
if maxIO <= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// At max 10 attempts to wait with 100 millisecond interval before proceeding
|
|
||||||
waitTick := 100 * time.Millisecond
|
|
||||||
|
|
||||||
tmpMaxWait := maxWait
|
|
||||||
|
|
||||||
if currentIO != nil {
|
|
||||||
for currentIO() >= maxIO+activeListeners() {
|
|
||||||
if tmpMaxWait > 0 {
|
|
||||||
if tmpMaxWait < waitTick {
|
|
||||||
time.Sleep(tmpMaxWait)
|
|
||||||
} else {
|
|
||||||
time.Sleep(waitTick)
|
|
||||||
}
|
|
||||||
tmpMaxWait -= waitTick
|
|
||||||
}
|
|
||||||
if tmpMaxWait <= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates opts with nopts
|
// Update updates opts with nopts
|
||||||
|
Loading…
x
Reference in New Issue
Block a user