Как оптимизировать запрос сервера, который должен отправить обратно несколько элементов с динамическими атрибутами, которые должны вычисляться каждый раз, когда пользователь запрашивает их?

У меня есть приложение Angular UI, подключающееся к Django API, которое использует GraphQL (с помощью Graphene) и Postgres для БД.

В моем приложении есть много курсов, и каждый курс может иметь несколько глав. Пользователи, входящие в систему, могут видеть доступные курсы, но не другие, потому что у курса может быть предварительное условие. Поэтому они увидят курс в списке, но он будет "заблокирован" для них, и в сообщении будет сказано, что они должны выполнить определенное предварительное условие, прежде чем смогут получить доступ к нему. Как и в этом случае, нам нужны некоторые другие атрибуты для отправки вместе со списком курсов:-

  1. 'locked' - Boolean - заблокирован ли курс для текущего вошедшего пользователя или нет.
  2. 'status' - ENUM - PENDING/SUBMITTED/GRADED/RETURNED/FLAGGED
  3. .
  4. 'completed' - Boolean - завершен ли курс или нет
  5. .

Когда пользователь запрашивает список курсов, эти 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

Как видно, для получения списка глав выполняется много динамической обработки. И это занимает довольно много времени, даже с учетом кэширования запроса из базы данных перед вычислением динамических атрибутов.

Я ищу стратегии для минимизации динамических вычислений. Какой подход лучше всего подходит для оптимизации производительности в подобных ситуациях?

Спасибо.

Вернуться на верх