import gleam/dynamic/decode
import gleam/int
import gleam/io
import gleam/result
import libsql
pub fn main() {
io.println("=== LibSQL Benchmark ===")
io.println("")
let _ = benchmark_local_operations()
io.println("")
let _ = benchmark_remote_operations()
io.println("")
let _ = benchmark_replica_operations()
io.println("")
let _ = benchmark_synced_operations()
io.println("")
io.println("=== Benchmark Complete ===")
Nil
}
fn benchmark_local_operations() {
io.println("--- Local Database Benchmarks ---")
let assert Ok(conn) = libsql.open(":memory:")
benchmark_exec(conn)
benchmark_insert(conn)
benchmark_select(conn)
benchmark_batch_insert(conn)
benchmark_prepared_statements(conn)
benchmark_transactions(conn)
let assert Ok(_) = libsql.close(conn)
}
fn print_result(label: String, iterations: Int, elapsed: Int) {
let qps = case elapsed {
0 -> 0
_ -> {iterations * 1000} / elapsed
}
io.println(label <> ": " <> int.to_string(iterations) <> " in " <> int.to_string(elapsed) <> "ms (" <> int.to_string(qps) <> " qps)")
}
fn print_result_rows(label: String, rows: Int, elapsed: Int) {
let qps = case elapsed {
0 -> 0
_ -> {rows * 1000} / elapsed
}
io.println(label <> ": " <> int.to_string(rows) <> " rows in " <> int.to_string(elapsed) <> "ms (" <> int.to_string(qps) <> " rows/sec)")
}
fn benchmark_exec(conn) {
let iterations = 1000
let start = now_ms()
let _ = run_times(iterations, fn(_) {
libsql.exec("select 1", conn)
})
let elapsed = now_ms() - start
print_result("exec('select 1')", iterations, elapsed)
}
fn benchmark_insert(conn) {
let assert Ok(_) = libsql.exec("create table bench_insert (id integer primary key, value text)", conn)
let iterations = 5000
let start = now_ms()
let _ = run_times(iterations, fn(i) {
let sql = "insert into bench_insert (value) values ('row_" <> int.to_string(i) <> "')"
libsql.exec(sql, conn)
})
let elapsed = now_ms() - start
print_result("insert", iterations, elapsed)
}
fn benchmark_select(conn) {
let assert Ok(_) = libsql.exec("create table bench_select (id integer primary key, value text)", conn)
let _ = run_times(1000, fn(i) {
let sql = "insert into bench_select (value) values ('row_" <> int.to_string(i) <> "')"
libsql.exec(sql, conn)
})
let iterations = 500
let start = now_ms()
let _ = run_times(iterations, fn(_) {
libsql.query(
"select value from bench_select where id = ?",
conn,
[libsql.int(500)],
decode.field(0, decode.string, decode.success),
)
})
let elapsed = now_ms() - start
print_result("select with param", iterations, elapsed)
}
fn benchmark_batch_insert(conn) {
let assert Ok(_) = libsql.exec("create table bench_batch (id integer primary key, name text, score real)", conn)
let batches = generate_batches(1000)
let iterations = 10
let start = now_ms()
let _ = run_times(iterations, fn(_) {
libsql.exec_batch(
"insert into bench_batch (name, score) values (?, ?)",
conn,
batches,
)
})
let elapsed = now_ms() - start
let total_rows = iterations * 1000
print_result_rows("batch insert", total_rows, elapsed)
}
fn generate_batches(count: Int) -> List(List(libsql.Value)) {
generate_batches_loop(count, [])
}
fn generate_batches_loop(n: Int, acc: List(List(libsql.Value))) -> List(List(libsql.Value)) {
case n {
0 -> acc
_ -> {
let batch = [libsql.text("name_" <> int.to_string(n)), libsql.float(int.to_float(n))]
generate_batches_loop(n - 1, [batch, ..acc])
}
}
}
fn benchmark_prepared_statements(conn) {
let assert Ok(_) = libsql.exec("create table bench_prepared (id integer primary key, name text)", conn)
let assert Ok(stmt) = libsql.prepare("insert into bench_prepared (name) values (?)", conn)
let iterations = 5000
let start = now_ms()
let _ = run_times(iterations, fn(i) {
libsql.exec_prepared(stmt, [libsql.text("name_" <> int.to_string(i))])
})
let _ = libsql.finalize(stmt)
let elapsed = now_ms() - start
print_result("prepared insert", iterations, elapsed)
}
fn benchmark_transactions(conn) {
let assert Ok(_) = libsql.exec("create table bench_tx (id integer primary key, name text)", conn)
let iterations = 100
let start = now_ms()
let _ = run_times(iterations, fn(i) {
libsql.transaction(conn, fn() {
use _ <- result.try(libsql.exec("insert into bench_tx (name) values ('tx_" <> int.to_string(i) <> "_1')", conn))
use _ <- result.try(libsql.exec("insert into bench_tx (name) values ('tx_" <> int.to_string(i) <> "_2')", conn))
use _ <- result.try(libsql.exec("insert into bench_tx (name) values ('tx_" <> int.to_string(i) <> "_3')", conn))
Ok(Nil)
})
})
let elapsed = now_ms() - start
print_result("transaction (3 inserts)", iterations, elapsed)
}
fn benchmark_remote_operations() {
io.println("--- Remote Database Benchmarks ---")
let url = "libsql://test-rednithin.aws-ap-south-1.turso.io"
let token = "DUMMY_TOKEN"
case libsql.open_remote(url, token) {
Ok(conn) -> {
// Clean up tables from previous runs to keep remote state small
let _ = libsql.exec("drop table if exists bench_remote", conn)
let _ = libsql.exec("drop table if exists bench_synced", conn)
let _ = benchmark_remote_exec(conn)
let _ = benchmark_remote_select(conn)
let _ = libsql.close(conn)
Ok(Nil)
}
Error(err) -> {
io.println("Remote benchmark skipped: " <> err.message)
Error(err)
}
}
}
fn benchmark_remote_exec(conn) {
let iterations = 10
let start = now_ms()
let _ = run_times(iterations, fn(_) {
libsql.exec("select 1", conn)
})
let elapsed = now_ms() - start
print_result("remote exec('select 1')", iterations, elapsed)
}
fn benchmark_remote_select(conn) {
let assert Ok(_) = libsql.exec("drop table if exists bench_remote", conn)
let assert Ok(_) = libsql.exec("create table bench_remote (id integer primary key, value text)", conn)
let _ = run_times(10, fn(i) {
libsql.exec("insert into bench_remote (value) values ('row_" <> int.to_string(i) <> "')", conn)
})
let iterations = 20
let start = now_ms()
let _ = run_times(iterations, fn(_) {
libsql.query(
"select value from bench_remote where id = ?",
conn,
[libsql.int(5)],
decode.field(0, decode.string, decode.success),
)
})
let elapsed = now_ms() - start
print_result("remote select", iterations, elapsed)
let assert Ok(_) = libsql.exec("drop table bench_remote", conn)
}
fn benchmark_replica_operations() {
io.println("--- Replica Database Benchmarks ---")
let url = "libsql://test-rednithin.aws-ap-south-1.turso.io"
let token = "DUMMY_TOKEN"
let db_path = "/tmp/libsql_benchmark_replica.db"
case libsql.open_replica(db_path, url, token) {
Ok(conn) -> {
let _ = benchmark_replica_sync(conn)
let _ = benchmark_replica_local_query(conn)
let _ = libsql.close(conn)
Ok(Nil)
}
Error(err) -> {
io.println("Replica benchmark skipped: " <> err.message)
Error(err)
}
}
}
fn benchmark_replica_sync(conn) {
let iterations = 1
let start = now_ms()
let _ = run_times(iterations, fn(_) {
libsql.sync(conn)
})
let elapsed = now_ms() - start
print_result("replica sync", iterations, elapsed)
}
fn benchmark_replica_local_query(conn) {
let _ = libsql.sync(conn)
let iterations = 500
let start = now_ms()
let _ = run_times(iterations, fn(_) {
libsql.query(
"select 1",
conn,
[],
decode.field(0, decode.int, decode.success),
)
})
let elapsed = now_ms() - start
print_result("replica local query", iterations, elapsed)
}
fn benchmark_synced_operations() {
io.println("--- Synced Database Benchmarks ---")
let url = "libsql://test-rednithin.aws-ap-south-1.turso.io"
let token = "DUMMY_TOKEN"
let db_path = "/tmp/libsql_benchmark_synced.db"
case libsql.open_synced_database(db_path, url, token) {
Ok(conn) -> {
// Initial sync to pull remote state
let _ = libsql.sync(conn)
let _ = benchmark_synced_local_insert(conn)
let _ = benchmark_synced_local_query(conn)
let _ = benchmark_synced_push(conn)
// Clean up
let _ = libsql.exec("drop table if exists bench_synced", conn)
let _ = libsql.close(conn)
Ok(Nil)
}
Error(err) -> {
io.println("Synced benchmark skipped: " <> err.message)
Error(err)
}
}
}
fn benchmark_synced_local_insert(conn) {
let assert Ok(_) = libsql.exec("drop table if exists bench_synced", conn)
let assert Ok(_) = libsql.exec("create table bench_synced (id integer primary key, name text)", conn)
let iterations = 1000
let start = now_ms()
let _ = run_times(iterations, fn(i) {
libsql.exec("insert into bench_synced (name) values ('name_" <> int.to_string(i) <> "')", conn)
})
let elapsed = now_ms() - start
print_result("synced local insert", iterations, elapsed)
}
fn benchmark_synced_local_query(conn) {
let iterations = 500
let start = now_ms()
let _ = run_times(iterations, fn(_) {
libsql.query(
"select name from bench_synced where id = ?",
conn,
[libsql.int(500)],
decode.field(0, decode.string, decode.success),
)
})
let elapsed = now_ms() - start
print_result("synced local query", iterations, elapsed)
}
fn benchmark_synced_push(conn) {
let iterations = 3
let start = now_ms()
let _ = run_times(iterations, fn(_) {
libsql.sync(conn)
})
let elapsed = now_ms() - start
print_result("synced push", iterations, elapsed)
}
fn run_times(n: Int, f: fn(Int) -> a) -> Nil {
case n {
0 -> Nil
_ -> {
let _ = f(n)
run_times(n - 1, f)
}
}
}
@external(erlang, "erlang", "monotonic_time")
fn monotonic_time() -> Int
@external(erlang, "erlang", "convert_time_unit")
fn convert_time_unit(time: Int, from: a, to: b) -> Int
fn now_ms() -> Int {
let time = monotonic_time()
convert_time_unit(time, 1_000_000_000, 1_000)
}