본문 바로가기
C.W.K.
Stream
Lesson 03 of 06 · published

SQLite + aiosqlite — 한 사람을 위한 정답 DB

~14 min · sqlite, schema, migration

Level 0호기심
0 XP0/69 lessons0/17 achievements
0/100 XP to next level100 XP to go0% complete

cwkPippa 가 SQLite 쓰는 이유

한 user. 한 Mac. 수만 개 메시지, 천천히 자람. Postgres 는 overkill. 전체 DB 가 <app-data>/ 의 단일 파일, Time Machine 이 다른 파일처럼 백업. 0 admin.

aiosqlite 가 SQLite 를 async API 로 감쌈. 같은 SQL, async/await 친화. FastAPI 에 자연스럽게 fit.

스키마는 plain SQL, ORM 없음

cwkPippa 는 SQLAlchemy 안 써. 스키마는 backend/store/conversations.py 의 몇 개 CREATE TABLE. 마이그레이션은 init_db 라는 idempotent 함수의 ALTER TABLE — 컬럼 추가? PRAGMA table_info 로 먼저 체크, missing 일 때만 추가.

mirror 원칙

SQLite 는 JSONL ground truth 의 convenience mirror. drift 시 (corruption, schema 변경 잘못, partial write) 복구는 그 conversation의 row를 비우고 JSONL을 replay. SQLite 직접 패치는 절대 안 해. Truth track 에서 어렵게 배움.

parameterized query 항상: 이 머신엔 나만 있어도, 모든 query 가 ? placeholder 거쳐. SQL injection 만이 이유 아냐 — 적절한 SQLite type coercion 받는 방법이기도. SQLite 에서 string 과 bytes 가 기대대로 자동 변환 안 됨.

한 사람을 위한 시스템이라는 말은 작은 장난감이라는 뜻이 아니야. workload와 운영 주체가 분명하다는 뜻이지. SQLite는 파일 하나라서 backup, integrity check, 복구 절차가 눈에 보여. 대신 concurrent writer가 많아지거나 여러 서버가 같은 DB를 공유해야 하는 순간엔 조건이 바뀌어. 도구를 사랑해서 붙드는 게 아니라 문제 모양이 맞아서 쓰는 거야.

migration도 같은 태도로 다뤄. startup 때 여러 번 돌아도 같은 결과를 내야 하고, 기존 row를 잃지 않아야 하며, 실패하면 어느 step에서 멈췄는지 보여야 해. "DB가 작으니까 직접 고쳐도 돼"라는 유혹이 제일 위험해. 작을수록 정식 복구 경로를 끝까지 시험하기 쉽거든.

Code

Schema init — ORM 없는 idempotent 마이그레이션·python
import aiosqlite

async def init_db(db_path: str):
    async with aiosqlite.connect(db_path) as db:
        await db.execute("""
            CREATE TABLE IF NOT EXISTS conversations (
                id TEXT PRIMARY KEY,
                title TEXT,
                created_at TEXT NOT NULL,
                claude_session_id TEXT
            )
        """)
        await db.execute("""
            CREATE TABLE IF NOT EXISTS messages (
                id TEXT PRIMARY KEY,
                conversation_id TEXT NOT NULL,
                role TEXT NOT NULL,
                content TEXT,
                parent_id TEXT,
                brain TEXT DEFAULT 'claude',
                reasoning_level TEXT,
                thinking TEXT,
                created_at TEXT NOT NULL,
                FOREIGN KEY (conversation_id) REFERENCES conversations(id)
            )
        """)
        # Idempotent column add — safe to run repeatedly
        cols = {r[1] for r in await (await db.execute('PRAGMA table_info(messages)')).fetchall()}
        if 'thinking' not in cols:
            await db.execute('ALTER TABLE messages ADD COLUMN thinking TEXT')
        await db.commit()

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 똘이warm💛 by 피파warm

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.