SQLAlchemy ORM에 테이블을 지연 반영

SQLAlchemy는 오랫동안 시장에 출시되었으며 지금까지 사용 가능한 최고의 ORM 중 하나입니다. Flask 또는 FastAPI 와 같은 백엔드 프레임워크에서 작업할 때 일반적으로 이 ORM을 접하게 됩니다.
SQLAlchemy를 사용할 수 있는 두 가지 접근 방식이 있습니다.
  • declarative_base 객체를 사용하여 스키마, 테이블을 수동으로 생성하고 마이그레이션합니다.
  • metadata를 사용하여 데이터베이스의 기존 개체를 반영합니다.

  • 이후 접근 방식의 문제는 작업할 테이블이 많을 때 모든 테이블에 대한 초기 반영에 많은 시간이 걸리고 응용 프로그램 부팅 시간이 증가한다는 것입니다. 애플리케이션 성능 저하 없이 기존 테이블을 반영해야 하는 상황을 해결하기 위한 접근 방식을 생각해 냈습니다.

    아이디어는 한 번에 모든 것을 로드하는 대신 요구 사항에 따라 테이블과 뷰를 느리게 반영하는 것입니다. 한 번에 모든 테이블을 요구하지는 않습니다. 그렇죠?

    API는 테이블의 하위 집합에서 쿼리하거나 CRUD 작업을 수행하기 때문에 다른 테이블 로드를 건너뛸 수 있는 공간이 있지만 이미 반영된 테이블을 지속적으로 유지할 수도 있습니다.

    요구 사항에 대한 테이블을 한 번만 반영하고 앞으로 동일한 개체를 사용할 수 있도록 유지하는 래퍼lazy를 만들었습니다.

    class LazyDBProp(object):
        """This descriptor returns sqlalchemy
        Table class which can be used to query
        table from the schema
        """
    
        def __init__(self) -> None:
            self._table = None
            self._name = None
    
        def __set_name__(self, _, name):
            self._name = name
    
        def __set__(self, instance, value):
            if isinstance(value, (CustomTable, Table)):
                self._table = value
    
        def __get__(self, instance, _):
            if self._table is None:
                self._table = CustomTable(
                    self._name, instance.metadata, autoload=True)
            return self._table
    


    이 클래스는 내부적으로 descriptors를 사용하여 table 또는 view 개체를 유지합니다. 또한 이러한 디스크립터 기반 테이블 개체를 보유할 동적 클래스를 생성하는 래퍼를 만들었습니다.

    def get_lazy_class(engine: Engine) -> object:
        """
        Function to create Lazy class for pulling table object
        using SQLalchemy metadata
        """
    
        def __init__(self, engine: Engine):
            self.metadata = MetaData(engine)
            self.engine = engine
    
        def __getattr__(self, attr):
            if attr not in self.__dict__:
                obj = self.__patch(attr)
            return obj.__get__(self, type(self))
    
        def __patch(self, attribute):
            obj = LazyDBProp()
            obj.__set_name__(self, attribute)
            setattr(type(self), attribute, obj)
            return obj
    
        # naming classes uniquely for different schema's
        # to avoid cross referencing
        LazyClass = type(f"LazyClass_{engine.url.database}", (), {})
        LazyClass.__init__ = __init__
        LazyClass.__getattr__ = __getattr__
        LazyClass.__patch = __patch
        return LazyClass(engine)
    


    위 클래스는 아래와 같이 간단하게 사용할 수 있습니다.

    from lazy_alchemy import get_lazy_class
    from sqlalchemy import create_engine
    
    db_engine = create_engine(DB_CONNECT_STRING)
    lazy_db = get_lazy_class(db_engine)
    
    db_model = lazy_db.my_db_table_foo
    query = session.query(db_model).filter(db_model.foo == "bar").all()
    


    일단 반영되면 이러한 개체를 반복적으로 참조할 수 있습니다. 필요한 객체만 반영하면 최소한의 오버헤드로 애플리케이션 성능이 향상됩니다.

    이로 인해 응용 프로그램 부팅 시간이 1분 이상에서 몇 초로 단축되었습니다 :).

    프로젝트에서 위의 내용을 구현하려면 간단히 내 pypi 패키지Lazy Alchemy를 사용할 수 있습니다.

    이 접근법에 대한 귀하의 견해와 대안을 듣고 싶습니다.

    이 글을 읽어 주셔서 감사합니다. 유용한 정보를 찾으셨기를 바랍니다.

    좋은 웹페이지 즐겨찾기