Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: [do not deserialize json objects or update local cache if remote response has not changed from the previous one] (FF-2576) #56

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

leoromanovsky
Copy link
Member

@leoromanovsky leoromanovsky commented Jul 3, 2024

motivation

Following integrating the Eppo SDK into their server, we have a customer report of high CPU and memory utilization.

The default polling interval on golang is every 10 seconds and almost always the flag configuration from the CDN is not changed. However each of these refreshes the SDK deserialized the JSON into go objects and updates the cache.

proposed changes

Only perform json deserialization if the received response is different than the cached version. This is determined by comparing the ETag of the received response with the value from the previously stored version.

The Fastly CDN supports this: https://www.fastly.com/blog/etags-what-they-are-and-how-to-use-them/

👇 An example from Eppo's frontend

Screenshot 2024-07-09 at 11 42 25 PM

👉 By default I have left this behavior disabled so that we do not need a major version bump. A beta customer can try this in this release - if the results are positive we can enable it by default in future major versions.

rollout

It is unknown whether this change will fully mitigate the high memory utilization. We have another changes in scope.

for reviewers

  • Do you think this behavior should be configurable during initialization with a flag?
  • Do you see any risks of the SDK getting stale?

…e response has not changed from the previous one] (FF-2576)
@leoromanovsky leoromanovsky marked this pull request as ready for review July 3, 2024 20:17
@leoromanovsky leoromanovsky marked this pull request as draft July 3, 2024 20:49
Copy link
Contributor

@aarsilv aarsilv left a comment

Choose a reason for hiding this comment

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

Love this train of thought 🤞

configStore: configStore,
httpClient: httpClient,
configStore: configStore,
deserializeCount: 0,
Copy link
Member Author

Choose a reason for hiding this comment

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

This is only for testing validation


return HttpResponse{
Body: string(b),
ETag: resp.Header.Get("ETag"), // Capture the ETag header
Copy link
Member Author

Choose a reason for hiding this comment

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

I am learning that there are two types but ETag (capital T) is the canonical form: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag

The Eppo Fastly CDN seems to return Etag prefix with a W which is a "weak" tag. I think this is because by default express, the web server underlying NestJS, adds this by default.

Before we release this, Eppo's web server might need to be updated to return "strong" etags.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Weak ETag is fine for our purposes as we don't care about byte-by-byte identity—we only need the json value to match.

@leoromanovsky leoromanovsky marked this pull request as ready for review July 10, 2024 06:56
Comment on lines +63 to +64
if cr.skipDeserializeAndUpdateFlagConfigIfUnchanged {
if result.ETag == cr.storedUFCResponseETag {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should use If-None-Match header on the request, so the server can respond with 304 Not Modified and skip sending the body, instead of doing this check on the client side

| `SdkKey` | The SDK key for authenticating requests. |
| `AssignmentLogger` | An implementation of the `IAssignmentLogger` interface for logging assignments. |
| `PollerInterval` | The interval at which the SDK polls for updates. Defaults to 10 seconds. |
| `SkipDeserializeAndUpdateFlagConfigIfUnchanged` | A boolean flag to skip deserialization and update if the configuration has not changed. Defaults to false. |
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we want it configurable? Is there any reason for a user to ever disable it?

SdkKey string
AssignmentLogger IAssignmentLogger
PollerInterval time.Duration
SkipDeserializeAndUpdateFlagConfigIfUnchanged bool // default false
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we want it enabled by default?


return HttpResponse{
Body: string(b),
ETag: resp.Header.Get("ETag"), // Capture the ETag header
Copy link
Collaborator

Choose a reason for hiding this comment

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

Weak ETag is fine for our purposes as we don't care about byte-by-byte identity—we only need the json value to match.

@@ -23,22 +26,26 @@ type SDKParams struct {
sdkVersion string
}

func newHttpClient(baseUrl string, client *http.Client, sdkParams SDKParams) *httpClient {
type HttpResponse struct {
Body string
Copy link
Collaborator

@rasendubi rasendubi Jul 10, 2024

Choose a reason for hiding this comment

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

This should be []byte to avoid copying response bytes when converting to/from string.

Suggested change
Body string
Body []byte

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