Skip to content

Commit

Permalink
aws/credentials: Update Credentials cache to use RWMutex (#1973)
Browse files Browse the repository at this point in the history
Updates the Credentials type's cache of the CredentialsValue to be synchronized with a RWMutex instead of Mutex. This reduces the overhead applications will encounter when many concurrent API requests are being made.

Benchmarks of Credential.Get improvements. This will be the normal case until credentials expire. Each benchmark increases the number of concurrent gets.

    benchmark                                         old ns/op     new ns/op     delta
    BenchmarkCredentials_Get/1-4                      64.0          33.5          -47.66%
    BenchmarkCredentials_Get/10-4                     1162          545           -53.10%
    BenchmarkCredentials_Get/100-4                    16016         5424          -66.13%
    BenchmarkCredentials_Get/500-4                    62655         27175         -56.63%
    BenchmarkCredentials_Get/1000-4                   137267        54247         -60.48%
    BenchmarkCredentials_Get/10000-4                  9040107       541044        -94.02%

Benchmark of Credentials.Get 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
    BenchmarkCredentials_Get_Expire/10000-1-4         214           176           -17.76%
    BenchmarkCredentials_Get_Expire/10000-10-4        1515          666           -56.04%
    BenchmarkCredentials_Get_Expire/10000-100-4       15988         5600          -64.97%
    BenchmarkCredentials_Get_Expire/10000-500-4       74389         28826         -61.25%
    BenchmarkCredentials_Get_Expire/10000-1000-4      172104        57977         -66.31%
    BenchmarkCredentials_Get_Expire/10000-10000-4     7772087       594408        -92.35%
    BenchmarkCredentials_Get_Expire/1000-1-4          1421          1172          -17.52%
    BenchmarkCredentials_Get_Expire/1000-10-4         2835          1867          -34.14%
    BenchmarkCredentials_Get_Expire/1000-100-4        17592         6986          -60.29%
    BenchmarkCredentials_Get_Expire/1000-500-4        53066         30900         -41.77%
    BenchmarkCredentials_Get_Expire/1000-1000-4       130093        60024         -53.86%
    BenchmarkCredentials_Get_Expire/1000-10000-4      6175162       588944        -90.46%
    BenchmarkCredentials_Get_Expire/100-1-4           13443         13497         +0.40%
    BenchmarkCredentials_Get_Expire/100-10-4          14762         13974         -5.34%
    BenchmarkCredentials_Get_Expire/100-100-4         26726         19201         -28.16%
    BenchmarkCredentials_Get_Expire/100-500-4         64969         44532         -31.46%
    BenchmarkCredentials_Get_Expire/100-1000-4        142072        76738         -45.99%
    BenchmarkCredentials_Get_Expire/100-10000-4       8396878       522469        -93.78%
  • Loading branch information
jasdel committed Jun 6, 2018
1 parent 501e7d9 commit e550ea2
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 3 deletions.
18 changes: 15 additions & 3 deletions aws/credentials/credentials.go
Expand Up @@ -178,7 +178,8 @@ func (e *Expiry) IsExpired() bool {
type Credentials struct {
creds Value
forceRefresh bool
m sync.Mutex

m sync.RWMutex

provider Provider
}
Expand All @@ -201,6 +202,17 @@ func NewCredentials(provider Provider) *Credentials {
// If Credentials.Expire() was called the credentials Value will be force
// expired, and the next call to Get() will cause them to be refreshed.
func (c *Credentials) Get() (Value, error) {
// Check the cached credentials first with just the read lock.
c.m.RLock()
if !c.isExpired() {
creds := c.creds
c.m.RUnlock()
return creds, nil
}
c.m.RUnlock()

// Credentials are expired need to retrieve the credentials taking the full
// lock.
c.m.Lock()
defer c.m.Unlock()

Expand Down Expand Up @@ -234,8 +246,8 @@ func (c *Credentials) Expire() {
// If the Credentials were forced to be expired with Expire() this will
// reflect that override.
func (c *Credentials) IsExpired() bool {
c.m.Lock()
defer c.m.Unlock()
c.m.RLock()
defer c.m.RUnlock()

return c.isExpired()
}
Expand Down
90 changes: 90 additions & 0 deletions aws/credentials/credentials_bench_test.go
@@ -0,0 +1,90 @@
// +build go1.9

package credentials

import (
"fmt"
"strconv"
"sync"
"testing"
"time"
)

func BenchmarkCredentials_Get(b *testing.B) {
stub := &stubProvider{}

cases := []int{1, 10, 100, 500, 1000, 10000}

for _, c := range cases {
b.Run(strconv.Itoa(c), func(b *testing.B) {
creds := NewCredentials(stub)
var wg sync.WaitGroup
wg.Add(c)
for i := 0; i < c; i++ {
go func() {
for j := 0; j < b.N; j++ {
v, err := creds.Get()
if err != nil {
b.Fatalf("expect no error %v, %v", v, err)
}
}
wg.Done()
}()
}
b.ResetTimer()

wg.Wait()
})
}
}

func BenchmarkCredentials_Get_Expire(b *testing.B) {
p := &blockProvider{}

expRates := []int{10000, 1000, 100}
cases := []int{1, 10, 100, 500, 1000, 10000}

for _, expRate := range expRates {
for _, c := range cases {
b.Run(fmt.Sprintf("%d-%d", expRate, c), func(b *testing.B) {
creds := NewCredentials(p)
var wg sync.WaitGroup
wg.Add(c)
for i := 0; i < c; i++ {
go func(id int) {
for j := 0; j < b.N; j++ {
v, err := creds.Get()
if err != nil {
b.Fatalf("expect no error %v, %v", v, err)
}
// periodically expire creds to cause rwlock
if id == 0 && j%expRate == 0 {
creds.Expire()
}
}
wg.Done()
}(i)
}
b.ResetTimer()

wg.Wait()
})
}
}
}

type blockProvider struct {
creds Value
expired bool
err error
}

func (s *blockProvider) Retrieve() (Value, error) {
s.expired = false
s.creds.ProviderName = "blockProvider"
time.Sleep(time.Millisecond)
return s.creds, s.err
}
func (s *blockProvider) IsExpired() bool {
return s.expired
}

0 comments on commit e550ea2

Please sign in to comment.