From 95814359bddd37718373dc6de090415e9e02977c Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Tue, 26 May 2020 12:52:24 -0700 Subject: [PATCH] cache disk info to avoid repeated calls (#9682) This value is requested on every upload when there are multiple zones. Since this will result in an RPC call to every remote disk this scales quite badly in a distributed setup. Load every 1second interval. 2 servers, localhost only. In large distributed setups much bigger gains can be expected. ``` Operations: 21743 -> 22454 * Average: +3.28% (+0.0 MiB/s) throughput, +3.28% (+11.9) obj/s * Fastest: +3.37% (+0.0 MiB/s) throughput, +3.37% (+13.0) obj/s * 50% Median: +3.03% (+0.0 MiB/s) throughput, +3.03% (+11.2) obj/s * Slowest: +8.03% (+0.0 MiB/s) throughput, +8.03% (+22.8) obj/s ``` For easy management of this a generic helper has been added. --- cmd/utils.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ cmd/xl-sets.go | 16 ++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/cmd/utils.go b/cmd/utils.go index 4f6e961a5..1b28ba915 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -631,3 +631,60 @@ func iamPolicyClaimNameOpenID() string { func iamPolicyClaimNameSA() string { return "sa-policy" } + +// timedValue contains a synchronized value that is considered valid +// for a specific amount of time. +// An Update function must be set to provide an updated value when needed. +type timedValue struct { + // Update must return an updated value. + // If an error is returned the cached value is not set. + // Only one caller will call this function at any time, others will be blocking. + // The returned value can no longer be modified once returned. + // Should be set before calling Get(). + Update func() (interface{}, error) + + // TTL for a cached value. + // If not set 1 second TTL is assumed. + // Should be set before calling Get(). + TTL time.Duration + + // Once can be used to initialize values for lazy initialization. + // Should be set before calling Get(). + Once sync.Once + + // Managed values. + value interface{} + lastUpdate time.Time + mu sync.Mutex +} + +// Get will return a cached value or fetch a new one. +// If the Update function returns an error the value is forwarded as is and not cached. +func (t *timedValue) Get() (interface{}, error) { + t.mu.Lock() + defer t.mu.Unlock() + if t.TTL <= 0 { + t.TTL = time.Second + } + if t.value != nil { + if time.Since(t.lastUpdate) < t.TTL { + v := t.value + return v, nil + } + t.value = nil + } + v, err := t.Update() + if err != nil { + return v, err + } + t.value = v + t.lastUpdate = time.Now() + return v, nil +} + +// Invalidate the value in the cache. +func (t *timedValue) Invalidate() { + t.mu.Lock() + t.value = nil + t.mu.Unlock() +} diff --git a/cmd/xl-sets.go b/cmd/xl-sets.go index 71ae20df7..131e6f00c 100644 --- a/cmd/xl-sets.go +++ b/cmd/xl-sets.go @@ -95,6 +95,8 @@ type xlSets struct { // Distribution algorithm of choice. distributionAlgo string + disksStorageInfoCache timedValue + // Merge tree walk pool *MergeWalkPool poolSplunk *MergeWalkPool @@ -368,7 +370,21 @@ func (s *xlSets) NewNSLock(ctx context.Context, bucket string, objects ...string } // StorageInfo - combines output of StorageInfo across all erasure coded object sets. +// Caches values for 1 second. func (s *xlSets) StorageInfo(ctx context.Context, local bool) StorageInfo { + s.disksStorageInfoCache.Once.Do(func() { + s.disksStorageInfoCache.TTL = time.Second + s.disksStorageInfoCache.Update = func() (interface{}, error) { + return s.storageInfo(ctx, local), nil + } + }) + v, _ := s.disksStorageInfoCache.Get() + return v.(StorageInfo) +} + +// storageInfo - combines output of StorageInfo across all erasure coded object sets. +// Use StorageInfo for a cached version. +func (s *xlSets) storageInfo(ctx context.Context, local bool) StorageInfo { var storageInfo StorageInfo storageInfos := make([]StorageInfo, len(s.sets))