SQLite: 임베디드 DB의 쓰임새
ML 프로젝트를 시작할 때 PostgreSQL을 설치하고 설정하는 것은 번거롭다. SQLite는 파일 하나가 곧 데이터베이스다. 설치가 필요 없고, Python에 기본 내장되어 있다.
SQLite란
섹션 제목: “SQLite란”SQLite는 서버가 없는(serverless) 임베디드 관계형 데이터베이스다. 별도 프로세스 없이 애플리케이션 프로세스 안에서 직접 DB 파일을 읽고 쓴다.
PostgreSQL 구조: SQLite 구조:
애플리케이션 애플리케이션 │ SQL 쿼리 │ ▼ │ (직접 파일 I/O)postgres 프로세스 ▼ │ myapp.db (단일 파일) ▼데이터 파일들import sqlite3
# 파일이 없으면 자동 생성conn = sqlite3.connect("myapp.db")cursor = conn.cursor()
cursor.execute(""" CREATE TABLE IF NOT EXISTS experiments ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, accuracy REAL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )""")
cursor.execute( "INSERT INTO experiments (name, accuracy) VALUES (?, ?)", ("bert-base-v1", 0.923))conn.commit()conn.close()언제 SQLite를 쓰는가
섹션 제목: “언제 SQLite를 쓰는가”적합한 유스케이스
섹션 제목: “적합한 유스케이스”1. 프로토타이핑 / 개발 초기 - 서버 설정 없이 즉시 시작 - .db 파일 하나로 데이터 공유 가능
2. ML 실험 추적 - 로컬에서 실험 메타데이터 저장 - 단일 사용자, 단일 프로세스
3. 임베디드 기기 / 엣지 AI - 라즈베리파이, Jetson 등 리소스 제한 환경 - 네트워크 없이 로컬 추론 결과 저장
4. 캐시 / 큐 - 웹 크롤링 결과, API 응답 캐시 - 단순 작업 큐
5. 테스트 환경 - CI/CD에서 메모리 DB(:memory:)로 빠른 테스트# 테스트용 인메모리 DBconn = sqlite3.connect(":memory:")부적합한 유스케이스
섹션 제목: “부적합한 유스케이스”1. 높은 동시 쓰기 - 여러 워커가 동시에 쓰면 "database is locked" 에러 - 기본 모드: 파일 전체에 쓰기 잠금 (WAL로 개선 가능)
2. 대규모 데이터 - 수백 GB 이상이면 PostgreSQL이 더 효율적 - 복잡한 쿼리 최적화 기능 부족
3. 네트워크 공유 - NFS 등 네트워크 파일 시스템에서 불안정 - 여러 서버가 같은 DB에 접근 불가
4. 엔터프라이즈 기능 - 사용자 권한 관리 없음 - 복제(Replication) 기본 미지원WAL 모드
섹션 제목: “WAL 모드”기본 SQLite는 쓰기 시 파일 전체를 잠근다(exclusive lock). 읽기도 막힌다.
WAL(Write-Ahead Logging) 모드를 사용하면 읽기와 쓰기가 동시에 가능하다.
import sqlite3
conn = sqlite3.connect("myapp.db")
# WAL 모드 활성화 (연결할 때마다 설정)conn.execute("PRAGMA journal_mode=WAL")
# 추가 성능 설정conn.execute("PRAGMA synchronous=NORMAL") # 기본값 FULL보다 빠름 (안전성 약간 감소)conn.execute("PRAGMA cache_size=10000") # 캐시 크기 (페이지 수)conn.execute("PRAGMA temp_store=MEMORY") # 임시 데이터 메모리에 저장WAL 모드의 동작:
기본 모드 (Journal): 쓰기 시 → 파일 전체 잠금 → 읽기 불가
WAL 모드: 쓰기 → WAL 파일에 먼저 기록 읽기 → 원본 파일 + WAL 체크포인트로 일관된 뷰 제공 → 읽기/쓰기 동시 가능 (단, 쓰기는 여전히 직렬화)WAL 모드에서도 쓰기는 직렬화된다. 동시 쓰기 워커가 많다면 SQLite 한계다. 이 경우 PostgreSQL로 전환을 고려한다(08-04 참고).
FastAPI에서 SQLite 사용
섹션 제목: “FastAPI에서 SQLite 사용”import sqlite3from contextlib import contextmanagerfrom fastapi import FastAPI, Depends
DATABASE_URL = "experiments.db"
@contextmanagerdef get_connection(): conn = sqlite3.connect(DATABASE_URL) conn.execute("PRAGMA journal_mode=WAL") conn.row_factory = sqlite3.Row # 딕셔너리처럼 접근 가능 try: yield conn conn.commit() except Exception: conn.rollback() raise finally: conn.close()
app = FastAPI()
@app.post("/experiments")async def create_experiment(name: str, accuracy: float): with get_connection() as conn: cursor = conn.execute( "INSERT INTO experiments (name, accuracy) VALUES (?, ?)", (name, accuracy) ) return {"id": cursor.lastrowid, "name": name, "accuracy": accuracy}
@app.get("/experiments")async def list_experiments(): with get_connection() as conn: rows = conn.execute( "SELECT * FROM experiments ORDER BY created_at DESC" ).fetchall() return [dict(row) for row in rows]핵심 정리
섹션 제목: “핵심 정리”- SQLite는 서버가 없는 단일 파일 DB다 — 설치 없이 Python 기본 내장으로 바로 사용 가능하다
- 프로토타이핑, 로컬 실험 추적, 임베디드 환경에 최적이다 — 빠른 시작이 최우선일 때 선택한다
- 동시 쓰기가 많으면 한계가 있다 — 기본 모드는 쓰기 시 전체 잠금이 발생한다
- WAL 모드로 읽기/쓰기 동시성을 개선한다 —
PRAGMA journal_mode=WAL한 줄로 활성화한다 - 쓰기 직렬화는 WAL에서도 유지된다 — 높은 동시 쓰기가 필요하면 PostgreSQL 전환을 고려한다