From b13976d01769e2ebc52cd840a5f20aa034b665e3 Mon Sep 17 00:00:00 2001 From: Bulat Kurbanov Date: Sat, 2 Apr 2022 17:30:12 +0300 Subject: [PATCH] Use sets for random --- fastapi_book_server/app/services/common.py | 43 ++++++++++++---------- fastapi_book_server/app/views/author.py | 8 ++-- fastapi_book_server/app/views/book.py | 8 ++-- fastapi_book_server/app/views/sequence.py | 7 +++- 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/fastapi_book_server/app/services/common.py b/fastapi_book_server/app/services/common.py index 2f6dc03..5f17345 100644 --- a/fastapi_book_server/app/services/common.py +++ b/fastapi_book_server/app/services/common.py @@ -4,6 +4,8 @@ from concurrent.futures import ThreadPoolExecutor from random import choice from typing import Optional, Generic, TypeVar, TypedDict, Union +from fastapi import BackgroundTasks + import aioredis from databases import Database from fastapi_pagination.api import resolve_params @@ -282,17 +284,19 @@ class GetRandomService(Generic[MODEL]): return [obj["id"] for obj in objects] @classmethod - async def _get_objects_from_cache( + async def _get_random_object_from_cache( cls, allowed_langs: frozenset[str], redis: aioredis.Redis - ) -> Optional[list[int]]: + ) -> Optional[int]: try: key = cls.get_cache_key(allowed_langs) - data = await redis.get(key) + active_key = f"{key}_active" - if data is None: + if not await redis.exists(active_key): return None - return orjson.loads(data) + data: bytes = await redis.srandmember(key) + + return int(data.decode()) except aioredis.RedisError as e: print(e) return None @@ -303,31 +307,32 @@ class GetRandomService(Generic[MODEL]): ) -> bool: try: key = cls.get_cache_key(allowed_langs) - await redis.set(key, orjson.dumps(object_ids), ex=cls.CACHE_TTL) + active_key = f"{key}_active" + + await redis.set(active_key, 1, ex=cls.CACHE_TTL) + await redis.delete(key) + await redis.sadd(key, *object_ids) return True except aioredis.RedisError as e: print(e) return False @classmethod - async def get_objects( - cls, allowed_langs: frozenset[str], redis: aioredis.Redis - ) -> list[int]: - cached_object_ids = await cls._get_objects_from_cache(allowed_langs, redis) + async def get_random_id( + cls, + allowed_langs: frozenset[str], + redis: aioredis.Redis, + background_tasks: BackgroundTasks, + ) -> int: + cached_object_id = await cls._get_random_object_from_cache(allowed_langs, redis) - if cached_object_ids is not None: - return cached_object_ids + if cached_object_id is not None: + return cached_object_id object_ids = await cls._get_objects_from_db(allowed_langs) - await cls._cache_object_ids(object_ids, allowed_langs, redis) - return object_ids + background_tasks.add_task(cls._cache_object_ids, allowed_langs, redis) - @classmethod - async def get_random_id( - cls, allowed_langs: frozenset[str], redis: aioredis.Redis - ) -> int: - object_ids = await cls.get_objects(allowed_langs, redis) return choice(object_ids) diff --git a/fastapi_book_server/app/views/author.py b/fastapi_book_server/app/views/author.py index 31e22a1..4c6c7c7 100644 --- a/fastapi_book_server/app/views/author.py +++ b/fastapi_book_server/app/views/author.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, Depends, Request, HTTPException, status +from fastapi import APIRouter, BackgroundTasks, Depends, Request, HTTPException, status from fastapi_pagination import Params from fastapi_pagination.ext.ormar import paginate @@ -55,10 +55,12 @@ async def create_author(data: CreateAuthor): @author_router.get("/random", response_model=Author) async def get_random_author( - request: Request, allowed_langs: frozenset[str] = Depends(get_allowed_langs) + request: Request, + background_tasks: BackgroundTasks, + allowed_langs: frozenset[str] = Depends(get_allowed_langs), ): author_id = await GetRandomAuthorService.get_random_id( - allowed_langs, request.app.state.redis + allowed_langs, request.app.state.redis, background_tasks ) return ( diff --git a/fastapi_book_server/app/views/book.py b/fastapi_book_server/app/views/book.py index 0cceb5d..17038d4 100644 --- a/fastapi_book_server/app/views/book.py +++ b/fastapi_book_server/app/views/book.py @@ -1,6 +1,6 @@ from typing import Union -from fastapi import APIRouter, Depends, Request, HTTPException, status +from fastapi import APIRouter, BackgroundTasks, Depends, Request, HTTPException, status from fastapi_pagination import Params @@ -57,10 +57,12 @@ async def create_book(data: Union[CreateBook, CreateRemoteBook]): @book_router.get("/random", response_model=BookDetail) async def get_random_book( - request: Request, allowed_langs: frozenset[str] = Depends(get_allowed_langs) + request: Request, + background_tasks: BackgroundTasks, + allowed_langs: frozenset[str] = Depends(get_allowed_langs), ): book_id = await GetRandomBookService.get_random_id( - allowed_langs, request.app.state.redis + allowed_langs, request.app.state.redis, background_tasks ) book = ( diff --git a/fastapi_book_server/app/views/sequence.py b/fastapi_book_server/app/views/sequence.py index 6754ad6..bec267b 100644 --- a/fastapi_book_server/app/views/sequence.py +++ b/fastapi_book_server/app/views/sequence.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, Depends, Request +from fastapi import APIRouter, BackgroundTasks, Depends, Request from fastapi_pagination import Params from fastapi_pagination.ext.ormar import paginate @@ -29,10 +29,13 @@ async def get_sequences(): @sequence_router.get("/random", response_model=Sequence) async def get_random_sequence( request: Request, + background_tasks: BackgroundTasks, allowed_langs: frozenset[str] = Depends(get_allowed_langs), ): sequence_id = await GetRandomSequenceService.get_random_id( - allowed_langs, request.app.state.redis + allowed_langs, + request.app.state.redis, + background_tasks, ) return await SequenceDB.objects.get(id=sequence_id)