Skip to content

Cache IMDS data that doesn't change within one boot cycle. - #4847

Merged
pengubco merged 1 commit into
aws:devfrom
pengubco:dev
Jan 28, 2026
Merged

pengubco merged 1 commit into
aws:devfrom
pengubco:dev

Conversation

@pengubco

Copy link
Copy Markdown
Contributor

Summary

Some instance metadata does not change once the instance is provisioned, and some metadata remains constant within a single boot cycle. The agent can cache these data instead of retrieving it from IMDS repeatedly. This helps mitigating the following issue caused by linklocal service throttle: 1,024 Packet Per Second (PPS) per ENI.

operation error ec2imds: GetMetadata, failed to get rate limit token, retry quota exceeded, 8 available, 10 requested

Implementation details

There are two caches added.

  1. cache map[string]string
  2. instanceIdentityDocCache *imds.InstanceIdentityDocument.

The cache caches following responses of Metadata.

  • mac
  • instance-id
  • placement/region
  • placement/availability-zone-id
  • network/interfaces/macs/%s/vpc-id
  • network/interfaces/macs/%s/subnet-id

Testing

Created a Go cli that starts 1500 goroutines where each goroutine calls the EC2MetadataClient.PrimaryENIMAC() for 60 seconds.

./imds-flood-test -goroutines 1500 -duration 60

Without cache.

# Sow repeated aws-sdk-go-v2 client side rate limit during the test lifetime. 
2026/01/20 20:53:50 Goroutine 140: Error getting MAC: operation error ec2imds: GetMetadata, failed to get rate limit token, retry quota exceeded, 4 available, 10 requested
2026/01/20 20:53:50 Goroutine 133: Error getting MAC: operation error ec2imds: GetMetadata, failed to get rate limit token, retry quota exceeded, 4 available, 10 requested
2026/01/20 20:53:50 Goroutine 796: Error getting MAC: operation error ec2imds: GetMetadata, failed to get rate limit token, retry quota exceeded, 4 available, 10 requested
2026/01/20 20:53:50 Goroutine 499: Error getting MAC: operation error ec2imds: GetMetadata, failed to get rate limit token, retry quota exceeded, 4 available, 10 requested

# linklocal throttle
bash-5.3# ethtool -S eth0 | grep linklocal_allowance_exceeded
     linklocal_allowance_exceeded: 387805

With cache

# Sow a few aws-sdk-go-v2 client side rate limit at the beginning of the test. This is caused by 1500 goroutines fire at the same time! 
2026/01/20 21:11:17 Goroutine 1264: Error getting MAC: operation error ec2imds: GetMetadata, failed to get rate limit token, retry quota exceeded, 1 available, 5 requested
2026/01/20 21:11:17 Goroutine 1360: Error getting MAC: operation error ec2imds: GetMetadata, failed to get rate limit token, retry quota exceeded, 2 available, 5 requested

# the linklocal throttle is 1.3% of the throttle compared without cache. 
bash-5.3# ethtool -S eth0 | grep linklocal_allowance_exceeded
     linklocal_allowance_exceeded: 5384

New tests cover the changes: yes

Description for the changelog

Enhancement - Cached IMDS responses to mitigate linklocal throttle.

Additional Information

Does this PR include breaking model changes? If so, Have you added transformation functions?
no

Does this PR include the addition of new environment variables in the README?
no

Licensing

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@pengubco
pengubco requested a review from a team as a code owner January 20, 2026 21:21
if err != nil {
return "", err
}
return c.getCachedMetadata("user-data", func() (string, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"user-data" can be a constant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking a closer look. I think it's OK and maybe clearer to just use literal "user-data" here. Not because the value "user-data" is only used once. But for how IMDS categorize metadata.

There are three categories of instance metadata.

  1. Instance metadata properties. Accessed through GetMetadata(path).
  2. Dynamic data. Accessed through GetDynamicData(path).
  3. User data. Access through GetUserData().
    https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html

We define consts for paths used in 1. and 2. These constants are one-to-one bind to IMDS metadata path, e.g. MacResource -> "/mac", InstanceIDResource -> /instance-id. However, "user-data" is not a IMDS path. It is just User Data.

// getCachedMetadata is a helper that implements the cache-aside pattern for metadata values.
// It checks the cache first, and if not found, calls the fetch function and stores the result.
func (c *ec2MetadataClientImpl) getCachedMetadata(key string, fetch func() (string, error)) (string, error) {
c.cacheMu.RLock()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: perhaps we can consider having just one lock operation. WDYT?

for e.g.

c.cacheMu.Lock()
defer c.cacheMu.Unlock()
...
return v, nil

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cacheMu here is a read-write lock, sync.RWMutex. For read operation, RLock(). For write operation, use Lock(). The RLock doesn't block other RLock.

@JoseVillalta JoseVillalta left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changes LGTM, I would like to discuss offline the long-term test strategy for this change. Should there be end-to-end tests?

@pengubco
pengubco merged commit e0e9e13 into aws:dev Jan 28, 2026
44 checks passed
This was referenced Jan 29, 2026
alexcmms pushed a commit to alexcmms/amazon-ecs-agent that referenced this pull request Jan 30, 2026
alexcmms pushed a commit to alexcmms/amazon-ecs-agent that referenced this pull request Jan 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants