Как реализовать разбиение на страницы для fastapi с помощью mongo db(двигатель)

#mongodb #pagination #pymongo #rest #fastapi

Вопрос:

У меня есть простой REST api, который представляет собой книжный магазин, созданный с помощью FastAPI и базы данных mongo в качестве бэкэнда (вместо этого я использовал Motor библиотеку Pymongo ). У меня есть GET конечная точка для получения всех книг в базе данных, которая также поддерживает строки запросов (например : пользователь может искать книги с одним автором или с типом жанра и т. Д.).

Ниже приведены соответствующие коды для этой конечной точки : routers.py

 
@router.get("/books", response_model=List[models.AllBooksResponse])
async def get_the_list_of_all_books(
    authors: Optional[str] = None,
    genres: Optional[str] = None,
    published_year: Optional[str] = None,
) -> List[Dict[str, Any]]:
    if authors is None and genres is None and published_year is None:
        all_books = [book for book in await mongo.BACKEND.get_all_books()]
    else:
        all_books = [
            book
            for book in await mongo.BACKEND.get_all_books(
                authors=authors.strip('"').split(",") if authors is not None else None,
                genres=genres.strip('"').split(",") if genres is not None else None,
                published_year=datetime.strptime(published_year, "%Y")
                if published_year is not None
                else None,
            )
        ]

    return all_books
 

Соответствующая модель :

 class AllBooksResponse(BaseModel):
    name: str
    author: str
    link: Optional[str] = None

    def __init__(self, name, author, **data):
        super().__init__(
            name=name, author=author, link=f"{base_uri()}book/{data['book_id']}"
        )
 

И внутренняя функция для получения данных:

 class MongoBackend:
    def __init__(self, uri: str) -> None:
        self._client = motor.motor_asyncio.AsyncIOMotorClient(uri)

    async def get_all_books(
        self,
        authors: Optional[List[str]] = None,
        genres: Optional[List[str]] = None,
        published_year: Optional[datetime] = None,
    ) -> List[Dict[str, Any]]:
        find_condition = {}
        if authors is not None:
            find_condition["author"] = {"$in": authors}
        if genres is not None:
            find_condition["genres"] = {"$in": genres}
        if published_year is not None:
            find_condition["published_year"] = published_year
        cursor = self._client[DB][BOOKS_COLLECTION].find(find_condition, {"_id": 0})
        return [doc async for doc in cursor]
 

Теперь я хочу реализовать разбиение на страницы для этой конечной точки . Здесь у меня есть несколько вопросов :

  1. Хорошо ли выполнять разбиение на страницы на уровне базы данных или на уровне приложения ?
  2. Есть ли у нас готовые библиотеки, которые могут помочь мне сделать это в fastapi ? Я проверил документацию на https://pypi.org/project/fastapi-pagination/ , но это, похоже, больше ориентировано на базы данных SQL
  3. Я также проверил эту ссылку : https://www.codementor.io/@arpitbhayani/fast-and-efficient-pagination-in-mongodb-9095flbqr в которой рассказывается о разных способа сделать это в монго дБ, но я думаю, что только первый вариант(через limit и skip ) будет работать для меня, потому что я хочу, чтобы заставить его работать, когда я использую другие параметры фильтра (например, для автора и жанра) и нет никакого способа, я могу знать то ObjectId, если я делаю первый запрос, чтобы получить данные, а затем я хочу сделать разбиение на страницы.

Но проблема везде, где я вижу использование limit , и skip это обескураживает.

Может ли кто-нибудь, пожалуйста, сообщить мне, каковы здесь лучшие практики и может ли что-то применяться к моим требованиям и варианту использования?

Заранее большое спасибо.

Ответ №1:

На такой вопрос нет правильного или неправильного ответа. Многое зависит от используемого вами технологического стека, а также от контекста, который у вас есть, учитывая также будущие направления как написанного вами программного обеспечения, так и используемого вами программного обеспечения (mongo).

Отвечаю на ваши вопросы:

  1. Это зависит от нагрузки, которой вы должны управлять, и используемого вами стека разработчиков. Обычно это делается на уровне базы данных, так как получение первых 110 и удаление первых 100 довольно глупо и требует много ресурсов (база данных сделает это за вас).
  2. Мне кажется довольно простым, как это сделать с помощью fastapi : просто добавьте в свою get функцию параметры limit: int = 10 skip: int = 0 и используйте их в функции фильтрации вашей базы данных. Fastapi проверит типы данных для вас, в то время как вы могли бы проверить, что ограничение не является отрицательным или выше, скажем, 100.
  3. В нем говорится, что серебряной пули нет и что с тех пор skip функция монго работает не очень хорошо. Таким образом, он считает, что второй вариант лучше, просто для выступлений. Если у вас миллиарды и миллиарды документов (например, amazon), что ж, возможно, стоит использовать что-то другое, хотя к тому времени, когда ваш сайт настолько разросся, я думаю, у вас будут деньги, чтобы заплатить целой команде экспертов, чтобы разобраться и, возможно, разработать свою собственную базу данных.

TL;DR

В заключение следует отметить, limit skip что подход и является наиболее распространенным. Обычно это делается на уровне базы данных, чтобы уменьшить объем работы приложения и пропускную способность.

Монго не очень эффективен в пропуске и ограничении результатов. Если в вашей базе данных есть, скажем, миллион документов, то я не думаю, что вы даже заметите. Вы даже можете использовать реляционную базу данных для такой рабочей нагрузки. Вы всегда можете сравнить имеющиеся у вас варианты и выбрать наиболее подходящий.

Я мало что знаю о монго, но я знаю, что в целом индексы могут помочь ограничить и пропустить записи (в данном случае документы), но я не уверен, что это относится и к монго.

Комментарии:

1. Большое вам спасибо за подробный ответ !! Я имел в виду фильтрацию по основам ObjectId, а не использование пропусков и ограничений было бы сложно, потому что я хотел бы выполнить разбиение на страницы по результатам возвращаемых результатов(например, поиск книг от определенного автора), и если я не получу результаты, я заранее не знаю, какой объект.

2. Я ничего не понимаю. Вы хотите отфильтровать результаты на основе идентификатора объекта, а затем ограничить и пропустить данные?

3. Если бы я мог использовать ObjectId для фильтрации, мне не нужно было бы использовать ограничение и пропускать, как упоминалось в сообщении codementor.io/@arpitbhayani/… . Это возможно, если я выполняю разбиение на страницы для всех книг в базе данных. Но в любом случае мне очень понравился ваш ответ, и я решил довольно много вещей.

4. Это правда, что вы можете использовать objectid для разбиения на страницы, хотя, что делать, если пользователь хочет сортировать книги? Или что, если есть новая книга, которую вы должны добавить после того, как идентификатор X и X 1 уже используются? Полагаться только на объект, на мой взгляд, не очень хорошая идея. Спасибо, что приняли мой ответ