DB
개인 프로젝트에 SQLite를 쓰기로 한 이유와 실제로 느낀 한계
PostgreSQL을 굳이 띄우지 않아도 되는 규모라면 SQLite가 생각보다 잘 맞는다. 단, 몇 가지 주의할 점이 있다.
사이드 프로젝트를 시작할 때마다 DB 선택에서 시간을 쓰곤 했다. PostgreSQL은 설정이 번거롭고, MySQL은 그나마 낫지만 Docker로 띄워야 한다. 혼자 쓰는 툴이나 소수만 쓰는 서비스라면 SQLite가 의외로 선택지가 된다.
SQLite가 잘 맞는 상황
- 동시 쓰기가 거의 없는 서비스 (읽기 위주, 또는 단일 사용자)
- 배포 환경에서 별도 DB 서버를 두기 싫을 때
- Prisma나 Drizzle로 스키마 관리를 하는 프로젝트
- 로컬 CLI 툴, 개인 대시보드, 소규모 관리 도구
SQLite는 파일 하나로 DB 전체가 움직인다. 백업이 cp db.sqlite backup/db.sqlite 한 줄이고, 이사도 파일 하나만 옮기면 끝난다.
Prisma와 쓸 때 설정
// schema.prisma
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
# .env
DATABASE_URL="file:./dev.db"
마이그레이션은 평소와 같다.
npx prisma migrate dev --name init
npx prisma migrate status # 현재 적용 상태 확인
운영 환경에서는 migrate deploy를 쓴다. migrate dev는 로컬 개발용이다.
WAL 모드를 켜면 읽기 성능이 올라간다
SQLite 기본 설정은 쓰기 중에 읽기가 블로킹된다. WAL(Write-Ahead Logging) 모드를 켜면 쓰기와 읽기가 동시에 가능해진다.
PRAGMA journal_mode=WAL;
Prisma에서는 connection URL에 붙일 수 있다.
DATABASE_URL="file:./dev.db?connection_limit=1&socket_timeout=20"
그리고 앱 시작 시 한 번 실행하거나, $connect 이후 $executeRaw로 설정한다.
실제로 부딪힌 한계
동시 쓰기가 늘어나면 SQLITE_BUSY 에러가 난다. 여러 프로세스(예: Next.js serverless function 다수)가 동시에 쓰기를 시도하면 이 에러를 보게 된다. WAL 모드와 busy_timeout 설정으로 어느 정도 완화할 수 있지만, 근본적인 해결책은 아니다.
파일 시스템이 NFS나 공유 스토리지면 쓰면 안 된다. SQLite는 파일 잠금에 의존하는데, NFS에서는 잠금이 제대로 동작하지 않아서 데이터 손상이 날 수 있다. Vercel이나 Railway처럼 ephemeral 스토리지를 쓰는 환경도 마찬가지다.
트래픽이 늘어날 것 같거나 서버리스 환경이라면 처음부터 PostgreSQL을 쓰는 게 낫다. 하지만 혼자 쓰는 도구나 소규모 서비스라면 SQLite의 단순함이 오히려 큰 장점이다.