首页 > 解决方案 > 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"
    }
]
...

我想要第二个一样的所有数据,但我希望它与精简版本一样快。我怎样才能做到这一点。

谢谢。

标签: pythondjangodjango-rest-frameworkdjango-serializer

解决方案


推荐阅读