microsoft / microsoft/mssql-rs
mssql-odbc: fetch path is significantly slower than msodbcsql18
- Dominant language
- Rust
- Stars
- 53
- Forks
- 14
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 137
Description
## Description
Benchmarking `mssql-odbc` against the native `msodbcsql18` driver with a C++ Google Benchmark harness (`mssql-odbc/tests/perf`) against a local SQL Server 2022 shows the fetch path is substantially slower.
Baseline measurements (median, 10k rows, 7 reps):
| Benchmark | mssql-odbc | msodbcsql18 | Ratio |
|---|---|---|---|
| `BM_Fetch_NarrowRows/10000` | 17.34 ms | 4.84 ms | 3.6× slower |
| `BM_Fetch_WideRows/10000` | 49.8 ms | 42.4 ms | 1.17× slower |
| `BM_Type_VarcharMax` | — | — | ~1049× slower |
| `BM_ExecDirect_SelectOne` | 218 µs | 119 µs | 1.8× slower |
## Analysis
The row path constructs and polls a fresh async state machine per row. `#[async_trait]` boxes every trait method call, so both the size and the frequency of that construction cost real time per row. Each `SQLFetch` pays:
- a boxed `#[async_trait]` future for the transport read
- a cancellation wrapper and a timeout wrapper
- two `Instant::now()` calls
- several `Arc` clones for metadata / parser context / decryptor
- a full ODBC mutex + DBC handle handoff
...all for a single row.
Additionally, every variable-length column allocates a fresh `Vec` that is freed when the row buffer is recycled, so wide rows pay an allocate/free pair per column.
`SQLGetData` on `varchar(max)` re-materialized and re-converted the entire payload on every chunked call, giving quadratic behavior.
## Scope
- [x] Batch row prefetch so the per-row fixed costs are amortized across a rowset
- [x] Recycle column byte buffers across batched rows
- [x] Fix the `varchar(max)` chunked-read quadratic behavior
- [ ] Investigate the execute path (`BM_ExecDirect_SelectOne`, `BM_PreparedExecute`), still ~1.5–1.8× slower
## Repro
```powershell
cd mssql-odbc\tests\perf
.\run_perf.ps1 -Uid -Pwd -MinTime 1.0 -Repetitions 7
```
Contributor guide
Research direction
Start in mssql-odbc/tests/perf and run run_perf.ps1 with the documented SQL Server credentials and benchmark settings. Compare BM_ExecDirect_SelectOne and BM_PreparedExecute against msodbcsql18, then identify and address the remaining execute-path overhead and rerun the benchmarks to verify the gap.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, rust, sql
- Domain
- backend, databases, performance
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100