2P by GN⁺ 4시간전 | ★ favorite | 댓글 1개
  • SQLite 가상 테이블도 쓰기 및 트랜잭션 지원이 가능하며, xUpdate, xSync, xCommit, xRollback 등의 훅을 구현해 사용함
  • SQLite는 기본적으로 롤백 저널 방식으로 원자성을 보장하며, 여러 DB 파일을 다룰 때는 슈퍼 저널로 전체 커밋을 조정함
  • 가상 테이블도 SQLite의 트랜잭션 프로토콜에 포함되어, xSync 실패 시 전체 트랜잭션을 롤백함
  • 커밋은 2단계로 나뉘며, xSync는 실패 가능성 있는 작업, xCommit은 단순 정리 작업만 수행해야 함
  • xCommitxRollback은 항상 호출될 수 있으므로 실패 없이 실행 가능한 정리용 함수로 작성해야 함

SQLite의 가상 테이블과 트랜잭션 처리

이전 글에서는 Go 언어를 통해 SQLite의 가상 테이블을 등록하고 쿼리하는 기본 방법을 소개했음. 이번 글에서는 쓰기 가능하고 트랜잭션을 지원하는 가상 테이블 구현법을 다룸.

가상 테이블의 쓰기 및 트랜잭션 지원

  • SQLite의 가상 테이블 인터페이스는 읽기 전용이 아님

  • xUpdate 훅을 구현하면 외부 데이터 소스에도 쓰기 가능

  • 진정한 트랜잭션 일관성을 위해서는 다음과 같은 트랜잭션 훅 필요:

    • xBegin: 트랜잭션 시작 알림
    • xSync: 디스크에 안전하게 커밋하기 위한 준비 (여기서 실패하면 전체 롤백)
    • xCommit: 최종 커밋 및 정리
    • xRollback: 트랜잭션이 중단됐을 경우 롤백 수행
  • 일반 테이블 또는 다른 가상 테이블과 함께 수정될 때도 SQLite는 모든 훅을 연동해 원자성을 보장

SQLite 트랜잭션의 내부 동작 방식

롤백 저널 (Rollback Journals)

  • SQLite는 기본적으로 페이지를 덮어쓰기 전에 백업 파일(저널)에 저장
  • 문제가 생기면 저널에서 복구하여 원자성 보장

참고: SQLite는 WAL 모드도 지원하지만 이 글의 범위에서는 제외됨

슈퍼 저널 (Super-Journals)

  • 여러 데이터베이스가 연결된 경우, 각 DB에 개별 저널만으로는 동기화 어려움

  • 슈퍼 저널이라는 상위 레벨 파일을 통해 여러 파일 간 커밋을 조정

  • 한 DB 파일 내 여러 가상 테이블만 다루는 경우는 슈퍼 저널 없이도 동기화 가능

  • 어떤 경우든 SQLite는 트랜잭션 흐름 안에서 xSync, xCommit, xRollback 훅을 자동으로 호출

가상 테이블과 함께하는 2단계 커밋

SQLite의 커밋 과정은 2단계로 이루어짐:

1단계: xSync (Durability 보장)

  • 모든 B-Tree 및 DB 파일의 페이지 또는 저널을 디스크에 안전하게 동기화
  • 가상 테이블도 각각 xSync 훅이 호출됨
  • 어느 한 xSync에서 실패하면 전체 트랜잭션이 롤백됨 → 원자성 유지

2단계: 정리 (xCommit)

  • 디스크에 저장이 완료되면 저널 파일을 삭제하고 가상 테이블 정리 수행

  • 아래는 vdbeaux.c 코드 일부임

    disable_simulated_io_errors();  
    sqlite3BeginBenignMalloc();  
    for(i=0; i<db->nDb; i++){  
      Btree *pBt = db->aDb[i].pBt;  
      if( pBt ){  
        sqlite3BtreeCommitPhaseTwo(pBt, 1);  
      }  
    }  
    sqlite3EndBenignMalloc();  
    enable_simulated_io_errors();  
    sqlite3VtabCommit(db);  
    
  • sqlite3VtabCommit() 안에서 실제로는 모든 xCommit 호출이 실패해도 무시됨 → 순수한 정리 단계

    int sqlite3VtabCommit(sqlite3 *db){  
      callFinaliser(db, offsetof(sqlite3_module,xCommit));  
      return SQLITE_OK;  
    }  
    
  • xSync로 내구성이 확보됐기 때문에, xCommit, xRollback은 실패해도 무시됨

가상 테이블 작성자를 위한 주의사항

  • 지속성 있는 작업은 반드시 xSync에 넣어야 함
    • 네트워크 I/O, 파일 쓰기 등 실패할 수 있는 작업은 여기서 처리해야 트랜잭션이 안전하게 중단됨
  • xSync 후에도 xRollback이 호출될 수 있음
    • 다른 테이블의 xSync가 실패하면 전체 롤백됨
  • xCommitxRollback은 실패하지 않는 정리용 함수로 작성
    • **idempotent(항등성)**하게, 여러 번 호출돼도 상태 변화가 없어야 함

결론

  • SQLite의 저널링 메커니즘은 기본 테이블과 가상 테이블을 포함한 모든 요소의 원자성 커밋을 보장
  • 가상 테이블의 트랜잭션 훅은 SQLite 트랜잭션 흐름에 자연스럽게 통합
  • 가상 테이블을 구현하는 개발자는 xSync에 집중하여 데이터 무결성을 확보하고, 정리 작업은 xCommit, xRollback으로 나눠야 함
Hacker News 의견
  • vtabs에 대한 글을 보니 좋음. 나는 Rust로 SQLite를 재구현하면서 vtab 지원을 구현했음. 그래서 최근에 vtab에 대해 많은 것을 배웠음. vtab은 매우 강력하고 아마도 충분히 활용되지 않음
  • 흥미로움. 하지만 이것은 mattn의 go-sqlite3 패키지를 사용함. 이는 CGO임
    • 현대 GO에서 이것이 일반적이거나 예상되는 요구사항인지 궁금함