我会尽力提供尽可能多的信息.虽然解决方案很棒,但我只想获得如何解决问题的指导.如何查看更多有用的日志文件等.因为我是服务器维护的新手.欢迎任何建议.
这是按时间顺序发生的事情:
>我正在运行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次
我会继续做研究并提供更多信息.同时欢迎任何意见.
谢谢.
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个视频的产品列表”.
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.有“产品”(课程).每个产品都包含许多视频.用户可以对产品页面本身和每个视频发表评论.
在许多情况下,我需要按照它获得的总评论数量(产品评论的总和和该产品的每个视频的评论)查询产品列表.
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并包括一些其他模型.
— 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毫秒)
Nginx error.log在同一时间段内
在冻结之前的DigitalOcean cpu使用率图表
冻结后的DigitalOcean cpu使用率图表
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)改善指数
尽管在草稿和已完成列上创建索引可能很诱人,但由于这些列没有足够的基数来成为索引的良好候选者,因此这将是徒劳的.但是,仍然可以创建条件索引.只有看到你的表后才能得出结论.