add a benchmark for connection time versus number of tables

SQLite performs poorly for connections as the number of tables increase.

Do we perform better? the same? worse?

Right now the answer is worse. Add a benchmark to help us compare.
This commit is contained in:
Glauber Costa 2025-07-01 19:58:02 -05:00
parent badbfd82f5
commit 471a95bd96
18 changed files with 2477 additions and 0 deletions

10
perf/connection/README.md Normal file
View file

@ -0,0 +1,10 @@
# Connection benchmarks
Run the following in `rusqlite` and `limbo` directories:
```
./gen-databases
./run-benchmark.sh
```
This benchmark tests the time it takes to open a connection to databases with varying numbers of tables (1,000, 10,000, and 100,000 tables).

48
perf/connection/gen-database.py Executable file
View file

@ -0,0 +1,48 @@
#!/usr/bin/env python3
"""Generate SQLite databases with specified number of tables for benchmarking."""
import argparse
import sqlite3
def main() -> None:
"""Generate database with specified number of tables."""
parser = argparse.ArgumentParser()
parser.add_argument("filename")
parser.add_argument("-t", "--tables", type=int, help="Number of tables to create")
args = parser.parse_args()
conn = sqlite3.connect(args.filename)
cursor = conn.cursor()
# Enable WAL mode
cursor.execute("PRAGMA journal_mode=WAL")
# Create the specified number of tables
for i in range(args.tables):
table_name = f"table_{i}"
cursor.execute(f"""
CREATE TABLE IF NOT EXISTS {table_name} (
id INTEGER PRIMARY KEY,
name TEXT,
value INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
# Insert a small amount of data in each table for realism
cursor.execute(
f"INSERT INTO {table_name} (name, value) VALUES (?, ?)",
(f"item_{i}", i),
)
print(f"Created {args.tables} tables in {args.filename}")
conn.commit()
conn.close()
if __name__ == "__main__":
main()

11
perf/connection/gen-databases Executable file
View file

@ -0,0 +1,11 @@
#!/bin/bash
echo "Generating databases with different table counts..."
# Create databases with 1,000, 5,000, and 10,000 tables
python3 gen-database.py database_10.db -t 10
python3 gen-database.py database_1k.db -t 1000
python3 gen-database.py database_5k.db -t 5000
python3 gen-database.py database_10k.db -t 10000
echo "Database generation complete."

1733
perf/connection/limbo/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
[package]
name = "limbo-connection-benchmark"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.5", features = ["derive"] }
env_logger = "0.11.0"
hdrhistogram = "7.5.2"
turso_core = { path = "../../../core" }
[profile.release]
debug = true
[workspace]

View file

@ -0,0 +1 @@
../gen-database.py

View file

@ -0,0 +1 @@
../gen-databases

View file

@ -0,0 +1 @@
../plot.py

View file

@ -0,0 +1,17 @@
#!/bin/bash
echo "Building benchmark..."
cargo build --release
echo "Running connection benchmarks..."
echo "database,iterations,p50,p90,p95,p99,p999,p9999,p99999" > results.csv
# Test each database with different table counts
for db in database_10.db database_1k.db database_5k.db database_10k.db
do
echo "Testing $db..."
./target/release/limbo-connection-benchmark $db --iterations 1000 | tail -1 >> results.csv
done
echo "Results written to results.csv"
cat results.csv

View file

@ -0,0 +1,56 @@
use clap::Parser;
use hdrhistogram::Histogram;
use std::{
sync::Arc,
time::Instant,
};
use turso_core::{Database, PlatformIO};
#[derive(Parser)]
struct Opts {
database: String,
#[arg(short, long, default_value = "100")]
iterations: usize,
}
fn main() {
env_logger::init();
let opts = Opts::parse();
let mut hist = Histogram::<u64>::new(2).unwrap();
let io = Arc::new(PlatformIO::new().unwrap());
println!("Testing connection performance with database: {}", opts.database);
for i in 0..opts.iterations {
let start = Instant::now();
// Open connection to database and prepare a statement
let db = Database::open_file(io.clone(), &opts.database, false, false).unwrap();
let conn = db.connect().unwrap();
let _stmt = conn.prepare("SELECT name FROM table_0 WHERE id = ?").unwrap();
let elapsed = start.elapsed();
hist.record(elapsed.as_nanos() as u64).unwrap();
if (i + 1) % 10 == 0 {
println!("Completed {} iterations", i + 1);
}
}
// Extract database name and table count for CSV output
let db_name = opts.database.replace(".db", "").replace("database_", "");
println!("database,iterations,p50,p90,p95,p99,p999,p9999,p99999");
println!(
"{},{},{},{},{},{},{},{},{}",
db_name,
opts.iterations,
hist.value_at_quantile(0.5),
hist.value_at_quantile(0.90),
hist.value_at_quantile(0.95),
hist.value_at_quantile(0.99),
hist.value_at_quantile(0.999),
hist.value_at_quantile(0.9999),
hist.value_at_quantile(0.99999)
);
}

55
perf/connection/plot.py Executable file
View file

@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""Plot connection benchmark results from CSV data."""
import csv
import sys
import matplotlib.pyplot as plt
def main() -> None:
"""Plot benchmark results from CSV file."""
if len(sys.argv) != 2:
print("Usage: python3 plot.py <results.csv>")
sys.exit(1)
# Read the CSV file
databases = []
p50 = []
p95 = []
p99 = []
with open(sys.argv[1]) as file:
reader = csv.DictReader(file)
for row in reader:
databases.append(row["database"])
p50.append(int(row["p50"]))
p95.append(int(row["p95"]))
p99.append(int(row["p99"]))
x = range(len(databases))
width = 0.25
plt.figure(figsize=(12, 8))
plt.bar([i - width for i in x], p50, width, label="p50", alpha=0.8)
plt.bar([i for i in x], p95, width, label="p95", alpha=0.8)
plt.bar([i + width for i in x], p99, width, label="p99", alpha=0.8)
plt.xlabel("Database (Number of Tables)")
plt.ylabel("Connection Time (nanoseconds)")
plt.title("Database Connection Performance by Table Count")
plt.xticks(x, databases)
plt.legend()
plt.yscale("log") # Use log scale for better visualization
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("connection_benchmark.png", dpi=300, bbox_inches="tight")
plt.show()
print("Chart saved as connection_benchmark.png")
if __name__ == "__main__":
main()

448
perf/connection/rusqlite/Cargo.lock generated Normal file
View file

@ -0,0 +1,448 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "anstream"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "clap"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "flate2"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "hashbrown"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
dependencies = [
"foldhash",
]
[[package]]
name = "hashlink"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown",
]
[[package]]
name = "hdrhistogram"
version = "7.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d"
dependencies = [
"base64",
"byteorder",
"crossbeam-channel",
"flate2",
"nom",
"num-traits",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "libsqlite3-sys"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]]
name = "memchr"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rusqlite"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rusqlite-connection-benchmark"
version = "0.1.0"
dependencies = [
"clap",
"hdrhistogram",
"rusqlite",
]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

View file

@ -0,0 +1,11 @@
[package]
name = "rusqlite-connection-benchmark"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.5", features = ["derive"] }
hdrhistogram = "7.5.2"
rusqlite = "0.34.0"
[workspace]

View file

@ -0,0 +1 @@
../gen-database.py

View file

@ -0,0 +1 @@
../gen-databases

View file

@ -0,0 +1 @@
../plot.py

View file

@ -0,0 +1,17 @@
#!/bin/bash
echo "Building benchmark..."
cargo build --release
echo "Running connection benchmarks..."
echo "database,iterations,p50,p90,p95,p99,p999,p9999,p99999" > results.csv
# Test each database with different table counts
for db in database_10.db database_1k.db database_5k.db database_10k.db
do
echo "Testing $db..."
./target/release/rusqlite-connection-benchmark $db --iterations 1000 | tail -1 >> results.csv
done
echo "Results written to results.csv"
cat results.csv

View file

@ -0,0 +1,50 @@
use clap::Parser;
use hdrhistogram::Histogram;
use rusqlite::Connection;
use std::time::Instant;
#[derive(Parser)]
struct Opts {
database: String,
#[arg(short, long, default_value = "100")]
iterations: usize,
}
fn main() {
let opts = Opts::parse();
let mut hist = Histogram::<u64>::new(2).unwrap();
println!("Testing connection performance with database: {}", opts.database);
for i in 0..opts.iterations {
let start = Instant::now();
// Open connection to database and prepare a statement
let conn = Connection::open(&opts.database).unwrap();
let _stmt = conn.prepare("SELECT name FROM table_0 WHERE id = ?").unwrap();
let elapsed = start.elapsed();
hist.record(elapsed.as_nanos() as u64).unwrap();
if (i + 1) % 10 == 0 {
println!("Completed {} iterations", i + 1);
}
}
// Extract database name and table count for CSV output
let db_name = opts.database.replace(".db", "").replace("database_", "");
println!("database,iterations,p50,p90,p95,p99,p999,p9999,p99999");
println!(
"{},{},{},{},{},{},{},{},{}",
db_name,
opts.iterations,
hist.value_at_quantile(0.5),
hist.value_at_quantile(0.90),
hist.value_at_quantile(0.95),
hist.value_at_quantile(0.99),
hist.value_at_quantile(0.999),
hist.value_at_quantile(0.9999),
hist.value_at_quantile(0.99999)
);
}