세계에서 가장 많이 쓰이는 데이터베이스, 25년 만에 데이터 타입 검사를 배우다

세계에서 가장 많이 쓰이는 데이터베이스, 25년 만에 데이터 타입 검사를 배우다

SQLite데이터베이스타입 안전성STRICT엔지니어링

데이터 소스:HN + Evan Hahn blog + web research · HN

2021년 11월 27일, SQLite가 3.37.0 버전을 출시했다. 성능이 두 배로 빨라지지도 않았고, 화려한 새 기능도 없었다. 그저 테이블 생성 구문 마지막에 개발자가 한 단어를 추가할 수 있게 되었을 뿐이다: STRICT.

무슨 뜻일까? 쉽게 풀어 말하면: 이날부터 SQLite는 마침내 한 가지를 거절하는 법을 배웠다——“이름을 전화번호 칸에 넣는 것.”

이때, SQLite의 탄생에서 무려 21년이 흐른 후였다. 그리고 바로 이 SQLite야말로 여러분의 휴대폰에 있는 모든 앱이 사용하는 보이지 않는 데이터베이스다.

SQLite 공식 로고——이 경량 데이터베이스 엔진은 전 세계적으로 1조 개 이상의 활성 데이터베이스를 구동한다. 출처: sqlite.org

휴대폰 속 당신이 모르는 ‘기초’

먼저 흔한 오해를 바로잡자: SQLite는 앱 스토어에서 다운로드할 수 있는 ‘소프트웨어’가 아니다. 여러분의 휴대폰에는 ‘SQLite’라는 아이콘이 없다. 그것은 데이터베이스 엔진이다——앱 내부에 숨어서 데이터를 저장하고 관리하는 일을 묵묵히 수행한다.

위챗의 채팅 기록, 알리페이의 거래 내역, 더우인의 비디오 캐시, 휴대폰 연락처, 브라우저에 저장된 비밀번호, 지도의 오프라인 내비게이션 데이터…… 이것들 뒤에는 모두 SQLite가 있다.

신뢰할 수 있는 추정에 따르면, 전 세계에서 동시에 실행 중인 SQLite 데이터베이스는 1조 개가 넘는다. 어떤 다른 데이터베이스도 이 숫자에 근접하지 못한다. 절대적인 ‘세계 1위’다.

하지만 이 챔피언에게는 믿기 어려운 ‘특기’가 하나 있다: 저장되는 데이터의 타입이 올바른지 전혀 확인하지 않는다는 점이다.

”나이를 ‘홍길동’으로? 문제없습니다, 들어오세요”

‘데이터 타입을 확인하지 않는다’는 것이 무슨 뜻일까? 현실 상황에 비유해보자.

은행에 가서 통장을 개설한다. 직원이 서류 한 장을 건넨다. 서류에는 ‘나이’라는 칸과 ‘이름’이라는 칸이 있다. 당신은 나이 칸에 ‘홍길동’을 쓰고, 이름 칸에 ‘42’를 썼다——정상적인 경우 직원이 서류를 도로 밀어낼 것이다: “고객님, 나이는 숫자를, 이름은 문자를 적으셔야 합니다.”

SQLite의 기본 모드 동작은, 직원이 한 번 쳐다보고 무표정하게 말하는 것과 같다: “좋아요, 뭘 쓰든 받아드립니다. 나이가 ‘홍길동’? 저장했습니다. 이름이 ‘42’? 괜찮습니다. 고객님의 자유입니다.”

코드로 표현하면 이렇다: 당신은 테이블을 만들면서 ‘나이’ 컬럼은 정수(INTEGER), ‘이름’ 컬럼은 텍스트(TEXT)로 선언했다. 그리고 이렇게 실행한다:

INSERT INTO 사용자테이블 (나이) VALUES ('나는 숫자가 아니에요');

MySQL, PostgreSQL 같은 데이터베이스에서는 이 구문이 즉시 오류를 발생시킨다. SQLite에서는? 실행 성공. 아무 경고도 없다. 당신 데이터베이스의 ‘나이’ 컬럼에는 이제 ‘나는 숫자가 아니에요’라는 텍스트 값이 살게 되었다.

이것은 에지 케이스가 아니다. 2026년 7월 11일, 개발자 Evan Hahn이 블로그 글 《SQLite에서는 STRICT 테이블을 우선 사용하자》를 발표했고, Hacker News에서 거의 200포인트, 89개 댓글을 기록했다. 댓글창은 개발자들이 ‘이 함정을 밟은’ 피눈물 나는 경험을 공유하는 장이 되었다.

그림 1: STRICT 모드 vs 비 STRICT 모드 동작 비교

STRICT 모드 비교: 왼쪽이 기본 모드(모든 데이터를 받아들임), 오른쪽이 STRICT 모드(타입 불일치 시 즉시 거부). 출처: 필자가 Evan Hahn 블로그 및 SQLite 공식 문서를 기반으로 정리

작업비 STRICT (기본)STRICT 모드
INTEGER 컬럼에 'abc' 쓰기(텍스트를 숫자 컬럼에)✅ 허용❌ 오류
INTEGER 컬럼에 '123' 쓰기(숫자 텍스트, 손실 없는 변환 가능)✅ 허용✅ 허용
컬럼 타입을 GARBAGE로 작성(오타/가상 타입)✅ 허용❌ 오류
ANY 컬럼에 아무 타입 쓰기✅ 허용✅ 허용
테이블 생성 시 컬럼 타입 생략✅ 허용❌ 오류
허용되는 타입제한 없음INT, INTEGER, REAL, TEXT, BLOB, ANY

20년 동안 계속된 철학 전쟁

이것은 실수도, 게으름도 아니다. SQLite의 창시자 D. Richard Hipp가 깊이 고민한 디자인 선택이다. SQLite 공식 웹사이트에는 《유연한 타입의 장점》이라는 페이지가 있어, ‘타입을 확인하지 않는 것’을 변호하는 데 할애되어 있다.

이 선택의 뿌리를 이해하려면 2000년으로 돌아가야 한다. 당시 Hipp는 해군 협력업체에서 일하며 군함 시스템용 경량 데이터베이스를 찾고 있었다. 시장에 나온 선택지는 너무 무겁거나 서버가 필요했다——군함 환경에서는 완전히 비현실적이었다. 그래서 직접 만들기로 했다.

핵심 영향 요소 중 하나는 TCL——Hipp가 가장 잘 알던 프로그래밍 언어였다. TCL은 ‘동적 타입’ 언어다: 프로그래머가 미리 변수 타입을 선언할 필요 없으며, 모든 것을 문자열로 처리할 수 있다. Hipp는 이 철학을 SQLite에 가져왔다: 컬럼 타입을 선언했는가? 좋다. 하지만 그것은 단지 권고사항일 뿐이다. 실제로 무엇을 저장할지는 당신이 결정한다.

그 후 20년 동안, ‘유연한 타입이 기능인가 버그인가’를 둘러싸고 데이터베이스 업계에서 긴 논쟁이 이어졌다.

지지 측(Hipp 본인과 그의 팀)의 핵심 논점은 세 가지다:

첫째, “나는 35년간 코드를 짜면서 타입 검사에 막힌 버그를 본 적이 없다.” Hipp는 공식 문서에서 TCL과 SQLite를 수십 년간 개발하면서 타입 제약 부족으로 인한 프로그램 장애가 기억나지 않는다고 썼다. 그의 결론은 타입 검사는 C나 C++처럼 하드웨어에 가까운 저수준 언어에서만 유용하며, 모든 데이터를 ‘값 객체’로 전달하는 SQL 엔진에서는 별 도움이 되지 않는다는 것이다.

둘째, “타입 검사가 막는 것은 전부 쉽게 발견할 수 있는 저수준 오류다.” 이 논점은 상당히 날카롭다: ‘이름’을 ‘나이’ 칸에 넣는 터무니없는 실수는 확실히 막힐 것이다——하지만 너무 쉽게 발견되므로, 아무 테스트나 해도 드러난다. 진짜 디버깅을 3일간 하게 만드는 것은 ‘성’과 ‘이름’을 바꿔 쓴 경우다——둘 다 텍스트라 타입 검사가 전혀 잡아내지 못한다. Hipp는 타입 검사가 개발자에게 ‘데이터가 이미 깨끗하다’는 그릇된 안도감을 준다고 생각한다.

셋째, “유연성은 다른 데이터베이스가 할 수 없는 일을 가능하게 한다.” 예를 들어 하나의 테이블을 임의 타입의 키-값 저장소로 사용하거나, 폐기된 컬럼을 여러 용도로 재활용하거나, Excel에서 내보낸 더러운 CSV 파일을 그대로 적재한 후 천천히 정제할 수 있다.

반대 측의 반박도 설득력 있다:

“바로 그 ‘쉽게 발견되는’ 오류가 수백만 행의 데이터 속에서 찾을 수 없는 바늘 하나가 된다. 타입 검사는 디버깅 중에 잡을 수 있는 버그를 막기 위한 것이 아니다——그것은 프로덕션 환경에서 새벽 3시에, 로그에 아무 오류도 없는데 사용자 데이터가 체계적으로 오염되기 시작하는 그 순간을 막기 위한 것이다.”

“35년간 코드를 짜면서 타입 버그를 본 적이 없다고? SQLite 자체도 C로 작성되었다. 당신은 SQLite를 컴파일하면서 C의 타입 검사를 누리고 있는 것이다. 엄격한 타입 시스템으로 SQLite 자체가 오류 없이 작동하도록 보장하면서, 타입 검사가 다른 사람에게는 중요하지 않다고 말하는가?”

Hacker News 댓글창에서 반복적으로 인용된 비유가 있다: “이것은 UDP를 TCP 대신 사용하는 것과 같다——속도와 단순성을 위해 데이터 검증을 포기하고, 나중에 애플리케이션 계층에서 재전송, 정렬, 검증을 수동으로 모두 추가한다. 다 추가하고 나면 당신은 단지 더 나쁜 TCP를 구현한 것뿐이다.”

또 다른 댓글 작성자는 더 직설적으로 말했다: “성능을 위해 기본값을 조정하는 것은 받아들일 수 있다. 정확성을 위해 기본값을 조정하는 것은——마음이 편치 않다.”

STRICT 모드는 정확히 무엇을 하는가

2021년 11월로 돌아가자. STRICT 키워드가 하는 일은 세 가지로 요약된다:

첫째, 타입이 일치하지 않는 쓰기를 거부한다. 정수 컬럼에 텍스트를 넣으려 하면? 오류. 텍스트 컬럼에 숫자를 넣으려 하면? 허용——숫자는 손실 없이 텍스트로 변환될 수 있기 때문이다. 정수 컬럼에 문자열 '123'을 넣으려 하면? 허용——'123'은 완벽하게 숫자 123으로 변환될 수 있기 때문이다. STRICT는 ‘값이 손실 없이 변환 가능한가’를 보며, 표면적인 타입만 보지 않는다. 이 점은 사실 많은 엄격 타입 데이터베이스보다 더 똑똑하다.

둘째, 가상의 데이터 타입을 거부한다. 비 STRICT 모드에서는 테이블 생성 시 컬럼 타입을 GARBAGE, DATETIME, JSON, UUID, BLOBB 등으로 써도 SQLite가 그대로 받아서 조용히 일반 타입으로 처리한다. STRICT 모드에서는 여섯 가지만 인정한다: INT, INTEGER, REAL, TEXT, BLOB, ANY. BLOBBLOBB로 오타 내면? 즉시 지적한다.

셋째, 유연성이 필요할 때는 ANY를 사용한다. STRICT는 전부 아니면 전무가 아니다. 특정 컬럼을 ANY 타입으로 선언하면 기본 모드와 동일하게 모든 데이터를 받을 수 있다. 차이점은: 유연한 곳을 당신이 지정하며, 모든 곳이 기본적으로 유연하지는 않다는 점이다.

왜 21년을 기다려야 했나?

2000년부터 2021년까지, 21년. 이렇게 기본적인 검사 메커니즘이 왜 두 세대의 엔지니어 경력을 가로질러서야 도입되었을까?

정답은 SQLite의 핵심 약속에 숨어 있다: 하위 호환성(backward compatibility).

SQLite 개발자에게는 편집증에 가까운 철칙이 있다——오늘 당신이 작성한 SQLite 코드는 10년 후 버전이 업그레이드되어도 100% 정상 작동해야 한다. 즉, 기본 동작은 절대 바꿀 수 없다. 바꾸면, 지구상에서 실행 중인 1조 개의 SQLite 인스턴스에 문제가 발생할 수 있다.

그림 2: SQLite 타입 안전성 진화 타임라인

2000 ─ SQLite 1.0 출시, 유연한 타입을 핵심 디자인 철학으로

      │   "컬럼 타입은 권고사항일 뿐, 제약이 아니다"

2009 ─ SQLite 3.6.19: 외래 키 제약 조건 문법 지원
      │   하지만 기본 꺼짐, 수동으로 PRAGMA foreign_keys = ON 필요

      │   이후 12년간, STRICT 모드 제안이 여러 번 논의됨
      │   하지만 매번 '하위 호환성' 철칙에 막힘

2021 ─ SQLite 3.37.0: STRICT 테이블 지원
      │   테이블 생성 마지막에 STRICT 키워드 추가, 테이블 단위로 선택적 활성화
      │   전역 스위치 없음——여전히 "스스로 선택할지 말지"의 철학

2026 ─ Evan Hahn 글 발표: "STRICT 테이블을 우선 사용하세요"
      │   HN 199포인트, 89개 댓글, 논쟁은 계속 중

세 개의 이정표는 21년에 걸쳐 있으며, 매번 동일한 원칙을 따른다: 기능은 추가할 수 있지만, 기본 동작은 변경할 수 없다.

이것은 예외적인 사례가 아니다. 외래 키 제약——사용자를 삭제했는데 주문 테이블에 ‘주인 없는 주문’ 1만 건이 남는 것을 방지——SQLite는 2009년에 이미 이 문법을 지원했지만, 여전히 기본 꺼짐이다. 데이터베이스 연결을 열 때마다 수동으로 이 명령어를 실행해야 한다:

PRAGMA foreign_keys = ON;

그래야 외래 키 검사가 활성화된다. 이유는 똑같다: 기본값을 바꾸면 하위 호환성이 깨진다.

HN 토론에서는 한 절충안이 제시되었다: 브라우저처럼 데이터베이스 생성 시 COMPAT_MODE=2026을 선언하면, 새 버전이 자동으로 당시 권장 설정을 활성화하는 방식이다. 하지만 이 제안은 아직 채택되지 않았다.

한 댓글 작성자가 이런 말을 남겼다: “SQLite는 기본값을 거의 바꾸지 않는다. 하위 호환성 약속이 거의 신성하기 때문이다. 그들은 개발자가 SQLite 3.53용으로 작성한 소프트웨어가 3.54로 업그레이드 후 CREATE TABLE이 갑자기 STRICT가 되어 전부 폭발하는 것을 원하지 않는다.”

이 말은 SQLite의 딜레마를 정확히 요약한다: 한편으로는 ‘계속 개선’하려는 진화 충동, 다른 한편으로는 ‘절대 변하지 않음’을 맹세한 호환성 서약.

SQLite의 성공은 정확히 ‘아무것도 관리하지 않기’ 덕분이다

여기까지 읽으면 반직관적인 질문이 자연스럽게 떠오른다: SQLite에 이렇게 많은 ‘기본적으로 안전하지 않은’ 설계가 있음에도, 왜 세계에서 가장 인기 있는 데이터베이스가 되었을까?

정답은 그 디자인 철학에 숨어 있다. SQLite의 성공은 대체로 ‘아무것도 관리하지 않기’에서 비롯된다.

설치 불필요, 서버 불필요, 설정 파일 불필요. 수백 KB의 라이브러리 파일 하나를 앱에 내장하면 실행된다. 데이터 타입을 신경 쓰지 않는다——당신이 뭘 저장하든 상관없다. 외래 키 관계를 신경 쓰지 않는다——문제가 생기면 당신이 책임진다. 트랜잭션 격리 수준을 신경 쓰지 않는다——일단 실행하고 보자.

이 극단적인 단순함의 보상은: SQLite를 휴대폰, 브라우저, IoT 센서, 라우터, 스마트 TV, 자동차 인포테인먼트, 비행기 엔터테인먼트 시스템에 넣어도 환경을 가리지 않고, 자원을 요구하지 않으며, 시작 실패를 하지 않는다.

마치 만능 콘센트와 같다——어떤 플러그든 꽂을 수 있다. 단락이 일어날지는 내가 알 바 아니다.

21년 동안 ‘아무것도 관리하지 않던’ 이 데이터베이스에 STRICT 모드가 등장했다는 것은, 마침내 현실을 인정했음을 의미한다: 사용자가 수십 명의 전문 C 프로그래머에서 전 세계 수백만 명의 수준이 다양한 앱 개발자로 확대되면서, 기본값인 ‘자유’가 기본값인 ‘위험’이 되어가고 있다는 것을.

에필로그

SQLite의 이 역사는 소프트웨어 엔지니어링의 큰 좌표계에서 보면, 한 업계가 점차 성숙해지는 과정의 축소판이다.

초기 소프트웨어는 소수의 전문 사용자를 대상으로 했으며, 디자인 철학은 ‘최대한의 자유를 주고, 문제가 생기면 당신 책임’이었다. 오늘날 소프트웨어는 수십억 명의 일반인을 대상으로 하며, 디자인의 중심은 ‘자유’에서 ‘안전’과 ‘실수 방지’로 이동했다.

STRICT 모드는 그렇게 가슴 뛰는 기술적 돌파구가 아니다——이것이 하는 일은 MySQL과 PostgreSQL이 탄생 첫날부터 해온 것이다. 하지만 21년이나 늦었다는 사실 자체가 한 가지 진실을 무언으로 말해준다: 오늘날 우리가 당연하게 여기는 많은 ‘기본 기능’은 십수 년, 이십 년의 업계 축적, 논쟁, 시행착오, 회고를 통해 조금씩 얻어진 것이다.

다음에 당신의 휴대폰 앱이 조용히 SQLite에 데이터를 저장할 때, 생각해보라: 당신의 휴대폰에서 수천 일 동안 성실히 일해온 보이지 않는 챔피언이, 태어난 지 21년 만에 유치원 아이가 마스터하는 기술 하나를 겨우 익혔다는 것을——

신발을 밥그릇에 넣지 않는 것.


참고 링크: