SQLite — 로컬 앱의 단일 파일 DB
SQLite — 로컬 앱의 단일 파일 DB
SQLite 는 단일 파일에 SQL 데이터베이스를 담는 임베디드 DB 입니다. 데스크탑·모바일 앱의 로컬 저장소로 가장 자주 쓰이는 선택지 중 하나입니다.
1. SQLite 에 대한 이야기
SQLite 는 D. Richard Hipp 이 2000 년 5 월 첫 릴리스한 라이브러리입니다. C 로 쓰여 있고 퍼블릭 도메인 (저작권을 의도적으로 포기) 으로 배포됩니다. 기여를 받기 위해 별도 CLA 절차가 있습니다.
공식 사이트는 자기 소개에서 "the most used database engine in the world" 라 표현합니다. iOS · Android · Windows 10 · macOS · 대부분의 브라우저 · 항공기 시스템 · 자동차 · 휴대전화에 내장돼 있다는 점이 근거로 거론됩니다.
라이선스가 퍼블릭 도메인이라 배포에 제약이 거의 없습니다. 상용 지원이 필요한 조직을 위해 SQLite Consortium · 유료 라이선스 옵션이 별도로 존재합니다.
2. 단일 파일 모델
DB 한 개 = 파일 하나 (my.db). 그 안에 모든 테이블·인덱스·트리거가 들어갑니다. 프로세스가 별도로 뜨는 클라이언트-서버 모델 (PostgreSQL · MySQL) 과 다르게 라이브러리가 호스트 프로세스 안에서 동작합니다.
이 모델의 결과:
- 설정이 거의 없음 — 호스트가 파일을 열기만 하면 됩니다.
- 백업이 단순함 — 파일을 복사합니다 (다만 라이브 DB 는 잠금 고려가 필요).
- 네트워크 지연 없음 — 같은 프로세스 안 함수 호출.
- 여러 프로세스에서 쓰기 — 가능은 하지만 락 경합이 있습니다. 단일 라이터 + 다중 리더 패턴이 가장 자연스럽습니다.
3. 저널 모드와 WAL
쓰기 도중 충돌해도 일관성을 유지하기 위해 SQLite 는 저널을 씁니다. 두 모드가 흔합니다.
- rollback journal (기본) — 변경 전 페이지를 별도 파일에 백업. 트랜잭션 완료 후 삭제.
- WAL (Write-Ahead Logging, 3.7.0 / 2010) — 변경을 별도
.db-wal에 추가하고 주기적으로 본 파일에 합칩니다 (checkpoint).
WAL 의 이점:
- 읽기와 쓰기가 동시에 가능 (읽기는 WAL 이 적용되기 전 스냅샷을 봄).
- 일반적으로 더 빠른 쓰기.
활성화는 한 줄:
PRAGMA journal_mode = WAL;
WAL 은 같은 파일 시스템 위의 동기화 정확성에 의존합니다. 일부 네트워크 파일 시스템 (NFS) 에서는 권장되지 않습니다 (공식 문서 명시).
4. 동시성과 타입
SQLite 의 동시성 모델은 단순합니다.
- 공유 락 (SHARED) — 여러 리더 동시.
- 예약 락 (RESERVED) — 다음 라이터 예약.
- 배타 락 (EXCLUSIVE) — 실제 쓰기.
WAL 모드에서는 리더와 라이터가 같은 락을 두고 다투지 않아 동시성이 더 좋습니다.
SQLite 는 type affinity 라는 느슨한 타입 모델을 가집니다. 컬럼 타입을 적어도 다른 타입의 값이 저장될 수 있습니다.
CREATE TABLE t (id INTEGER, name TEXT);
INSERT INTO t VALUES ("hello", 42); -- 통과
3.37.0 (2021) 부터 STRICT 옵션이 추가되어 엄격한 타입 검사를 켤 수 있습니다.
CREATE TABLE t (id INTEGER, name TEXT) STRICT;
5. 확장 기능
- JSON1 — 내장. JSON 값을 저장·인덱스·쿼리.
- FTS5 — Full-text search.
- R*Tree — 공간 인덱스.
- vector / sqlite-vec — 벡터 검색 (외부 확장, 2024).
이 모든 것이 단일 파일 안에서 동작합니다.
6. Tauri 와의 통합
Tauri 에서 SQLite 를 쓰는 길은 두 갈래입니다.
tauri-plugin-sql — 공식 플러그인. Rust 의 sqlx 를 통해 SQLite/MySQL/Postgres 를 같은 인터페이스로 사용. JS 에서 SQL 문자열을 보냅니다.
import Database from "@tauri-apps/plugin-sql"
const db = await Database.load("sqlite:app.db")
await db.execute("CREATE TABLE IF NOT EXISTS food (id INTEGER PRIMARY KEY, name TEXT)")
await db.execute("INSERT INTO food (name) VALUES ($1)", ["김치"])
const rows = await db.select<{id: number; name: string}[]>("SELECT * FROM food")
rusqlite 직접 — Rust 코드 안에서만 SQLite 를 만지고 JS 에는 도메인 함수만 노출. 타입 안전성·성능이 더 좋지만 코드량이 늘어납니다.
#[tauri::command]
fn add_food(name: String, db: tauri::State<DbPool>) -> Result<i64, String> {
let conn = db.0.get().map_err(|e| e.to_string())?;
conn.execute("INSERT INTO food (name) VALUES (?1)", [&name])
.map_err(|e| e.to_string())?;
Ok(conn.last_insert_rowid())
}
7. 파일 위치 관습
각 OS 의 사용자 데이터 디렉토리에 두는 것이 표준입니다.
- Windows:
%APPDATA%\<app-id>\(C:\Users\Alice\AppData\Roaming\com.example.app\) - macOS:
~/Library/Application Support/<app-id>/ - Linux:
~/.local/share/<app-id>/(XDG) - Android:
/data/data/<package>/files/ - iOS: 앱 샌드박스의
Library/Application Support/
Tauri 의 app.path().app_data_dir() 가 이를 추상화합니다.
8. 백업·동기화 패턴
라이브 DB 파일을 단순 복사하면 트랜잭션 도중일 수 있어 손상 위험이 있습니다. 안전한 길:
VACUUM INTO 'backup.db'— 라이브 DB 의 일관 사본을 만듭니다.- 백업 API (
sqlite3_backup_*) — 라이브 DB 를 다른 DB 로 페이지 단위 복사.
9. 브라우저·로컬 저장 후보들
| 저장소 | 위치 | 용량 | 특징 |
|---|---|---|---|
localStorage |
브라우저 | 약 5~10 MB | 동기 API · 문자열만 · 작은 설정. |
sessionStorage |
브라우저 | 비슷 | 탭 단위 수명. |
| IndexedDB | 브라우저 | 수백 MB~수 GB (쿼터) | 비동기 · 객체 저장 · 인덱스. 쿼리 표현 빈약. |
| OPFS | 브라우저 | 큼 | 파일 단위 API. WASM SQLite 와 결합 가능. |
| WASM SQLite | 브라우저 | OPFS 위 | 공식. 브라우저 안에서 진짜 SQLite. |
| SQLite (네이티브 앱) | 호스트 파일 시스템 | 디스크 한도 | 본 글의 주제. |
| Dexie.js | IndexedDB 위 | 친숙한 API 래퍼. |
웹 앱이라면 IndexedDB 또는 WASM SQLite, 데스크탑·모바일이라면 네이티브 SQLite 가 자연스러운 선택입니다.
10. 권장 PRAGMA
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;
PRAGMA temp_store = MEMORY;
synchronous = NORMAL 은 성능과 내구성의 절충입니다. 금융 데이터 같은 자리는 FULL 이 더 안전합니다.
11. 자주 걸리는 자리
여러 프로세스 쓰기 — 한 시점에 라이터는 하나입니다. 다른 프로세스가 쓰는 동안 시도하면 SQLITE_BUSY. busy_timeout 또는 큐로 직렬화합니다.
스레드 안전성 — SQLite 는 컴파일 옵션에 따라 thread-safe 모드가 다릅니다. rusqlite 는 connection 객체를 스레드 간 공유하지 못하도록 강제합니다 (Send + !Sync). 풀 (r2d2) 또는 채널을 거칩니다.
WAL 파일 동반 — db.db 를 백업할 때 db.db-wal · db.db-shm 도 함께 가야 합니다. VACUUM INTO 를 써서 단일 파일로 만드는 편이 안전합니다.
네트워크 파일 시스템 — NFS 같은 환경은 락 의미가 다릅니다. 손상이 보고되며 공식적으로 비권장입니다.
타입의 느슨함 — STRICT 없이 만든 테이블은 잘못된 타입의 값이 들어가도 통과합니다. 새 테이블은 STRICT 권장.
공유 디렉토리 — 클라우드 동기화 폴더 (OneDrive · iCloud · Dropbox) 안에 라이브 DB 를 두면 동기화가 락과 충돌해 손상 위험이 있습니다.
이모지·서로게이트 — SQLite 는 UTF-8 을 다루지만 BMP 외 문자 (이모지) 가 잘못된 위치에서 잘리면 손상으로 이어질 수 있습니다. 입력 단계에서 정규화합니다.
하고픈 말
SQLite 는 로컬 앱의 가장 단순한 답입니다. 단일 파일 + 표준 SQL + 퍼블릭 도메인 라이선스 셋이 다른 후보를 압도합니다. WAL 모드 + STRICT 테이블 + foreign_keys ON 만 처음에 켜면 운영 부담이 거의 없습니다.
Next
- ocr-stt-tts
- native-integrations
SQLite 공식 · SQLite About · Most Widely Deployed · WAL Mode · Datatype In SQLite · tauri-plugin-sql · rusqlite · sqlx · SQLite WASM · Dexie.js 를 참고합니다.