HomePlaygroundDocsDriversBenchmarksStatus GitHub
315,708
Queries/Second (Zig)
331,885
Queries/Second (Rust)
50M
Total Queries

📊 50 Million Query Benchmark

Prepared statements • Pipeline mode • 10,000 queries per batch

Driver Protocol Queries/Second vs Rust
Rust qail-pg Rust Native tokio 331,885 🏆 100%
Zig (prepared) Zig Native std.net 315,708 95%
Zig (simple query) Zig Simple Query 92,000 28%

🦎 Only 5% slower than native Rust!

Zig's native I/O + Rust encoding = best of both worlds

⚡ Fair Comparison: QAIL vs pg.zig

Single query mode • Both parse responses • Apples-to-apples

📖 Why This Comparison Matters

pg.zig by @karlseguin is a pure Zig PostgreSQL driver using the Extended Query Protocol. We compared single-query performance to test raw driver speed.

  • Initial test: QAIL-Zig was 2.1x faster... but we weren't parsing responses!
  • We built: Response parsing FFI (qail_decode_response, qail_response_get_*)
  • Linker fix: Static lib (119MB) crashed Zig → Dynamic lib (1.7MB) works!
Driver Work Done Queries/Second Rows Parsed
QAIL-Zig Zig Zig I/O + Rust FFI → Parse responses 33,866 🏆 55,000
pg.zig Zig Pure Zig → Parse responses 16,990 55,000

🔧 Response Parsing FFI (Built for this benchmark)

New Functions Added:
qail_decode_response(bytes) → handle
qail_response_row_count(handle) → usize
qail_response_get_i32(handle, row, col)
qail_response_get_string(handle, row, col)
qail_response_free(handle)
Linker Challenge Solved:

• Static library: 119MB → Zig linker segfault 💀
• Dynamic library: 1.7MB → Works perfectly! ✅
• Feature flag: --features response

🔥 QAIL-Zig is 2.0x faster than pg.zig!

Both parse 55,000 rows • Same work • Fair comparison

Why is QAIL Still 2x Faster?

1 msg
Simple Query Protocol
vs 4 msgs (Extended)
Rust
Optimized Decoder
Battle-tested qail-pg
0
Allocations
Buffer reuse ftw

🏗️ Architecture

┌─────────────────────────────────────────────────────────────────┐
│  qail-zig (Zig native I/O)                                      │
│  ├── std.net.tcpConnectToAddress()     ← Native Zig TCP         │
│  ├── stream.write(bytes)               ← Zero-copy send         │
│  └── stream.read(response)             ← Native receive         │
├─────────────────────────────────────────────────────────────────┤
│  qail-encoder (Rust FFI)                ~60MB static lib        │
│  ├── qail_encode_parse()               ← Prepare statement      │
│  ├── qail_encode_bind_execute_batch()  ← Pipeline encoding      │
│  └── qail_encode_sync()                ← Wait for response      │
├─────────────────────────────────────────────────────────────────┤
│  PostgreSQL                             Wire Protocol            │
└─────────────────────────────────────────────────────────────────┘

💡 Key Findings

  • Prepared statements matter: 315K vs 92K q/s (3.4x difference)
  • Wire bytes: 30 bytes/query (prepared) vs 42 bytes/query (simple)
  • FFI overhead: ~1M calls/sec, negligible vs I/O
  • Memory: Zero allocations in hot path (both Rust and Zig)
← Back to All Benchmarks