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

时间:2014-04-02 15:02:11

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

我有一个非常大的数据库(6 GB),我想使用Django-REST-Framework。特别是,我有一个与django.contrib.auth.models.User表(不是那么大)有一个ForeignKey关系的模型和一个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;

好的,所以这不好。很明显,我们正在做“预取相关”或“select_related”或类似的东西,以便访问所有产品;但这张桌子很大。

进一步检查会发现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()

如果不是readonly,self.queryset = ALL !!

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

1 个答案:

答案 0 :(得分:1)

最后,我们必须简单地创建自己的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秒数据库查询中爆炸我们的服务器!