Skip to content

Conversation

@Hweinstock
Copy link
Contributor

@Hweinstock Hweinstock commented Feb 20, 2025

Problem

The toolkit recreates each SDK client before each request is made. As an example see:

export class Ec2Client {
public constructor(public readonly regionCode: string) {}
private async createSdkClient(): Promise<EC2> {
return await globals.sdkClientBuilder.createAwsService(EC2, undefined, this.regionCode)
}
public async getInstances(filters?: EC2.Filter[]): Promise<AsyncCollection<SafeEc2Instance>> {
const client = await this.createSdkClient()
const requester = async (request: EC2.DescribeInstancesRequest) => client.describeInstances(request).promise()
const collection = pageableToCollection(
requester,
filters ? { Filters: filters } : {},
'NextToken',
'Reservations'
)
const extractedInstances = this.getInstancesFromReservations(collection)
const instances = await this.updateInstancesDetail(extractedInstances)
return instances
}

This is done as an unnecessary guard against the client's internal state interfering with higher level business logic. Creating clients for each request is not cheap, and there is likely a better pattern to be had here.

See internal docs expanding more on this issue.

Solution

  • Create a map that caches the clients.
  • Key the function arguments such that it detects config changes.
  • Cache clients with new method getAwsService that wraps createAwsService.
  • Allow consumers to pass a ignoreCache flag to force creation of a new client.
  • On deactivation, call destroy on all the clients.

Testing Strategy

  • Create clients with methods that make it easy to determine equality.

  • Treat all work as PUBLIC. Private feature/x branches will not be squash-merged at release time.
  • Your code changes must meet the guidelines in CONTRIBUTING.md.
  • License: I confirm that my contribution is made under the terms of the Apache 2.0 license.

@Hweinstock Hweinstock changed the title refactor(sdkv3): cache clients on creation. (WIP) refactor(sdkv3): cache clients on creation. Feb 21, 2025
@Hweinstock Hweinstock marked this pull request as ready for review February 21, 2025 16:40
@Hweinstock Hweinstock requested a review from a team as a code owner February 21, 2025 16:40
}

/**
* Generalization of the {@link memoize} method that accepts async methods, and allows user to pass mapping from keys to args.
Copy link
Contributor

Choose a reason for hiding this comment

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

can we extend the existing memoize()? also probably want direct test coverage of this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was trying to maintain its type signature. Making this one return an async method would cause a lot of unrelated changes. However, no longer relevant since I refactored away from this.

Copy link
Contributor

@justinmk3 justinmk3 Feb 25, 2025

Choose a reason for hiding this comment

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

Making this one return an async method

would an "overload" work?

].join(':')
}

public getAwsService = memoizeWith(this.createAwsService.bind(this), this.keyAwsService.bind(this))
Copy link
Contributor

Choose a reason for hiding this comment

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

memoize may not be gaining much for this use-case.

because this is an important core module, we may want more visibility into the long-lived container that stores the clients. this module could store them in a map, that could be inspected and/or logged.

Copy link
Contributor Author

@Hweinstock Hweinstock Feb 21, 2025

Choose a reason for hiding this comment

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

That makes sense, this will make it easier to add custom-logic later. Implementing it this way also makes it seem like a natural place to ensure all clients call destroy on deactivation.

@Hweinstock
Copy link
Contributor Author

/runIntegrationTests

@Hweinstock Hweinstock merged commit 7bc1d42 into aws:feature/sdkv3 Feb 26, 2025
21 of 23 checks passed
@Hweinstock Hweinstock deleted the sdkv3/cacheClient branch February 26, 2025 14:43
// Serializing certain objects in the args allows us to detect when nested objects change (ex. new retry strategy, endpoints)
return [
String(serviceOptions.serviceClient),
JSON.stringify(serviceOptions.clientOptions),
Copy link
Contributor

Choose a reason for hiding this comment

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

Is JSON.stringify guaranteed to return a stable ordering of keys for the same input? I think we have some "hash" code, or some other way of dealing with that, somewhere...

return service as C
}

public async createAwsService<C extends AwsClient>(serviceOptions: AwsServiceOptions<C>): Promise<C> {
Copy link
Contributor

@justinmk3 justinmk3 Feb 26, 2025

Choose a reason for hiding this comment

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

should the docstring call out the fact that the client will be re-used?

important for callers not to "modify" the client, except in specific "safe" ways...

is it possible that a cached client is modified such that its key no longer semantically matches its value? e.g. could its region be changed, but the "key" still has the old region?

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.

3 participants