> blog // engineering

Bring your own OpenSSL: SQL Server scans, three times faster

September 29, 2026 · lib/x/openssl, lib/e/net/tls.e, lib/x/microsoft/tds.e, benchmarks/db

Neper's SQL Server driver speaks TDS inside TLS 1.3, and every byte it reads is decrypted by Neper's own AES-GCM. That cipher is portable and constant-time, and it is slow: it held the driver's scans to 28–35% of the fastest language in the e.db benchmark. A program can now hand record encryption to the host's OpenSSL, if the host has one. Scans get 3.2–3.6 times faster, and on Windows Neper's scan is now the fastest of the five. Neper still ships no OpenSSL, and a program that asks for it still runs where there is none.

Where the time went

SQL Server 2022 and later can run TDS entirely inside TLS: the handshake comes before the first TDS byte, so the login and every result travel encrypted. Neper's driver, x.microsoft.tds, only connects that way. So a scan of 20,000 rows arrives as about 600 KB of TLS records, and each record is authenticated and decrypted with AES-128-GCM before the driver reads a byte of it.

e.crypto.aead's AES-GCM is written in portable Neper. It avoids secret-dependent table lookups and branches: four blocks at a time, bitsliced into eight 64-bit words, with a GHASH built from masked integer products (D1645). That is already 63 times faster than the version it replaced. It still runs on general-purpose instructions, while OpenSSL, Go and rustls use the CPU's AES and carry-less-multiply instructions. Sealing one record, measured from the same Neper program on the same laptop:

One TLS recordNeper AES-GCMOpenSSLFactor
100 bytes, Windows11.1 µs0.47 µs24×
100 bytes, Linux11.7 µs0.70 µs17×
16 KB, Windows669 µs2.5 µs263×
16 KB, Linux730 µs2.8 µs262×

A lookup sends one small record and reads one back, so the cipher costs it tens of microseconds out of a couple of hundred. A scan is almost all large records, and there the cipher was most of the time.

What moves to OpenSSL, and what does not

Only the record cipher moves. e.net.tls has a new hook, use_aead, that a caller sets before the handshake. Its cipher must seal and open exactly as e.crypto.aead's AES-128-GCM does, and from then on every protected record goes through it: the handshake's encrypted messages and the application data (D1646). Everything else stays in Neper:

Handing all of TLS to libssl was the other design, and it would have moved certificate policy out of Neper as well. The narrow hook keeps the part that decides whom to trust, verified and portable, and outsources only arithmetic whose output is fully specified and checked.

Using it

x.openssl.crypto adapts libcrypto's EVP AES-128-GCM to the hook. load answers NotFound when the host has no OpenSSL, so the natural shape is to try it and fall back:

use x.microsoft.tds
use x.openssl.crypto

// The host's OpenSSL if it has one; Neper's own cipher otherwise.
let (openssl, load_error) = crypto.load(a)
var c: db.Connection = zero
if load_error == ok {
    c = try tds.open_with_cipher(a, options, crypto.aead_of(openssl))
} else {
    c = try tds.open(a, options)
}

Everything after that is plain e.db, and the connection behaves identically either way. The hook is not specific to SQL Server: any e.net.tls client can take it before its handshake.

var stream = try tls.client(a, source, sink, config)
try tls.use_aead(&stream, crypto.aead_of(openssl))
try tls.handshake(&stream)

One Crypto holds one OpenSSL cipher context, so it serves one connection on one thread; a program with several connections loads one per connection.

Optional, and nothing we ship

How it is tested

A new fixture, link/x_openssl, runs on both hosts with one exit code per check:

With PATH cut down to the Windows system directories, load answers NotFound, and the fixture fails when told OpenSSL is required and passes when it is not. And link/x_tds stores 100 KB of text and bytes in SQL Server through an OpenSSL-sealed connection and reads them back through a portable one.

Performance

The workload is the e.db benchmark's: 20,000 rows of (bigint, text, double) inserted through one prepared statement in one transaction, scanned back through one streaming reader, and 5,000 primary-key lookups. SQL Server 2025 Developer ran locally: an instance on Windows 11, and Microsoft's mssql-server package under WSL2 Ubuntu 24.04, on a Core i5-12500H laptop.

All five check the server's certificate against the same pinned one. Each round ran every implementation once, alternated, 9 rounds after a warm-up; the tables report medians. The interquartile spread was 3–14% on Linux and up to 43% on Windows, so read the Windows cells as rough.

1st2ndlastin each row

Windows 11NeperNeper + OpenSSLCGoRust
Insert, rows/s6,0217,5918,9127,0937,570
Scan, rows/s707,3892,572,5461,719,336987,1181,942,502
Lookup, µs160.3135.3121.2171.0126.1
Linux (WSL2)NeperNeper + OpenSSLCGoRust
Insert, rows/s3,7204,2664,4043,5354,601
Scan, rows/s618,0791,962,3731,910,7841,226,2452,015,439
Lookup, µs261.8237.0235.1299.5223.6
Scans: from last to first.

With OpenSSL, Neper's scan goes from 707,389 to 2,572,546 rows per second on Windows (3.6×) and from 618,079 to 1,962,373 on Linux (3.2×). On Windows that is the fastest scan of the five, ahead of Rust's 1.94 million and C's 1.72 million. On Linux it is second, 3% behind Rust and ahead of C. Without it, Neper's scan is last on both hosts.

Inserts and lookups gain less, as they should. Each statement waits on a round trip to the server, and its records are small, so the cipher was a smaller share of the time. With OpenSSL, inserts are 15% faster on Linux and 26% on Windows, and lookups take 9% and 16% less time. That brings Neper's lookups within 1% of C's on Linux and 12% on Windows, and its inserts within 7% of Rust's on Linux; on Windows they match Rust's and trail C's by 15%.

Why the scan can win. Once the cipher is the same code the others run, what is left is the driver. Neper's parses TDS packets in place and copies each value once, into the reader's row. The C baseline goes through ODBC, which asks for every value with a separate SQLGetData call and converts the text to UTF-16. So Neper's scan is not fast because of OpenSSL; OpenSSL just stops hiding it.

What comes next

The better fix is for Neper not to need OpenSSL at all. Item C098 in the compiler backlog is to emit the AES and carry-less-multiply instructions directly, behind a CPU check, with the portable code as the fallback. The compiler already does this for the SHA-256 and CRC-32C it runs over its own build artifacts. Until then, OpenSSL is one line away for programs that want it, and nothing changes for those that do not.

Try it

The package is lib/x/openssl, specified in docs/packages/x.openssl.crypto.md; the hook is tls.use_aead, and the SQL Server entry point tds.open_with_cipher. To reproduce the tables, build benchmarks/db/src/sqlserver.e in release mode as <prefix>-sqlserver, set up the servers as the e.db post describes, and run:

$ pwsh benchmarks/db/run-windows-all.ps1 build/bench/neper results-windows.json -Drivers sqlserver -Openssl
$ NEPER_BENCH_OPENSSL=1 bash benchmarks/db/run-linux-all.sh build/linux/bench/neper results-linux.json 9 sqlserver

On Windows the script puts <tools>\openssl first on PATH; put a libcrypto-3-x64.dll there. The raw samples are in benchmarks/db/results/, as windows-2026-09-29-sqlserver-openssl.json and linux-2026-09-29-sqlserver-openssl.json.