유연한 타입 규칙의 좋은 점
유연한 타입 규칙의 좋은 점 (Flexible Typing)
SQLite 는 컬럼에 선언된 데이터타입과 무관하게 원하는 어떤 형식으로든 내용을 저장할 수 있는 자유를 개발자에게 제공해요. 어떤 사람들은 이 기능을 골치 아프게 여기기도 해요. INTEGER 로 표시된 컬럼에 텍스트를 넣을 수 있다는 사실에 충격을 받는 개발자도 있어요. 이 문서는 SQLite 의 유연한 타입 규칙을 옹호해요.
출처: Flexible Typing
본문
1. 소개
SQLite 는 개발자에게 컬럼의 선언된 데이터타입과 무관하게 내용을 원하는 어떤 형식으로든 저장할 자유를 줘요. 어떤 사람들은 이 기능을 문제 삼아요. INTEGER 로 표시된 컬럼에 텍스트를 삽입하는 것이 가능하다는 사실에 충격을 받는 개발자도 있어요. 이 문서는 SQLite 의 유연한 타입 규칙을 옹호해요.
2. 유연한 타이핑에 대하여
SQLite 의 유연한 타입 시스템에 대한 자세한 내용은 별도의 Datatypes In SQLite 문서에 있어요. 여기 간단한 요약이에요:
-
컬럼 정의의 데이터타입 이름은 선택 사항이에요. 컬럼 정의는 컬럼 이름만으로 구성될 수 있고 다른 건 없어도 돼요.
-
데이터타입 이름을 제공할 때는 거의 어떤 텍스트든 될 수 있어요. SQLite 는 컬럼 정의의 데이터타입 이름을 기반으로 컬럼의 선호 데이터타입을 추론하려고 시도하지만, 그 선호 데이터타입은 권고일 뿐 강제가 아니에요. 선호 데이터타입을 "컬럼 어피니티(column affinity)"라고 불러요.
-
들어오는 데이터를 컬럼의 선호 데이터타입으로 변환하려는 시도가 이루어져요. (SQLite 만이 아니라 모든 SQL 데이터베이스 엔진이 이렇게 해요.) 이 변환이 성공하면 모든 것이 잘 돼요. 하지만 실패하면 에러를 일으키는 대신 SQLite 는 원래 데이터타입으로 내용을 저장해요.
-
위 내용은 엄격한 타이핑을 주장하는 사람들이 불편하게 여기는 상황을 만들 수 있어요:
| Column Datatype | Types Allowed In That Column |
|---|---|
| INTEGER | INTEGER, REAL, TEXT, BLOB |
| REAL | REAL, TEXT, BLOB |
| TEXT | TEXT, BLOB |
| BLOB | INTEGER, REAL, TEXT, BLOB |
- INTEGER 나 REAL 값은 항상 동등한 TEXT 표현으로 변환될 수 있고 항상 변환되므로, TEXT 컬럼에 저장되는 일은 절대 없어요. 마찬가지로 INTEGER 는 항상 REAL 로 변환되므로 REAL 컬럼에 저장되지 않아요. 하지만 TEXT 가 항상 INTEGER 나 REAL 값처럼 보이는 것은 아니므로 항상 변환될 수는 없어요. 그리고 BLOB 은 어떤 것으로도 변환될 수 없고, 다른 어떤 것도 BLOB 으로 변환될 수 없어요.
3. 유연한 타이핑이 유용한 경우
첫 접하는 독자 중에는 SQLite 의 유연한 타이핑을 보고 "이게 어떻게 유용할 수 있지?"라고 스스로에게 묻는 사람들이 있어요. 여기 그 질문에 대한 답을 시도해볼게요.
3.1. 속성 테이블 (Attribute tables)
많은 애플리케이션, 특히 SQLite 를 애플리케이션 파일 형식으로 사용하는 애플리케이션은 썸네일 이미지(BLOB 값), 짧은 텍스트(사용자 이름), 숫자, 날짜, JSON 값 같은 기타 속성을 저장할 곳이 필요해요. 이 저장을 처리하는 단일 테이블을 만드는 것이 편리해요:
CREATE TABLE attribute(name TEXT PRIMARY KEY, value) WITHOUT ROWID;
유연한 타이핑이 없다면 그러한 테이블은 가능한 각 데이터 타입에 대해 별도의 컬럼을 두어 더 복잡해져야 해요. "value" 컬럼의 유연한 타이핑은 테이블을 개념적으로 더 단순하게, 더 공간 효율적으로, 더 접근하고 갱신하기 쉽게 만들어요.
Fossil 버전 관리 시스템에서 각 저장소에는 모든 종류의 설정을 가능한 모든 데이터타입으로 저장하는 데 사용되는 CONFIG 테이블이 있어요. Fossil 의 사용자별 구성 파일(~/.fossil 파일)은 별도의 SQLite 데이터베이스로, 모든 저장소에 걸친 사용자별 상태를 담는 단일 속성 테이블을 포함해요.
어떤 애플리케이션은 SQLite 데이터베이스를 순수 키-값 저장소로 사용해요. 데이터베이스 스키마는 다음과 같은 단일 테이블을 포함해요:
CREATE TABLE storage(name TEXT PRIMARY KEY, value ANYTHING);
3.2. json_tree 가상 테이블의 "value" 컬럼 출력
SQLite 에 내장된 json_tree 와 json_each 테이블-값 함수는 둘 다 해당 JSON 필드의 타입에 따라 INTEGER, REAL, TEXT 타입의 값을 담을 수 있는 "value" 컬럼이 있어요. 예를 들어:
SELECT typeof(value) FROM json_each('{"a":1,"b":2.5,"c":"hello"}');
위 쿼리는 각각 "integer", "real", "text" 값이 있는 한 컬럼 세 행을 반환해요.
3.3. 더러운 데이터(Dirty data) 저장
분석가들은 때때로 일부 컬럼이 정수, 실수, 텍스트 데이터의 혼합을 담고 있는 CSV 파일을 만나요. Excel 스프레드시트 내보내기에서 얻은 CSV 파일이 흔히 이런 특성을 가져요. 그러한 "더러운 데이터"를 SQL 데이터베이스로 가져올 때 가져올 유연하게 타이핑된 컬럼이 있으면 편리해요.
물론 더러운 데이터가 Excel 에서 나온 CSV 파일에만 국한되지는 않아요. 단일 필드에 타입 혼합이 담길 수 있는 데이터 소스가 많아요. 예를 들어 데이터 컬럼이 때로는 1970 이후의 초 수를, 다른 경우에는 텍스트 날짜 문자열을 담을 수 있어요. 이러한 불일치 표현을 정리하는 것이 바람직하지만, 동시에 정리 작업이 진행되는 동안 중간 데이터베이스의 같은 컬럼에 모든 다른 표현을 저장할 수 있으면 편리해요.
3.4. 동적 프로그래밍 언어
SQLite 는 TCL 확장으로 시작해 나중에 밖으로 퍼져나간 프로젝트예요. TCL 은 프로그래머가 데이터타입을 알 필요가 없다는 점에서 동적 언어예요. 내부적으로 TCL 은 모든 값의 데이터타입을 꼼꼼하게 추적하지만, 개발자와 TCL 프로그램 사용자에게는 모든 것이 문자열처럼 보여요. 유연한 타이핑은 TCL 같은 동적 프로그래밍 언어와 함께 쓰기에 자연스러운데, 동적 프로그래밍 언어에서는 변수가 어떤 데이터타입을 담을지 미리 항상 예측할 수 없기 때문이에요. 그래서 그 변수의 값을 데이터베이스에 저장해야 할 때, 유연한 타이핑을 지원하는 데이터베이스가 있으면 저장이 훨씬 쉬워져요.
3.5. 데이터 타입 이름 교차 호환성
모든 SQL 데이터베이스 엔진은 각자 고유한 지원 데이터타입 이름 세트를 가진 것처럼 보여요:
- BIGINT
- UNSIGNED SMALL INT
- TEXT
- VARCHAR
- VARYING CHARACTER
- NATIONAL VARYING CHARACTER
- NVARCHAR
- JSON
- REAL
- FLOAT
- DOUBLE PRECISION
- * ... 등등 ...*
SQLite 가 이 이름들 중 어떤 것이든 유효한 타입 이름으로 받아들이고, 컬럼에 어떤 종류의 내용이든 저장하게 해준다는 사실은, 다른 SQL 데이터베이스 엔진에서 실행되도록 작성된 스크립트가 SQLite 에서도 동작할 가능성을 높여줘요.
3.6. 레거시 데이터베이스의 미사용 또는 폐기된 컬럼 재활용
SQLite 데이터베이스 파일이 디스크의 단일 파일이기 때문에, 어떤 애플리케이션은 SQLite 를 애플리케이션 파일 형식으로 사용해요. 이는 애플리케이션의 단일 인스턴스가 그 수명 동안 각각 별도 파일인 수백 또는 수천 개의 개별 데이터베이스와 대화할 수 있다는 뜻이에요. 그러한 애플리케이션이 수년에 걸쳐 진화하면서 기본 데이터베이스의 일부 컬럼은 그 의미가 미묘하게 바뀔 수 있어요. 아니면 기존 컬럼을 두 가지 이상의 목적으로 재활용하는 것이 바람직할 수도 있어요. 컬럼이 유연한 데이터타입을 가지면 이것이 훨씬 쉬워요.
4. 유연한 타이핑의 인지된 단점 (반박 포함)
다음은 Hacker News, Reddit 및 비슷한 포럼의 개발자들이 이런 것들을 논하는 수많은 게시물에서 수집·정리한 유연한 타이핑의 인지된 단점이에요. 유연한 타이핑이 나쁜 생각인 다른 이유를 생각할 수 있다면 SQLite 개발자에게 연락하거나 SQLite Forum 에 게시물을 남겨서 목록에 추가될 수 있게 해주세요.
4.1. 우리는 전에 그렇게 해본 적이 없어요
유연한 타이핑의 많은 회의론자는 단순히 충격과 불신을 표현하지만, 왜 유연한 타이핑이 나쁜 생각이라고 생각하는지에 대한 근거를 제시하지 않아요. 지지 논거 없이는 그들이 유연한 타이핑을 싫어하는 이유가 자신이 익숙한 것과 다르기 때문이라고 가정해야 해요.
아마도 SQLite 의 유연한 타이핑에 겁먹은 많은 개발자는 그저 전에 그런 것을 접해본 적이 없기 때문에 그런 느낌일 거예요. 데이터베이스, 특히 SQL 데이터베이스에 대한 모든 이전 경험은 엄격한 타이핑을 수반했고, 독자의 SQL 에 대한 정신 모델은 엄격한 타이핑을 근본적인 특징으로 포함해요. 유연한 타이핑은 그들의 세계관을 뒤흔들어요.
네, 유연한 타이핑은 SQL 데이터베이스의 데이터에 대해 생각하는 새로운 방식이에요. 하지만 새로운 것이 반드시 나쁜 것은 아니에요. 때때로, 특히 유연한 타이핑의 경우, 혁신이 개선으로 이어져요.
4.2. 엄격한 타입 강제는 애플리케이션 버그를 예방하는 데 도움이 돼요
많은 프로그래머 사이에서 애플리케이션 버그를 예방하는 가장 좋은 방법은 엄격한 타입 강제라는 것이 하나의 교리가 돼버렸어요. 하지만 나는 이를 지지하는 증거를 찾지 못했어요.
확실히 엄격한 타입 강제는 머신 하드웨어에 가까운 모델을 제시하는 C 나 C++ 같은 저수준 언어의 어떤 종류의 버그를 예방하는 데 도움이 돼요. 하지만 모든 데이터가 다양한 저수준 데이터 타입의 하위 클래스인 어떤 종류의 "Value" 슈퍼클래스로 전달되는 더 높은 추상화 언어에서는 그렇지 않은 것 같아요. 모든 것이 Value 객체일 때, 특정 데이터타입은 중요하지 않게 돼요.
이 기술 노트는 SQLite 의 원저자가 작성했어요. 나는 27년 동안 TCL 프로그램을 작성해왔어요. TCL 에는 어떤 타입 강제도 전혀 없어요. TCL 의 "Value" 클래스(Tcl_Obj 라고 함)는 많은 다른 데이터타입을 담을 수 있지만, 프로그램과 애플리케이션 사용자에게는 내용을 문자열로 제시해요. 그리고 수년 동안 그 TCL 프로그램들에서 버그가 많았어요. 하지만 엄격한 타입 시스템이 잡아낼 수 있었을 버그의 단일 사례도 기억나지 않아요. 나는 또한 35년에 걸쳐 많은 C 코드를 작성했는데, 그중 가장 중요한 것이 SQLite 그 자체예요. 나는 C 의 타입 시스템이 문제를 찾고 예방하는 데 매우 도움이 된다는 것을 알았어요. C 로 작성된 Fossil 버전 관리 시스템을 위해 나는 심지어 컴파일 이전에 Fossil 소스 코드를 스캔해서 컴파일러가 놓치는 문제를 찾는 보조 정적 분석 프로그램도 구현했어요. 이는 컴파일된 프로그램에서 잘 작동해요.
SQL 언어 모델은 C/C++ 보다 더 높은 수준의 추상화예요. SQLite 에서 모든 데이터 항목은 메모리에 "sqlite3_value" 객체로 저장돼요. 이 객체에는 문자열, 정수, 부동소수점 수, blob 및 기타 표현을 위한 하위 클래스가 있어요. SQLite 가 구현하는 SQL 언어 내부에서 모든 것이 "sqlite3_value" 객체로 전달되므로 기본 데이터타입은 실제로 중요하지 않아요. 나는 TCL 이나 SQLite 처럼 어떤 데이터 요소든 표현하는 데 사용되는 단일 "Value" 슈퍼클래스를 가진 언어에서 엄격한 타입 강제가 도움이 된다고 느껴본 적이 없어요. Fossil 은 그 구현에서 SQLite 를 광범위하게 사용해요. Fossil 의 14년 역사 동안 버그가 많았지만, SQLite 에서 엄격한 타입 강제가 예방할 수 있었을 단일 버그도 기억나지 않아요. 일부 C 언어 버그는 더 나은 타입 강제로 잡혔을 수도 있지만(그래서 보조 소스 코드 스캐너를 작성했어요), SQL 버그는 없었어요.
수십 년의 경험에 기반해 나는 엄격한 타입 강제가 애플리케이션 버그를 예방하는 데 도움이 된다는 주장을 거부해요. 나는 약간 수정된 주장을 받아들이고 믿을게요. 엄격한 타입 강제는 단일 최상위 "Value" 슈퍼클래스가 없는 언어에서 애플리케이션 버그를 예방하는 데 도움이 돼요. 하지만 SQLite 에는 단일 "sqlite3_value" 슈퍼클래스가 있으므로 그 속담은 적용되지 않아요.
4.3. 엄격한 타입 강제는 데이터 오염을 예방해요
어떤 사람들은 스키마에 엄격한 제약, 특히 컬럼 데이터타입의 엄격한 강제가 있으면 잘못된 데이터가 데이터베이스에 추가되는 것을 예방하는 데 도움이 된다고 주장해요. 이것은 사실이 아니에요. 타입 강제가 극도로 잘못된 데이터가 시스템에 들어오는 것을 예방하는 데 도움이 될 수 있다는 것은 사실이에요. 하지만 타입 강제는 미묘하게 잘못된 데이터가 기록되는 것을 막는 데는 도움이 되지 않아요.
예를 들어, 엄격한 타입 강제는 고객 이름(텍스트)이 정수형 Customer.creditScore 컬럼에 삽입되는 것을 성공적으로 예방할 수 있어요. 반면 그 실수가 발생하면 문제를 발견하고 영향받은 모든 행을 찾는 것은 아주 쉽다는 점에 주목하세요. 하지만 타입 강제는 고객의 성과 이름이 뒤바뀌는 버그를 예방하는 데는 도움이 되지 않아요. 둘 다 텍스트 필드이기 때문이에요.
엄격한 타입 강제는 쉽게 감지할 수 있는 에러를 억제하고 감지하기 어려운 에러만 통과시키므로, 실제로 버그를 찾고 고치기 더 어렵게 만들 수 있어요. 데이터 에러는 모이는 경향이 있어요. 20 개의 다른 데이터 소스가 있다면 대부분의 데이터 에러는 보통 그중 2~3 개 소스에서만 나와요. 극심한 에러(정수 컬럼의 텍스트 같은)의 존재는 뭔가 잘못됐다는 편리한 조기 경고 신호예요. 문제의 소스를 빠르게 추적하고 극심한 에러의 소스에 추가 조사를 적용해서, 미묘한 에러도 고칠 수 있기를 바라요. 극심한 에러가 억제되면 미묘한 에러를 감지하고 고치는 데 도움이 되는 중요한 신호를 잃게 돼요.
데이터 에러는 불가피해요. 타입 검사를 얼마나 많이 하든 그것들은 발생해요. 엄격한 타입 강제는 그런 경우 중 작은 부분집합, 즉 가장 명백한 경우만 잡을 수 있어요. 더 미묘한 경우를 찾고 고치는 데는 아무 도움이 되지 않아요. 그리고 어떤 데이터 소스가 문제인지에 대한 신호를 억제함으로써, 때로는 미묘한 에러를 찾기 더 어렵게 만들 수 있어요.
4.4. 다른 SQL 데이터베이스 엔진은 이렇게 작동하지 않아요
SQLite 가 덜 제한적이고 더 많은 것을 할 수 있게 해주기 때문에, 다른 데이터베이스 엔진에서 작동하는 SQL 스크립트는 보통 SQLite 에서도 작동해요. 하지만 처음에 SQLite 용으로 작성된 스크립트는 더 제한적인 데이터베이스 엔진에서는 작동하지 않을 수도 있어요. 개발자가 프로토타이핑과 테스트에 SQLite 를 사용한 다음 배포를 위해 애플리케이션을 더 제한적인 SQL 엔진으로 마이그레이션할 때 문제가 발생할 수 있어요. 애플리케이션이 (의도치 않게) SQLite 의 유연한 타이핑을 활용하고 있었다면, 마이그레이션 시 실패할 거예요.
사람들은 이 문제를 이용해 SQLite 가 데이터타입에 대해 더 제한적이어야 한다고 주장해요. 하지만 그 논증을 뒤집어서 다른 데이터베이스 엔진이 데이터타입에 대해 더 유연해야 한다고 말할 수도 있어요. 결국 애플리케이션은 마이그레이션 전에는 SQLite 에서 올바르게 작동하고 있었으니까요. 엄격한 타입 강제가 정말 그렇게 유용하다면, 왜 이전에 작동하던 애플리케이션을 망가뜨렸을까요?
5. 그래도 엄격한 타입 강제를 원한다면...
SQLite 버전 3.37.0 (2021-11-27) 부터 SQLite 는 STRICT 테이블을 사용해 이 개발 스타일을 지원해요.
STRICT 테이블이 애플리케이션의 버그를 예방했거나 예방할 수 있었던 실제 사례를 찾는다면 SQLite Forum 에 메시지를 올려서 그 이야기를 이 문서에 추가할 수 있게 해주세요.
6. 자유를 받아들여요
SQL 데이터베이스의 유연한 타이핑이 당신에게 새로운 개념이라면, 한번 시도해보길 권해요. 아마 문제를 일으키지 않을 것이고, 프로그램을 더 단순하게 만들고 작성·유지하기 더 쉽게 만들 수도 있어요. 처음에는 회의적이어도 유연한 타이핑을 한번 시도해보면 결국 더 나은 접근 방식임을 깨닫게 될 것이고, 완전한 SQLite 스타일 타입 유연성은 아니더라도 적어도 ANY 데이터타입을 지원하도록 다른 데이터베이스 공급업체를 장려하기 시작할 거라고 생각해요.
대부분의 경우 유연한 타이핑은 중요하지 않아요. 컬럼이 단일 잘 정의된 타입을 저장하기 때문이에요. 하지만 가끔 유연한 타입 시스템이 있다면 문제 해결을 더 깔끔하고 쉽게 만들어주는 상황을 만나게 될 거예요.
더 알아보기 (Learn more)
- Datatypes In SQLite — 유연한 타입 시스템 기본
- STRICT 테이블 — 엄격한 타입 강제
- json_each / json_tree — JSON 테이블-값 함수