多个Postgres SELECT进程(django GET请求)卡住,导致100%的CPU使用率

前端之家收集整理的这篇文章主要介绍了多个Postgres SELECT进程(django GET请求)卡住,导致100%的CPU使用率前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

我会尽力提供尽可能多的信息.虽然解决方案很棒,但我只想获得如何解决问题的指导.如何查看更多有用的日志文件等.因为我是服务器维护的新手.欢迎任何建议.

这是按时间顺序发生的事情:

>我正在运行2个数字海洋飞沫(Ubuntu 14.04 VPS)
> Droplet#1运行django,Nginx,gunicorn
> Droplet#2运行postgres
>一切都运行良好一个月,突然postgres droplet
cpu使用率飙升至100%
>发生这种情况时,您可以看到htop日志.我附上了截图
>另一个截图是Nginx error.log,你可以看到那个问题
从15:56:14开始,我用红框突出显示
> sudo poweroff Postgres Droplet并重启它并没有解决问题
问题
>将postgres droplet恢复到我上次的备份(20小时前)解决了问题,但它仍然继续发生.这是2天内的第7次

我会继续做研究并提供更多信息.同时欢迎任何意见.

谢谢.

Postgres server htop log when problem occurs


nginx error.log when problem occurs

2016年5月20日更新

>按照e4c5的建议,在Postgres服务器上启用慢速查询日志记录
> 6小时后,服务器在上午8:07再次冻结(100%cpu使用率).我附上了所有相关的截图
>如果在冻结期间尝试访问站点,则浏览器显示502错误
> sudo服务重启postgresql(和gunicorn,django服务器上的Nginx)不修复
冻结(我认为这是一个非常有趣的观点)
>但是,将Postgres服务器恢复到我以前的备份(现在为2天)确实可以修复冻结
>罪魁祸首Postgres日志消息无法向客户端发送数据:已损坏

>罪魁祸首Nginx日志消息是一个简单的django-rest-framework
api调用只返回20个项目(每个都有一些外键数据
查询)

2016年5月20日更新#2
当冻结发生时,我尝试按时间顺序执行以下操作(关闭所有内容并逐个转回)

> sudo service stop postgresql – > cpu使用率降至0-10%
> sudo service stop gunicorn – > cpu使用率保持在0-10%
> sudo service stop Nginx – > cpu使用率保持在0-10%
> sudo service restart postgresql – > cpu使用率保持在0-10%
> sudo service restart gunicorn – > cpu使用率保持在0-10%
> sudo service restart Nginx – > cpu使用率上升至100%并保持不变
那里

那么这不是关于服务器负载或长查询时间呢?

这是非常令人困惑的,因为如果我将数据库恢复到我的最新备份(2天前),即使没有触及Nginx / gunicorn / django服务器,一切都恢复在线…

2016年6月8日更新
我开启了慢查询记录.将其设置为记录超过1000毫秒的查询.

我得到这个查询多次出现在日志中.

SELECT
     "products_product"."id","products_product"."seller_id","products_product"."priority","products_product"."media","products_product"."active","products_product"."title","products_product"."slug","products_product"."description","products_product"."price","products_product"."sale_active","products_product"."sale_price","products_product"."timestamp","products_product"."updated","products_product"."draft","products_product"."hitcount","products_product"."finished","products_product"."is_marang_offline","products_product"."is_seller_beta_program",COUNT("products_video"."id") AS "num_video"
 FROM "products_product"
 LEFT OUTER JOIN "products_video" ON ( "products_product"."id" = "products_video"."product_id" )
 WHERE ("products_product"."draft" = false AND "products_product"."finished" = true)
 GROUP BY
     "products_product"."id","products_product"."is_seller_beta_program"
 HAVING COUNT("products_video"."id") >= 8
 ORDER BY "products_product"."priority" DESC,"products_product"."hitcount" DESC
 LIMIT 100

我知道这是一个丑陋的查询(由django聚合生成).在英语中,此查询仅表示“向我提供其中包含超过8个视频的产品列表”.

这里是这个查询的EXPLAIN输出

                  QUERY PLAN                                                                                                                                                                                                                 
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=351.90..358.40 rows=100 width=933)
   ->  GroupAggregate  (cost=351.90..364.06 rows=187 width=933)
         Filter: (count(products_video.id) >= 8)
         ->  Sort  (cost=351.90..352.37 rows=187 width=933)
               Sort Key: products_product.priority,products_product.hitcount,products_product.id,products_product.seller_id,products_product.media,products_product.active,products_product.title,products_product.slug,products_product.description,products_product.price,products_product.sale_active,products_product.sale_price,products_product."timestamp",products_product.updated,products_product.draft,products_product.finished,products_product.is_marang_offline,products_product.is_seller_beta_program
               ->  Hash Right Join  (cost=88.79..344.84 rows=187 width=933)
                     Hash Cond: (products_video.product_id = products_product.id)
                     ->  Seq Scan on products_video  (cost=0.00..245.41 rows=2341 width=8)
                     ->  Hash  (cost=88.26..88.26 rows=42 width=929)
                           ->  Seq Scan on products_product  (cost=0.00..88.26 rows=42 width=929)
                                 Filter: ((NOT draft) AND finished)

    (11排)

— 2016年6月8日更新#2 —
由于很多人提出了许多建议.因此,我将尝试逐个应用修复程序并定期报告.

@ e4c5
这是您需要的信息:

您可以将我的网站看作有点像在线课程市场Udemy.有“产品”(课程).每个产品都包含许多视频.用户可以对产品页面本身和每个视频发表评论.

在许多情况下,我需要按照它获得的总评论数量(产品评论的总和和该产品的每个视频的评论)查询产品列表.

与上面的EXPLAIN输出对应的django查询

all_products_exclude_draft = Product.objects.all().filter(draft=False)
products_that_contain_more_than_8_videos =  all_products_exclude_draft.annotate(num_video=Count('video')).filter(finished=True,num_video__gte=8).order_by('timestamp')[:30]

我只是注意到我(或我团队中的其他开发人员)使用这两条python线命中数据库两次.

这是产品和视频的django模型:

from django_model_changes import ChangesMixin

class Product(ChangesMixin,models.Model):
    class Meta:
        ordering = ['-priority','-hitcount']
    seller = models.ForeignKey(SellerAccount)
    priority = models.PositiveSmallIntegerField(default=1)
    media = models.ImageField(blank=True,null=True,upload_to=download_media_location,default=settings.MEDIA_ROOT + '/images/default_icon.png',storage=FileSystemStorage(location=settings.MEDIA_ROOT))
    active = models.BooleanField(default=True)
    title = models.CharField(max_length=500)
    slug = models.SlugField(max_length=200,blank=True,unique=True)
    description = models.TextField()
    product_coin_price = models.IntegerField(default=0)
    sale_active = models.BooleanField(default=False)
    sale_price = models.IntegerField(default=0,blank=True) #100.00
    timestamp = models.DateTimeField(auto_now_add=True,auto_now=False,null=True)
    updated = models.DateTimeField(auto_now_add=False,auto_now=True,null=True)
    draft = models.BooleanField(default=True)
    hitcount = models.IntegerField(default=0)
    finished = models.BooleanField(default=False)
    is_marang_offline = models.BooleanField(default=False)
    is_seller_beta_program = models.BooleanField(default=False)

    def __unicode__(self):
        return self.title

    def get_avg_rating(self):
        rating_avg = self.productrating_set.aggregate(Avg("rating"),Count("rating"))
        return rating_avg

    def get_total_comment_count(self):
        comment_count = self.video_set.aggregate(Count("comment"))
        comment_count['comment__count'] += self.comment_set.count()
        return comment_count

    def get_total_hitcount(self):
        amount = self.hitcount
        for video in self.video_set.all():
            amount += video.hitcount
        return amount

    def get_absolute_url(self):
        view_name = "products:detail_slug"
        return reverse(view_name,kwargs={"slug": self.slug})

    def get_product_share_link(self):
        full_url = "%s%s" %(settings.FULL_DOMAIN_NAME,self.get_absolute_url())
        return full_url

    def get_edit_url(self):
        view_name = "sellers:product_edit"
        return reverse(view_name,kwargs={"pk": self.id})

    def get_video_list_url(self):
        view_name = "sellers:video_list"
        return reverse(view_name,kwargs={"pk": self.id})

    def get_product_delete_url(self):
        view_name = "products:product_delete"
        return reverse(view_name,kwargs={"pk": self.id})

    @property
    def get_price(self):
        if self.sale_price and self.sale_active:
            return self.sale_price
        return self.product_coin_price

    @property
    def video_count(self):
        videoCount = self.video_set.count()
        return videoCount

class Video(models.Model):
    seller = models.ForeignKey(SellerAccount)
    title = models.CharField(max_length=500)
    slug = models.SlugField(max_length=200,blank=True)
    story = models.TextField(default=" ")
    chapter_number = models.PositiveSmallIntegerField(default=1)
    active = models.BooleanField(default=True)
    featured = models.BooleanField(default=False)
    product = models.ForeignKey(Product,null=True)
    timestamp = models.DateTimeField(auto_now_add=True,null=True)
    draft = models.BooleanField(default=True)
    hitcount = models.IntegerField(default=0)
    objects = VideoManager()

    class Meta:
        unique_together = ('slug','product')
        ordering = ['chapter_number','timestamp']

    def __unicode__(self):
        return self.title

    def get_comment_count(self):
        comment_count = self.comment_set.all_jing_jing().count()
        return comment_count

    def get_create_chapter_url(self):
        return reverse("sellers:video_create",kwargs={"pk": self.id})

    def get_edit_url(self):
        view_name = "sellers:video_update"
        return reverse(view_name,kwargs={"pk": self.id})

    def get_video_delete_url(self):
        view_name = "products:video_delete"
        return reverse(view_name,kwargs={"pk": self.id})

    def get_absolute_url(self):
        try:
            return reverse("products:video_detail",kwargs={"product_slug": self.product.slug,"pk": self.id})
        except:
            return "/"

    def get_video_share_link(self):
        full_url = "%s%s" %(settings.FULL_DOMAIN_NAME,self.get_absolute_url())
        return full_url      

    def get_next_url(self):
        current_product = self.product
        videos = current_product.video_set.all().filter(chapter_number__gt=self.chapter_number)
        next_vid = None
        if len(videos) >= 1:
            try:
                next_vid = videos[0].get_absolute_url()
            except IndexError:
                next_vid = None
        return next_vid

    def get_prevIoUs_url(self):
        current_product = self.product
        videos = current_product.video_set.all().filter(chapter_number__lt=self.chapter_number).reverse()
        next_vid = None
        if len(videos) >= 1:
            try:
                next_vid = videos[0].get_absolute_url()
            except IndexError:
                next_vid = None
        return next_vid

这是我从命令获得的产品和视频表的索引:

my_database_name=# \di

注意:这是photoshopped并包括一些其他模型.

Indexes of Product and Video models

— 2016年6月8日更新#3 —
@Jerzyk
如你所料.在我再次检查所有代码之后,我发现我确实做了“切片内存”:我尝试通过这样做来改组前10个结果:

def get_queryset(self):
        all_product_list = Product.objects.all().filter(draft=False).annotate(
        num_video=Count(
                Case(
                    When(
                        video__draft=False,then=1,)
                )
            )
        ).order_by('-priority','-num_video','-hitcount')
        the_first_10_products = list(all_product_list[:10])
        the_11th_product_onwards = list(all_product_list[10:])
        random.shuffle(copy)
        finalList = the_first_10_products + the_11th_product_onwards

注意:在上面的代码中,我需要计算未处于草稿状态的视频数量.

所以这也是我需要解决的问题之一.谢谢. > _<

—这是相关的截图—

发生冻结时的Postgres日志(log_min_duration = 500毫秒)

Postgres log 20 May 2016

Postgres日志(从上面的截图中得到补充)

Postgres log 20 May 2016 (page2)

Nginx error.log在同一时间段内

Nginx log 20 May 2016

在冻结之前的DigitalOcean cpu使用率图表

DigitalOcean graph 20 May 2016 (1)

冻结后的DigitalOcean cpu使用率图表

DigitalOcean graph 20 May 2016 (2)

最佳答案
我们可以得出结论,你的问题是由有问题的慢查询引起的.单独查询的每次运行似乎都不足以导致超时.但是,这些查询中的一些可能会同时执行,并可能导致崩溃.你可以做两件事来加快速度.

1)缓存结果

可以缓存长时间运行的查询的结果.

from django.core.cache import cache

def get_8x_videos():
    cache_key = 'products_videos_join'
    result = cache.get(cache_key,None)
    if not result:
        all_products_exclude_draft = Product.objects.all().filter(draft=False)
        result =  all_products_exclude_draft.annotate(num_video=Count('video')).filter(finished=True,num_video__gte=8).order_by('timestamp')[:30]

        result = Product.objects.annotate('YOUR LONG QUERY HERE')
        cache.set(cache_key,result)

    return result

查询现在来自memcache(或用于缓存的任何内容),这意味着如果您连续两次点击快速连续使用此页面,则第二个将对数据库没有影响.您可以控制对象在内存中缓存的时间.

2)优化查询

从解释中跳出来的第一件事是你正在对products_products和product_videos表进行顺序扫描.通常,顺序扫描不如索引扫描所需.但是,由于您在其上具有COUNT()和HAVING COUNT()子句以及其上的大量GROUP BY子句,因此可能无法在此查询上使用索引扫描.

更新:

你的查询有一个LEFT OUTER JOIN,INNER JOIN或子查询可能会更快,为了做到这一点,我们需要认识到product_id上的Video表上的分组可以给我们提供的一组视频至少8个产品.

inner = Rawsql('SELECT id from product_videos GROUP BY product_id HAVING COUNT(product_id) > 1',params=[])

Product.objects.filter(id__in=b)

上面提到了LEFT OUTER JOIN并引入了一个子查询.但是,这并不能轻松访问每个产品的实际视频数量,因此该查询的当前形式可能无法完全使用.

3)改善指数

尽管在草稿和已完成列上创建索引可能很诱人,但由于这些列没有足够的基数来成为索引的良好候选者,因此这将是徒劳的.但是,仍然可以创建条件索引.只有看到你的表后才能得出结论.

猜你在找的Nginx相关文章