python - Django Rest Framework 帮助找出响应时间的瓶颈。我尝试了很多事情都没有成功
问题描述
我在 PostgreSQL 数据库中有 1000 篇博客文章的响应时间为 4-5 秒。我已经使用cProfile
了我的家伙Haki Benita和我的序列化程序在 0.003 秒区域中序列化 1000 个对象。我有很多事情要做,比如分页,to_representation
正如你将看到的,所以我希望有人能帮助我看到一些我不知道的东西。我的问题是,如何在返回我定义的所有不同数据点的同时提高响应时间?请看一下,请温柔一点:)
楷模:
import uuid
from datetime import timedelta
from django.conf import settings
from django.db.models import Manager, Model, DateTimeField, TextField, CharField, EmailField, IntegerField, \
BooleanField, ForeignKey, ManyToManyField, OneToOneField, SlugField, CASCADE, SET_NULL
from django.core.exceptions import ValidationError
from django.utils import timezone
from django.template.defaultfilters import slugify
from django.contrib.postgres.search import SearchVectorField
from django.contrib.postgres.indexes import GinIndex
from django.contrib.auth import get_user_model
User = get_user_model()
from blog_api.users.model_validators import validate_min_3_characters, validate_no_special_chars
from blog_api.posts.validators import validate_min_8_words
from blog_api.posts.managers import PostManager, TagManager
class BaseModel(Model):
'''Base model to subclass.'''
created_at = DateTimeField(editable=False, null=True)
updated_at = DateTimeField(editable=False, null=True)
class Meta:
abstract = True
def save(self, *args, **kwargs):
if not self.id:
self.created_at = timezone.now()
self.updated_at = timezone.now()
return super(BaseModel, self).save(*args, **kwargs)
class Tag(BaseModel):
'''
Tag model.
'''
pub_id = CharField(editable=False, unique=True, max_length=50, null=True)
name = CharField(
blank=False, null=False, max_length=30, unique=True,
validators=[validate_no_special_chars, validate_min_3_characters]
)
objects = Manager()
items = TagManager()
class Meta:
ordering = ['name']
def __str__(self):
return self.name
def get_post_count(self):
return self.posts.filter(is_active=True).count()
def save(self, *args, **kwargs):
'''
--User model save method--
1) Sets self.pub_id to UUID hex on first save.
'''
if not self.id:
self.pub_id = str(uuid.uuid4().hex)
return super(Tag, self).save(*args, **kwargs)
class Post(BaseModel):
'''
Blog Post Model.
'''
slug = SlugField(editable=False, max_length=255, unique=True)
title = CharField(max_length=255, blank=False, null=False, validators=[validate_min_8_words])
author = ForeignKey(User, on_delete=SET_NULL, null=True, related_name='posts')
featured = BooleanField(default=False)
estimated_reading_time = IntegerField(default=0, editable=False)
content = TextField(blank=False, null=False)
bookmarks = ManyToManyField(User, related_name='bookmarked_posts', blank=True)
previouspost = ForeignKey('self', related_name='previous_post', on_delete=SET_NULL, blank=True, null=True)
nextpost = ForeignKey('self', related_name='next_post', on_delete=SET_NULL, blank=True, null=True)
is_active = BooleanField(default=True)
search_vector = SearchVectorField(editable=False, null=True)
tags = ManyToManyField(Tag, related_name='posts', blank=True)
score = IntegerField(editable=False, default=0)
objects = Manager()
items = PostManager()
class Meta:
ordering = ['-created_at']
verbose_name_plural = 'Posts'
indexes = [GinIndex(fields=['search_vector'])]
def __str__(self):
return self.title
def get_overview(self):
return self.content[:300]
def _get_estimated_reading_time(self):
content_string = self.content.strip()
word_count = 1
for char in content_string:
if char == " ":
word_count += 1
word_count = (word_count / 300)
if not word_count > 1:
return 1
return word_count
def _get_unique_slug(self):
slug = slugify(self.title)
uid = uuid.uuid4().hex
unique_slug = f'{slug}-{uid}'
qs_exists = Post.objects.filter(slug=unique_slug)
if qs_exists:
_get_unique_slug(self)
return unique_slug
def bookmark(self, pubid):
'''
Bookmarks this post with provided user.
'''
if not self.is_active:
return {
'bookmarked': False,
'message': 'Post inactive. Can not bookmark.'
}
try:
user = User.objects.get(pub_id=pubid)
if not user.is_active:
raise User.DoesNotExist()
except User.DoesNotExist:
return {
'bookmarked': False,
'message': 'No user found with provided id.'
}
if self.author == user:
return {
'bookmarked': False,
'message': 'You can not bookmark your own post.'
}
if not user in self.bookmarks.all():
self.bookmarks.add(user)
self.save()
return {
'bookmarked': True,
'message': f'Post {self.slug} bookmarked successfully.'
}
else:
self.bookmarks.remove(user)
self.save()
return {
'bookmarked': False,
'message': f'Post {self.slug} un-bookmarked successfully.'
}
def get_bookmark_count(self):
return self.bookmarks.all().count()
def get_likes_count(self):
return self.likes.users.all().count()
def get_dislikes_count(self):
return self.dislikes.users.all().count()
def get_like_score(self):
return self.get_likes_count() - self.get_dislikes_count()
def set_like_score(self):
try:
self.score = self.get_likes_count() - self.get_dislikes_count()
self.save()
except (Post.likes.RelatedObjectDoesNotExist, Post.dislikes.RelatedObjectDoesNotExist):
self.score = 0
self.save()
def save(self, *args, **kwargs):
if not self.id:
self.slug = self._get_unique_slug()
if self.nextpost:
if self.nextpost.author != self.author:
raise ValidationError({'nextpost': 'Next post choices limited to author.'})
if self.previouspost:
if self.previouspost.author != self.author:
raise ValidationError({'previouspost': 'Previous post choices limited to author.'})
if self.nextpost and self.previouspost:
if self.previouspost.slug == self.nextpost.slug:
raise ValidationError({
'nextpost': 'Next post and previous post can not be same.',
'previouspost': 'Previous post and next post can not be same.'
}
)
self.estimated_reading_time = self._get_estimated_reading_time()
return super(Post, self).save(*args, **kwargs)
def __str__(self):
return self.title
序列化器:
from rest_framework.serializers import ModelSerializer, ValidationError, PrimaryKeyRelatedField, SerializerMethodField, CharField
from django.contrib.auth import get_user_model
User = get_user_model()
from blog_api.posts.models import Tag, Post
class AuthorBookmarkLikedSerializer(ModelSerializer):
following_count = SerializerMethodField()
followers_count = SerializerMethodField()
post_count = SerializerMethodField()
class Meta:
model = User
fields = [
'pub_id', 'username', 'following_count', 'followers_count', 'post_count',
]
read_only_fields = fields
def get_following_count(self, obj):
return obj.get_following_count()
def get_followers_count(self, obj):
return obj.get_followers_count()
def get_post_count(self, obj):
return obj.get_post_count()
class NextPostPreviousPostSerializer(ModelSerializer):
class Meta:
model = Post
fields = ['slug', 'title',]
read_only_fields = fields
class TagSerializer(ModelSerializer):
post_count = SerializerMethodField()
class Meta:
model = Tag
fields = ['pub_id', 'name', 'post_count',]
read_only_fields = fields
def get_post_count(self, obj):
return obj.get_post_count()
class PostOverviewSerializer(ModelSerializer):
author = AuthorBookmarkLikedSerializer(read_only=True)
overview = SerializerMethodField()
tags = TagSerializer(many=True, read_only=True)
bookmark_count = SerializerMethodField()
likes_count = SerializerMethodField()
dislikes_count = SerializerMethodField()
like_score = SerializerMethodField()
nextpost = NextPostPreviousPostSerializer(read_only=True)
previouspost = NextPostPreviousPostSerializer(read_only=True)
class Meta:
model = Post
fields = [
'slug', 'title', 'author', 'featured', 'estimated_reading_time', 'overview',
'previouspost', 'nextpost', 'created_at', 'updated_at', 'bookmark_count', 'likes_count',
'dislikes_count', 'like_score', 'tags',
]
read_only_fields = fields
def get_overview(self, obj):
return obj.get_overview()
def get_bookmark_count(self, obj):
return obj.get_bookmark_count()
def get_likes_count(self, obj):
return obj.get_likes_count()
def get_dislikes_count(self, obj):
return obj.get_dislikes_count()
def get_like_score(self, obj):
return obj.get_like_score()
意见:
from rest_framework.decorators import api_view, permission_classes, throttle_classes
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from rest_framework.throttling import AnonRateThrottle
from blog_api.posts.models import Tag, Post
from blog_api.posts.api.serializers import PostOverviewSerializer
def get_paginated_queryset(request, qs, serializer_obj, page_size=10):
paginator = PageNumberPagination()
paginator.page_size = page_size
page = paginator.paginate_queryset(qs, request)
serializer = serializer_obj(page, many=True, context={'request': request})
return paginator.get_paginated_response(serializer.data)
@api_view(['GET'])
@permission_classes((AllowAny,))
@throttle_classes([AnonRateThrottle])
def all_posts(request):
'''
--All posts view--
==========================================================================================================
Returns nested representations of all posts. Paginates the queryset.
==========================================================================================================
'''
posts_to_paginate = (
Post.objects.prefetch_related('bookmarks')
.prefetch_related('tags')
.select_related('author')
.select_related('previouspost')
.select_related('nextpost')
.filter(is_active=True)
)
all_posts = get_paginated_queryset(request, posts_to_paginate, PostOverviewSerializer)
return all_posts
更新:
通过精简 SerializerMethodFields 并将作者减少到只是用户名和标签只是名称,我能够加速所有帖子视图。它要快得多,我将在前端使整个内容可点击,然后在帖子详细信息中,他们将能够喜欢/不喜欢,书签等。所以它有效,但我仍然想知道如何制作我的原创设置更快!查看下面的新临时代码。
新的序列化器:
class AuthorUsernameSerializer(ModelSerializer):
class Meta:
model = User
fields = ['username']
read_only_fields = fields
class TagNameSerializer(ModelSerializer):
class Meta:
model = Tag
fields = ['name',]
read_only_fields = fields
class PostOverviewSerializer(ModelSerializer):
author = AuthorUsernameSerializer(read_only=True)
tags = TagNameSerializer(many=True, read_only=True)
like_score = SerializerMethodField()
class Meta:
model = Post
fields = [
'created_at', 'slug', 'title', 'author', 'featured',
'estimated_reading_time','tags', 'like_score',
]
read_only_fields = fields
def get_like_score(self, obj):
return obj.get_like_score()
使用那个新的序列化程序,我得到了这个:
HTTP/1.0 200 OK
Allow: OPTIONS, GET
Content-Language: en
Content-Length: 4180
Content-Type: application/json
Date: Tue, 03 Aug 2021 22:23:32 GMT
Referrer-Policy: same-origin
Server: Werkzeug/1.0.1 Python/3.9.6
Server-Timing: TimerPanel_utime;dur=1638.8899999999999;desc="User CPU time",
TimerPanel_stime;dur=217.87099999999998;desc="System CPU time",
TimerPanel_total;dur=1856.761;desc="Total CPU time",
TimerPanel_total_time;dur=1823.192834854126;desc="Elapsed time",
SQLPanel_sql_time;dur=36.33713722229004;desc="SQL 44 queries",
CachePanel_total_time;dur=0.12755393981933594;desc="Cache 2 Calls"
Vary: Accept, Accept-Language, Cookie, Origin
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
"count": 1000,
"next": "http://127.0.0.1:8000/api/v1/posts/posts/?page=2",
"previous": null,
"results": [
...
{
"author": {
"username": "someuser1"
},
"created_at": "2021-08-03T18:06:04.656803-04:00",
"estimated_reading_time": 1,
"featured": false,
"like_score": 0,
"slug": "this-is-a-title-longer-than-eight-words-e26c396b673b4b4788b5b7f74693afcd",
"tags": [
{
"name": "django"
},
{
"name": "djangorestframework"
},
{
"name": "docker"
},
{
"name": "javascript"
},
{
"name": "react"
},
{
"name": "solidity"
}
],
"title": "This is a title longer than eight words."
}
]
而不是这个:
HTTP/1.0 200 OK
Allow: OPTIONS, GET
Content-Language: en
Content-Length: 17000
Content-Type: application/json
Date: Tue, 03 Aug 2021 22:46:27 GMT
Referrer-Policy: same-origin
Server: Werkzeug/1.0.1 Python/3.9.6
Server-Timing: TimerPanel_utime;dur=4853.861;desc="User CPU time",
TimerPanel_stime;dur=458.42499999999995;desc="System CPU time",
TimerPanel_total;dur=5312.286;desc="Total CPU time",
TimerPanel_total_time;dur=5284.070014953613;desc="Elapsed time",
SQLPanel_sql_time;dur=164.94297981262207;desc="SQL 154 queries",
CachePanel_total_time;dur=0.11157989501953125;desc="Cache 2 Calls"
Vary: Accept, Accept-Language, Cookie, Origin
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
"count": 1000,
"next": "http://127.0.0.1:8000/api/v1/posts/posts/?page=2",
"previous": null,
"results": [
...
{
"author": {
"date_joined": "2021-08-03T18:05:34.747294-04:00",
"followers_count": 0,
"following_count": 0,
"name": null,
"post_count": 1000,
"pub_id": "eba0d13e14af4f83a46b010768bd7672",
"username": "someuser1"
},
"bookmark_count": 0,
"content": " Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla pharetra arcu id nulla consequat, a venenatis justo maximus. In libero dolor, lobortis viverra est vitae, consectetur ornare ipsum. Maecenas fermentum leo risus, in efficitur nisl iaculis sed. Duis ut porttitor risus, pretium sagittis dolor. Maecenas nec lacinia ex. Etiam massa urna, ultrices sed sapien eget, iaculis accumsan felis. Nullam luctus sapien sit amet eros aliquet commodo. Integer commodo aliquet eros, sit amet euismod arcu ullamcorper gravida. Proin tellus risus, posuere ac tellus vitae, ullamcorper consequat enim.",
"created_at": "2021-08-03T18:06:04.656803-04:00",
"dislikes_count": 0,
"estimated_reading_time": 1,
"featured": false,
"like_score": 0,
"likes_count": 0,
"nextpost": null,
"previouspost": null,
"slug": "this-is-a-title-longer-than-eight-words-e26c396b673b4b4788b5b7f74693afcd",
"tags": [
{
"name": "django",
"post_count": 1000,
"pub_id": "465436bc0ffa455d8dc348a21c6af712"
},
{
"name": "djangorestframework",
"post_count": 1000,
"pub_id": "3b295fcb486a4cb0987a5c37b07ff860"
},
{
"name": "docker",
"post_count": 1000,
"pub_id": "b3f7a635cf5144539db5011a732828eb"
},
{
"name": "javascript",
"post_count": 1000,
"pub_id": "94288fc20e4a490ebddaa6710a93b6f0"
},
{
"name": "react",
"post_count": 1000,
"pub_id": "f6f46f7d4e1c46b5a38993bbf7b58d3d"
},
{
"name": "solidity",
"post_count": 1000,
"pub_id": "4dfaa34e21084c90ab86f30d341c027a"
}
],
"title": "This is a title longer than eight words.",
"updated_at": "2021-08-03T18:06:10.945686-04:00"
}
]
...
我想要第二个一样的所有数据,但我希望它与精简版本一样快。我怎样才能做到这一点。
谢谢。
解决方案
推荐阅读
- apache-kafka - Spring Kafka - 遇到“Magic v0 不支持记录头”错误
- javascript - 如何从另一个数组中的对象属性创建数组
- scala - 使用 Acolyte 模拟 JDBC 驱动程序 - 事务支持?
- php - PHP(laravel) 创建一个 m3u 播放列表 URL,可直接在 VLC 中播放
- swift - 查询 Firebase Firestore 帖子的最有效方法
- excel - Excel:轮盘赌的动态单元格引用
- sql-server - 在 SSRS 中,使用 IIF 设置文本框的字体颜色不起作用
- excel - 突出显示唯一重复值并从不同列求和
- java - 如何跨多个时区处理 java.sql.Dates?
- c# - 聚合上的 C# DDD 模式查询