일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 |
- ansi2html
- 독서
- ktor client
- AWS EC2
- kotlin list
- exoplayer cache
- build with ai
- list map
- 스피너
- video caching
- ListAdapter
- android custom view
- 안드로이드
- map
- kotlin collection
- android exoplayer
- Python
- ChatGPT
- doc2vec
- android
- ktor api call
- DiffUtil.ItemCallback
- 유튜브
- llm
- ListAdapter DiffUtil
- FastAPI
- android ktor
- getChangePayload
- ExoPlayer
- 시행착오
- Today
- Total
버튼 수집상
[Python] 쿼리 읽기가 어려워서 ORM을 써봤다. 본문
빠르게 보는 결론
쿼리를 일반 문자열로 적었더니 재사용도 어렵고 가독성도 떨어져서 ORM을 도입해봤다.
그랬더니 리스폰스 속도가 너무 느려졌다.
일단 잠정 중단하고 무엇을 어떻게 도입해봤는지 행적을 남긴다.ㅠㅠ
배경
리스트를 뿌리는 기능이 대부분인 BackOffice 사이트에 FastAPI로 API 백엔드를 만들었다.
그런데 내가 쿼리를 못 짜서인지, get 함수 바디가 엄청나게 길고 안 읽혔다.
그냥 통짜 문자열이라서 신택스 하이라이팅이 안 되는 게 너무 불편했다.
문득 ORM이라는 것을 기억해내고 장단점을 찾아봤다.
복잡한 데이터 조작없이 select 쿼리가 대부분이라면 ORM을 써도 괜찮지 않을까 싶어서 chatGPT에 물어봤다.
Yes, considering your emphasis on selecting data with minimal modification, using an ORM could enhance code readability, simplify data retrieval, and make your dashboard/back office development more intuitive. Consider the trade-offs and whether the potential downsides of ORM are acceptable for your project.
물론 chatGPT가 하는 말이라고 딱히 근거가 있는 건 아니다.
그래도 어느정도 확신을 얻고 한 번 도입해봤다.
세팅
SQLAlchemy를 install
pip install sqlalchemy
FastAPI 제작자가 만들어서 호환성이 더 좋다는 SQLModel이라는 라이브러리도 있다.
SQLAchemy를 쓴 이유는 연식이 오래됐고 sql server 관련해서 자료가 더 많은 것 같아서다.
DB 연결
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from contextlib import contextmanager
user = "USER"
pw = "PW"
server = "SERVER_URL"
db_name = "Test"
engine = create_engine(f"mssql+pymssql://{user}:{pw}@{server}/{db_name}")
session= scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
Base = declarative_base() # 여기에 정의
@contextmanager
def db():
try:
yield session
finally:
session.close()
데이터 모델
from sqlalchemy.orm import relationship
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
from database.sqlalchemy import Base
class User(Base):
__tablename__ = "ud_users"
ud_idx = Column(Integer, primary_key=True)
ds_user_idx = Column(Integer) # different DB
schedules = relationship('Schedule', back_populates='user')
class Schedule(Base):
__tablename__ = "ud_schedule"
schedule_idx = Column(Integer, primary_key=True)
ud_idx = Column(Integer, ForeignKey('ud_users.ud_idx'))
user = relationship('User', back_populates='schedules')
regdate = Column(DateTime)
schedule_name = Column(String)
teacher_name = Column(String)
academy_name = Column(String)
curriculum_name = Column(String)
@property
def teacher_academy_subject(self):
return f"{self.teacher_name}-{self.academy_name}-{self.curriculum_name}"
모델끼리의 관계를 정의하는 relationship이라는 메소드도 있다.
관계를 정의하는 것이기 때문에 one to one, one to many 관계가 명확한 테이블끼리 쓰면 좋겠다.
예시 데이터는 유저 한 명당 여러개의 일정을 관리할 수 있기 때문에
User 클래스에 schedules라는 변수를, Schedule 클래스에 user 라는 변수를 각각 정의했다.
API 메소드
from fastapi import APIRouter
# import 생략 ...
router = APIRouter()
# 일정을 전부 가져온다.
# TODO server-side pagination
@router.get("/schedule-list")
def get_schedule_list():
result_list = []
with db() as session:
for schedule in session.query(Schedule).options(joinedload(Schedule.user)).all():
user = schedule.user
result = {
"schedule": schedule.schedule_name,
"location": get_location_by_user(schedule.ud_idx),
"teacher_academy_subject": schedule.teacher_academy_subject,
"user": user
}
# 다른 데이터베이스의 테이블
with ds_db() as session1:
ds_user: DSUser = session1.query(DSUser).get(user.ds_user_idx)
result["user_name"] = ds_user.name
result_list.append(result)
return result_list
리팩토링 한 번
# generated by chatGPT 3.5
@router.get("/schedule-list")
def get_schedule_list():
result_list = []
with db() as session:
schedules = session.query(Schedule).options(joinedload(Schedule.user)).all()
# 다른 데이터베이스에 있는 정보를 싱글쿼리로 가져온다
user_ids = [schedule.user.ds_user_idx for schedule in schedules]
with ds_db() as session1:
ds_users = session1.query(DSUser).filter(DSUser.user_idx.in_(user_ids)).all()
ds_user_dict = {ds_user.user_idx: ds_user for ds_user in ds_users}
for schedule in schedules:
user = schedule.user
ds_user = ds_user_dict.get(user.ds_user_idx)
result = {
"schedule": schedule.schedule_name,
"location": get_location_by_user(schedule.ud_idx),
"teacher_academy_subject": schedule.teacher_academy_subject,
"user": user,
"user_name": ds_user.name if ds_user else None # ds_user가 없을 때
}
result_list.append(result)
return result_list
아래처럼 고쳐도 62개밖에 안 되는 데이터를 가져오는데 3.5초나 걸린다..
피드백
어차피 파이썬 백엔드와 자바스크립트 프런트엔드가 나뉘어있는 프로젝트에서 이런 매핑은 굳이 없어도 될 것 같다.
피드백
인프런 등등 자바 Spring에서 JPA를 잘만 쓰고 있는데, 왜 파이썬에 ORM을 도입했을 땐 느린걸까??
'TIL - Python' 카테고리의 다른 글
[FastAPI] 인트라넷 앱을 위한 크롤링 API 만들기 - 1. screen 세션 (0) | 2024.02.13 |
---|---|
[FastAPI] 인트라넷 앱을 위한 크롤링 API 만들기 - 0. 소개 (0) | 2024.02.13 |
[FastAPI] Pydantic field 타입 에러 (fastapi.exceptions.FastAPIError: Invalid args for response field!) (0) | 2023.07.19 |
[Python] pymssql INSERT 할 때 중복값 체크해서 업데이트하기 (0) | 2023.05.23 |