Cache
Description
Usually django cache is used to cache page or basic GET request. However, in the context of gRPC, we use POST request and there is no native cache system in gRPC in Python.
Fortunately, DSG bring a layer of abstraction between gRPC request and Django request allowing us to use Django cache system.
To enable it follow the Django instructions then use the cache_endpoint decorator or the cache_endpoint_with_deleter to cache your endpoint.
cache_endpoint
Th cache_endpoint decorator is used to adapt the cache_page decorator to work with grpc.
It took the same parameters and do the exact same things. See Use Django decorators in DSG for more informations.
This decorator will cache response depending on:
Meaning that if you have a filter in your request, the cache will be different for each filter.
Warning
If you have request parameters that are not considered as filters or pagination, the cache will not be different for each request.
Example:
from django_socio_grpc.decorators import cache_endpoint
...
class UnitTestModelWithCacheService(generics.AsyncModelService, mixins.AsyncStreamModelMixin):
queryset = UnitTestModel.objects.all().order_by("id")
serializer_class = UnitTestModelWithCacheSerializer
@grpc_action(
request=[],
response=UnitTestModelWithCacheSerializer,
use_generation_plugins=[
ListGenerationPlugin(response=True),
],
)
@cache_endpoint(300)
async def List(self, request, context):
return await super().List(request, context)
cache_endpoint_with_deleter
The cache_endpoint_with_deleter decorator work the same cache_endpoint but allow to automatically delete the cache when a django signals is called from the models passed in parameters or the one used for the queryset if not specified.
As DSG is an API framework it’s logic to add utils to invalidate cache if data is created, updated or deleted.
Warning
The cache will not be deleted if using bulk operations. This also integrate the usage of filter(…).update() method. See caveats of each meathod you wish to use to be sure of the behavior
The cache will also not be deleted if modifying date in an other process that is not gRPC (Django commands, admin, shell, …). You can make your own decorator to handle this case if needed by registering decorator parameter in a global context and then listen to all django event to see if one matching. We decide to not integrate it because listening all django events and making check on signals and senders may add an unwanted request overhead.
There is also caveats to understand when usings cache-endpoint-with-deleter. As only Redis cache allow a pattern like deleter, if not using redis cache each specified signals on the specified models of the deleter will delete the entire cache.
To address this issue, you can:
Use a redis cache
Use a cache per model
Note
If you do not follow above advice a warning will show up everytimes you start the server. You can mute the logger by muting the django_socio_grpc.cache_deleter logger in your logging settings.
Example:
# SETTINGS
CACHES = {
"UnitTestModelCache": {
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
"LOCATION": "unit_test_model_cache_table",
}
}
# SERVICES
from django_socio_grpc.decorators import cache_endpoint_with_deleter
...
class UnitTestModelWithCacheService(generics.AsyncModelService, mixins.AsyncStreamModelMixin):
queryset = UnitTestModel.objects.all().order_by("id")
serializer_class = UnitTestModelWithCacheSerializer
@grpc_action(
request=[],
response=UnitTestModelWithCacheSerializer,
use_generation_plugins=[
ListGenerationPlugin(response=True),
],
)
@cache_endpoint_with_deleter(
300,
cache="UnitTestModelCache", # Cache is not mandatory. But it is for working as expecting is using any other cache system than redis.
# key_prefix="UnitTestModel-List", # You can specify a key prefix if needed. It will allow you to use a specific pattern for cache action. Default is <ServiceName>-<ActionName>
# senders=(UnitTestModel,), # You can specify a list of models to listen to. Default is the queryset model.
# signals=(signals.post_save, signals.post_delete), # You can specify a list of signals to listen to. Default is (signals.post_save, signals.post_delete)
)
async def List(self, request, context):
return await super().List(request, context)
vary_on_metadata
Working like django vary_on_headers it’s just a convenient renaming using Use Django decorators in DSG.
It allow the cache to also vary on metadata and not only filters and paginations.
Example:
from django_socio_grpc.decorators import cache_endpoint, vary_on_metadata
...
class UnitTestModelWithCacheService(generics.AsyncModelService, mixins.AsyncStreamModelMixin):
queryset = UnitTestModel.objects.all().order_by("id")
serializer_class = UnitTestModelWithCacheSerializer
@grpc_action(
request=[],
response=UnitTestModelWithCacheSerializer,
use_generation_plugins=[
ListGenerationPlugin(response=True),
],
)
@cache_endpoint(300)
@vary_on_metadata("custom-metadata", "another-metadata")
async def List(self, request, context):
return await super().List(request, context)
Any other decorator
As you can use Django decorators in DSG. You can try to use any django decorators as long as they are wrapped into http_to_grpc decorator.
If the one you are trying to use is not working as expected and it’s not listed in the documentation page please fill an issue.