django - DRF - 如何让 WritableField 不将整个数据库加载到内存中?

标签 django postgresql django-models django-rest-framework

我有一个非常大的数据库 (6 GB),我想使用 Django-REST-Framework。特别是,我有一个模型与 django.contrib.auth.models.User 表(不是很大)有外键关系,并且有一个 BIG 表的外键(我们称之为产品) .该模型如下所示:

class ShoppingBag(models.Model):

    user     = models.ForeignKey('auth.User', related_name='+')
    product  = models.ForeignKey('myapp.Product', related_name='+')
    quantity = models.SmallIntegerField(default=1)

同样,有 6GB 的产品。

序列化器如下:

class ShoppingBagSerializer(serializers.ModelSerializer):

    product = serializers.RelatedField(many=False)
    user    = serializers.RelatedField(many=False)

    class Meta:
        model  = ShoppingBag
        fields = ('product', 'user', 'quantity')

到目前为止这很好 - 我可以在列表和个人购物袋上执行 GET,一切都很好。作为引用,查询(使用查询记录器)看起来像这样:

SELECT * FROM myapp_product WHERE product_id=1254
SELECT * FROM auth_user WHERE user_id=12
SELECT * FROM myapp_product WHERE product_id=1404
SELECT * FROM auth_user WHERE user_id=12
...

因为有很多购物袋被退回。

但我希望能够POST 来创建新的购物袋,但是serializers.RelatedField 是只读的。让我们让它读写:

class ShoppingBagSerializer(serializers.ModelSerializer):

    product = serializers.PrimaryKeyRelatedField(many=False)
    user    = serializers.PrimaryKeyRelatedField(many=False)

    ...

现在情况变得糟糕了... GET 请求 list 操作需要超过 5 分钟,我注意到我的服务器内存跳升至 ~6GB;为什么?!好吧,回到 SQL 查询,现在我看到了:

SELECT * FROM myapp_products;
SELECT * FROM auth_user;

好吧,那可不好。很明显,我们正在做“预取相关”或“选择相关”或类似的事情,以便访问所有产品;但是这张 table 很大。

进一步的检查揭示了在 Line 68 of relations.py in DRF 上发生这种情况的位置:

def initialize(self, parent, field_name):
    super(RelatedField, self).initialize(parent, field_name)
    if self.queryset is None and not self.read_only:
        manager = getattr(self.parent.opts.model, self.source or field_name)
        if hasattr(manager, 'related'):  # Forward
            self.queryset = manager.related.model._default_manager.all()
        else:  # Reverse
            self.queryset = manager.field.rel.to._default_manager.all()

如果不是只读的,self.queryset = ALL!!

所以,我很确定这就是我的问题所在;我需要说,不要在这里选择相关,但如果这是问题或在哪里处理,我不是 100%。似乎所有的东西都应该是内存安全的分页,但事实并非如此。我将不胜感激任何建议。

最佳答案

最后,我们不得不简单地创建我们自己的 PrimaryKeyRelatedField 类来覆盖 Django-Rest-Framework 中的默认行为。基本上我们确保查询集是 None 直到我们想要查找对象,然后我们执行查找。这非常烦人,我希望 Django-Rest-Framework 的人注意这一点!

我们的最终解决方案:

class ProductField(serializers.PrimaryKeyRelatedField):

    many = False

    def __init__(self, *args, **kwargs):
        kwarsgs['queryset'] = Product.objects.none() # Hack to ensure ALL products are not loaded
        super(ProductField, self).__init__(*args, **kwargs)

    def field_to_native(self, obj, field_name):
        return unicode(obj)

    def from_native(self, data):
        """
        Perform query lookup here.
        """
        try:
            return Product.objects.get(pk=data)
        except Product.ObjectDoesNotExist:
            msg = self.error_messages['does_not_exist'] % smart_text(data)
            raise ValidationError(msg)
        except (TypeError, ValueError):
            msg = self.error_messages['incorrect_type'] % type(data)
            raise ValidationError(msg)

然后我们的序列化器如下:

class ShoppingBagSerializer(serializers.ModelSerializer):

    product = ProductField()
    ...

此 hack 确保不会将整个数据库加载到内存中,而是根据数据执行一次性选择。它的计算效率不高,但它也不会用加载到内存中的 5 秒数据库查询来破坏我们的服务器!

关于django - DRF - 如何让 WritableField 不将整个数据库加载到内存中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22815534/

相关文章:

python - 在 django 中使用 python 格式化字符串

postgresql postgis : defining a new subzone index consistent with the old one

mysql - 如何使用 Tableau 处理非常大的数据

django 翻译模型选择

python - Django REST Framework - 通过路由器将模型传递给 ViewSet

python - django中的日期时间显示和时区转换

javascript - 通过 RESTful API 使用 django 和 backbone 进行用户注册

django - 函数 UNIX_TIMESTAMP 不存在

django - KeyError : ('profiles' , 'talk' ) - 我该如何解决?

django - 你是如何设置你的 Django 开发环境的?