Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 89 additions & 24 deletions .github/workflows/rust-benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ name: Rust Benchmark
on:
push:
branches: [main, master]
paths: ['rust/**', '.github/workflows/rust-benchmark.yml']
paths: ['rust/**', 'spacetime-module/**', '.github/workflows/rust-benchmark.yml']
pull_request:
branches: [main, master]
paths: ['rust/**', '.github/workflows/rust-benchmark.yml']
paths: ['rust/**', 'spacetime-module/**', '.github/workflows/rust-benchmark.yml']

env:
CARGO_TERM_COLOR: always
Expand All @@ -20,38 +20,74 @@ jobs:
test:
name: Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 360
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4

- name: Setup Rust (nightly-2022-08-22)
- name: Setup Rust (nightly)
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2022-08-22
toolchain: nightly
components: rustfmt, clippy
targets: wasm32-unknown-unknown

- name: Cache cargo registry
uses: actions/cache@v4
uses: Swatinem/rust-cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
workspaces: rust -> target
cache-on-failure: "true"

- name: Check formatting
run: cargo fmt --all -- --check

- name: Run Clippy
run: cargo clippy --all-targets

- name: Install SpacetimeDB CLI
shell: bash
run: |
curl -sSf https://install.spacetimedb.com | sh -s -- -y
echo "$HOME/.local/bin" >> $GITHUB_PATH
working-directory: .

- name: Build SpacetimeDB module (WASM)
run: cargo build --release --target wasm32-unknown-unknown
working-directory: rust/spacetime-module

- name: Start SpacetimeDB server
shell: bash
run: |
spacetime start &
echo "Waiting for SpacetimeDB server to be ready..."
for i in $(seq 1 30); do
if curl -sf http://localhost:3000/ > /dev/null 2>&1; then
echo "SpacetimeDB server is ready"
break
fi
sleep 1
done
working-directory: .

- name: Publish SpacetimeDB module
shell: bash
run: |
spacetime publish \
--server http://localhost:3000 \
--bin-path target/wasm32-unknown-unknown/release/spacetime_module.wasm \
--yes \
benchmark-links
working-directory: rust/spacetime-module

- name: Run tests
run: cargo test --release
env:
SPACETIMEDB_URI: http://localhost:3000
SPACETIMEDB_DB: benchmark-links
# Run tests sequentially to avoid parallel interference with shared SpacetimeDB state.
run: cargo test -- --test-threads=1

benchmark:
name: Benchmark
Expand All @@ -64,10 +100,11 @@ jobs:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Rust (nightly-2022-08-22)
- name: Setup Rust (nightly)
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2022-08-22
toolchain: nightly
targets: wasm32-unknown-unknown

- name: Setup Python
uses: actions/setup-python@v5
Expand All @@ -78,15 +115,41 @@ jobs:
run: pip install matplotlib numpy

- name: Cache cargo registry
uses: actions/cache@v4
uses: Swatinem/rust-cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: ubuntu-cargo-bench-${{ hashFiles('rust/Cargo.lock') }}
restore-keys: |
ubuntu-cargo-bench-
workspaces: rust -> target
cache-on-failure: "true"

- name: Install SpacetimeDB CLI
run: |
curl -sSf https://install.spacetimedb.com | sh -s -- -y
echo "$HOME/.local/bin" >> $GITHUB_PATH
working-directory: .

- name: Build SpacetimeDB module (WASM)
run: cargo build --release --target wasm32-unknown-unknown
working-directory: rust/spacetime-module

- name: Start SpacetimeDB server
run: |
spacetime start &
for i in $(seq 1 30); do
if curl -sf http://localhost:3000/ > /dev/null 2>&1; then
echo "SpacetimeDB server is ready"
break
fi
sleep 1
done
working-directory: .

- name: Publish SpacetimeDB module
run: |
spacetime publish \
--server http://localhost:3000 \
--bin-path target/wasm32-unknown-unknown/release/spacetime_module.wasm \
--yes \
benchmark-links
working-directory: rust/spacetime-module

- name: Build benchmark
run: cargo build --release
Expand All @@ -95,6 +158,8 @@ jobs:
env:
BENCHMARK_LINK_COUNT: 1000
BACKGROUND_LINK_COUNT: 3000
SPACETIMEDB_URI: http://localhost:3000
SPACETIMEDB_DB: benchmark-links
run: cargo bench --bench bench -- --output-format bencher | tee out.txt

- name: Generate charts
Expand Down
64 changes: 45 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Benchmark comparing [SpacetimeDB 2](https://github.com/clockworklabs/SpacetimeDB) vs [Doublets](https://github.com/linksplatform/doublets-rs) performance for basic CRUD operations with links.

SpacetimeDB is benchmarked via its SQLite storage backend (the same engine SpacetimeDB 2 uses internally). Doublets is benchmarked with its in-memory (volatile) storage variants.
SpacetimeDB is benchmarked using the official `spacetimedb-sdk` Rust crate connected to a running SpacetimeDB 2.0 server. Doublets is benchmarked with both in-memory (volatile) and file-backed (non-volatile) storage variants.

## Benchmark Operations

Expand All @@ -18,16 +18,18 @@ SpacetimeDB is benchmarked via its SQLite storage backend (the same engine Space

## Backends Benchmarked

### SpacetimeDB (SQLite backend)
- **SpacetimeDB Memory** — in-memory SQLite with B-tree indexes on `id`, `source`, `target`
### SpacetimeDB
- **SpacetimeDB** — connects to a running SpacetimeDB 2.0 server via the official `spacetimedb-sdk` Rust crate; uses the `links` table defined in the `spacetime-module` WebAssembly module

SpacetimeDB 2 uses SQLite as its underlying data store for persistent tables. This benchmark measures the performance of SpacetimeDB's storage layer (SQLite + WAL mode) for link CRUD operations.
The benchmark uses the official SpacetimeDB Rust client SDK, calling reducers to mutate data and reading from the client-side subscription cache.

### Doublets
- **Doublets United Volatile** — in-memory store; links stored as contiguous `(index, source, target)` units
- **Doublets Split Volatile** — in-memory store; separate data and index memory regions
- **Doublets United NonVolatile** — file-backed store; same contiguous layout but memory-mapped to a single file; data persists to disk
- **Doublets Split NonVolatile** — file-backed store; separate data and index files; both memory-mapped; data persists to disk

Doublets is a custom in-memory doublet link data structure with O(1) lookup by id and O(log n + k) traversal by source/target using balanced tree indexes.
Doublets uses a recursive-less size-balanced tree for O(1) lookup by id and O(log n + k) traversal by source/target. The file-backed variants use `memmap2` for memory-mapped file I/O, flushing changes to disk on drop via `sync_all()`. See [`rust/doublets-patched/PATCHES.md`](rust/doublets-patched/PATCHES.md) for why a local patched copy is used instead of the published crates.io version.

## Benchmark Background

Expand All @@ -44,15 +46,17 @@ Each benchmark iteration pre-populates the database with background links to sim

## Operation Complexity

| Operation | SpacetimeDB (SQLite) | Doublets United | Doublets Split |
| Operation | SpacetimeDB | Doublets United | Doublets Split |
|---|---|---|---|
| Create | O(log n) + disk I/O | O(log n) | O(log n) |
| Delete | O(log n) + disk I/O | O(log n) | O(log n) |
| Update | O(log n) + disk I/O | O(log n) | O(log n) |
| Query All | O(n) + disk I/O | O(n) | O(n) |
| Query by Id | O(log n) | O(1) | O(1) |
| Query by Source | O(log n + k) | O(log n + k) | O(log n + k) |
| Query by Target | O(log n + k) | O(log n + k) | O(log n + k) |
| Create | O(log n) + network | O(log n) | O(log n) |
| Delete | O(log n) + network | O(log n) | O(log n) |
| Update | O(log n) + network | O(log n) | O(log n) |
| Query All | O(n) cache read | O(n) | O(n) |
| Query by Id | O(n) cache scan | O(1) | O(1) |
| Query by Source | O(n) cache scan | O(log n + k) | O(log n + k) |
| Query by Target | O(n) cache scan | O(log n + k) | O(log n + k) |

The algorithmic complexity is the same for volatile and non-volatile Doublets variants. The non-volatile variants have additional I/O overhead due to memory-mapped file writes (flushed to disk on drop).

## Related Benchmarks

Expand All @@ -64,18 +68,33 @@ Each benchmark iteration pre-populates the database with background links to sim

### Prerequisites

- Rust nightly-2022-08-22 (see `rust/rust-toolchain.toml`)
- Rust nightly (see `rust/rust-toolchain.toml`)
- SpacetimeDB CLI: `curl -sSf https://install.spacetimedb.com | sh`

### Start SpacetimeDB server and publish module

```bash
# Start the local SpacetimeDB server
spacetime start &

# Build and publish the links module
spacetime build --project-path spacetime-module
spacetime publish --project-path spacetime-module benchmark-links
```

### Run benchmarks

```bash
cd rust

# Full benchmark run (1000 links, 3000 background)
cargo bench --bench bench -- --output-format bencher | tee out.txt
SPACETIMEDB_URI=http://localhost:3000 SPACETIMEDB_DB=benchmark-links \
cargo bench --bench bench -- --output-format bencher | tee out.txt

# Quick benchmark run (CI scale)
BENCHMARK_LINK_COUNT=10 BACKGROUND_LINK_COUNT=100 cargo bench --bench bench
BENCHMARK_LINK_COUNT=10 BACKGROUND_LINK_COUNT=100 \
SPACETIMEDB_URI=http://localhost:3000 SPACETIMEDB_DB=benchmark-links \
cargo bench --bench bench

# Generate charts from results
python3 out.py
Expand All @@ -85,7 +104,7 @@ python3 out.py

```bash
cd rust
cargo test --release
SPACETIMEDB_URI=http://localhost:3000 SPACETIMEDB_DB=benchmark-links cargo test
```

### Code quality
Expand All @@ -100,14 +119,21 @@ cargo clippy --all-targets

```
.
├── spacetime-module/ # SpacetimeDB WASM module (links table + reducers)
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs # Table definition and reducers using `spacetimedb` crate
├── rust/
│ ├── Cargo.toml # Package manifest with pinned dependencies
│ ├── doublets-patched/ # Local patches to doublets-rs for modern nightly compatibility
│ │ └── PATCHES.md # Documents why patches are needed and what was changed
│ ├── rust-toolchain.toml # Pinned Rust nightly toolchain
│ ├── rustfmt.toml # Rust formatting config
│ ├── out.py # Chart generation script (matplotlib)
│ ├── src/
│ │ ├── lib.rs # Links trait, constants (BENCHMARK_LINK_COUNT, BACKGROUND_LINK_COUNT)
│ │ ├── spacetimedb_impl.rs # SpacetimeDB SQLite client (implements Links)
│ │ ├── module_bindings/ # spacetimedb-sdk client bindings for the links module
│ │ ├── spacetimedb_impl.rs # SpacetimeDB SDK client (implements Links)
│ │ ├── doublets_impl.rs # Doublets store adapters (implements Links)
│ │ ├── exclusive.rs # Exclusive<T> wrapper for interior mutability
│ │ ├── fork.rs # Fork<B> — benchmark iteration isolation
Expand All @@ -116,7 +142,7 @@ cargo clippy --all-targets
│ │ ├── spacetimedb_benched.rs # Benched impl for SpacetimeDB
│ │ └── doublets_benched.rs # Benched impls for Doublets stores
│ └── benches/
│ └── bench.rs # Criterion benchmark suite (7 operations x 3 backends)
│ └── bench.rs # Criterion benchmark suite (7 operations x 5 backends)
└── .github/
└── workflows/
└── rust-benchmark.yml # CI: test on 3 OS + benchmark + chart generation
Expand Down
12 changes: 7 additions & 5 deletions changelog.d/20260224_spacetimedb_vs_doublets_benchmark.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,26 @@ bump: minor

### Added

- **SpacetimeDB 2 vs Doublets benchmark** (`rust/`): New Rust benchmark suite comparing SpacetimeDB's SQLite backend against Doublets in-memory link stores for basic CRUD operations, following best practices from the Neo4j and PostgreSQL comparison benchmarks.
- **SpacetimeDB 2 vs Doublets benchmark** (`rust/`): New Rust benchmark suite comparing SpacetimeDB 2.0 against Doublets in-memory link stores for basic CRUD operations.

- **7 benchmark operations**: Create, Delete, Update, Query All, Query by Id, Query by Source, Query by Target
- **3 backends**: SpacetimeDB Memory (SQLite/WAL), Doublets United Volatile, Doublets Split Volatile
- **3 backends**: SpacetimeDB 2.0 (via official `spacetimedb-sdk` Rust crate), Doublets United Volatile, Doublets Split Volatile
- **Configurable scale**: `BENCHMARK_LINK_COUNT` and `BACKGROUND_LINK_COUNT` environment variables
- **Criterion harness**: Uses criterion 0.3.6 with custom `iter_custom` timing to exclude setup/teardown from measurements
- **Fork/unfork lifecycle**: Each iteration starts from a clean database state with pre-populated background links

- **`rust/src/lib.rs`**: `Links` trait as the shared interface for both SpacetimeDB and Doublets backends; `Benched` trait for benchmark lifecycle management.
- **`spacetime-module/`**: SpacetimeDB WebAssembly module using the official `spacetimedb` Rust crate, defining the `links` table and CRUD reducers.

- **`rust/src/spacetimedb_impl.rs`**: SpacetimeDB SQLite client implementing the `Links` trait using the same schema and indexes that SpacetimeDB 2 uses internally.
- **`rust/src/module_bindings/`**: Client-side SDK bindings for the links module (equivalent to `spacetime generate --lang rust` output).

- **`rust/src/spacetimedb_impl.rs`**: SpacetimeDB 2.0 implementation using the official `spacetimedb-sdk` Rust crate. Connects to a running SpacetimeDB server, subscribes to the links table, and calls reducers for CRUD operations.

- **`rust/src/doublets_impl.rs`**: Doublets store adapters implementing the `Links` trait for both United Volatile and Split Volatile storage layouts.

- **`rust/benches/bench.rs`**: Criterion benchmark suite with 21 benchmark functions (7 operations × 3 backends).

- **`rust/out.py`**: Python script for generating comparison charts (linear and log scale PNG) and a Markdown results table from benchmark output.

- **`.github/workflows/rust-benchmark.yml`**: GitHub Actions CI workflow that runs tests on Ubuntu/macOS/Windows and generates benchmark charts on push to main.
- **`.github/workflows/rust-benchmark.yml`**: GitHub Actions CI workflow that installs the SpacetimeDB CLI, starts a local server, publishes the module, runs tests on Ubuntu/macOS/Windows, and generates benchmark charts on push to main.

- **Updated `README.md`**: Benchmark description, operation complexity table, and usage instructions.
Loading