From ddeb9641ba94cea9b19e8ba72de79c6759ef1f8a Mon Sep 17 00:00:00 2001 From: "Y.D.X." <73375426+YDX-2147483647@users.noreply.github.com> Date: Sun, 25 Aug 2024 21:59:46 +0800 Subject: [PATCH 1/4] =?UTF-8?q?perf:=20=E7=BB=84=E5=8D=B7=E6=97=B6?= =?UTF-8?q?=E6=94=B9=E7=94=A8`bulk=5Fcreate`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这样初次访问`/contest/`时可以减少18次SQL(69 → 51)。 --- contest/quiz/views.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/contest/quiz/views.py b/contest/quiz/views.py index 65e4e84..baeac19 100644 --- a/contest/quiz/views.py +++ b/contest/quiz/views.py @@ -22,7 +22,7 @@ from django.views.generic import TemplateView from .constants import constants -from .models import Choice, DraftResponse, Question +from .models import Choice, DraftAnswer, DraftResponse, Question from .util import is_open, is_student, is_student_taking_contest, pass_or_forbid, student_only if TYPE_CHECKING: @@ -30,7 +30,7 @@ from django.contrib.auth.models import AnonymousUser - from .models import DraftAnswer, Student, User + from .models import Student, User from .util import AuthenticatedHttpRequest @@ -222,10 +222,9 @@ def contest(request: AuthenticatedHttpRequest) -> HttpResponse: # 保存 draft_response.save() - for q in questions: - draft_response.answer_set.create( - question=q, - ) + draft_response.answer_set.bulk_create( + [DraftAnswer(question=q, response=draft_response) for q in questions] + ) return render( request, From 8f654ddcbf31a4d33510c26a7101e8109848914f Mon Sep 17 00:00:00 2001 From: "Y.D.X." <73375426+YDX-2147483647@users.noreply.github.com> Date: Sun, 25 Aug 2024 22:18:28 +0800 Subject: [PATCH 2/4] =?UTF-8?q?perf:=20`/contest/`=E9=A2=84=E5=85=88?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2`answer=5Fset`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这样初次访问`/contest/`时可以减少20次SQL(51 → 31)。 --- contest/quiz/templates/contest.html | 2 +- contest/quiz/views.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/contest/quiz/templates/contest.html b/contest/quiz/templates/contest.html index c4d1db6..b398c18 100644 --- a/contest/quiz/templates/contest.html +++ b/contest/quiz/templates/contest.html @@ -39,7 +39,7 @@
{% csrf_token %} - {% for a in draft_response.answer_set.all %} + {% for a in answer_set.all %}

diff --git a/contest/quiz/views.py b/contest/quiz/views.py index baeac19..202b1b5 100644 --- a/contest/quiz/views.py +++ b/contest/quiz/views.py @@ -231,6 +231,8 @@ def contest(request: AuthenticatedHttpRequest) -> HttpResponse: "contest.html", { "draft_response": draft_response, + # 为渲染模板预先从数据库查询相关内容 + "answer_set": draft_response.answer_set.select_related("question", "choice"), "constants": constants, }, ) From 677249fc9a68f74493600ad0dad2b38c5bfcc9e9 Mon Sep 17 00:00:00 2001 From: "Y.D.X." <73375426+YDX-2147483647@users.noreply.github.com> Date: Sun, 25 Aug 2024 22:51:14 +0800 Subject: [PATCH 3/4] =?UTF-8?q?perf:=20`/contest/`=E9=A2=84=E5=85=88?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2`question=5F=5Fchoice=5Fset`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这样初次访问`/contest/`时可以减少19次SQL(31 → 12)。 --- contest/quiz/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contest/quiz/views.py b/contest/quiz/views.py index 202b1b5..deab74b 100644 --- a/contest/quiz/views.py +++ b/contest/quiz/views.py @@ -232,7 +232,9 @@ def contest(request: AuthenticatedHttpRequest) -> HttpResponse: { "draft_response": draft_response, # 为渲染模板预先从数据库查询相关内容 - "answer_set": draft_response.answer_set.select_related("question", "choice"), + "answer_set": draft_response.answer_set.select_related( + "question" + ).prefetch_related("question__choice_set"), "constants": constants, }, ) From a09ab66c91e11aadeacfb2ddc7e1cb743fc20e50 Mon Sep 17 00:00:00 2001 From: "Y.D.X." <73375426+YDX-2147483647@users.noreply.github.com> Date: Sun, 25 Aug 2024 23:17:24 +0800 Subject: [PATCH 4/4] fix: Inconsistent cache key --- contest/quiz/views.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/contest/quiz/views.py b/contest/quiz/views.py index deab74b..2de7e90 100644 --- a/contest/quiz/views.py +++ b/contest/quiz/views.py @@ -34,7 +34,7 @@ from .util import AuthenticatedHttpRequest -def continue_or_finalize(student_: Student) -> bool: +def continue_or_finalize(draft_response: DraftResponse) -> bool: """自动提交 若草稿已超期,则定稿,否则什么也不做。 @@ -47,13 +47,9 @@ def continue_or_finalize(student_: Student) -> bool: https://docs.djangoproject.com/en/4.2/ref/models/instances/#django.db.models.Model.delete https://docs.djangoproject.com/en/4.2/ref/models/instances/#refreshing-objects-from-database """ - draft_response: DraftResponse = student_.draft_response - if draft_response.outdated(): - # 提交之前的草稿 - cache_key = f"{student_.user}_json" # 从 Redis 获取现有的答案缓存 - cached_answers = cache.get(cache_key, {}) + cached_answers = cache.get(f"{draft_response.id}_json", {}) if cached_answers: for question_id, choice_id in cached_answers.items(): @@ -77,16 +73,18 @@ def continue_or_finalize(student_: Student) -> bool: answer.save() - cache.delete(f"{student_.user}_json") - cache.delete(f"{student_.user}_ddl") + cache.delete(f"{draft_response.id}_json") + cache.delete(f"{draft_response.id}_ddl") + + # 提交之前的草稿 # 1. Convert from draft - response, answers = student_.draft_response.finalize(submit_at=timezone.now()) + response, answers = draft_response.finalize(submit_at=timezone.now()) # 2. Save response.save() response.answer_set.bulk_create(answers) - student_.draft_response.delete() + draft_response.delete() return True @@ -103,7 +101,7 @@ def manage_status( if not hasattr(student, "draft_response"): return "not taking" else: - finalized = continue_or_finalize(student) + finalized = continue_or_finalize(student.draft_response) if finalized: return "deadline passed" else: