cwkPippa 가 SQLite 쓰는 이유
공개 서비스가 아니라 한 집안이 쓰는 시스템이야. Mac 도 한 대고. 2026-10-12 기준 메시지 row 가 16,000 개쯤이고, 그중 Master Chief 대화 하나는 끝나는 법이 없어(truth 트랙 레슨 8). Postgres 는 과해. 대화 저장소는 <app-data>/ 아래 파일 하나야. 그래도 돌봐야 하는 건 맞고, 그 돌봄은 스크립트가 해. WAL DB 를 파일별로 차례차례 복사하다 그 사이에 checkpoint 가 끼면 사본이 깨질 수 있거든. 그래서 scripts/pippa-db-snapshot.py 가 먼저 SQLite online backup API 로 검증된 사본을 뜨고, 날마다 fleet 로 가는 미러는 라이브 파일 대신 그 사본을 실어 날라. Carbon Copy Cloner 가 트리를 통째로 옮기는 NAS 쪽에선 그 스냅샷 폴더가 복구 지점이야.
aiosqlite 가 SQLite 를 async API 로 감쌈. 같은 SQL, async/await 친화. FastAPI 에 자연스럽게 fit.
스키마는 plain SQL, ORM 없음
cwkPippa 는 SQLAlchemy 안 써. 스키마는 backend/store/conversations.py 의 몇 개 CREATE TABLE. 마이그레이션은 init_db 라는 함수 안의 ALTER TABLE 문들이야. 부팅할 때마다 돌고 멱등이야. 컬럼 추가는 하나하나 try 안에서 돌고, except 가 에러를 삼켜. 컬럼이 이미 있을 때 나는 에러가 그렇게 사라지는 거야. 한 번을 돌리든 백 번을 돌리든 스키마는 같은 모양으로 모여. 다만 실제 except 는 그 일에 필요한 것보다 넓어. 에러를 전부 삼키니까, DB 가 잠겼거나 디스크가 찼을 때 같은 진짜 실패도 똑같이 사라지고, 부팅은 그 컬럼 없이 그냥 넘어가. 더 엄격한 모양은 SQLite 의 duplicate column name 에러가 아니면 다시 던져. 그래야 실패한 마이그레이션이 어느 단계에서 멈췄는지 말해.
mirror 원칙
SQLite 는 JSONL ground truth 의 convenience mirror. drift 시 (corruption, schema 변경 잘못, partial write) 복구는 그 conversation의 row를 비우고 JSONL을 replay. SQLite 직접 패치는 절대 안 해. Truth track 에서 어렵게 배움.
? placeholder 거쳐. SQL injection 만이 이유 아냐 — 적절한 SQLite type coercion 받는 방법이기도. SQLite 에서 string 과 bytes 가 기대대로 자동 변환 안 됨.
한 사람을 위한 시스템이라는 말은 작은 장난감이라는 뜻이 아니야. workload와 운영 주체가 분명하다는 뜻이지. SQLite는 파일 하나라서 backup, integrity check, 복구 절차가 눈에 보여. 대신 concurrent writer가 많아지거나 여러 서버가 같은 DB를 공유해야 하는 순간엔 조건이 바뀌어. 도구를 사랑해서 붙드는 게 아니라 문제 모양이 맞아서 쓰는 거야.
migration도 같은 태도로 다뤄. startup 때 여러 번 돌아도 같은 결과를 내야 하고, 기존 row를 잃지 않아야 하며, 실패하면 어느 step에서 멈췄는지 보여야 해. "DB가 작으니까 직접 고쳐도 돼"라는 유혹이 제일 위험해. 작을수록 정식 복구 경로를 끝까지 시험하기 쉽거든.