본문 바로가기

IT & AI

SQLite의 오래된 기본값, Rust식 에디션으로 바꿀 수 있을까요

728x90

SQLite의 오래된 기본값, Rust식 에디션으로 바꿀 수 있을까요

IT & AI 뉴스 썸네일
IT & AI 뉴스 썸네일

SQLite는 오래된 프로그램을 깨뜨리지 않으려고 보수적인 기본값을 지켜 왔어요. 그 결과 외래 키 검사, 엄격한 자료형, 동시 쓰기 대기, WAL 같은 설정을 프로젝트마다 따로 챙겨야 해요. 한 개발자가 Rust처럼 연도별 에디션을 선택해 최신 권장 설정을 한꺼번에 켜자는 아이디어를 제안했어요. SQLite 공식 로드맵이 아니라 개발자 개인의 제안이라는 점은 구분해서 봐야 해요. 1

핵심 요약

구분핵심왜 볼 만한가요
제안`PRAGMA edition = 2026`으로 최신 기본값 묶음을 선택해요기존 앱의 동작을 보존하면서 새 프로젝트는 더 안전하게 시작할 수 있어요
무결성외래 키 검사를 기본으로 켜고 엄격한 테이블을 적용해요잘못된 참조와 예상 밖 자료형 저장을 초기에 막을 수 있어요
동시성잠금 대기 시간과 WAL을 에디션 설정에 포함해요간헐적인 `SQLITE_BUSY`와 불필요한 디스크 동기화 부담을 줄이는 출발점이 돼요
쟁점연결, DB 파일, 개별 테이블 설정의 범위가 서로 달라요하나의 에디션 값을 어디에 저장하고 어떻게 적용할지 설계가 더 필요해요

1. SQLite의 낡은 기본값을 에디션 하나로 묶자는 제안이에요

외래 키를 선언해도 기본 설정에서는 검사하지 않아요

SQLite에서 외래 키를 스키마에 적는 것만으로는 제약이 작동하지 않아요. 연결을 열 때 `PRAGMA foreign_keys = ON`을 따로 실행해야 해요. 이를 빠뜨리면 존재하지 않는 부모 ID를 참조하는 행을 넣거나, 자식 행을 남긴 채 부모 행을 지울 수 있어요. 2

`INTEGER PRIMARY KEY`는 `ROWID`의 별칭으로 동작해요. SQLite는 일부 상황에서 삭제된 행의 ID를 다시 쓸 수 있어요. 외래 키 검사가 꺼진 상태에서 사용자만 지우고 게시물을 남기면, 나중에 같은 ID를 받은 다른 사용자에게 예전 게시물이 연결되는 사례도 만들 수 있어요. 조회는 정상 결과처럼 보여서 단순한 참조 오류보다 늦게 발견될 수 있어요.

728x90

```sql PRAGMA foreign_keys = ON; ```

새 프로젝트라면 이 설정을 연결 생성 코드나 데이터베이스 드라이버 구성에 고정하는 편이 안전해요. 에디션 제안은 이런 필수 설정을 애플리케이션마다 반복하지 말고, 한 번의 선택으로 활성화하자는 접근이에요.

STRICT 테이블은 유용하지만 매번 직접 지정해야 해요

일반 SQLite 테이블의 열은 강한 자료형 제약보다 타입 친화도를 사용해요. `INTEGER` 열에도 숫자로 바꿀 수 없는 문자열이 들어갈 수 있어요. 스키마만 보고 값의 형태를 확신한 애플리케이션에서는 정리하기 까다로운 데이터가 쌓일 수 있어요.

SQLite는 이런 문제를 줄이려고 STRICT 테이블을 지원해요. 잘못된 자료형을 넣으면 오류를 내지만, 모든 `CREATE TABLE` 문 끝에 `STRICT`를 붙여야 해요. 데이터베이스 전체에 적용하는 전역 설정은 없어요. 2

```sql CREATE TABLE music ( id INTEGER PRIMARY KEY, name TEXT, duration_sec INTEGER ) STRICT; ```

엄격한 모드에도 호환성 비용은 있어요. 기존 SQLite 프로젝트는 `DATETIME`, `COLOR`처럼 사용자 정의 타입 이름을 문서나 직렬화 힌트로 쓰기도 해요. STRICT 테이블은 허용하는 타입 이름이 제한돼 이 방식과 충돌해요. 글쓴이는 타입 별칭과 제약을 정의할 수 있는 `CREATE DOMAIN` 계열 기능도 함께 검토할 만하다고 설명해요.

동시 쓰기와 WAL도 프로젝트마다 설정이 갈려요

SQLite는 여러 읽기를 처리할 수 있지만 쓰기는 한 번에 하나만 진행해요. 기본 상태에서는 다른 쓰기가 잠금을 잡고 있을 때 `SQLITE_BUSY`가 즉시 돌아올 수 있어요. `PRAGMA busy_timeout = 5000`을 설정하면 잠금 해제를 최대 5초 기다릴 수 있어요.

다만 `busy_timeout`이 모든 잠금 승격 실패를 해결하지는 못해요. 읽기 트랜잭션을 쓰기로 바꾸는 과정처럼 즉시 실패할 수 있는 경로가 있어요. 쓰기 작업을 한 스레드에 모으거나 `BEGIN IMMEDIATE`를 쓰는 등 트랜잭션 구조도 함께 살펴야 해요. 1

WAL도 기본으로 켜지지 않아요. 원문 제안은 새 에디션이 아래 설정을 묶어서 적용하는 모습을 예로 들어요. 이 값들이 모든 서비스에 정답이라는 뜻은 아니에요. 저장장치 특성, 복구 요구 수준, 쓰기 패턴에 따라 따로 측정해야 해요.

```sql PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 5000; PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; ```

Rust식 연도 에디션은 호환성을 깨지 않는 선택지예요

Rust 에디션은 기존 코드를 그대로 돌리면서 새 프로젝트가 최신 언어 규칙을 선택하게 해요. SQLite에도 `PRAGMA edition = 2026` 같은 상위 설정을 두면 오래된 애플리케이션은 현재 동작을 유지할 수 있어요. 새 애플리케이션은 여러 프라그마와 테이블 옵션을 일일이 조합하지 않고 권장 기본값 묶음을 선택할 수 있어요. 2

구현하려면 설정 범위를 정해야 해요. `foreign_keys`는 연결마다 적용하고, WAL은 데이터베이스 파일에 남으며, STRICT는 테이블별 속성이에요. 하나의 에디션 값이 서로 다른 수명의 설정을 언제 바꾸는지 합의가 필요해요. 오래된 SQLite 라이브러리가 더 최신 에디션의 DB 파일을 열었을 때 안전하게 거부하거나 경고하는 방식도 풀어야 할 문제예요.

왜 중요한가요

SQLite를 쓰는 팀은 라이브러리 하나만 추가하면 저장소를 시작할 수 있다는 장점을 누려요. 반면 안전성과 성능에 영향을 주는 설정이 여러 위치에 흩어져 있어요. 새 연결에서 외래 키 프라그마를 한 번 빼먹거나, 새 테이블에 STRICT를 붙이지 않아도 애플리케이션은 당장 실행될 수 있어요. 문제는 잘못된 데이터가 쌓이거나 운영 중 잠금이 겹친 뒤에 드러나기 쉬워요. 2

에디션 방식이 도입된다면 프로젝트 생성기, ORM, 데이터베이스 드라이버가 같은 기준점을 공유할 수 있어요. 특정 연도의 기본값 묶음을 명시하면 코드 리뷰와 장애 분석에서도 실제 활성 설정을 파악하기 쉬워져요. 다만 이번 글은 SQLite 프로젝트가 채택한 공식 제안이 아니에요. 현재 프로젝트에서는 에디션을 기다리기보다 연결 초기화 프라그마, 마이그레이션의 STRICT 여부, WAL과 동기화 정책을 테스트로 고정하는 편이 현실적이에요.

참고 자료

  1. SQLite에도 Rust 스타일의 에디션이 필요함 — GeekNews
  2. SQLite should have (Rust-style) editions — Mort's Ramblings
728x90