GN⁺: SQLite가 Bytecode를 사용하는 이유
(sqlite.org)SQL 데이터베이스 엔진의 일반적인 작동 방식
- 모든 SQL 데이터베이스 엔진은 비슷한 방식으로 동작함
- 입력된 SQL 문을 "준비된 문장(Prepared Statement)"으로 변환
- 준비된 문장을 "실행"해서 결과를 생성
- SQLite에서 준비된 문장은 sqlite3_stmt 객체의 인스턴스로 표현됨
- 준비된 문장을 표현하는 방법은 크게 두 가지
- 바이트코드 방식: SQLite에서 사용
- 객체 트리 방식: MySQL, PostgreSQL에서 사용
바이트코드 방식의 장점
- 이해하기 쉬움
- 단순한 명령어들의 나열로 구성되어 쉽게 출력 가능
- EXPLAIN 키워드를 사용하면 SQL 문장의 바이트코드 확인 가능
- 디버깅이 용이함
- 파싱/분석 단계와 실행 단계를 명확히 구분
- 디버깅 빌드에서 PRAGMA vdbe_trace=ON 명령으로 바이트코드 실행 추적 가능
- 증분식으로 실행 가능
- 바이트코드로 작성된 SQL 문은 한 행씩 실행하고 중지했다 재개 가능
- 객체 트리 방식은 전체 트리를 한번에 실행하므로 증분 실행이 어려움
- 메모리 사용량이 적음
- 바이트코드는 AST보다 크기가 작음
- Prepared Statement는 오래 메모리에 캐싱되므로 메모리 사용량이 중요
- 실행 속도가 빠름
- 각 단계에서 결정해야 할 사항이 적어 실행이 빠름
객체 트리 방식의 장점
- 런타임에 쿼리 계획을 수정할 수 있음
- 객체 트리는 실행 중에도 수정이 용이함
- 쿼리의 진행 상황에 따라 동적으로 최적화 가능
- 병렬화하기 쉬움
- 각 처리 노드를 별도 스레드에 할당 가능
- 노드 간 데이터 전달만 동기화하면 됨
- 대용량 분석 쿼리(OLAP)를 다중 코어에서 실행할 때 유리
GN⁺의 의견
- SQLite의 주요 목표는 사물인터넷 환경에서의 트랜잭션 처리(OLTP)이므로, 바이트코드 방식이 적합해 보임. 간단하고 가벼우면서도 빠른 성능을 제공할 수 있기 때문임.
- 반면 MySQL이나 PostgreSQL은 대용량 데이터 분석에도 많이 사용되므로, 쿼리 실행 계획을 동적으로 최적화하고 병렬화할 수 있는 객체 트리 방식의 장점이 더 부각될 수 있음.
- 다만 객체 트리 방식도 디버깅이나 성능 분석이 어렵다는 단점이 있음. 또한 트리 순회 비용 등으로 인해 간단한 쿼리의 경우 오히려 바이트코드보다 느릴 가능성도 있음.
- 중요한 점은 용도와 목적에 맞는 적절한 방식을 선택하는 것. 범용 RDBMS의 경우 두 방식의 장단점을 절충한 하이브리드 방식을 사용하는 것도 고려해볼 만함.
Hacker News 의견
-
SQLite가 SQL 쿼리 실행을 위해 추상 구문 트리(AST) 대신 바이트코드 가상 머신(VM)을 사용하는 것은 데이터베이스에 있어 흥미로운 설계 선택임. 바이트코드가 AST보다 갖는 장점은 다음과 같음:
- 컴팩트함: 바이트코드는 하위 표현식에 대한 숨겨진 malloc/객체 헤더와 포인터가 필요하지 않아 AST보다 더 컴팩트함.
- 성능: 캐시 활용도가 높고 포인터 추적으로 인한 캐시 미스가 적어 바이트코드 실행이 더 빠름.
- 증분 실행: 명시적 스택을 사용하여 기본 스택을 풀지 않고도 실행을 일시 중지하고 재개하는 것이 바이트코드로 더 쉬움.
-
바이트코드 VM과 인터프리터는 종종 범용 프로그래밍 언어와 연관되지만, 다음과 같은 다른 맥락에서도 놀랍도록 유용할 수 있음:
- eBPF: Linux 커널의 확장 메커니즘.
- DWARF 표현 언어: GDB, LLDB 같은 디버거에서 사용됨.
- RAR 파일 포맷: 사용자 정의 데이터 변환을 위한 바이트코드 인코딩을 포함함.
-
Microsoft SQL Server는 내부적으로 객체 트리를 사용하지만, 쿼리 플랜 출력은 여전히 테이블 형태로, 객체 트리를 테이블로 렌더링하는 것이 어려움을 보여줌.
-
프로그래머는 종종 어떤 인덱스 조회가 루프에서 일어나야 하는지 정확히 알고 있으므로, 경우에 따라 SQL 대신 바이트코드를 직접 작성하거나 고수준의 명령형 언어를 사용하는 것이 유리할 수 있음. SQL로 이를 표현하는 것은 부담이 될 수 있음.
-
병목현상이 바이트코드 실행에 있지 않다면(예: 메모리나 디스크 속도), JIT 컴파일을 통해 네이티브 코드로 변환하는 것이 꼭 필요하지 않을 수 있음.
-
Python, Ruby, Lua 등 많은 프로그래밍 언어가 내부적으로 바이트코드나 AST를 사용함. 데이터베이스 설계 결정으로 인해 오류가 발생하기 쉬운 서드파티 라이브러리나 ORM 구현에 쉽게 파싱 가능한 명령문이 유용할 수 있음.