Skip to content

Commit fd6cb23

Browse files
committed
aws/credentials: Update Credentials cache to have less overhead
Updates the Credentials type's cache of the CredentialsValue to be synchronized with an atomic value in addition to the Mutex. This reduces the overhead applications will encounter when many concurrent API requests are being made. Benchmarks of SafeCredentialsProvider.Retrieve improvements. This will be the normal case until credentials expire. Each benchmark increases the number of concurrent credential retrieves. benchmark old ns/op new ns/op delta BenchmarkSafeCredentialsProvider_Retrieve/1-4 72.3 26.1 -63.90% BenchmarkSafeCredentialsProvider_Retrieve/10-4 1055 121 -88.53% BenchmarkSafeCredentialsProvider_Retrieve/100-4 15720 1216 -92.26% BenchmarkSafeCredentialsProvider_Retrieve/500-4 65662 6194 -90.57% BenchmarkSafeCredentialsProvider_Retrieve/1000-4 121962 12673 -89.61% BenchmarkSafeCredentialsProvider_Retrieve/10000-4 9958536 127224 -98.72% Benchmark of SafeCredentialsProvider.Retrieve with the credentials periodically expiring. The {rate}-{concurrent} benchmarks will expire the credentials every {rate} attempts to get credentials, with {concurrent} credential getters. benchmark old ns/op new ns/op delta BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/10000-1-4 223 171 -23.32% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/10000-10-4 1527 313 -79.50% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/10000-100-4 14528 1779 -87.75% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/10000-500-4 89016 7764 -91.28% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/10000-1000-4 151669 14048 -90.74% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/10000-10000-4 10024041 201702 -97.99% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/1000-1-4 1450 1423 -1.86% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/1000-10-4 3043 1483 -51.27% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/1000-100-4 24763 2956 -88.06% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/1000-500-4 68852 8801 -87.22% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/1000-1000-4 121360 15952 -86.86% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/1000-10000-4 2133773 177960 -91.66% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/100-1-4 13688 13012 -4.94% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/100-10-4 15560 13239 -14.92% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/100-100-4 35299 15879 -55.02% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/100-500-4 110204 20949 -80.99% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/100-1000-4 259354 31070 -88.02% BenchmarkSafeCredentialsProvider_Retrieve_Invalidate/100-10000-4 7847174 196509 -97.50%
1 parent ff1a530 commit fd6cb23

File tree

2 files changed

+113
-8
lines changed

2 files changed

+113
-8
lines changed

aws/credentials.go

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package aws
33
import (
44
"math"
55
"sync"
6+
"sync/atomic"
67
"time"
78

89
"github.com/aws/aws-sdk-go-v2/internal/sdk"
@@ -85,7 +86,7 @@ type CredentialsProvider interface {
8586
type SafeCredentialsProvider struct {
8687
RetrieveFn func() (Credentials, error)
8788

88-
creds Credentials
89+
creds atomic.Value
8990
m sync.Mutex
9091
}
9192

@@ -95,28 +96,45 @@ type SafeCredentialsProvider struct {
9596
//
9697
// Retruns and error if RetrieveFn returns an error.
9798
func (p *SafeCredentialsProvider) Retrieve() (Credentials, error) {
99+
if creds := p.getCreds(); creds != nil {
100+
return *creds, nil
101+
}
102+
98103
p.m.Lock()
99104
defer p.m.Unlock()
100105

101-
if p.creds.HasKeys() && !p.creds.Expired() {
102-
return p.creds, nil
106+
// Make sure another goroutine didn't already update the credentials.
107+
if creds := p.getCreds(); creds != nil {
108+
return *creds, nil
103109
}
104110

105111
creds, err := p.RetrieveFn()
106112
if err != nil {
107113
return Credentials{}, err
108114
}
115+
p.creds.Store(&creds)
109116

110-
p.creds = creds
117+
return creds, nil
118+
}
119+
120+
func (p *SafeCredentialsProvider) getCreds() *Credentials {
121+
v := p.creds.Load()
122+
if v == nil {
123+
return nil
124+
}
111125

112-
return p.creds, nil
126+
c := v.(*Credentials)
127+
if c != nil && c.HasKeys() && !c.Expired() {
128+
return c
129+
}
130+
131+
return nil
113132
}
114133

115134
// Invalidate will invalidate the cached credentials. The next call to Retrieve
116135
// will cause RetrieveFn to be called.
117136
func (p *SafeCredentialsProvider) Invalidate() {
118137
p.m.Lock()
119-
defer p.m.Unlock()
120-
121-
p.creds = Credentials{}
138+
p.creds.Store((*Credentials)(nil))
139+
p.m.Unlock()
122140
}

aws/credentials_bench_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package aws
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"sync"
7+
"testing"
8+
"time"
9+
)
10+
11+
func BenchmarkSafeCredentialsProvider_Retrieve(b *testing.B) {
12+
retrieveFn := func() (Credentials, error) {
13+
return Credentials{
14+
AccessKeyID: "key",
15+
SecretAccessKey: "secret",
16+
Source: "benchmark",
17+
}, nil
18+
}
19+
20+
cases := []int{1, 10, 100, 500, 1000, 10000}
21+
for _, c := range cases {
22+
b.Run(strconv.Itoa(c), func(b *testing.B) {
23+
p := SafeCredentialsProvider{
24+
RetrieveFn: retrieveFn,
25+
}
26+
var wg sync.WaitGroup
27+
wg.Add(c)
28+
for i := 0; i < c; i++ {
29+
go func() {
30+
for j := 0; j < b.N; j++ {
31+
v, err := p.Retrieve()
32+
if err != nil {
33+
b.Fatalf("expect no error %v, %v", v, err)
34+
}
35+
}
36+
wg.Done()
37+
}()
38+
}
39+
b.ResetTimer()
40+
41+
wg.Wait()
42+
})
43+
}
44+
}
45+
46+
func BenchmarkSafeCredentialsProvider_Retrieve_Invalidate(b *testing.B) {
47+
retrieveFn := func() (Credentials, error) {
48+
time.Sleep(time.Millisecond)
49+
return Credentials{
50+
AccessKeyID: "key",
51+
SecretAccessKey: "secret",
52+
Source: "benchmark",
53+
}, nil
54+
}
55+
56+
expRates := []int{10000, 1000, 100}
57+
cases := []int{1, 10, 100, 500, 1000, 10000}
58+
for _, expRate := range expRates {
59+
for _, c := range cases {
60+
b.Run(fmt.Sprintf("%d-%d", expRate, c), func(b *testing.B) {
61+
p := SafeCredentialsProvider{
62+
RetrieveFn: retrieveFn,
63+
}
64+
var wg sync.WaitGroup
65+
wg.Add(c)
66+
for i := 0; i < c; i++ {
67+
go func(id int) {
68+
for j := 0; j < b.N; j++ {
69+
v, err := p.Retrieve()
70+
if err != nil {
71+
b.Fatalf("expect no error %v, %v", v, err)
72+
}
73+
// periodically expire creds to cause rwlock
74+
if id == 0 && j%expRate == 0 {
75+
p.Invalidate()
76+
}
77+
}
78+
wg.Done()
79+
}(i)
80+
}
81+
b.ResetTimer()
82+
83+
wg.Wait()
84+
})
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)