Refine Django project - WBowam/wbowam.github.com GitHub Wiki

Date: 2015-04-23
Title: 优化Django项目
Tags: Django
Category: IT

优化工作一般是放在后期来做,早期的优化是“万恶之源”

加速Query查询

1. 善于使用批量方法
Entry.objects.bulk_create([
    Entry(headline="Python 3.0 Released"),
    Entry(headline="Python 3.1 Planned")
])

#优于

Entry.objects.create(headline="Python 3.0 Released")
Entry.objects.create(headline="Python 3.1 Planned")
#前者只连接一次数据库,而后者连接两次。
my_band.members.add(me, my_friend)
#优于
my_band.members.add(me)
my_band.members.add(my_friend)
2. 对于一次性取出来的关联记录,获取外键的时候,直接取关联表的属性,而不是取关联属性,如:
entry.blog.id
#优于
entry.blog_id
3. QuerySets是有缓存的,一旦取出来,它就会在内存里呆上一段时间,尽量重用它,如下
>>> entry.blog   # 博客实体第一次取出,是要访问数据库的
>>> entry.blog   # 第二次再用,那它就是缓存里的实体了,不再访问数据库

但是,all,count exists是调用函数,需要连接数据库处理结果的,如

>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all()   # 第一次all函数会查询数据库
>>> entry.authors.all()   # 第二次all函数还会查询数据库
4. 单一动作(如:同一个页面)需要多次连接数据库时,最好一次性取出所有需要的数据,减少连接数据库次数。此类需求推荐使用QuerySet.select_related() 和 prefetch_related()
5. 相反,别取出你不需要的东西,模版templates里往往只需要实体的某几个字段而不是全部,这时QuerySet.values() 和 values_list(),对你有用,它们只取你需要的字段,返回字典dict和列表list类型的东西,在模版里够用即可,这可减少内存损耗,提高性能。
6. QuerySet.defer()和only()对提高性能也有很大的帮助,一个实体里可能有不少的字段,有些字段包含很多元数据,比如博客的正文,很多字符组成,Django获取实体时(取出实体过程中会进行一些python类型转换工作),我们可以延迟大量元数据字段的处理,只处理需要的关键字段,这时QuerySet.defer()就派上用场了,在函数里传入需要延时处理的字段即可;而only()和defer()是相反功能。
7. 使用QuerySet.count()代替len(queryset),同理判断记录存在时,QuerySet.exists()比if queryset实在强得太多了
8. 找到包含过多的query数的页面,并采取措施减少query数
  • 在ORM中使用select_related()减少query数: 使用select_related()会自动扩展外键关系, 将外键中的数据提前合并到本次query. 如果使用CBV, django-braces的SelectRelatedMixin达到同样的目地. 但要小心query扩展的过深.

  • 如果同样的query发生多次, 那么将其移到view中, 在使用context将其传到template中.

  • 使用redis等cache

  • 使用django.utils.functional.cached_property修饰器, 将query结果cache在内存中

数据库优化

  1. 索引,搜索频率高的字段加上索引
  2. 使用适当字段类型,如本来varchar就搞定的字段,就别要text类型
  3. 不属于数据库的文件不要放数据库 有两种数据不应该储存在数据库中, 一个是log信息, 另一个则是经常变化的数据. log信息在开发时看似没有什么影响, 但在正式服务器上运行时, 可能会拖慢数据库, 因此我们建议使用Splunk, Loggly这样的第三方服务或使用NoSQL数据库保存这些数据. 经常变换的数据比如django.contrib.sessions, django.contrib.messages等应尽量保存到Memcached, Redis, Riak或其他NoSQL数据库中.

使用Memcached或Redis进行Query Cache

  1. Django为缓存提供很多的选择。目前最好的无疑是Memcache,用Django安装memcache非常地简单
  2. 重要的是, 你需要确定哪些需要cache, 哪些不需要cache. 你需要考虑, 哪些view/template包含的query最多? 哪些URL被浏览的最多? 被cache的页面何时需要失效处理?

压缩HTML, CSS和JavaScript等静态文件

虽然Django自带了GZipMiddleware和{% spaceless %} template tag, 还有WSGI的 middleware都能帮助我们减小这些文件.

但使用以上方法都会增加Django自身的系统资源占有量, 可能会导致瓶颈. 最好的方式则是将这一操作交给Apache或Nginx这些web server, 比如利用PageSpeed Module.

当然django的第三方package来压缩和最小化CSS和JavaScript文件也是可行的, 常见的插件有django-pipeline, django-compressor, django-htmlmin等.

使用Upstream caching

使用Varnish等upstream caching也能加快系统的载入速度.

使用CDN

部署自己的CDN, 为全球的用户提供快速的图片, 视频, CSS文件,javaScript等静态文件的载入.

适当的配置便于迁移

1. 在配置中使用相对路径能够确保你的Django项目在部署过程中能够轻松的来回迁移。
import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

TEMPLATE_DIRS = (
    BASE_DIR + '/templates',
)
2. 使用{%url%}标签

使用独立的媒体服务器

通过一台独立的服务器来处理静态文件,性能将得到有效的提升

其他

  1. 利用好模板的with标签:
    模板中多次使用的变量,要用with标签,把它看成变量的缓存行为吧。
  2. django-debug-toolbar可以找到query的来源, 并且找到:
  • 重复的query
  • 产生大量query的ORM语句
  • 慢query

你对哪些页面载入较慢应该有个大致的了解, 所以你只需要使用django-debug-toolbar打开这些页面, 查看是哪些query拖慢了整体速度.