Tom*_*Tom 8 django mongodb pymongo mongoengine
我正在努力优化(主要)由MongoDB支持的Django应用程序.它在负载测试下死亡.在当前有问题的页面上,New Relic显示了700多个来电pymongo.collection:Collection.find.大部分代码都是由初级编码员编写的,通常我会寻找添加标记,进行更智能连接和删除循环以减少查询调用的地方,但这里不能选择连接.我做了什么(在添加基于EXPLAINs的指标之后)尝试通过进行一般查询然后在循环中过滤那个较小的集合来降低循环中的成本.虽然我已经从900个查询中得到了这个数字,但即使在页面上进行了大量的工作,700仍然看起来很疯狂.我想甚至可能find在过滤现有的查询集时被调用,但代码表明它始终是数据库查询.
我已经添加了一些日志记录到mongoengine来查看查询来自哪里以及查看EXPLAIN语句,但是我没有大量的运气来筛选信息.mongoengine本身似乎是性能问题的一部分:我转而使用mongomallard作为测试,并在页面上获得了50%的性能提升.不幸的是,我得到了很多其他的页面上的错误(尽我可以告诉它过滤现有查询集时,会出现野鸭做得不好;错误抱怨的电话deepcopy这是发生在一台发电机,你不能做 - 我在那里打了一堵砖墙.虽然Mallard对我们来说似乎不是一个可行的替代品,但它确实表明在mongoengine中将很多时间用于将对象转换为Python和从Python转换对象.
我该怎么做才能进一步减少通话?或者我是否专注于错误的事情,应该在其他地方攻击问题?
编辑:提供一些代码/模型
相关页面显示课程的教学大纲,显示课程中的所有模块,课程和课程中的概念.对于每个概念,还显示了用户在概念中的进展.所以有很多循环来使层次结构变得清晰(并且它不是根据Mongo文档建议的任何模式存储的).
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())
Run Code Online (Sandbox Code Playgroud)
课程的模块,课程和概念存储在courseware_containers; 我们需要获取所有概念,以便我们可以获取id列表teaching_element_instances以找到用户已经处理过的最新的概念(如果有的话),然后查找他们的进度.
*为了清楚起见,我正在使用一个分析器并查看时间和行为方式正确的方式我知道,不仅仅是改变事物,希望最好.
Ros*_*oss 10
代码示例并不是很糟糕,但是应该考虑许多方面,这可能有助于提高性能.
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())
Run Code Online (Sandbox Code Playgroud)
评论
无限列表.
course_instances,courseware_containers,teaching_element_instances
如果这些字段无限制并且不断增长,则文档将随着磁盘的增长而在磁盘上移动,从而导致负载较重的系统上的磁盘争用.有两种模式可以帮助减少这种情况:
a)打开两种尺寸的电源.这将花费磁盘空间,但应该随着文档的增长而降低io churn的数量
b)初始填充 - 自定义填充插入的文档,以便将其放入更大的范围,然后移除填充.真的是反模式,但它可能会给你一些里程.
最后的障碍是最大文档大小 - 16MB你不能将数据增加到更大.
ReferenceFields列表 - course_instances
MongoDB没有连接,因此需要额外查询才能查找ReferenceField- 实质上它们是应用程序中的连接.这对于每个人来说都不错,但对于理解这种权衡很重要.默认情况下,mongoengine不会自动取消引用该字段,只会执行course_version.course_instances另一个查询,然后填充整个引用列表.因此,它可能会花费您另一个查询 - 如果您不需要数据,那么exclude()它将从查询中停止任何泄漏的查询.
EmbeddedFields
这些字段是文档的一部分,因此除了传输和加载数据的线缆成本之外,它们不需要任何费用.**由于它们是文档的一部分,因此您无需select_related获取此数据.
teaching_element_instances
这些是id的列表吗?它StringField在上面的代码示例中说明了它.无论哪种方式,如果您不需要取消引用整个列表,那么如果编码正确,则存储_ids为a StringField和手动解除引用可能更有效 - 特别是如果您只需要最新的(最后?)id.
模型复杂性
这CoursewareContainer很复杂.对于CourseVersion你n CoursewareContainers自己拥有的任何给定的n容器列表,每个n容器都有容器,并且......
查找最新的实例
我们需要获取所有概念,以便我们可以获取id列表
teaching_element_instances以找到用户已经处理过的最新的概念(如果有的话),然后查找他们的进度.
我不确定您是否有一个实例或每个容器一个或每个课程一个实例.无论哪种方式 - 都应该检查查询数据的逻辑.如果它是你所追求的单个实例 - 则可以针对用户存储,以简化查找的逻辑.如果它的每个课程或容器然后提高性能确保您最小化查询的数量 - 如果可能的话收集所有ids,然后最后发出一个$in查询,而不是每个容器进行一次查询.
Mongoengine成本
目前,将数据加载到Mongoengine类中会产生性能成本 - 如果您不需要这些类并且很乐意使用简单的词典,那么要么发出原始pymongo查询,要么使用as_pymongo.
架构设计
模式看起来足够合理,但是它适用于用例 - 实质上它是使用MongoDB的优势还是在文档数据库形状的孔中放置关系挂钩?我不能回答你,但我确实知道通往MongoDB的快乐路径的方法是基于其用例设计架构.使用关系数据库,模式设计从一开始就很简单 - 您可以使用文档数据库对数据的使用方式进行规范化,这是一个主要因素.
MongoDB最佳实践
还有许多其他最佳实践,mongodb有一个可能感兴趣的指南:MongoDB操作最佳实践.
欢迎通过Mongoengine邮件列表与我联系,进一步讨论,如果需要私下讨论.
罗斯