惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Secure Thoughts
Recent Commits to openclaw:main
Recent Commits to openclaw:main
H
Heimdal Security Blog
SecWiki News
SecWiki News
H
Hacker News: Front Page
N
News | PayPal Newsroom
L
LINUX DO - 最新话题
N
News and Events Feed by Topic
TaoSecurity Blog
TaoSecurity Blog
AI
AI
C
Cybersecurity and Infrastructure Security Agency CISA
Scott Helme
Scott Helme
PCI Perspectives
PCI Perspectives
S
Securelist
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Cyberwarzone
Cyberwarzone
A
Arctic Wolf
Forbes - Security
Forbes - Security
T
Tor Project blog
Spread Privacy
Spread Privacy
WordPress大学
WordPress大学
I
Intezer
Martin Fowler
Martin Fowler
Help Net Security
Help Net Security
P
Proofpoint News Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Cisco Talos Blog
Cisco Talos Blog
Latest news
Latest news
博客园 - 司徒正美
W
WeLiveSecurity
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V
V2EX
P
Palo Alto Networks Blog
Google DeepMind News
Google DeepMind News
IT之家
IT之家
阮一峰的网络日志
阮一峰的网络日志
V
Vulnerabilities – Threatpost
Jina AI
Jina AI
S
Security Affairs
Hacker News - Newest:
Hacker News - Newest: "LLM"
Simon Willison's Weblog
Simon Willison's Weblog
Project Zero
Project Zero
T
Threatpost
P
Privacy International News Feed
人人都是产品经理
人人都是产品经理
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
博客园 - Franky
Hugging Face - Blog
Hugging Face - Blog
Apple Machine Learning Research
Apple Machine Learning Research

Posts on WKLKEN THINKING

apisix 中的 lrucache apisix 中的服务发现机制 apisix 中的负载均衡 apisix etcd机制 聊聊框架 关于 k8s 的 zero downtime deployment 一些建议 apisix 遇到的一些问题 关于在除夕前一天换了一个洗衣机的故事 DRF 的一些实践 Part1: Serializer DRF继承关系图 Better Code: 关于接口的灵活性 新的仓库: wklken/naming 缓存使用的一些经验 Better Code: 抽象: 可扩展性与可维护性的抉择 Better Code: 异常时, 该提示用户哪些信息? Better Code: 更好的异常日志打印 Go: some libs Go: go-redis/cache升级的坑 Go: logrus性能提升 Go: gin validation 远程办公的一点总结 Go: 开发过程中的一些bug 项目管理实践: 风险驱动开发 Go: 一种error wrap调用链处理方式 漫谈技术选型 Go: 基于 apitest 做handler层单元测试 Go: go-sql-driver interpolateparams参数优化 [分享]深度工作 你需要更多的思考时间 Django项目重构小结 工作七年小结: 学习,生活及其他 [分享]bash日常: bash-utils 极客时间推广海报 2017总结: 予时光以意义 k8s APIServer源码: api注册详细细节 k8s APIServer源码: api注册主体流程 k8s APIServer源码: 服务启动 k8s APIServer源码: go-restful框架 重构 - 读书笔记(Python示例) 写给新人的沟通建议 vim 杂谈 - 关于快速编辑 vim 杂谈 - 关于移动 读书笔记-重构: 章11 处理概括关系 读书笔记-重构: 章10 简化函数调用 读书笔记-重构: 章9 简化表达式 读书笔记-重构: 章8 重新组织数据 读书笔记-重构: 章7 在对象之间搬移特性 读书笔记-重构: 章6 重新组织函数 Python 代码规范小结 [分享]关于vim ElasticSearch集群部署文档 Logstash+ElasticSearch处理mysql慢查询日志 [分享]关于代码调试DE那些事 Logstash+ElasticSearch+Kibana- 实现相对通用的数据收集分析 ELK维护的一些点(二) [分享]Python源码剖析-数据结构 一些Centos Python生产环境的部署命令 摘录<<6个月学会任何一种外语>> ELK 维护的一些点 也许是一个新的开始 一些vim的个性化配置 读书笔记-调试九法 这段时间的一些想法 Python 源码阅读 - 垃圾回收机制 我为什么要写博客 APUE笔记-第一章 UNIX基础知识 Python源码阅读-闭包的实现 Python源码阅读-内存管理机制(二) Python源码阅读-内存管理机制(一) Python-基础-数据结构小结 '活动'设计的一些trick 一些简单的Python测试题 我的tmux配置及说明【k-tmux】 Review and Restart 工作四周年小结 vim插件: surround & repeat[成对符号编辑] vim插件: gundo[时光机] vim插件: expand-region[区域选中] vim插件: quickrun[快速执行] vim插件: trailing-whitespace[行尾空格处理] vim插件: closetag[成对标签补全] vim插件: ctrlp[文件搜索] vim插件: airline[状态栏增强] vim插件: theme[主题] vim插件: tagbar[大纲式导航] vim插件: nerdcommenter[快速注释] vim插件: rainbow_parentheses[括号高亮] vim插件: syntastic[语法检查] vim插件: delimitmate[符号自动补全] vim插件: matchit[成对标签跳转] vim插件: easy-align[快速对齐] vim插件: multiple-cursors[多光标操作] vim插件: vim-signature[快速标记跳转] vim插件: easymotion[快速跳转] vim插件: vundle[管理插件] Elasticsearch几个问题的解决 分享一份 Vim 简介PPT k-vim 更新9.0版本 关于知识管理工具的思考 Logstash+ElasticSearch+Kibana处理nginx访问日志
Django DRF 性能优化
2022-10-19 · via Posts on WKLKEN THINKING

1. DB 查询分析

步骤:

  1. 通过日志/监控等, 统计top 10的接口(URL及请求参数)
  2. 通过 APM 等, 统计慢接口top 10, 以及得到慢查询 SQL
  3. 开发环境, 开启SQL打印django.db.backends, 可以参考Logging raw SQL to the console in Django
  4. 构造 1/2步中收集的请求, 在开发环境复现
  5. 获取执行的 SQL
  6. 分析

分析:

  1. SQL 的查询条件是否命中索引, 是否有全表扫描(mysql explain)
  2. SQL 中查询返回字段是否可以减少
  3. 是否某些 SQL 不应该执行, 但是执行了(误用)
  4. 是否某些 SQL 可以合并成一次查询
  5. 是否可以不执行/只执行一次, 例如通过加入缓存

重点: 高频查询, 增加索引能解决很大一部分问题; 尽量避免全表扫描的存在.

2. serializer 优化

2.1 property or SerializerMethodField

N+1 selects problem

这两种, 在里面做了一些特殊操作, 例如执行一次db查询

class Foo(models.Model):
    @property
    def last_check_time(self):
        # property 放大
        cr = CheckRecords.objects.filter(is_success=True).latest()
        return cr.check_time if cr or None

class FooOutputSLZ(serializers.ModelSerializer):
    last_check_time = serializers.DateTimeField()
    bar = serializers.SerializerMethodField()

    def get_bar(self, obj):
        return Bar.objects.filter(foo_type=obj.type).first()

此时, 列表查询, page_size=100, 会产生 1+100+100=201 次 db 查询

优化:

  1. 不返回
  2. 加缓存, 例如增加外部一个方法get_bar_from_cache(foo_type: str)
  3. 通过serializer context在上层提前查好, 适合所有items公共的数据, 例如用户职位名称
class UserListApi(generics.ListAPIView):
    def get_serializer_context(self):
        # set into context, for slz to_representation
        return {"position_name_map": get_position_name_map_from_db()}

class UserOutputSLZ(serializers.ModelSerializer):
    position_name = serializers.SerializerMethodField()
    def get_position_name(self, obj):
        m =  self.context.get("position_name_map")
        return m.get(obj.position_id)

2.2 Serializer本身的性能问题

可以参考: Improve Serialization Performance in Django Rest Framework: How we reduced serialization time by 99%!

小改:

  1. read_only=True
  2. 自己构建数据, 不使用serializer
class UserOutputSLZ(serializers.ModelSerializer):
    position = serializers.CharField(read_only=True)
data = []
for u in users:
    data.append({
        "id": user.id,
        "name": user.name,
    })
return Response(data=data, status=status.HTTP_200_OK)

3. queryset 优化

3.1 defer / only 等, 只查询想要的字段

如果模型中存在一些本次逻辑用不上的字段, 那么不要查询出来, 或者声明只查询想要的字段

  • 例如create_time/update_time
  • description/content/extras等 TextField or JsonField

使用 django queryset only() 只获取指定字段

Department.objects.only("id", "name")

使用 django queryset defer() 延迟获取某些字段

Department.objects.defer("description", "extras", "create_time")

可以在打印的 SQL 中看到 SELECT 字段列表的变化.

3.2 values()/values_list()

以dict/tuple的形式获取结果列表, 避免对象创建

# got List[Dict]
Department.objects.values("id", "name")

# got List[Tuple]
Department.objects.values_list("id", "name")

values_list()如果只获取一个字段, 那么可以加上flat=True, 直接获取返回列表

Department.objects.values_list('id', flat=True)

N+1 selects problem

这两个的目的都是将可能的多次查询合并, 减少 DB 查询次数, 可以参考这篇文章 Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化

  • select_related(): “follow” foreign-key relationships, selecting additional related-object data when it executes its query. (SQL JOIN)
  • prefetch_related(): automatically retrieve, in a single batch, related objects for each of the specified lookups.(ANOTHER SQL)

注意, select_related/prefetch_related 的表如果数据量特别大(百万), 或者单页page_size特别大(几千/上万), 可能导致查询直接卡死

3.4 其他

use count()

# bad
len(User.objects.all())


# good
User.objects.count()

use exists()

# bad
if User.objects.filter(username=x):
    dosomething

# good
if  User.objects.filter(username=x).exists():
    dosomething

use bulk_create()/bulk_update()

Entry.objects.bulk_create(objs, 1000)
Entry.objects.bulk_update(objs, ['headline'], 1000)

specific the updated fields

product.name = 'Name changed again'
product.save(update_fields=['name'])

use F

Reporter.objects.update(stories_filed=F('stories_filed') + 1)

4. 缓存: local memory or redis

没有加一层缓存解决不了的问题, 如果有, 加两层……

有些数据库查询, 每次请求都会触发; 有些请求会触发 N 次相同的重复查询;

这些不一定能通过上面列举的方法优化

可以根据业务场景, 考虑是否能加缓存

细粒度:

from django.core.cache import caches

def get_default_department_id_from_cache() -> int:
    cache = caches["locmem"]
    id = cache.get(DEFAULT_DEPT_ID)
    if id is not None:
        return id

    id = Department.objects.get_default().id
    cache.set(DEFAULT_DEPT_ID, category_id, 1 * 60)
    return id

粗粒度: cache_page: The per-view cache