SQLite 의 아키텍처
SQLite 의 아키텍처
이 문서는 SQLite 라이브러리의 아키텍처를 설명해요. SQLite의 내부 동작을 이해하거나 수정하고 싶은 분들에게 유용한 내용을 담고 있어요.
본문
소개 (Introduction)
이 문서는 SQLite 라이브러리의 아키텍처를 설명해요. 여기 있는 정보는 SQLite의 내부 동작을 이해하거나 수정하고 싶은 분들에게 유용해요.
Interface ← C 언어 인터페이스 (sqlite3_…)
SQL 컴파일러 (SQL Compiler)
- Tokenizer → Parser → Code Generator
가상 머신 (Virtual Machine / VDBE)
B-Tree
Pager (Page Cache)
OS 인터페이스 (VFS)
유틸리티 (Utilities) · 테스트 코드 (Test Code)
위의 그림은 SQLite의 주요 구성 요소와 그들이 어떻게 상호 동작하는지 보여줘요. 아래 본문은 각 구성 요소의 역할을 설명해요.
개요 (Overview)
SQLite는 SQL 텍스트를 bytecode로 컴파일한 다음, 그 bytecode를 가상 머신으로 실행하는 방식으로 동작해요.
sqlite3_prepare_v2()와 그 계열 인터페이스는 SQL 텍스트를 bytecode로 변환하는 컴파일러 역할을 해요. sqlite3_stmt 객체는 단일 SQL 문을 구현하는 단일 bytecode 프로그램의 컨테이너예요. sqlite3_step() 인터페이스는 bytecode 프로그램을 가상 머신에 전달하고, 프로그램이 완료되거나, 반환할 결과 행을 만들거나, 치명적 오류에 도달하거나, 중단(interrupt)될 때까지 실행해요.
인터페이스 (Interface)
C 언어 인터페이스의 상당 부분은 main.c, legacy.c, vdbeapi.c 소스 파일에 들어 있지만, 일부 루틴은 파일 스코프의 데이터 구조에 접근할 수 있는 다른 파일들에 흩어져 있어요. sqlite3_get_table() 루틴은 table.c에 구현되어 있어요. sqlite3_mprintf() 루틴은 printf.c에 있어요. sqlite3_complete() 인터페이스는 complete.c에 있어요. TCL 인터페이스는 tclsqlite.c로 구현되어 있어요.
이름 충돌을 피하기 위해 SQLite 라이브러리의 모든 외부 심볼은 sqlite3 접두어로 시작해요. 외부 사용을 위한 심볼(즉, SQLite의 API를 이루는 심볼)은 밑줄을 추가해 sqlite3_ 로 시작해요. 확장 API는 때때로 밑줄 앞에 확장 이름을 추가하는데, 예를 들어 sqlite3rbu_ 나 sqlite3session_ 같이요.
토크나이저 (Tokenizer)
SQL 문을 담은 문자열이 평가될 때, 그것은 먼저 토크나이저로 보내져요. 토크나이저는 SQL 텍스트를 토큰으로 쪼개고 그 토큰들을 하나씩 파서에게 전달해요. 토크나이저는 tokenize.c** 파일에 수작업으로 코딩되어 있어요.
이 설계에서 토크나이저가 파서를 호출한다는 점에 주목하세요. YACC와 BISON에 익숙한 분들은 반대로 — 파서가 토크나이저를 호출하는 방식 — 을 하기에 익숙할 거예요. 하지만 토크나이저가 파서를 호출하는 것이 더 좋아요. 스레드 안전하게 만들 수 있고 더 빠르게 실행되기 때문이에요.
파서 (Parser)
파서는 토큰에 그 문맥에 기반해 의미를 부여해요. SQLite의 파서는 Lemon 파서 생성기를 사용해 생성돼요. Lemon은 YACC/BISON과 같은 일을 하지만, 오류가 덜 발생하는 다른 입력 문법을 사용해요. Lemon은 또한 재진입 가능하고 스레드 안전한 파서를 생성해요. 그리고 Lemon은 비-터미널 소멸자(non-terminal destructor)라는 개념을 정의해서, 문법 오류를 만났을 때 메모리 누수가 없게 해요. Lemon을 구동하고 SQLite가 이해하는 SQL 언어를 정의하는 문법 파일은 parse.y에 있어요.
Lemon은 개발 머신에 보통 없는 프로그램이기 때문에, Lemon의 완전한 소스 코드(단 하나의 C 파일)가 SQLite 배포판의 "tool" 하위 디렉토리에 포함되어 있어요.
코드 생성기 (Code Generator)
파서가 토큰을 파스 트리로 조립한 후, 코드 생성기가 실행되어 파스 트리를 분석하고 SQL 문의 작업을 수행하는 bytecode를 생성해요. 준비된 문(prepared statement) 객체가 이 bytecode의 컨테이너예요. 코드 생성기에는 많은 파일이 있는데, 그중에는 다음이 있어요: attach.c, auth.c, build.c, delete.c, expr.c, insert.c, pragma.c, select.c, trigger.c, update.c, vacuum.c, where.c, wherecode.c, whereexpr.c. 이 파일들에서 진지한 마법이 대부분 일어나요. expr.c는 표현식의 코드 생성을 처리해요. where.c*는 SELECT, UPDATE, DELETE 문의 WHERE 절에 대한 코드 생성을 처리해요. attach.c, delete.c, insert.c, select.c, trigger.c, update.c, vacuum.c 파일은 같은 이름의 SQL 문에 대한 코드 생성을 처리해요. (이 각 파일은 필요에 따라 expr.c와 where.c의 루틴을 호출해요.) 그 외 모든 SQL 문은 build.c에서 코드화돼요. auth.c 파일은 sqlite3_set_authorizer()의 기능을 구현해요.
코드 생성기, 특히 where.c*와 select.c의 로직은 때때로 쿼리 플래너(query planner)라고 불려요. 특정 SQL 문에 대해 답을 계산하는 데는 수백, 수천, 수백만 가지의 다른 알고리즘이 있을 수 있어요. 쿼리 플래너는 이 수백만 가지 선택에서 최상의 알고리즘을 선택하려고 노력하는 AI예요.
바이트코드 엔진 (Bytecode Engine)
코드 생성기가 만든 bytecode 프로그램은 가상 머신으로 실행돼요.
가상 머신 자체는 단일 소스 파일 vdbe.c에 완전히 들어 있어요. vdbe.h 헤더 파일은 가상 머신과 SQLite 라이브러리의 나머지 사이의 인터페이스를 정의하고, vdbeInt.h는 가상 머신 자체에 비공개인 구조와 인터페이스를 정의해요. 다양한 vdbe.c* 파일들이 가상 머신의 도우미 역할을 해요. vdbeaux.c 파일은 가상 머신이 사용하는 유틸리티와 라이브러리의 나머지가 VM 프로그램을 구성하는 데 사용하는 인터페이스 모듈을 담고 있어요. vdbeapi.c 파일은 sqlite3_bind_int()와 sqlite3_step() 같은 가상 머신에 대한 외부 인터페이스를 담고 있어요. 개별 값(문자열, 정수, 부동소수점, BLOB)은 vdbemem.c가 구현하는 "Mem"이라는 내부 객체에 저장돼요.
SQLite는 SQL 함수를 C 언어 루틴에 대한 콜백으로 구현해요. 내장 SQL 함수조차 이렇게 구현돼요. 대부분의 내장 SQL 함수(예: abs(), count(), substr() 등)는 func.c 소스 파일에서 찾을 수 있어요. 날짜·시간 변환 함수는 date.c에 있어요. coalesce()와 typeof() 같은 일부 함수는 코드 생성기가 직접 bytecode로 구현해요.
B-Tree
SQLite 데이터베이스는 btree.c 소스 파일에 있는 B-tree 구현을 사용해 디스크에 유지돼요. 데이터베이스의 각 테이블과 각 인덱스에 대해 별도의 B-tree가 사용돼요. 모든 B-tree는 같은 디스크 파일에 저장돼요. 파일 형식 세부사항은 안정적이고 잘 정의되어 있으며 향후에도 호환될 것이 보장돼요.
B-tree 하위시스템과 SQLite 라이브러리 나머지 사이의 인터페이스는 btree.h 헤더 파일로 정의돼요.
페이지 캐시 (Page Cache)
B-tree 모듈은 고정 크기 페이지 단위로 디스크에서 정보를 요청해요. 기본 page_size는 4096바이트지만 512에서 65536바이트 사이의 2의 거듭제곱이면 무엇이든 될 수 있어요. 페이지 캐시는 이 페이지들을 읽고, 쓰고, 캐싱하는 책임을 져요. 페이지 캐시는 또한 롤백과 원자적 커밋 추상화를 제공하고 데이터베이스 파일의 잠금을 처리해요. B-tree 드라이버는 페이지 캐시에 특정 페이지를 요청하고, 페이지를 수정하거나 변경을 커밋하거나 롤백하고 싶을 때 페이지 캐시에 알려줘요. 페이지 캐시는 요청이 빠르고, 안전하고, 효율적으로 처리되도록 하는 모든 지저분한 세부사항을 다뤄요.
주요 페이지 캐시 구현은 pager.c 파일에 있어요. WAL 모드 로직은 별도의 wal.c 파일에 있어요. 인메모리 캐싱은 pcache.c와 pcache1.c 파일로 구현돼요. 페이지 캐시 하위시스템과 SQLite의 나머지 사이의 인터페이스는 pager.h 헤더 파일로 정의돼요.
OS 인터페이스 (OS Interface)
운영 체제 간 이식성을 제공하기 위해 SQLite는 VFS라는 추상 객체를 사용해요. 각 VFS는 디스크에서 파일을 열고, 읽고, 쓰고, 닫는 메서드와, 현재 시간을 찾거나 내장 의사난수 생성기를 초기화할 난수를 얻는 것 같은 다른 OS 특정 작업을 위한 메서드를 제공해요. SQLite는 현재 unix용 VFS(os_unix.c 파일)와 Windows용 VFS(os_win.c 파일)를 제공해요.
유틸리티 (Utilities)
메모리 할당, 대소문자 무시 문자열 비교 루틴, 이식 가능한 텍스트-숫자 변환 루틴과 기타 유틸리티는 util.c에 있어요. 파서가 사용하는 심볼 테이블은 hash.c의 해시 테이블로 유지돼요. utf.c 소스 파일에는 Unicode 변환 서브루틴이 들어 있어요. SQLite는 자체 비공개 printf() 구현(약간의 확장 포함)을 printf.c에, 자체 의사난수 생성기(PRNG)를 random.c에 갖고 있어요.
테스트 코드 (Test Code)
소스 트리의 "src/" 폴더에서 이름이 test로 시작하는 파일들은 테스트 전용이며 라이브러리의 표준 빌드에는 포함되지 않아요.