Python flask + gunicorn으로 vm에서 앱 자동 실행하기 Flask로 백엔드를 구성한 후에 gunicorn에게 맡기는 방법
파이썬 백엔드 개발에서 자주 사용되는 SQLAlchemy와 Flask-SQLAlchemy가 PostgreSQL에 어떻게 접근하고 다루는지 알아보았습니다. 특히 db.Model을 상속받은 클래스가 어떻게 동작하고 model.query를 통해 실제 데이터베이스와 어떻게 상호작용하는지 살펴보겠습니다.
SQLAlchemy 라이브러리
SQLAlchemy는 파이썬용 SQL 툴킷 및 ORM(Object Relational Mapper) 라이브러리예요. 2005년 Mike Bayer가 개발했으며, 파이썬 생태계에서 가장 인기 있는 데이터베이스 접근 라이브러리 중 하나입니다.
주요 특징으로는:
- SQL 추상화: 다양한 데이터베이스를 일관된 방식으로 다룰 수 있어요
- ORM 기능: 데이터베이스 테이블과 파이썬 클래스를 매핑해줘요
- 다양한 데이터베이스 지원: PostgreSQL, MySQL, SQLite, Oracle, MS SQL 등을 지원해요
- 두 가지 사용 방식: 저수준 SQL 조작을 위한 Core API와 객체 지향적 접근을 위한 ORM
Flask-SQLAlchemy는 이러한 SQLAlchemy를 Flask 웹 프레임워크에 쉽게 통합할 수 있게 해주는 확장 라이브러리예요.
Flask-SQLAlchemy 사용 방법
Flask 애플리케이션에서 데이터베이스를 사용하려면, 일반적으로 SQLAlchemy를 직접 설정하고 세션을 관리하는 등의 작업이 필요해요. 하지만 Flask-SQLAlchemy를 사용하면 이 과정이 훨씬 간단해집니다.
기본적인 설정은 다음과 같이 이루어져요:
python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@localhost/dbname'
db = SQLAlchemy(app)이렇게 db 객체를 생성하면, 이 객체를 통해 모델을 정의하고 데이터베이스 작업을 수행할 수 있어요. 특히 db.Model을 상속받아 모델 클래스를 정의하는 패턴이 Flask-SQLAlchemy에서 가장 일반적으로 사용되는 방식입니다.
python
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120), unique=True)이렇게 정의된 모델 클래스는 자동으로 데이터베이스 테이블과 매핑되고, User.query와 같은 방식으로 쿼리를 시작할 수 있는데, 이게 엄청 간편합니다. Flask-SQLAlchemy의 장점 중 하나라고 볼 수 있겠스요.
간편함에는 대가가 있습니다. 어떻게 db.Model을 상속받은 클래스가 자동으로 데이터베이스와 연결되고, .query 속성을 사용할 수 있게 되는 걸까요?
db.Model 상속 클래스는 어떻게 자동으로 등록되나
- 선언적 기반 클래스: Flask-SQLAlchemy의
db.Model은 SQLAlchemy의declarative_base()로 만들어진 기본 클래스예요. 이걸 상속하면 특별한 메타클래스 처리가 일어납니다. - 메타클래스의 마법:
db.Model은 내부적으로DeclarativeMeta메타클래스를 사용하는데 이 메타클래스는 모델 클래스가 정의될 때 다음 작업들을 수행합니다:- 클래스의 속성들을 검사해서 Column 객체들을 찾기
- 테이블 이름을 결정 (명시적으로 지정하지 않으면 클래스 이름을 소문자화, 스네이크 케이스로 변환)
- SQLAlchemy Table 객체를 생성하고 등록
- 매핑 정보를 설정
- 레지스트리 등록: 모델 클래스는 SQLAlchemy의 레지스트리에 자동으로 등록되고 Flask-SQLAlchemy가 이 레지스트리를 관리합니다.
query 속성 사용하기
User.query 로 모델 클래스에서 바로 쿼리를 시작할 수 있는 이유는 Flask-SQLAlchemy의 특별한 기능 때문입니다.
-
쿼리 프로퍼티 주입: Flask-SQLAlchemy는 모든 모델 클래스에
query속성을 주입합니다. 이 속성은 실제로는_QueryProperty클래스의 인스턴스. -
동적 쿼리 객체 생성:
query속성에 접근하면, 이 프로퍼티는 현재 애플리케이션 컨텍스트에서 세션을 찾아 해당 세션에 연결된Query객체를 동적으로 만들어 줍니다 -
내부 구현: 간단히 표현하면 이런 식으로 동작해요:
python
class _QueryProperty: def __get__(self, obj, type): # 현재 앱 컨텍스트에서 세션 가져오기 session = _app_ctx_stack.top.db_session # 모델 클래스에 맞는 Query 객체 반환 return session.query(type) -
세션 자동 관리: Flask-SQLAlchemy는 요청 컨텍스트와 함께 세션을 자동으로 관리해줘서, 개발자가 직접 세션을 관리할 필요 없이
Model.query로 바로 쿼리를 시작할 수 있는 것입니다.
실제 코드 예시
python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db = SQLAlchemy(app)
class User(db.Model): # db.Model 상속
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
# 이제 User.query를 사용할 수 있어요
# 예: User.query.filter_by(username='admin').first()*그러면 다음은 실제로 DB에 어떻게 접근하고 테이블을 조회, 수정, 삭제하는지 궁금해졌습니다.
PostgreSQL과 실제로 어떻게 연결되나요?
1. 연결 설정 (초기화 단계)
먼저 Flask-SQLAlchemy를 사용하는 애플리케이션에서 PostgreSQL과 연결을 설정합시다:
python
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@localhost/dbname'
db = SQLAlchemy(app)이 단계에서 SQLAlchemy는:
- PostgreSQL 드라이버(보통 psycopg2)를 통해 연결 풀을 만들고
- 세션 팩토리를 설정하고
- DB 엔진을 초기화합니다.
2. 쿼리 실행 흐름
User.query.filter_by(name='엄준식').first()와 같은 코드가 실행될 때 어떤 일이 일어나는지 살펴볼게요:
-
쿼리 객체 생성:
User.query에 접근하면_QueryProperty디스크립터가 현재 애플리케이션 컨텍스트의 세션을 가져와session.query(User)쿼리 객체를 반환. -
쿼리 빌딩:
.filter_by(name='엄준식')와 같은 메소드를 호출하면 실제 SQL이 바로 실행되지 않고, 내부적으로 쿼리 객체가 구성되었다가 -
SQL 변환:
.first()와 같은 실행 메소드가 호출되면, 쿼리 객체는 다음과 같은 SQL로 변환됩니다:sql
SELECT users.id, users.name, ... FROM users WHERE users.name = '홍길동' LIMIT 1 -
실제 데이터베이스 접근: 변환된 SQL은 SQLAlchemy의 내부 엔진을 통해 PostgreSQL 드라이버(psycopg2)로 전달되고
-
결과 처리:
- PostgreSQL은 쿼리를 실행하고 결과를 반환
- SQLAlchemy는 결과 행들을 User 객체의 인스턴스로 변환
- 예시에서
.first()의 경우 첫 번째 결과 객체만 반환하겠습니다.
3. CRUD 작업 예시
데이터 조회 (Read)
python
# 단일 레코드 조회
user = User.query.get(5) # id=5인 사용자 조회
# 필터링 조회
users = User.query.filter(User.age > 25).all() # 25세 초과 사용자 모두 조회
# 조인 쿼리
results = db.session.query(User, Post).join(Post).filter(Post.user_id == User.id).all()데이터 생성 (Create)
python
# 새 사용자 생성
new_user = User(name='김철수', email='kim@example.com')
db.session.add(new_user) # 세션에 추가
db.session.commit() # DB에 커밋, 실제 INSERT SQL 실행데이터 수정 (Update)
python
# 사용자 정보 수정
user = User.query.filter_by(name='김철수').first()
user.email = 'new_email@example.com' # 객체 속성 변경
db.session.commit() # 변경사항 커밋, UPDATE SQL 실행데이터 삭제 (Delete)
python
# 사용자 삭제
user = User.query.get(5)
db.session.delete(user) # 삭제 예약
db.session.commit() # 실제 DELETE SQL 실행4. 트랜잭션 처리
Flask-SQLAlchemy는 요청 컨텍스트와 함께 세션을 관리해요:
python
# 트랜잭션 처리 예시
try:
# 여러 작업 수행
db.session.add(user1)
db.session.add(user2)
db.session.commit() # 모든 변경사항 커밋
except Exception as e:
db.session.rollback() # 오류 발생 시 롤백
raise e5. 내부적인 동작 원리
- Identity Map: 세션은 이미 로드된 객체들을 추적하는 “Identity Map”을 유지, 같은 세션에서 같은 행을 두 번 요청하면 같은 파이썬 객체를 반환
- 변경 감지(Change Tracking): 커밋할 때 SQLAlchemy는 객체의 상태가 변경되었는지 확인하고, 변경된 경우에만 UPDATE SQL을 생성
- 지연 로딩(Lazy Loading): 관계된 객체는 기본적으로 필요할 때만 로드
python
# 사용자의 게시물은 이 속성에 접근할 때 별도의 SQL 쿼리로 로드돼요
posts = user.posts # SELECT * FROM posts WHERE user_id = ?마무리
SQLAlchemy는 파이썬 코드와 PostgreSQL 사이의 다리 역할을 하면서, ORM을 통해 객체 지향적으로 데이터베이스를 다룰 수 있게 해주고 있습니다. 복잡한 SQL을 직접 작성하지 않고도 파이썬 코드만으로 데이터베이스를 효율적으로 조작할 수 있네요.
Flask-SQLAlchemy의 db.Model과 model.query는 이런 SQLAlchemy의 장점을 더욱 사용하기 쉽게 포장해 준 것이라고 볼 수 있겠습니다.
Python flask + gunicorn으로 vm에서 앱 자동 실행하기 Flask로 백엔드를 구성한 후에 gunicorn에게 맡기는 방법