Django 使用mongoengine减少对MongoDB的调用次数

Django 使用mongoengine减少对MongoDB的调用次数,django,mongodb,pymongo,mongoengine,Django,Mongodb,Pymongo,Mongoengine,我正在优化一个由MongoDB(主要)支持的Django应用程序。它在负载测试下就快死了。在当前有问题的页面上,NewRelic显示了对pymongo.collection:collection.find的700多个调用。大部分代码都是由初级程序员编写的,通常我会寻找添加标记、进行更智能的连接和删除循环以减少查询调用的位置,但连接在这里不是一个选项。我所做的(在根据解释添加标记后)是通过进行一般查询,然后过滤循环中较小的集合*,来降低循环中的成本。虽然我已经从900个查询中得到了这个数字,但70

我正在优化一个由MongoDB(主要)支持的Django应用程序。它在负载测试下就快死了。在当前有问题的页面上,NewRelic显示了对pymongo.collection:collection.find的700多个调用。大部分代码都是由初级程序员编写的,通常我会寻找添加标记、进行更智能的连接和删除循环以减少查询调用的位置,但连接在这里不是一个选项。我所做的(在根据解释添加标记后)是通过进行一般查询,然后过滤循环中较小的集合*,来降低循环中的成本。虽然我已经从900个查询中得到了这个数字,但700个仍然看起来很疯狂,即使在页面上做了大量的工作。我原以为即使在筛选现有查询集时也会调用
find
,但代码表明它始终是一个查询集

我已经在mongoengine中添加了一些日志,以查看查询来自何处,并查看解释语句,但我在筛选信息墙时运气不佳。mongoengine本身似乎是性能问题的一部分:作为测试,我切换到mongoengine,页面上的性能提高了50%。不幸的是,我在许多其他页面上都出现了错误(据我所知,Mallard在过滤现有查询集时似乎做得不好;错误抱怨在生成器中调用了
deepcopy
,这是你做不到的——我碰到了一堵墙)。虽然Mallard对我们来说似乎不是一个可行的替代品,但它确实表明,在mongoengine中,在Python和Python之间转换对象需要花费大量的处理时间

我能做些什么来进一步减少通话?还是我把注意力放在了错误的事情上,应该在别的地方解决问题

编辑:提供一些代码/模型

相关页面显示课程的教学大纲,显示课程中的所有模块、它们的课程以及课程下的概念。对于每个概念,还显示了用户在概念方面的进展。因此,有很多循环来梳理层次结构(并且它不是根据任何一种方法存储的)

课程的模块、课程和概念存储在
课件容器中
;我们需要获得所有的概念,这样我们就可以在
教学元素\u实例中获得ID列表
,找到用户为该概念所做的最新一个ID(如果有),然后查看他们的进度


*要明确的是,我正在使用一个探查器,以我所知的正确方式查看时间和做事,而不是简单地改变事情并期待最好的结果。

根据sae,代码示例并不坏,但有许多方面应该考虑,可能有助于提高性能

class CourseVersion(Document):
    ...
    course_instances = ListField(ReferenceField('CourseInstance'))
    courseware_containers = ListField(EmbeddedDocumentField('CoursewareContainer'))

class CoursewareContainer(EmbeddedDocument):
    id = UUIDField(required=True, binary=False, default=uuid.uuid4)
    ....
    courseware_containers = ListField(EmbeddedDocumentField('self'))
    teaching_element_instances = ListField(StringField())
回顾

  • 无限列表。
    课程\u实例
    课件\u容器
    教学元素\u实例

    如果这些字段是无界的并且不断增长,那么文档将随着其增长而在磁盘上移动,从而在负载较重的系统上造成磁盘争用。有两种模式可帮助最大限度地减少这种情况:

    a) 。这将占用磁盘空间,但随着文档的增长,io流失量会降低

    b) 初始填充-自定义在插入时填充文档,以便将其放入更大的范围,然后删除填充。确实是一种反模式,但它可能会给你一些里程

    最后一个障碍是最大文档大小—16MB—您不能将数据增长到超过16MB

  • 参考字段列表-
    课程\u实例

    MongoDB没有连接,因此需要额外的查询来查找
    引用字段
    ——本质上它们是应用内连接。根据sae,这并不坏,但了解折衷很重要。默认情况下,mongoengine不会仅在执行
    course\u版本时自动取消对字段的引用。course\u实例将执行另一个查询,然后填充整个引用列表。因此,它可能会占用您另一个查询—如果您不需要数据,则
    exclude()
    将其从查询中排除,以停止任何泄漏的查询

  • 嵌入字段

    这些字段是文档的一部分,因此除了传输和加载数据的有线成本外,它们没有任何成本**因为它们是文档的一部分,所以不需要
    选择\u related
    来获取此数据

  • 教学元素实例

    这些是身份证的清单吗?在上面的代码示例中,它表示它是一个
    StringField
    。无论哪种方式,如果您不需要取消引用整个列表,则将
    \u id
    存储为
    StringField
    ,如果编码正确,手动取消引用可能会更有效-尤其是如果您只需要最新(最后一个?)id

  • 模型复杂性

    CoursewarContainer
    非常复杂。对于任何给定的
    CourseVersion
    您都有
    n
    课件收纳者
    自己有一个
    n
    容器列表,每个容器都有
    n
    容器等等

  • 查找最近的实例

    我们需要得到所有的概念,这样我们就可以在
    教授元素实例
    查找用户最近拥有的元素实例 为这个概念工作(如果有的话),然后查看他们的进展

    我不确定是否有一个单一的实例,你是追求或一个集装箱或一个课程。无论哪种方式-都应该检查查询数据的逻辑。如果您要查找的是单个实例,那么可以针对用户存储该实例,以简化查找该实例的逻辑。如果是每个课程或容器,那么为了提高性能,请确保将查询数量降至最低-如果可能,收集所有
    ID
    ,然后在最后发出一个
    $in
    查询,而不是每个容器进行查询。
    class CourseVersion(Document):
        ...
        course_instances = ListField(ReferenceField('CourseInstance'))
        courseware_containers = ListField(EmbeddedDocumentField('CoursewareContainer'))
    
    class CoursewareContainer(EmbeddedDocument):
        id = UUIDField(required=True, binary=False, default=uuid.uuid4)
        ....
        courseware_containers = ListField(EmbeddedDocumentField('self'))
        teaching_element_instances = ListField(StringField())