aws-powertools/powertools-lambda-python

Feature request: Create a dedicated Cache utility in Powertools (based on current idempotency layer)

Opened this issue · 4 comments

Use case

Customers are using the Powertools Idempotency utility for caching purposes. Idempotency is a specific implementation that includes a "caching" mechanism via the storage provider classes. Idempotency is a particular use case, and there is inbuilt functionality for dealing with the records in a specific way which I feel is dangerous for caching use cases.

Solution/User Experience

from aws_lambda_powertools.utilities.caching import (
    DynamoDBPersistenceLayer,
    Cache,
)

table = os.getenv("CACHE_TABLE", "")
persistence_layer = DynamoDBPersistenceLayer(table_name=table)
cache =  Cache(persistence_store=peristsance_layer)
my_ttl = 300

def lambda_handler(event: dict, context: LambdaContext):
    try:
        previous_value = cache.get(event.order_id, "default_value")

        payment: Payment = create_subscription_payment(event, previous_value)

        cache.set(order_id, payment.detail, my_ttl)

        return {
            "payment_id": payment.payment_id,
            "message": "success",
            "statusCode": 200,
        }
    except Exception as exc:
        raise PaymentError(f"Error creating payment {str(exc)}")


def create_subscription_payment(event: dict) -> Payment:
    return Payment(**event)

Alternative solutions

Acknowledgment

The idea of this feature request is to look at creating a new Caching feature for Powertools, separate from the Idempotency utility, which is not built for caching (not exactly).

Another addition for caching would be to add a decorator to enable cached data to be injected into a handler, similar to the following. the decorator would fetch from cache the value based on the jmespath event key and could also incorportae a custom internal LRU memory cache so we only go to the database when we have to (in case of multiple cahce lookups for same key across several transactiosn):

from aws_lambda_powertools.utilities.caching import (
    DynamoDBPersistenceLayer,
    lru_cache,
)

table = os.getenv("CACHE_TABLE", "")
persistence_layer = DynamoDBPersistenceLayer(table_name=table)

@lru_cache(persistance_store=persistance_layer, ttl=300, event_key_jmespath="order_id")
def lambda_handler(event: dict, context: LambdaContext):
    try:
        previous_value = app.cache.get(order_id)

        payment: Payment = create_subscription_payment(event, previous_value)

        cache.set(order_id, payment.detail, my_ttl)

        return {
            "payment_id": payment.payment_id,
            "message": "success",
            "statusCode": 200,
        }
    except Exception as exc:
        raise PaymentError(f"Error creating payment {str(exc)}")


def create_subscription_payment(event: dict) -> Payment:
    return Payment(**event)

@walmsles - Really love the idea of this! A really nice addition to powertools! The lru_cache would be such a nice utility to have in lambda.

I am curious on how a user can manage the cache externally. In this new potential cache utility would the original key be stored in a manner that it can be managed from a completely different service or lambda function.

For example, in the case of an order stored by order_id, if another lambda function is responsible for canceling the order and we need to delete it from the cache. Is there a way to look up the cache item and delete it manually? Or would that require the second feature #7071 to be completed as well?

I didn't make it clear in first writeup. The cache will use a storage provider class much like Idempotency does. Therefore storage would be used to persist the cached item allowing multiple lambda to access cachn storage and the same key.

I am planning to do more research on best DevEx. The lru-cache idea would combine an internal short term memory cache allowing multiple accesses (across distinct invocations) without needing to go back to storage.

I like being able to inject cached items through a decorator which fits a lot of the utility use cases today and using jmespath works nice for most use cases.

#7071 is a seperate feature I feel is useful to improve function hooks and is unrelated given this cache utility will work for your use case better.

I think we're still looking into this, I'll bring this issue up in the next planning meeting.