使用Django REST Framework进行批量插入的最佳设计模式是什么?

时间:2016-01-21 17:31:53

标签: django django-rest-framework celery

背景

我有一个Django应用程序,允许通过Django REST Framework插入记录。

记录将由询问电子表格和其他数据库的客户端应用程序逐行批量插入。 REST API允许从Django中抽象出处理数据转换等的其他应用程序。

问题

我想将实际的记录插入与API分离,以提高容错能力和可扩展性。

建议的方法

我正在考虑用Celery做这个,但我之前没用过它。我正在考虑在我现有的DRF ModelViewSets中覆盖perform_create()DRF 3.0中添加了perform_create())来创建工作人员在后台抓取并处理的Celery任务。

DRF文档说perform_create()应“通过调用serializer.save()来保存对象实例”。我想知道,在我的情况下,我是否可以忽略此建议,而是让我的Celery任务调用适当的序列化程序来执行对象保存。

实施例

例如,如果我有几个模型:

class Book(models.Model):
    name = models.CharField(max_length=32)

class Author(models.Model):
    surname = models.CharField(max_length=32)

我已经为这些模型提供了DRF视图和序列化器:

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book

class AuthorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Author

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = Book

class AuthorViewSet(viewsets.ModelViewSet):
    queryset = Author.objects.all()
    serializer_class = Author

在例如中覆盖perform_create()是否是个好主意? BookViewSet

def perform_create(self, serializer):
    create_book_task(serializer.data)

其中create_book_task分别是:

@shared_task
def create_book_task(data):
    serializer = BookSerializer(data=data)
    serializer.save()

我真的找不到其他开发人员做类似事情或试图解决同样问题的例子。我是不是太复杂了?在物理插入方面,我的数据库仍然是限制因素,但至少它不会阻止API客户端排队数据。如果不合适,我不会对Celery承诺。这是最好的解决方案,它有明显的问题,还是有更好的选择?

1 个答案:

答案 0 :(得分:2)

我发现你的方法很合理,Celery很棒,除了一些边境案例,根据我的经验可能会有点讨厌(但我不希望在你在问题中提出的用例中遇到这种情况)。 / p>

但是,使用Redis考虑如下简化方法。它有一些优点和缺点。

在BookViewSet中:

from redis import StrictRedis
from rest_framework import viewsets, renderers

redis_client = StrictRedis()

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = Book

    def perform_create(self, serializer):
        json = renderers.JSONRenderer().render(serializer.data)
        redis_client.lpush('create_book_task', json)

在单独的工作人员脚本中:

from django.utils.six import BytesIO
from redis import StrictRedis
from rest_framework.parsers import JSONParser
from myproject import BookSerializer, Book

MAX_BATCH_SIZE = 1000

def create_book_task():
    bookset = []
    for json in redis_client.brpop(('create_book_task',)):
       stream = BytesIO(json)
       data = JSONParser().parse(stream)
       serializer = BookSerializer(data=data)
       assert serializer.is_valid()
       bookset.append(serializer.instance)
       if len(bookset) >= MAX_BATCH_SIZE:
           break

    if len(bookset) > 0:
        Book.objects.bulk_create(bookset)

while True:
    create_book_task()

<强>赞成

  • 您不需要添加Celery(再次,喜欢它,但它会使测试变得有点棘手,有时会根据工作负载,配置等而变得有点毛茸茸)
  • 它处理批量创建,因此如果您在短时间内(秒或不到一秒)内收到数千本图书,则只会在数据库上执行少量插入(而不是数千个插入)

<强>缺点

  • 你自己负责低级序列化,而不是Celery“神奇地”做它
  • 您需要自己管理工作脚本(将其作为管理命令,将其打包为管理命令,处理重启等),而不是将其交给Celery

当然以上是第一种方法,你可能想让它更通用,可以重复使用其他模型,将MAX_BATCH_SIZE移动到你的设置,使用酸洗而不是JSON或其他各种调整,改进或设计决策满足您的特定需求。

最后,我可能会接受我的回答中概述的方法,除非你预计会有其他几个任务被卸载到异步处理,其中使用Celery的情况会变得更强。

PS:由于实际插入将以异步方式完成,请考虑使用202 Accepted response code代替201 Created进行回复(除非这会搞砸您的客户)。