Как оптимизировать запрос сервера, который должен отправить обратно несколько элементов с динамическими атрибутами, которые должны вычисляться каждый раз, когда пользователь запрашивает их?
У меня есть приложение Angular UI, подключающееся к Django API, которое использует GraphQL (с помощью Graphene) и Postgres для БД.
В моем приложении есть много курсов, и каждый курс может иметь несколько глав. Пользователи, входящие в систему, могут видеть доступные курсы, но не другие, потому что у курса может быть предварительное условие. Поэтому они увидят курс в списке, но он будет "заблокирован" для них, и в сообщении будет сказано, что они должны выполнить определенное предварительное условие, прежде чем смогут получить доступ к нему. Как и в этом случае, нам нужны некоторые другие атрибуты для отправки вместе со списком курсов:-
- 'locked' - Boolean - заблокирован ли курс для текущего вошедшего пользователя или нет.
- 'status' - ENUM - PENDING/SUBMITTED/GRADED/RETURNED/FLAGGED .
- 'completed' - Boolean - завершен ли курс или нет .
Когда пользователь запрашивает список курсов, эти 3 атрибута вычисляются для каждого элемента списка, прежде чем он будет составлен и отправлен обратно пользователю.
И это делается для каждой главы внутри курса. А глава может содержать до 30 глав или около того. Так что это действительно занимает много времени!
Я также реализовал кэширование, но поскольку эти значения часто меняются (например, когда пользователь завершает главу), они постоянно аннулируются, и не имеет смысла хранить эти атрибуты в кэше на стороне сервера.
Вот код того, как обрабатываются главы для запроса списка глав:-
@login_required
@user_passes_test(lambda user: has_access(user, RESOURCES['CHAPTER'], ACTIONS['LIST']))
def resolve_chapters(root, info, course_id=None, searchField=None, limit=None, offset=None, **kwargs):
current_user = info.context.user
# Checking if this is cached
cache_entity = CHAPTER_CACHE[0]
cache_key = generate_chapters_cache_key(cache_entity, searchField, limit, offset, course_id, current_user)
cached_response = fetch_cache(cache_entity, cache_key)
if cached_response:
return cached_response
# If not cached...
qs = rows_accessible(current_user, RESOURCES['CHAPTER'], {'course_id': course_id})
if searchField is not None:
filter = (
Q(searchField__icontains=searchField.lower())
)
qs = qs.filter(filter)
if offset is not None:
qs = qs[offset:]
if limit is not None:
qs = qs[:limit]
set_cache(cache_entity, cache_key, qs)
return qs
И я использую этот код для динамической вставки трех атрибутов в каждый элемент списка глав, который возвращает вышеприведенный код:-
class ChapterType(DjangoObjectType):
completed = graphene.Boolean()
completion_status = graphene.String()
locked = graphene.String()
def resolve_completed(self, info):
user = info.context.user
completed = CompletedChapters.objects.filter(participant_id=user.id, chapter_id=self.id).exists()
return completed
def resolve_completion_status(self, info):
user = info.context.user
status = ExerciseSubmission.StatusChoices.PENDING
try:
completed = CompletedChapters.objects.get(participant_id=user.id, chapter_id=self.id)
status = completed.status
except:
pass
return status
def resolve_locked(self, info):
user = info.context.user
locked = is_chapter_locked(user, self)
return locked
class Meta:
model = Chapter
А метод is_chapter_locked() сам по себе довольно сложен:-
def is_chapter_locked(user, chapter):
locked = None
# Letting the user see it if they are a grader
user_role = user.role.name;
grader = user_role == USER_ROLES_NAMES['GRADER']
# Checking if the user is the author of the course or a grader
if chapter.course.instructor.id == user.id or grader:
# If yes, we mark it as unlocked
return locked
course_locked = is_course_locked(user, chapter.course) # Checking if this belongs to a course that is locked
if course_locked:
# If the course is locked, we immediately return locked is true
locked = 'This chapter is locked for you'
return locked
# If the course is unlocked we
completed_chapters = CompletedChapters.objects.all().filter(participant_id=user.id)
required_chapters = MandatoryChapters.objects.all().filter(chapter_id=chapter.id)
required_chapter_ids = required_chapters.values_list('requirement_id',flat=True)
completed_chapter_ids = completed_chapters.values_list('chapter_id',flat=True)
pending_chapter_ids = []
for id in required_chapter_ids:
if id not in completed_chapter_ids:
pending_chapter_ids.append(id)
if pending_chapter_ids:
locked = 'To view this chapter, you must have completed '
pending_chapters_list = ''
for id in pending_chapter_ids:
try:
chapter= Chapter.objects.get(pk=id, active=True)
if pending_chapters_list != '':
pending_chapters_list += ', '
pending_chapters_list += '"' + str(chapter.section.index) +'.'+str(chapter.index)+'. '+chapter.title +'"'
except:
pass
locked += pending_chapters_list
return locked
Как видно, для получения списка глав выполняется много динамической обработки. И это занимает довольно много времени, даже с учетом кэширования запроса из базы данных перед вычислением динамических атрибутов.
Я ищу стратегии для минимизации динамических вычислений. Какой подход лучше всего подходит для оптимизации производительности в подобных ситуациях?
Спасибо.