When trying to get long (~200-300 items) list of entity with it's relationships using response_model the request is extremely slow (10-20 seconds). How can I avoid it?
In ML ploject I have an entity "intent" with examples of user's requests representing this intent.
My current database model looks so:
class Intent(Base):
__tablename__ = 'intent'
id = Column(GUID(), primary_key=True, default=uuid.uuid4, unique=True, nullable=False, index=True)
name = Column(String, unique=True)
examples = relationship("IntentExample", back_populates="intent", cascade="all, delete-orphan")
class IntentExample(Base):
__tablename__ = 'intent_example'
text = Column(String, primary_key=True)
intent_id = Column(GUID(), ForeignKey('intent.id'))
intent = relationship("Intent", back_populates="examples")
I try to get full list of intents with following code (using crud_base.py from full-stack-fastapi-postgresql project):
@router.get("/", response_model=List[Intent])
def get_intents(db: Session = Depends(get_db)):
data = crud.intent.list(db)
print(data)
return data
I see that data is selected from postgres in a second and printed to stdout in upper example code.
After that something inside fastapi / pydantic / ??? is converting response to response_model and it takes a lot of time.
If I do just @router.get("/") without response_model the response is returned very fast, but it doesn't contain list of examples which I need.
Pydantic schemas for Intent:
class IntentExample(BaseModel):
text: str
class Config:
orm_mode = True
class Intent(BaseModel):
id: UUID4
name: str
examples: List[IntentExample] = []
class Config:
orm_mode = True
Please help me to find a correct way to do this using fastapi.
Thanks in advance!
You could profile the code to get specifics on where it鈥檚 slow, but usually the answer is the database. This looks like SQLAlchemy models. My guess is that you are lazy-loading the relationship in your crud function. This means that when you initially query, it only selects the Intent and not the IntentExample.
Then, when Pydantic tries to fill in the examples, SQLAlchemy lazy-loads the relationship for each record. This is known as the N+1 problem (query once initially then again for each example).
There are a couple ways to solve it. If you always want to include examples every time you load any Intent then you can put lazy=False in your relationship declaration. See this documentation for more info.
The other option is to tell SQLAlchemy to load the examples in your query inside crud.intent.list. You can do this by adding something like .options(joinedload(Intent.examples)) to the end of your query. See these docs for more info.
@dbanty, thank you! First option worked fine for me.
Thanks a lot for the help here @dbanty ! :clap: :bow:
Thanks for reporting back and closing the issue @Ezhvsalate :+1: