Неожиданное поведение при одновременных запросах ModelViewset

У меня возникла проблема с конечной точкой ModelViewSet в Django Rest Framework с именем projects/. У меня есть набор запросов (PATCH, DELETE, затем GET), которые вызывают неожиданное поведение. Временная шкала запросов и ответов выглядит следующим образом:

  1. Запрос PATCH в 14:45:09.420
  2. DELETE запрос в 14:45:12.724 3.Ответ DELETE 204 в 14:45:12.852
  3. PATCH 200 ответ в 14:45:13.263
  4. GET запрос в 14:45:13.279
  5. GET 200 ответ в 14:45:13.714 Все ответы свидетельствуют об успехе. Однако ответ GET, который следует за DELETE, включает якобы удаленную модель. Если я вызову конечную точку GET немного позже, удаленная модель больше не будет указана.

Такое поведение говорит о потенциальном состоянии гонки или проблеме с кэшированием, когда операция PATCH завершается после DELETE, или запрос GET возвращает кэшированный список, не отражающий удаление.

Код представления, сериализатора и модели довольно ванильный:

class ProjectViewSet(ModelViewSet):
    parser_classes = (MultiPartParser, FormParser, JSONParser)
    queryset = Project.objects.all()
    serializer_class = ProjectSerializer
    pagination_class = ProjectPagination

class ProjectSerializer(serializers.ModelSerializer):
    creator = UserUUIDField(default=serializers.CurrentUserDefault())
    image = serializers.ImageField(required=False)

    class Meta:
        model = Project
        fields = "__all__"

class Project(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    creator = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    image = models.ForeignKey(
        wand_image,
        on_delete=models.DO_NOTHING,
        null=True,
        blank=True,
        related_name="projects"
    )

У одной модели есть внешняя ключевая ссылка на эту модель, но поведение on_delete заключается в установке ее в null.

Я запускаю это с Google Cloud Run, бессерверным бэкэнд-сервисом

Миксины Rest Framework не являются атомарными операциями, поэтому для операций UPDATE и DELETE необходимо использовать собственные миксины:

class AtomicDestroyModelMixin:
    """
    Destroy a model instance.
    """
    def destroy(self, request, *args, **kwargs):
        try:
            with transaction.atomic():
                instance = self.get_object()
                self.perform_destroy(instance)
        except OperationalError:
            raise DatabaseOperationException()

        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()


class AtomicUpdateModelMixin:
    """
    Update a model instance.
    """
    def update(self, request, *args, **kwargs):
        try:
            with transaction.atomic():
                partial = kwargs.pop("partial", False)
                instance = self.get_object()
                serializer = self.get_serializer(instance, data=request.data, partial=partial)
                serializer.is_valid(raise_exception=True)
                self.perform_update(serializer)

                if getattr(instance, "_prefetched_objects_cache", None):
                    instance._prefetched_objects_cache = {}
        except OperationalError:
            raise DatabaseOperationException()

        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs["partial"] = True
        return self.update(request, *args, **kwargs)
Вернуться на верх