我有一个构建API,它应该返回来自一个大型查询集的10个随机选择的结果。
我有以下4种型号:
class ScrapingOperation(models.Model):
completed = models.BooleanField(default=False)
(...)
indexes = [
models.Index(fields=['completed'], name='completed_idx'),
models.Index(fields=['trusted'], name='trusted_idx'),
]
@property
def ads(self):
"""returns all ads linked to the searches of this operation"""
return Ad.objects.filter(searches__in=self.searches.all())
class Search(models.Model):
completed = models.BooleanField(default=False)
scraping_operation = models.ForeignKey(
ScrapingOperation,
on_delete=models.CASCADE,
related_name='searches'
)
(...)
class Ad(models.Model):
searches = models.ManyToManyField('scraper.Search', related_name='ads')
(...)
class Label(models.Model):
value = models.Integerfield()
linked_ad = models.OneToOneField(
Ad, on_delete=models.CASCADE, related_name='labels'
)数据库中当前有400.000 + Ad对象,但平均每个ScrapingOperation有14000个Ad对象链接到它。我希望API从这些+/- 14000中返回10个随机结果,这些结果还没有一个链接的Label对象(每个操作最多只有几百个)
因此,必须从包含14.000个对象的查询中返回10个随机结果。
早期版本只能返回一个结果,但使用的sort_by('?')方法要慢得多。当我不得不放大它来返回随机的10个Ad对象时,我使用了一种基于this stackoverflow answer的新方法。
下面是选择(并返回)10个随机对象的代码:
# Get all ads linked to the last completed operation
last_op_ads = ScrapingOperation.objects.filter(completed=True).last().ads
# Get all ads that don't have an label yet
random_ads = last_op_ads.filter(labels__isnull=True)
# Get list ids of all potential ads
id_list = random_ads.values_list('id', flat=True)
id_list = list(id_list)
# Select a random sample of 10, get objects with PK matches
samples = rd.sample(id_list, min(len(id_list), 10))
selected_samples = random_ads.filter(id__in=samples)
return selected_samples然而,尽管我优化了这个查询,但是这个查询需要10+几秒钟才能完成,从而创建了一个非常慢的API。
这种长延迟仅仅是随机查询所固有的吗?(如果是的话,其他程序员如何处理这一限制?)或者我的代码中是否有我遗漏的错误/低效率?
编辑:基于响应,我在下面包含了原始sql查询(注意:这些查询在本地环境上运行,只包含生产环境中包含的数据的5% )。
{'sql': 'SELECT "scraper_scrapingoperation"."id",
"scraper_scrapingoperation"."date_started",
"scraper_scrapingoperation"."date_completed",
"scraper_scrapingoperation"."completed",
"scraper_scrapingoperation"."round",
"scraper_scrapingoperation"."trusted" FROM "scraper_scrapingoperation"
WHERE "scraper_scrapingoperation"."completed" = true ORDER BY
"scraper_scrapingoperation"."id" DESC LIMIT 1', 'time': '0.001'}
{'sql': 'SELECT "database_ad"."id" FROM "database_ad" INNER JOIN
"database_ad_searches" ON ("database_ad"."id" =
"database_ad_searches"."ad_id") LEFT OUTER JOIN "classifier_label" ON
("database_ad"."id" = "classifier_label"."ad_id") WHERE
("database_ad_searches"."search_id" IN (SELECT U0."id" FROM
"scraper_search" U0 WHERE U0."scraping_operation_id" = 6) AND
"classifier_label"."id" IS NULL)', 'time': '1.677'}编辑2:我尝试了另一种方法,使用更深层次的参数
random_ads = ScrapingOperation.objects.prefetch_related(
'searches__ads__labels',
).filter(completed=True).last().ads.exclude(
labels__isnull=True
)
id_list = random_ads.values_list('id', flat=True)
id_list = list(id_list)
samples = rd.sample(id_list, min(
len(id_list), 10))
selected_samples = random_ads.filter(
id__in=samples)
return selected_samples它生成以下SQL查询:
{'time': '0.008', 'sql': 'SELECT "scraper_search"."id",
"scraper_search"."item_id", "scraper_search"."date_started",
"scraper_search"."date_completed", "scraper_search"."completed",
"scraper_search"."round", "scraper_search"."scraping_operation_id",
"scraper_search"."trusted" FROM "scraper_search" WHERE
"scraper_search"."scraping_operation_id" IN (6)'}
{'time': '0.113', 'sql': 'SELECT ("database_ad_searches"."search_id")
AS "_prefetch_related_val_search_id", "database_ad"."id",
"database_ad"."item_id", "database_ad"."item_state",
"database_ad"."title", "database_ad"."seller_id",
"database_ad"."url", "database_ad"."price",
"database_ad"."transaction_type", "database_ad"."transaction_method",
"database_ad"."first_seen", "database_ad"."last_seen",
"database_ad"."promoted" FROM "database_ad" INNER JOIN
"database_ad_searches" ON ("database_ad"."id" =
"database_ad_searches"."ad_id") WHERE
"database_ad_searches"."search_id" IN (130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160)'}
{'time': '0.041', 'sql': 'SELECT "classifier_label"."id",
"classifier_label"."set_by_id", "classifier_label"."ad_id",
"classifier_label"."date", "classifier_label"."phone_type",
"classifier_label"."seller_type", "classifier_label"."sale_type" FROM
"classifier_label" WHERE "classifier_label"."ad_id" IN (1, 3, 6, 10, 20, 29, 30, 35, 43, (and MANY more of these numbers) ....'}
{'time': '1.498', 'sql': 'SELECT "database_ad"."id" FROM "database_ad"
INNER JOIN "database_ad_searches" ON ("database_ad"."id" = "database_ad_searches"."ad_id") LEFT OUTER JOIN "classifier_label" ON
("database_ad"."id" = "classifier_label"."ad_id") WHERE
("database_ad_searches"."search_id" IN (SELECT U0."id" FROM
"scraper_search" U0 WHERE U0."scraping_operation_id" = 6) AND NOT
("classifier_label"."id" IS NOT NULL))'}每个ScrapingOperation 'only‘都有+/- 14000链接广告,但是生产中的广告总数是400.000 (而且还在增长)。上面的所有代码都在我的本地环境中返回有效的结果(只包含5%的数据),但是在生产中返回API上的502个错误。
发布于 2018-07-23 07:53:13
我会尝试先隔离链接广告,然后使用一个生成的随机列的顺序从它们中得到10个随机的广告。我不确定这在生成的sql中如何有效。当然,我更喜欢在任务上创建一个存储过程,因为这是明确的数据挖掘操作,其结果是随机样本。
https://stackoverflow.com/questions/51460227
复制相似问题