Fastapi 서버 개발할 때 postgresql 대신 sqlite를 개발용으로 사용하기
[영상]
안녕하세요, 지난 번에 이런 포스팅을 작성했던 적이 있습니다.
[20250306] Fastapi와 함께 쓰는 DB로 초기 개발할 때는SQLite로, 이후 Postgresql로 이전하는 것이 유리함.
[20250306] Fastapi와 함께 쓰는 DB로 초기 개발할 때는SQLite로, 이후 Postgresql로 이전하는 것이 유리함. fastapi에서 postgresql 말고 sqlite 쓰는게 개발에서 유리한가요? 개발 후에 postgresql로 바꾸는건 쉽나… fastapi에서 postgresql 말고 sqlite 쓰는게 개발에서 유리한가요? 개발 후에 postgresql로 바꾸는건 쉽나…
오늘은 그 포스팅을 이어서 작성해보겠습니다.
- 개발은 빠르고 간단하게 → SQLite
- 운영은 확장성과 성능 고려해서 → PostgreSQL + asyncpg
처음엔 좋습니다. SQLite는 서버 없이 바로 쓸 수 있고, PostgreSQL은 스케일과 기능이 뛰어나니까요. 하지만 어느 순간, 이 두 환경의 차이가 의외의 문제들을 만들어냅니다.
이 글에서는 개발과 운영 환경의 차이로 생기는 실전 문제들과, 그걸 우아하게 해결할 수 있는 전략을 정리합니다.
✅ 왜 이 조합을 쓰는가?
| 환경 | DB | 장점 |
|---|---|---|
| 개발 | SQLite | 실행 즉시 사용 가능, 서버 불필요, 테스트 빠름 |
| 운영 | PostgreSQL | JSONB, ARRAY, GIN 인덱스 등 강력한 기능 제공 |
⚠️ 실전에서 겪는 문제들
- JSONB vs JSON
| DB | 타입 | 설명 |
|---|---|---|
| PostgreSQL | JSONB | 빠르고 인덱싱 가능함 |
| SQLite | JSON | 기본적으로 텍스트 수준의 지원 |
- Column(JSONB)를 쓰면 SQLite에서 작동 안 함
- JSON 연산자(@>, jsonb_path_query) 등도 SQLite 미지원
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
from sqlalchemy import JSON
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.types import TypeDecorator
import json
class JsonType(TypeDecorator):
impl = JSON
cache_ok = True
def load_dialect_impl(self, dialect):
if dialect.name == "postgresql":
return dialect.type_descriptor(JSONB())
return dialect.type_descriptor(JSON())
def process_bind_param(self, value, dialect):
if value is None:
return None
if dialect.name == "sqlite":
return json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value is None:
return None
if dialect.name == "sqlite" and isinstance(value, str):
return json.loads(value)
return value
모델에서 아래처럼 사용:
1
2
3
4
5
6
from sqlalchemy import Column
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
settings = Column(JsonType)
- UUIDType, EnumType, ArrayType 등도 TypeDecorator로 감싸기
- Enum은 Enum(String)으로 처리, UUID는 String으로 대체
1
2
3
4
5
6
7
8
9
10
11
if os.getenv("ENVIRONMENT") == "production":
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with SessionLocal() as session:
yield session
else:
def get_db() -> Generator[Session, None, None]:
db = SessionLocal()
try:
yield db
finally:
db.close()
- JSONB, UUID, ARRAY 같은 타입이 SQLite에서 에러 발생
- Alembic이 다르게 인식해서 마이그레이션 충돌
- SQLite에서는 마이그레이션 시 타입 대체 처리
- 예: from sqlalchemy.dialects.sqlite import JSON as JSONB로 우회
🧩 실전 구조 제안
1
2
3
4
5
6
7
8
9
your_project/
├── app/
│ ├── db/
│ │ ├── types.py ← JSONB/UUID 등 커스텀 타입들
│ │ ├── session.py ← async/sync 분기 포함
│ ├── models/
│ │ ├── user.py ← Column(JsonType) 등 적용
│ ├── core/
│ │ └── config.py ← settings.ENVIRONMENT 등 분기 기준
.env 예:
1
ENVIRONMENT=development
🎯 결론 & 베스트 프랙티스
| 전략 | 설명 |
|---|---|
| 커스텀 타입 래퍼 | JSONB, UUID, Enum 등 대응 가능하게 만들기 |
| DB 엔진 모듈화 | engine_sync.py, engine_async.py 등으로 분리 |
| get_db() 자동 분기 | async/sync 선택적으로 제공 |
| 마이그레이션 트릭 | Alembic에서 타입 우회 정의로 통일 |
FastAPI에서 SQLite를 개발용으로 쓰는 건 정말 편리합니다. 하지만 PostgreSQL과의 차이를 명확히 인식하고 미리 구조를 잘 짜두면, 운영 환경 전환 시 큰 시행착오 없이 자연스럽게 넘어갈 수 있습니다.
+2025.04.18 추가) ** **현재 시점에는 더 이상 “개발용”, “프로덕션용” db 프레임워크를 따로 나누지 않으려 합니다. 관련한 자세한 이유는 다음 블로그 포스팅에서 다루었습니다 :) https://blog.naver.com/devramyun/223838892076
[20250418] 왜 나는 더 이상 SQLite를 쓰지 않고 PostgreSQL로만 개발을 시작하기로 했는가 요약 0. 어차피 production에서는 비동기, 병목현상 등을 이유로 sqlite가 아닌 postgresql을 사용해야 함…. 요약 0. 어차피 production에서는 비동기, 병목현상 등을 이유로 sqlite가 아닌 postgresql을 사용해야 함….
감사합니다.


