데이터과학 유망주의 매일 글쓰기 — 열세 번째 일요일
Tags: Model, Sqlalchemy, Database, Flask Sqlalchemy, Flask

(Flask_SQLAlchemy아닌)SQLAlchemy

# Flask, #Flask_SQLAlchemy, #SQLAlchemy

flask와 sqlalchemy를 섞지 않고도, “따로 또 같이” 사용할 수 있다.

오늘 한일:

지난 2주 동안 Flask를 공부하면서, Flask_SQLAlchemy를 사용하였다. 파이썬 언어를 통해 SQL 데이터베이스에 접근할 수 있게 해주는 마법같은 모듈이었다. 이 과정에서 그냥 SQLAlchemy에 대해서도 알게되었다.

Flask_SQLAlchemy는 Flask와의 연동을 위해 사용하는 SQLAlchemy 모듈이고, SQLAlchemy는 Flask와 상관없이 SQL 데이터베이스에 접근할 수 있게 해주는 모듈이다. 이번 과정에서는 Flask를 이용해 웹을 만드는 과정이었기 때문에 Flask_SQLAlchemy만 사용해도 충분했다.

그러나, 앞으로 Django등 다른 웹 프레임워크를 사용한다면, 과연 이 모듈을 같이 사용할 수 있을까라는 생각이 들었다. 그래서 오늘은 SQLAlchemy에 대해 좀 더 알아보기로 했고, Flask_SQLAlchemy와의 차이점이 정확히 무엇인지 알아보기로 했다.

SQLAlchemy와 Flask_SQLAlchemy

SQLAlchemy란?

파이썬 SQL 툴킷(toolkit)으로써, Object Relational Mapper(ORM)역할을 한다. 개발자로 하여금 에러에 민감하지 않으면서, 부드러운 데이터베이스 트랜젝션 동작이 가능한 앱을 만들 수 있게한다. ORM은 파이썬 클래스를 관계형 데이터베이스의 테이블로 해석하고, 파이썬형태의 SQL Alchemy 표현언어를 SQL로 변환해준다. 이 기능은 개발자가 파이썬 syntax안에서 SQL 쿼리를 작성할 수 있도록 해준다. SQLAlchemy는 또한, 데이터베이스의 연결을 추상화해주며, 자동으로 연결에 대한 유지 및 보수 기능까지 제공한다.

Flask란?

Flask는 웹 프레임워크로써 파이썬을 이용하여 웹 어플을 만들 수 있게 해준다. Flask는 매우 잘 정돈되어있고, 사용법이 간단하여, 누구나 프로그래밍 수준에 관계없이 쉽게 자신만의 웹 어플을 만드는데 사용할 수 있다는 장점이있다.

예를 들어, “Hello World!”를 프린하는 간단한 웹 어플을 아래와 같이 작성할 수 있다.

from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return "Hello, World!" if __name__ == '__main__': app.run()

Flask_SQLAlchemy란?

Flask_SQLAlchemy는 Flask의 extension으로써, Flask를 이용하여 SQLAlchemy의 사용을 단순화 시킨 모듈이다. 데이터베이스와의 연결을 위한 기본적인 설정 및 도구들을 포함하고 있어, 대부분의 데이터베이스 연동이 필요한 웹 어플을 만드는데 사용할 수 있다. 그러나, 대부분의 경우 Flask_SQLAlchemy가 필요없이, 기본적인 SQLAlchemy를 이용하여 웹 어플의 데이터베이스 연결을 안정화할 수 있는 것도 사실이다.

Flask_SQLAlchemy의 목적은, 데이터베이스와의 연결이 반환하는 worker threading가 있을 수 있는데, 이 것이 가져올 수 있는 문제를 방지하기 위함이다. 이는 유저가 하나의 라우트(route)에서 다른 라우트로 전환활 때 발생할 수 있다. 아래는 Flask 앱에 threading 문제가 일어났을 때 발생할 수 있는 대표적인 에러다.

sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread. The object was created in thread id 12345 and this is thread id 54321.

이는 특정 세션이나 데이터베이스 연결이 앱에서 사용가능한 상태가 아닐때 발생할 수 있다. 이러한 에러는 앱을 망가뜨릴 수 있으며, 앱에 더 많은 기능을 추가하거나 추가적인 개발을 진행하기전에 반드시 해결되어야할 문제이다.

Flask_SQLAlchemy의 단점

Flask_SQLAlchemy의 기본설정은 이러한 에러를 막아주지만, 여전히 이를 사용함으로써 얻는 단점이 있다.

첫째는 Flask_SQLAlchemy가 자신만의 API를 가지고 있다는 것이다. SQLAlchemy API에 익숙한 이들에게는 모델링이나 ORM 쿼리를 하는 법이 좀 더 복잡하게 느껴질 수 있다.

둘째는 Flask_SQLAlchemy에 익숙해질 경우, Flask이외의 환경에서 데이터베이스를 사용하는게 어려워질 수 있다는 것이다. 이는 Flask_SQLAlchemy가 모델, 데이터베이스 연결 설정 및 어플리케이션을 모두 app.py 파일에 담을 수 있게 해 주기 때문이다. 그러나 모델을 어플리케이션 파일에 담는 이러한 간편함은, 어플리케이션 밖에서 데이터베이스와 상호작용하는 것을 어렵게 만든다. 자연스럽게, 어플리케이션 밖에서 데이터를 불러오는 것이 어려워진다. 여기에 더해서, Flask 밖에서 데이터를 가져오는 것이 어려워질 수 있다.

Flask와 SQLAlchemy는 제대로 사용한다면 서로 잘 작동할 수 있다. 굳이 Flask_SQLAlchemy를 쓰지 않고도 말이다.

SQLAlchemy의 장점

모델과 데이터베이스를 한 번만 정의한다.

모델과 데이터베이스는 한 번만 정의하는 것이 가장 좋을 것이다. 데이터베이스 파일과 모델 파일을 분리함으로써, 데이터베이스와 모델을 한번에 정의할 수 있으며, 필요할 때마다 불러올 수 있다. 이러한 분리는 Flask_SQLAlchemy를 사용할 경우에는 상대적으로 더 힘든 작업이다. 데이터베이스를 앱 안에서만 정의해야 하기 때문이다.

SQLAlchemy를 사용하여 아래와 같이 데이터베이스 연결 파일을 만들 수 있다.

From sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker SQLALCHEMY_DATABASE_URL = “sqlite:///./test.db” engine = create_engine( SQLALCHEMY_DATABASE_URL, connect_arg={“check_same_thread”: False} ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base()

위 데이터베이스 파일(database.py)에서 “Base” 클래스를 가져와 아래의 models.py 파일에서 declaratice_base()를 통해 활용할 수 있다.

from sqlalchemy import Column, Integer, String from sqlalchemy.types import Date from .database import Base Class Record(Base): __tablename__ = “Records” id = Column(Integer, primary_key=True, index=True) date = Column(Date) country = Column(String, index=True) cases = Column(Integer) deaths = Column(Integer) recoveries = Column(Integer)

이 파일은 데이터베이스에서 “Records”라는 테이블 또는스키마를 생성한다.

SQLAlchemy의 declarative_base()를 사용하면, 한 테이블당 어플리케이션이 사용가능한 하나의 모델을 생성할 수 있다. 이후 이 모델은 데이터베이스상에서 어플리케이션 밖에서도 파이썬으로 활용이 가능하다.

2. 어플리케이션 밖에서 데이터 불러오기

이렇게 분리된 파이썬 파일을 가지고 있으면, 어플리케이션 밖에서 같은 모델을 사용하여 데이터를 쿼리하고 불러오는 작업이 한결 용이해진다. 추가적으로, 각 모델과 데이터베이스에 대해 하나의 버전만을 가지게 되므로 개발과정이 더욱 단순해진다.

이 모델과 데이터베이스 연결은 데이터 파이프라인, 리포트 생성, 또는 다른 목적을 위해 얼마든지 사용될 수 있다.

이렇게 앱 밖에서 모델과 데이터베이스를 불러올 수 있다는 것 만으로도 SQLAlchemy의 사용가치는 충분하다. 데이터를 불러오기위해 어플리케이션을 사용하는대신, 분리된 파이썬 파일에서 데이터베이스를 불러올 수 있게 하기 때문이다.

아래는 CSV파일에서 데이터를 읽어 데이터베이스에 저장하는 파이썬 예제 파일이다.

Import csv Import datetime from app import models from app.database import SessionLocal, engine db = SessionLocal() models.Base.metadata.create_all(bind=engine) with open(“sars_2003_complete_dataset_clean.csv”, “r”) as f: csv_reader = csv.DictReader(f) for row in csv_reader: db_record = models.Record( data=datetime.datetime.strptime(row[“date”], “%Y-%m-%d”), country=row[“country”], cases = row[“cases”], deaths=row[“deaths”], recoveries=row[“recoveries”], ) db.add(db_record) db.commit() db.close()

위 코드처럼 다른 파이썬 파일에서 정의한 모델, custom 세션인 SessionLocal 및 엔진을 불러올 수 있다. 이렇게 분리된 파일은 어플리케이션을 돌리지 않고도 데이터베이스에 데이터를 입력할 수 있게 해준다.

3. Declarative Base 및 MetaData

이전 데이터베이스 파일에서 확인했던 “declarative_base()” 클래스는, 새로 정의한 “Table” 객체를 가져오는 MetaData객체를 포함한다. 이 MetaData 객체는 “models.Base.metadata.create_all()”를 사용하여 모든 테이블을 생성할 때 접근할 수 있다.

4. Threading문제 해결하기

SQLAlchemy는 또한 사용자가 정의한 세션(Session)을 세우는데 필요할 한 도구 객체를 제공한다. “scoped_session”기능을 통해, SQLAlchemy는 threading 문제를 다룰 수 있다.

sessionmaker는 엔진의 연결 풀을 요청하고, 새로운 세션 객체와 연결하여, 새로운 세션 객체를 초기화하는 자동화 기능을 수행한다.

새로운 세션 객체를 초기화하는 것은 새로운 연결을 체크아웃(“checking out”)하는 것이라 할 수 있다. 데이터베이스는 이러한 연결 및 프로세스의 리스트를 저장한다. 그래서, 새로운 세션을 시작한다는 것은, 데이터베이스에서 새로운 프로세스를 시작하는 것이라 할 수 있다.

scoped_session은 이러한 생성된 세션 객체들의 레지스트리(registry)라 할 수 있다. 여기서 레지스트리의 Key는 특정한 형태의 thread-safe id.라고 할 수 있다.

우리는 위와 같이, Session 자동화, sessionmaker 및 SessionLocal를 관련 변수를 이용하여 불러와 SessionLocal을 정의할 수 있다.

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

“Scopefunc”는 scoped_session에 사용될 수 있는 optional한 인자로써, 새로운 세션을 등록하거나 검색을 위한 key를 반환하는 identity getter 기능으로써 활용될 수 있다. “_app_ctx_stack.__ident_func__”를 더하는 것은 아래 둘 중 하나의 기능을 더하는 것이라 할 수 있다.

greenlet이 설치되었다면, getcurrent를 사용한다. (From greenlet import get current)

그렇지 않다면, get_ident를 사용한다. (From threading import get_indent)이며 thread id를 반환한다.

기본적으로 scopefunc는 get_ident이며, 간단한 어플리케이션은 아래와 같은 코드를 사용할 수 있다.

db_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))

새로운 SessionLocal은 글로벌한 scoped_session에서 부를 수 있는 어플리케이션의 여러 다른 섹션을 허용한다. 따라서, 모든 앱 라우트가 라우트에 직접적으로 세션을 패스하지 않고도 같은 세션을 공유할 수 있다. 레지스트리에 등록된 SessionLocal은 “scoped_session.remove()”를 통해 직접적으로 명령하지 않는한 레지스트리에 남아있을 것이다.

Custom 세션인 SessionLocal을 인자로 사용하여 scoped_session을 불러올 수 있다.

from flask import Flask, _app_ctx_stack, jsonify, url_for from flask_cors import CORS from sqlalchemy.orm import scoped_session from . import models from .database import SessionLocal, engine models.Base.metadata.create_all(bind=engine) app = Flask(__name) CORS(app) app.session = scoped_session(SessionLocal, scopefunc=_app_ctx_stack.__ident_func__)

추가적으로, scoped session은 query_propery에 접근할 수 있게 해 준다. 그러므로 flask_sqlalchemy에서 쓰던 방식이 그대로 SQLAlchemy에서도 사용이 가능하다.

Base = declarative_base() Base.query = db_session.query_property()

5. 트렌잭션 마치기(closing transactions)

“db_session.close()” 메소드는 로컬 세션 객체에 대해서만 트랜잭션을 종료한다. 그러나 데이터베이스와의 연결을 종료하지는 않으며, 풀에 연결을 자동으로 반환하지 않는다. 그러므로 “db_session.remove()”를 이용하여 연결을 종료할 수 있다.

“db_session.remove()”매소드는 먼저 “app.db_session.close()”를 실행하고, 이후 연결 풀에 연결을 반환한다. 그러므로, 로컬과 리모트 데이터베이스에서 에서 프로세스를 종료한다. 만약 데이터베이스가 연결을 종료하지 못하면, 최대 허용 연결 횟수에 도달하게 된다. 데이터베이스는 결국, 연결 이후 아무런 동작이 없는 idle(처리되지 않는)한 프로세스를 끝낼 것이다. 그러나, 그렇게 되기 까지는 수 시간이 걸릴 수 있다. SQLAlchemy는 이러한 상황을 막기 위한 풀 옵션을 제공하지만, 가장 좋은 것은 필요없는 연결을 제거하는 것이다.

결론

개발자들이 일부 에러를 피하기 위해 Flask_SQLAlchemy를 찾지만, SQLAlchemy 역시 이러한 에러를 다루기 위한 툴을 제공한다. sessionmaker와 scoped_session을 잘 활용한다면, Flask 어플리케이션은 threading 문제없이 라우트를 통해 데이터베이스에 연결할 수 있다.

그러므로 데이터베이스 세션 및 threading의 문제가 있다면, Flask와 SQLAlchemy를 사용하는 것이 Flask_SQLAlchemy를 사용하는 것보다 나을 수 있다.

앞으로 할일:

오늘은 Flask_SQLAlchemy가 아닌, SQLAlchemy에 대해 알아보았다. 이렇게 보니, 어쩌면 SQLAlchemy를 배워 놓고 이를 활용하는 것이 앞으로 더 유용할 수 있겠다라는 생각이 들었다. 어플리케이션 밖에서 데이터베이스에 접속해야할 상황이 분명히 있을 수 있기 때문이다.

반대로, 어플리케이션 안에서만 데이터베이스에 접속할 수 있어야 하는 경우에는 이야기가 달라질 수 있다. 예를 들어, 내가 사용하는 모바일 뱅킹 앱을 통해서만 내 계좌에 접근할 수 있어야 하는 경우, 보안이 중요하기 때문에, 오히려 Flask_SQLAlchemy처럼 모든 체계가 잡혀진 모듈을 쓰는 것이 더 나을지도 모른다. 하지만, 그 어플리케이션이 Flask를 사용하지 않는다면 SQLAlchemy가 여전히 더 나은 선택일 수 있다.

결국 모든 도구들은 각 상황에 맞게 가장 효율적인 것을 쓰는 것이 좋다는 사실을 다시 한번 상기하게 되었다. 하지만 오늘을 통해, SQLAlchemy를 좀 더 공부해보고 싶다는 생각이 들었다. 이번 한 주 동안은 부족한 것을 보충하는 휴식 및 학습 기간인데, 이 기회를 통해 SQLAlchemy를 좀 공부해 볼 생각이다. 내일 블로그는 SQLAlchemy와 Flask를 활용하여 간단한 예제를 하나 다루어 볼 계획이다.