본문 바로가기

IT & AI

SQLite는 신뢰성을 어떻게 설계했을까요

728x90

SQLite는 신뢰성을 어떻게 설계했을까요

AI 뉴스 썸네일
AI 뉴스 썸네일

SQLite는 스마트폰과 브라우저, 임베디드 기기 안에서 조용히 데이터를 맡고 있어요. Richard Hipp은 54분 분량의 발표에서 이 작은 데이터베이스가 어떻게 장애를 견디는 소프트웨어로 자랐는지 설명했어요. 출발점은 거창한 연구가 아니라 자주 멈추던 Informix 서버를 우회하려는 현실적인 필요였어요. 1

핵심 요약

구분핵심개발자가 볼 지점
테스트 기준배포 객체 코드의 기계어 분기까지 양방향으로 실행해 100% MCDC를 확인해요소스 커버리지 숫자만으로 품질을 단정하기 어려워요
오류 재현대체 VFS와 메모리 할당자로 I/O 오류, 메모리 부족, 전원 차단을 의도적으로 만들어요희귀 장애를 재현할 통제 지점을 제품 구조에 넣어야 해요
장기 유지보수방대한 자동 검사가 세 명의 커미터에게 큰 리팩터링을 시도할 여유를 줘요테스트 투자는 변경 속도와 성능 개선에도 연결돼요
한계 보완MCDC 뒤에도 퍼징, 의미론적 검사, AI가 만든 병적 입력을 계속 받아들여요하나의 품질 기법을 최종 답으로 여기면 새 결함을 놓칠 수 있어요

1. 신뢰성은 테스트 개수보다 구조에서 시작해요

SQLite는 별도 서버 없이 애플리케이션 안에서 실행되는 C 라이브러리예요. 데이터베이스 전체를 파일 하나에 담고 ACID 트랜잭션과 전원 장애 복구를 제공해요. Hipp은 활성 데이터베이스 수가 1조 개를 훨씬 넘을 것으로 추산하지만, 무료로 배포돼 정확한 설치 수를 셀 수 없다는 조건도 함께 밝혔어요. 2

SQLite의 품질 기준은 "테스트하지 않았다면 작동하지 않는다"에 가까워요. 여기서 100% MCDC는 소스의 각 줄을 한 번 실행했다는 뜻이 아니에요. 실제 기계어에 남은 모든 분기 명령을 양쪽 방향으로 실행하고, 비트 마스크의 각 비트가 판단 결과를 바꾸는지도 확인해요. GCC의 GCOV 결과에서 실행되지 않은 분기를 찾아 테스트를 더하는 방식이에요.

728x90

소스가 맞더라도 컴파일된 결과가 같다고 가정하지 않아요. GCC, Clang, MSVC로 만든 실제 객체 코드까지 검사해요. SQLite 팀은 이 과정에서 컴파일러 버그를 발견했고, 오래된 컴파일러를 쓰는 사용자를 위해 우회 코드를 남겼어요. 개발팀이 배포하는 산출물과 테스트한 산출물을 최대한 같게 유지하려는 선택이에요. 3

실패를 기다리지 않고 직접 만들어요

메모리 부족이나 디스크 오류는 평소에는 드물지만, 발생하면 데이터 손상으로 이어질 수 있어요. SQLite는 메모리 할당자와 파일 시스템 접근 계층을 교체할 수 있게 했어요. 테스트에서는 첫 번째 `malloc`, 두 번째 `malloc`처럼 실패 지점을 차례로 옮겨 가며 오류 반환, 스택 정리, 메모리 누수를 확인해요.

VFS도 같은 역할을 해요. 운영체제의 파일 함수를 직접 호출하는 대신 교체 가능한 함수 포인터를 거쳐요. 테스트용 VFS는 원하는 시점에 읽기와 쓰기를 실패시키고, 쓰기 순서를 바꾸거나 일부 기록을 누락해 갑작스러운 전원 차단을 흉내 내요. 재시작한 데이터베이스가 트랜잭션을 모두 반영했거나 모두 되돌렸는지 검사할 수 있어요.

이런 통제 지점은 완성된 제품에 나중에 덧붙이기 어려워요. SQLite에는 테스트 제어 인터페이스와 오류 시뮬레이션 지점이 제품 코드 안에 남아 있어요. 배포판과 다른 특별한 코드만 검사하는 일을 피하려는 설계예요.

커버리지 100%도 끝은 아니에요

TH3 테스트 체계가 2009년 100% MCDC를 달성한 뒤 Android 등에서 들어오던 버그 보고가 크게 줄었어요. 그래도 2013년 무렵 프로파일 유도 퍼징이 등장하자 기존 테스트가 찾지 못한 결함이 다시 드러났어요. SQLite 팀은 데이터베이스 파일과 SQL을 함께 변형하는 전용 퍼저를 만들었고, SQL 결과의 일관성을 비교하는 의미론적 검사도 더했어요.

최근에는 AI가 만든 병적 입력도 새 시험대가 됐어요. 발표에 나온 `median` 집계 함수 사례에서는 100만 개 항목이 자체 퀵 정렬의 재귀 깊이를 키워 스택을 넘치게 했어요. AI가 제안한 수정안을 그대로 쓰지는 않았어요. 팀은 작은 파티션에만 재귀를 쓰고 큰 파티션을 반복문으로 처리해 깊이를 줄였어요. AI는 사람이 떠올리기 힘든 입력을 찾는 데 쓸모가 있었지만, 최종 수정은 개발자가 성능과 알고리듬을 다시 판단했어요. 3

왜 중요한가요

SQLite 사례는 테스트를 개발 마지막 단계의 확인 절차로만 두면 놓치는 영역이 많다는 점을 보여줘요. 파일 시스템, 메모리, 스레드처럼 실패할 수 있는 경계를 교체 가능하게 두면 운영 장애를 기다리지 않고 복구 경로를 반복해서 확인할 수 있어요. 결제, 동기화, 로컬 저장처럼 데이터 손실 비용이 큰 기능을 설계할 때 바로 적용할 수 있는 기준이에요. 2

높은 커버리지는 변경을 막는 장벽이 아니라 안전하게 바꾸기 위한 기반으로 쓰였어요. 세 명의 커미터는 큰 코드 구간을 다시 쓰면서도 포인트 릴리스로 내보낼 수 있었고, 2009년 이후 작은 최적화를 누적해 성능을 3배 이상 높였다고 설명해요. 테스트 코드가 제품 코드보다 훨씬 크더라도 유지보수 속도와 장애 비용을 함께 봐야 하는 이유예요.

다만 100%라는 숫자만 복사해 적용하면 충분하지 않아요. MCDC는 실행 경로를 확인하지만 잘못된 SQL 결과나 병적인 입력을 모두 보장하지 못해요. SQLite도 퍼징과 의미론적 검사, 새로운 입력 생성 기법을 계속 추가했어요. 팀이 다루는 실패 유형을 먼저 정하고, 그 실패를 재현할 구조와 판정 기준을 함께 마련하는 편이 더 실용적이에요.

참고 자료

  1. SQLite에서 배운 신뢰성의 교훈 - Richard Hipp 유튜브 — GeekNews
  2. GeekNews 토픽 32324 Markdown 전문 — GeekNews
  3. Reliability Lessons From SQLite - Richard Hipp | SSW 2026 — Software Should Work, YouTube
728x90