Оптимизация агрегации Django по подмножествам
В настоящее время я пытаюсь выполнить некоторые расчеты сводной статистики для подмножеств на основе дат большой таблицы "соревнования" (~3M строк), хранящейся в SQLite. В частности, я пытаюсь вычислить статистику для:
- этот год
- прошлый год
- срок жизни конкурента
Вот разбивка моделей:
class Competitor(models.Model):
# ... ID is the only important part here
class Venue(models.Model):
# ... ID is the only important part here
class Division(models.Model):
venue = models.ForeignKey(Venue)
# ...
class Level(models.Model):
division = models.ForeignKey(Division)
# ...
class Class(models.TextChoices):
STANDARD = "Standard", _("Standard")
JWW = "JWW", _("JWW")
class Run(models.Model):
competitor_id = models.ForeignKey(Competitor, related_name="runs", db_index=True)
date = models.DateField(verbose_name="Date created", db_index=True)
MACH = models.IntegerField(..., db_index=True)
PACH = models.IntegerField(..., db_index=True)
yps = models.FloatField(..., db_index=True)
score = models.IntegerField(..., db_index=True)
qualified = models.BooleanField(..., db_index=True)
division = models.ForeignKey(Division, db_index=True)
level = models.ForeignKey(Level, db_index=True)
cls = models.CharField(max_length=..., choices=Class.choices)
# ... Other fields that aren't relevant
Для каждого Competitor
я хочу создать сводную статистику, описывающую их производительность в этом году, в прошлом году и за все время, и сохранить ее в Report
модели:
class CurrPrevLifetime(models.Model):
curr_yr = models.FloatField(default=0)
prev_yr = models.FloatField(default=0)
lifetime = models.FloatField(default=0)
class Report(models.Model):
... = models.ForeignKey(CurrPrevLifetime, related_name=...)
# repeat as needed for as many fields need this
Моя текущая установка агрегации выглядит следующим образом:
curr_yr = Q(date__year=datetime.date.today().year)
prev_yr = Q(date__year=datetime.date.today().year-1)
JWW = Q(cls=Class.JWW)
standard = Q(cls=Class.STANDARD)
aggregates = {
"curr_yr_max_standard_MACH": Max("MACH", filter=curr_yr & standard),
"curr_yr_max_standard_PACH": Max("PACH", filter=curr_yr & standard),
"curr_yr_average_yps_standard": Avg("yps", filter=curr_yr & standard),
"curr_yr_max_yps_standard": Max("yps", filter=curr_yr & standard),
"curr_yr_max_JWW_MACH": Max("MACH", filter=curr_yr & JWW),
"curr_yr_max_JWW_PACH": Max("PACH", filter=curr_yr & JWW),
"curr_yr_average_yps_JWW": Avg("yps", filter=curr_yr & JWW),
"curr_yr_max_yps_JWW": Max("yps", filter=curr_yr & JWW),
"curr_yr_MACH_points": Sum("MACH", filter=curr_yr),
"curr_yr_PACH_points": Sum("PACH", filter=curr_yr),
"prev_yr_max_standard_MACH": Max("MACH", filter=prev_yr & standard),
"prev_yr_max_standard_PACH": Max("PACH", filter=prev_yr & standard),
"prev_yr_average_yps_standard": Avg("yps", filter=prev_yr & standard),
"prev_yr_max_yps_standard": Max("yps", filter=prev_yr & standard),
"prev_yr_max_JWW_MACH": Max("MACH", filter=prev_yr & JWW),
"prev_yr_max_JWW_PACH": Max("PACH", filter=prev_yr & JWW),
"prev_yr_average_yps_JWW": Avg("yps", filter=prev_yr & JWW),
"prev_yr_max_yps_JWW": Max("yps", filter=prev_yr & JWW),
"prev_yr_MACH_points": Sum("MACH", filter=prev_yr),
"prev_yr_PACH_points": Sum("PACH", filter=prev_yr),
"lifetime_max_standard_MACH": Max("MACH", filter=standard),
"lifetime_max_standard_PACH": Max("PACH", filter=standard),
"lifetime_average_yps_standard": Avg("yps", filter=standard),
"lifetime_max_yps_standard": Max("yps", filter=standard),
"lifetime_max_JWW_MACH": Max("MACH", filter=JWW),
"lifetime_max_JWW_PACH": Max("PACH", filter=JWW),
"lifetime_average_yps_JWW": Avg("yps", filter=JWW),
"lifetime_max_yps_JWW": Max("yps", filter=JWW),
"lifetime_MACH_points": Sum("MACH"),
"lifetime_PACH_points": Sum("PACH"),
}
competitor.runs.aggregate(**aggregates)
Затем я беру результаты, разбиваю их на тройки (curr, prev, lifetime)
и сохраняю их как CurrPrevLifetime
, используя CurrPrevLifetime.objects.bulk_create
.
Эта агрегация занимает удивительно много времени, учитывая, что операции максимизации, усреднения и суммирования (~5 с). Для сравнения, у конкурента за все время его работы было где-то в районе ~500 прогонов.
Что виновато в снижении производительности? Индексы применяются к вычисляемым полям и отношениям между моделями, я пробовал идти по пути "filter then aggregate", без заметной разницы и со значительным увеличением LOC written.
При 40 000 конкурентах 5 секунд на отчет - это слишком долго. Как мне ускорить этот процесс? Я буду рад перестроить структуру, если это необходимо.
Я пытался решить эту проблему несколькими способами:
Разбиение
competitor.runs
наcurr_yr_runs
,prev_yr_runs
иlifetime_runs
, и выполнение агрегатов по отдельности (плохая производительность).Разбив их по
cls
наJWW_runs
иstandard_runs
, и выполнив агрегаты по датам таким образом (низкая производительность).