-
Notifications
You must be signed in to change notification settings - Fork 139
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
feat(parameters): AppConfigProvider to return the last valid value when the API returns empty value on subsequent calls #1365
Conversation
Add a local cache to AppConfigProvider to handle the scenario where the maxAge is expired, but the AppConfig session still has the latest configuration. In this case, AppConfig returns an empty value. We don't want to return empty values to our callers. This uses the same data structure we have in place to track NextPollConfigurationToken. This approach roughly the Python library's behavior.
Includes update to e2e lambda config type that allows test writers to specify a test function duration. Needed because to wait for parameter expiration, we needed a longer-than-default timeout.
packages/parameters/tests/e2e/appConfigProvider.class.test.functionCode.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Brian, thank you so much for opening the PR and contributing a fix.
I have left a few comments here and there but essentially I don't think we should add an additional layer of caching and the fix could be as simple as converting the maxAge
input to seconds (number * 1000
) in the current logic.
Hi @dreamorosi, I appreciate the review and feedback. I can definitely pivot this back to just looking at I tried the simple fix and still saw unexpected behavior where until But, maybe this isn't actually unexpected? You're right that removing the extra cache exposes But as I understood it, part of the purpose of the library is to simplify this exposure, or at least generalize interaction across Parameter providers. If I swap from Python implements this secondary cache pattern here, which inspired this approach: |
Thank you for pushing back on this, after re reading your comment I went back to check the implementation in Python and you're indeed right. The discussion that led to implementing that second cache layer comes from these two messages (here and here). I now agree with you that we should do the same. However, if you don't mind I'd prefer to keep the PR scoped to the issue at hand, also and in addition to the fact that we are still unsure whether the initial caching is fixed or not. Once we know that Would this work? |
Absolutely! I appreciate the careful consideration! I probably should have broken the changes out in the first place to not confuse the issue, sorry about that. I'd say we should HOLD on this PR until after we verify Then I'm happy to help with a new PR on a new issue tackling the |
As discussed on a quick call, we are going to:
const param1 = provider.get('hello', { maxAge: 0 });
const param2 = provider.get('hello');
// test that param1 === param2, where param2 is from this second cache
|
This reverts commit 3fea0d7. Further testing shows that the original algorithm was correct.
Hey guys! I see you've covered the second empty response from |
Interesting idea @shdq. It makes sense to me to tackle that, maybe in a separate issue/PR? It definitely would have implications for what I do like giving clients a way to completely force-flush out all state, and it makes sense to me to use that parameter to do so. For what it's worth, I don't see this implemented in the Python repository (unless I'm missing something), so it may need some additional discussion with the wider team. |
Drop wait, use maxAge instead. Add comment for Test case 7. Fix test case naming (test '4' was skipped on one of the files).
Not needed anymore since we're not waiting for Test 7.
Doesn't refactor ExpirableValue to take an injection of Date.now, just implements tests based on what we can do with the existing interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you so much for addressing all the comments.
I think we are very close to getting this in shape to be merged. I have also run the integration tests on your branch and are passing.
I've left a couple of very minor comments and expect this to be the last round of reviews.
@@ -111,4 +111,24 @@ export const handler = async (_event: unknown, _context: Context): Promise<void> | |||
error: err.message | |||
}); | |||
} | |||
|
|||
// Test 7 | |||
// get parameter twice, but wait long enough that cache expires count SDK calls and return values |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to reword this to reflect that we are no longer using the delay but instead avoiding cache with maxAge: 0
.
try { | ||
providerWithMiddleware.clearCache(); | ||
middleware.counter = 0; | ||
const expiredResult1 = await providerWithMiddleware.get(freeFormBase64encodedPlainText, { maxAge: 0, transform: 'base64' }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const expiredResult1 = await providerWithMiddleware.get(freeFormBase64encodedPlainText, { maxAge: 0, transform: 'base64' }); | |
const expiredResult1 = await providerWithMiddleware.get( | |
freeFormBase64encodedPlainText, { | |
maxAge: 0, | |
transform: 'base64' | |
} | |
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: I've just put the line on multiple lines so that it's not too long
const expiredResult2 = await providerWithMiddleware.get(freeFormBase64encodedPlainText, { maxAge: 0, transform: 'base64' }); | ||
logger.log({ | ||
test: 'get-expired', | ||
value: middleware.counter, // should be 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any chance that we can keep the original test
/value
structure? i.e.
logger.log({
test: 'get-expired',
value: {
counter: middleware.counter,
results: [
expiredResult1,
expiredResult2,
]
},
});
or similar
|
||
test('when session returns an empty configuration on the second call, it returns the last value', async () => { | ||
|
||
client.reset(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only test using this reset. We should possibly add a afterEach()
block to the whole test fixture that does it so that it gets applied after every test.
Maybe we can do this for this file, and then possibly in a future PR we can also apply the change to all other tests under packages/parameters/tests/unit/*
.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, that works for me. I added it here to be surgical - the resolvesOnce
chain was acting weirdly without it. I like the consistency of using an afterEach
instead.
Indeed, this was an open topic/question and I should take ownership of the fact that I misunderstood both the problem and the question originally. Brian was conducting some internal testing before the imminent beta launch and noticed this behavior. So after he compared the implementation with the one in Python he proposed this PR.
I think it's an interesting proposal. At the moment I'd like us to focus on launching the utility with feature parity and equivalent behavior with its Python corresponding one. After getting enough usage and feedback we can start iterating and proposing new features. |
… longer waiting during Test 7 runs.
Specfically test/value as top-level properties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work Brian, appreciate the time you've put into this.
Let's 🚢 it!
Sounds great to me! Happy to help, and appreciate the feedback and patience! |
Description of your changes
Fixes #1363
When AppConfig parameters were requested beyond the cache expiration, they were returning empty. This was due to AppConfig
GetLatestConfiguration
returning an empty response if the active session already has the latest value.This change introduces a local cache inside the
AppConfigProvider
that returns the most recent non-empty value in this scenario, matching the behavior of the Python PowerTools Repository.How to verify this change
I added unit and e2e tests that cover this scenario. The simplest way is to develop a Lambda that uses the Parameters library with the AppConfig Provider, then invoke it a series of times, waiting beyond the
maxAge
of the configured provider. Confirm that you get the same return value in cold start, withinmaxAge
, and beyondmaxAge
scenarios.Related issues, RFCs
Issue number:
#1363
Checklist
Breaking change checklist
Is it a breaking change?: NO
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.