Repository navigation
Bug: idempotent functions sharing a persistence layer silently use the first function's IdempotencyConfig #8659
Description
Expected Behaviour
When two idempotent functions share one persistence layer instance but use different IdempotencyConfigs, each function should build its idempotency key (and payload hash, expiry, local cache settings) from its own config.
Current Behaviour
BasePersistenceLayer.configure() refreshes function_name on every call, but everything derived from the config is only applied on the first call:
self.function_name = key_prefix or ...
if self.configured:
# Prevent being reconfigured multiple times
return
self.configured = True
self.event_key_jmespath = config.event_key_jmespath
...So the second function silently uses the first function's event_key_jmespath, payload_validation_jmespath, expires_after_seconds, use_local_cache and hash_function. Because function_name is still per-function, keys don't collide across functions, but within the second function different requests can hash to the same key and get back another request's stored response.
In the snippet below, notify is keyed on order_id (from charge's config) instead of user_id, so Bob's call returns Alice's result.
Code snippet
import warnings
from aws_lambda_powertools.utilities.idempotency import IdempotencyConfig, idempotent_function
from aws_lambda_powertools.utilities.idempotency.exceptions import (
IdempotencyItemAlreadyExistsError,
IdempotencyItemNotFoundError,
)
from aws_lambda_powertools.utilities.idempotency.persistence.base import BasePersistenceLayer
warnings.simplefilter("ignore")
class InMemoryLayer(BasePersistenceLayer):
def __init__(self):
super().__init__()
self.items = {}
def _get_record(self, idempotency_key):
if idempotency_key not in self.items:
raise IdempotencyItemNotFoundError
return self.items[idempotency_key]
def _put_record(self, data_record):
if data_record.idempotency_key in self.items:
raise IdempotencyItemAlreadyExistsError(old_data_record=self.items[data_record.idempotency_key])
self.items[data_record.idempotency_key] = data_record
def _update_record(self, data_record):
self.items[data_record.idempotency_key] = data_record
def _delete_record(self, data_record):
self.items.pop(data_record.idempotency_key, None)
persistence_layer = InMemoryLayer()
@idempotent_function(
data_keyword_argument="order",
persistence_store=persistence_layer,
config=IdempotencyConfig(event_key_jmespath="order_id"),
)
def charge(order):
return {"charged": order["order_id"]}
@idempotent_function(
data_keyword_argument="msg",
persistence_store=persistence_layer,
config=IdempotencyConfig(event_key_jmespath="user_id"),
)
def notify(msg):
return {"notified": msg["user_id"]}
charge(order={"order_id": "o-1"})
print(notify(msg={"user_id": "alice", "order_id": "o-1"})) # {'notified': 'alice'}
print(notify(msg={"user_id": "bob", "order_id": "o-1"})) # {'notified': 'alice'}, expected {'notified': 'bob'}The same happens with DynamoDBPersistenceLayer / CachePersistenceLayer; the in-memory layer just keeps the snippet self-contained.
Possible Solution
I don't think there is an obvious "just reconfigure" fix, because configure() mutates shared state: with nested idempotent functions on the same instance, the inner call would reconfigure the layer before the outer one calls save_success. Two options I can see:
- Keep the single-config contract but make it explicit: when an already configured layer receives a config whose settings differ from the ones it was configured with, raise (or warn) telling the user to create one persistence layer per config, and document it. Comparing the settings rather than the object identity matters, since
@idempotentwithoutconfigcreates a newIdempotencyConfig()per invocation. - Support sharing properly by moving the config-derived settings out of the persistence layer and into the per-call handler path. This is a larger change.
Happy to send a PR for whichever direction you prefer.
Steps to Reproduce
Run the snippet above.
Powertools for AWS Lambda (Python) version
latest (develop @ 45f7a4d)
AWS Lambda function runtime
3.11
Packaging format used
PyPi
Debugging logs
No response
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTriage
Activity
boring-cyborg commentedon Oct 25, 2026
Thanks for opening your first issue here! We'll come back to you as soon as we can.
In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: Invite link
powertools-for-aws-oss-automation commentedon Oct 25, 2026
Thanks for opening your first issue here! We'll come back to you as soon as we can.
In the meantime, check out our GitHub Discussions: Join the conversation
leandrodamascena commentedon Oct 25, 2026
Thanks @soujasK, confirmed, and this one was already on my list in #8603 as the item that needed a maintainer decision, so good timing. Let's do your option 1 now: when
configure()gets a config whose settings differ from the ones applied, emit a warning that points to "one persistence layer per config", compare settings rather than identity, and document it. A warning rather than an exception, because people have this in production without noticing. Option 2 is the right long-term shape and I'll keep that conversation in #8603. It's yours, with a test for the differing-config case. This goes into 3.40.0.soujasK commentedon Oct 25, 2026
Thanks @leandrodamascena, PR is up: #8664 (warning on differing settings, compared by value so
@idempotentwithout a config doesn't trigger it, plus a docs note and tests).