From 495128c679f22d3509de4fd06d3cc4b5068a5f3a Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Mon, 4 May 2026 17:26:16 -0600 Subject: [PATCH] Phase 21.1: executemany perf - it was the autocommit cliff (2026.05.04.6) Investigation of the Phase 21 baseline finding that executemany(N) cost scaled linearly per-row (1.74 ms x N) regardless of batch size. Root cause: every autocommit=True INSERT forces a server-side transaction-log flush. Not a wire-protocol bug. Numbers: * executemany(1000) autocommit=True: 1.72 s (1.72 ms/row) * executemany(1000) in single txn: 32 ms (32 us/row) 53x speedup from changing the transaction boundary, not the driver. Pure protocol overhead is ~32 us/row -> ~31K rows/sec sustained throughput on a single connection. Comparable to pg8000. Added test_executemany_1000_rows_in_txn benchmark to make this visible. Updated README headline numbers and added a "Performance gotchas" section explaining when autocommit=False matters. Decision: don't pipeline. The remaining 32 us is already excellent; the autocommit gotcha is the real user-facing footgun. Docs > code. If someone reports needing >31K rows/sec single-connection, that becomes Phase 22. --- CHANGELOG.md | 33 + pyproject.toml | 2 +- tests/benchmarks/README.md | 28 +- tests/benchmarks/baseline.json | 927 ++++++++++++++------------- tests/benchmarks/test_insert_perf.py | 66 +- uv.lock | 2 +- 6 files changed, 604 insertions(+), 454 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62f9b75..396312f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,39 @@ All notable changes to `informix-db`. Versioning is [CalVer](https://calver.org/) — `YYYY.MM.DD` for date-based releases, `YYYY.MM.DD.N` for same-day post-releases per PEP 440. +## 2026.05.04.6 — `executemany` perf finding: it was the autocommit cliff + +Investigation of the Phase 21 finding that `executemany(N)` cost scaled linearly per-row (1.74 ms × N) regardless of batch size. **Root cause: every autocommit-True INSERT forces a server-side transaction-log flush.** Not a wire-protocol bug. + +### Added + +- **`test_executemany_1000_rows_in_txn`** benchmark — same workload, but inside a single transaction with one COMMIT at the end. Isolates pure protocol cost from server-storage cost. +- New module-scoped `txn_conn` fixture in `tests/benchmarks/test_insert_perf.py` for autocommit-False benchmarks. + +### Findings + +| Mode | Total | Per row | +|-|-:|-:| +| `executemany(1000)` autocommit=True | 1.72 s | 1.72 ms | +| `executemany(1000)` in single txn | 32 ms | **32 µs** | + +**53× speedup from changing the transaction boundary, not the driver.** Pure protocol overhead is ~32 µs/row → ~31,000 rows/sec sustained throughput on a single connection. Comparable to mature pure-Python drivers (pg8000). + +### Changed + +- **`tests/benchmarks/README.md`** — updated headline numbers to show both modes, added a "Performance gotchas" section explaining when to use `autocommit=False` for bulk loads. +- **`tests/benchmarks/baseline.json`** — refreshed to include the new txn-mode measurement (now 29 entries, was 28). + +### Decision: don't pipeline + +Pipelining BIND+EXECUTE PDUs (writing N without waiting for responses between them) could potentially halve the 32 µs/row figure on loopback. Decided against: + +- The remaining 32 µs is already excellent — single-connection bulk-load performance is not where users hit limits. +- Pipelining adds complexity around TCP send-buffer management, partial-failure semantics, and error reporting (which row failed when 50 are in flight). +- The autocommit gotcha is the *real* user-facing footgun. Better docs > more code. + +If someone reports needing >31K rows/sec single-connection, this becomes Phase 22 work. + ## 2026.05.04.5 — Performance benchmarks (Phase 21) Adds `tests/benchmarks/` — a `pytest-benchmark` driven suite covering codec micro-benchmarks (no server required) and end-to-end SELECT/INSERT/pool/async benchmarks. Establishes a committed `baseline.json` so future PRs can be compared against the floor and regressions caught at review. diff --git a/pyproject.toml b/pyproject.toml index debb729..04b5d3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "informix-db" -version = "2026.05.04.5" +version = "2026.05.04.6" description = "Pure-Python driver for IBM Informix IDS — speaks the SQLI wire protocol over raw sockets. No CSDK, no JVM, no native libraries." readme = "README.md" license = { text = "MIT" } diff --git a/tests/benchmarks/README.md b/tests/benchmarks/README.md index 3afa6cb..df88966 100644 --- a/tests/benchmarks/README.md +++ b/tests/benchmarks/README.md @@ -21,22 +21,40 @@ Performance baselines for `informix-db`. Two layers: | **Cold connect + close** (login handshake) | **11.2 ms** | **89** | | 1000-row SELECT * | 1.56 ms | 640 | | INSERT (single, prepared) | 1.88 ms | 530 | -| `executemany(100 rows)` | 181 ms | 5.5 (i.e. ~550 rows/sec) | -| `executemany(1000 rows)` | 1.74 s | 0.57 (i.e. ~575 rows/sec) | +| `executemany(100)` autocommit=True | 181 ms | ~550 rows/sec | +| `executemany(1000)` autocommit=True | 1.72 s | ~580 rows/sec | +| **`executemany(1000)` in single transaction** | **32 ms** | **~31,000 rows/sec** | ### What these tell you - **Pool gives 72× speedup** over cold connect. If your app opens a connection per request, fix that first. +- **Wrap bulk INSERTs in a transaction.** That's a **53× speedup** over + the autocommit-True default. With autocommit on, each row forces the + server to flush its transaction log; in transaction mode the flush + happens once at COMMIT. Per-row cost drops from 1.72 ms (storage-bound) + to 32 µs (pure protocol). PEP 249's default `autocommit=False` was + designed for this — we just default to `False`. - **Codec is not the bottleneck.** Per-row decode (2.9 µs) is 1000× faster than wire round-trip (177 µs for `SELECT 1`). Network and server-side cost dominate. - **UTF-8 carries no measurable cost.** `decode_varchar_utf8` runs at 216 ns vs `decode_varchar_short` at 170 ns — the 27% delta is the multibyte string walk inherent in UTF-8 decoding, not Phase 20 overhead. -- **`executemany` doesn't scale linearly.** 100 rows in 181 ms = 1.81 ms/row; - 1000 rows in 1.74 s = 1.74 ms/row. Suggests per-row cost dominates over - PREPARE amortization. Worth investigating in Phase 21.x. + +### Performance gotchas + +- **`autocommit=True` + `executemany` is the slowest reasonable pattern.** + Use it only when each row genuinely needs to land independently. For + bulk loads, default `autocommit=False` and call `conn.commit()` at the + end of the batch. +- **Single `INSERT` in a tight loop is 1.88 ms each** — strictly worse + than `executemany` (which saves PREPARE/RELEASE overhead). If you find + yourself looping over `cur.execute("INSERT...")` hundreds of times, + switch to `executemany`. +- **Cold connect is 11 ms.** The login handshake is *expensive* compared + to anything you'll do with the connection. Pool everything in + long-lived processes. ## Regression policy diff --git a/tests/benchmarks/baseline.json b/tests/benchmarks/baseline.json index c8938ab..0abd8b7 100644 --- a/tests/benchmarks/baseline.json +++ b/tests/benchmarks/baseline.json @@ -27,14 +27,14 @@ "arch_string_raw": "x86_64", "vendor_id_raw": "AuthenticAMD", "brand_raw": "AMD Ryzen 9 9950X 16-Core Processor", - "hz_advertised_friendly": "5.3197 GHz", - "hz_actual_friendly": "5.3197 GHz", + "hz_advertised_friendly": "5.4323 GHz", + "hz_actual_friendly": "5.4323 GHz", "hz_advertised": [ - 5319676000, + 5432299000, 0 ], "hz_actual": [ - 5319676000, + 5432299000, 0 ], "model": 68, @@ -227,9 +227,9 @@ } }, "commit_info": { - "id": "bea1a1cd0c6dcb9a4e39f2a9140000877b2bd458", - "time": "2026-05-04T17:13:19-06:00", - "author_time": "2026-05-04T17:13:19-06:00", + "id": "90ce035a00ab2776cbccad314af68d245f1e913b", + "time": "2026-05-04T17:21:12-06:00", + "author_time": "2026-05-04T17:21:12-06:00", "dirty": true, "project": "python-library", "branch": "main" @@ -251,22 +251,22 @@ "warmup": false }, "stats": { - "min": 0.0001684790477156639, - "max": 0.0011105990270152688, - "mean": 0.0002503058588353841, - "stddev": 7.879092215867795e-05, - "rounds": 807, - "median": 0.00022741907741874456, - "iqr": 8.91221861820668e-05, - "q1": 0.00019388252985663712, - "q3": 0.0002830047160387039, - "iqr_outliers": 23, - "stddev_outliers": 127, - "outliers": "127;23", - "ld15iqr": 0.0001684790477156639, - "hd15iqr": 0.000417000032030046, - "ops": 3995.112238494022, - "total": 0.20199682808015496, + "min": 0.0001783499028533697, + "max": 0.0037694860948249698, + "mean": 0.0002708047498068165, + "stddev": 0.0001398595253029065, + "rounds": 920, + "median": 0.00024159951135516167, + "iqr": 0.00010358949657529593, + "q1": 0.00020531500922515988, + "q3": 0.0003089045058004558, + "iqr_outliers": 24, + "stddev_outliers": 53, + "outliers": "53;24", + "ld15iqr": 0.0001783499028533697, + "hd15iqr": 0.000472119078040123, + "ops": 3692.697416545937, + "total": 0.24914036982227117, "iterations": 1 } }, @@ -286,22 +286,22 @@ "warmup": false }, "stats": { - "min": 0.003062664996832609, - "max": 0.029818528098985553, - "mean": 0.004503745338297449, - "stddev": 0.0034922472024760173, - "rounds": 56, - "median": 0.0038903244421817362, - "iqr": 0.0008543684962205589, - "q1": 0.0036485749878920615, - "q3": 0.00450294348411262, - "iqr_outliers": 1, - "stddev_outliers": 1, - "outliers": "1;1", - "ld15iqr": 0.003062664996832609, - "hd15iqr": 0.029818528098985553, - "ops": 222.03742105410205, - "total": 0.25220973894465715, + "min": 0.0033279670169577003, + "max": 0.009101188974454999, + "mean": 0.004628980516309978, + "stddev": 0.001321010090952204, + "rounds": 51, + "median": 0.004433745052665472, + "iqr": 0.0012122082989662886, + "q1": 0.0037467457295861095, + "q3": 0.004958954028552398, + "iqr_outliers": 4, + "stddev_outliers": 5, + "outliers": "5;4", + "ld15iqr": 0.0033279670169577003, + "hd15iqr": 0.00792649108916521, + "ops": 216.0302892778552, + "total": 0.23607800633180887, "iterations": 1 } }, @@ -321,22 +321,22 @@ "warmup": false }, "stats": { - "min": 1.5549943782389164e-07, - "max": 1.6168004367500543e-06, - "mean": 1.6961191494723554e-07, - "stddev": 2.6046845798695555e-08, - "rounds": 61017, - "median": 1.6439938917756081e-07, - "iqr": 3.800960257649406e-09, - "q1": 1.6279984265565873e-07, - "q3": 1.6660080291330813e-07, - "iqr_outliers": 5575, - "stddev_outliers": 2819, - "outliers": "2819;5575", - "ld15iqr": 1.57100148499012e-07, - "hd15iqr": 1.7239945009350778e-07, - "ops": 5895812.215262645, - "total": 0.01034921021433547, + "min": 1.552992034703493e-07, + "max": 9.124004282057285e-07, + "mean": 1.697415664394928e-07, + "stddev": 2.1092413887246777e-08, + "rounds": 46105, + "median": 1.655996311455965e-07, + "iqr": 6.3003972172736995e-09, + "q1": 1.6330042853951454e-07, + "q3": 1.6960082575678824e-07, + "iqr_outliers": 2795, + "stddev_outliers": 1970, + "outliers": "1970;2795", + "ld15iqr": 1.552992034703493e-07, + "hd15iqr": 1.790898386389017e-07, + "ops": 5891308.893726195, + "total": 0.007825934920692817, "iterations": 100 } }, @@ -356,22 +356,22 @@ "warmup": false }, "stats": { - "min": 1.5366822481155396e-07, - "max": 2.6446650736033916e-06, - "mean": 1.6539855734150994e-07, - "stddev": 2.7642353379565506e-08, - "rounds": 193424, - "median": 1.6166983793179194e-07, - "iqr": 2.999634792407352e-09, + "min": 1.536643443008264e-07, + "max": 2.973666414618492e-06, + "mean": 1.66776047066651e-07, + "stddev": 3.477156332297996e-08, + "rounds": 195311, + "median": 1.6200356185436248e-07, + "iqr": 4.334530482689541e-09, "q1": 1.6033494224150975e-07, - "q3": 1.633345770339171e-07, - "iqr_outliers": 14435, - "stddev_outliers": 5898, - "outliers": "5898;14435", - "ld15iqr": 1.5599653124809265e-07, - "hd15iqr": 1.6796402633190154e-07, - "ops": 6046001.948706421, - "total": 0.03199205055522422, + "q3": 1.646694727241993e-07, + "iqr_outliers": 11999, + "stddev_outliers": 7420, + "outliers": "7420;11999", + "ld15iqr": 1.5399806822339693e-07, + "hd15iqr": 1.7133230964342753e-07, + "ops": 5996064.888145216, + "total": 0.032573196528634676, "iterations": 30 } }, @@ -391,23 +391,23 @@ "warmup": false }, "stats": { - "min": 1.6370150088160127e-07, - "max": 4.313103595955504e-05, - "mean": 1.8310772680288325e-07, - "stddev": 1.0266830393448733e-07, - "rounds": 196077, - "median": 1.7740832710707628e-07, - "iqr": 4.0702245853565455e-09, - "q1": 1.7555861699360387e-07, - "q3": 1.7962884157896042e-07, - "iqr_outliers": 17345, - "stddev_outliers": 2844, - "outliers": "2844;17345", - "ld15iqr": 1.696257472590164e-07, - "hd15iqr": 1.858850872075116e-07, - "ops": 5461265.9851132715, - "total": 0.03590321374832894, - "iterations": 27 + "min": 1.6692166145031268e-07, + "max": 3.5261529354521863e-06, + "mean": 1.8541564986363696e-07, + "stddev": 4.0746058984269045e-08, + "rounds": 199206, + "median": 1.7923034297732207e-07, + "iqr": 3.846183132666796e-09, + "q1": 1.7730949016717764e-07, + "q3": 1.8115567329984444e-07, + "iqr_outliers": 14741, + "stddev_outliers": 8889, + "outliers": "8889;14741", + "ld15iqr": 1.715424542243664e-07, + "hd15iqr": 1.8692718675503363e-07, + "ops": 5393288.003118643, + "total": 0.036935909946735665, + "iterations": 26 } }, { @@ -426,23 +426,23 @@ "warmup": false }, "stats": { - "min": 1.5758620253924665e-07, - "max": 1.505169824793421e-06, - "mean": 1.697304733385437e-07, - "stddev": 1.6849243481658756e-08, - "rounds": 194550, - "median": 1.6689541396395912e-07, - "iqr": 3.448302118942672e-09, - "q1": 1.6551850170924745e-07, - "q3": 1.6896680382819012e-07, - "iqr_outliers": 13912, - "stddev_outliers": 5416, - "outliers": "5416;13912", - "ld15iqr": 1.6034805569155464e-07, - "hd15iqr": 1.7414126416732525e-07, - "ops": 5891693.932918009, - "total": 0.03302106358801368, - "iterations": 29 + "min": 1.5533296391367912e-07, + "max": 5.582331990202268e-06, + "mean": 1.7290553887433694e-07, + "stddev": 4.3992245312702247e-08, + "rounds": 196077, + "median": 1.6533304005861283e-07, + "iqr": 4.6643738945325145e-09, + "q1": 1.6366830095648767e-07, + "q3": 1.6833267485102018e-07, + "iqr_outliers": 14681, + "stddev_outliers": 10089, + "outliers": "10089;14681", + "ld15iqr": 1.5699770301580428e-07, + "hd15iqr": 1.7533311620354651e-07, + "ops": 5783504.718878745, + "total": 0.03390279934586336, + "iterations": 30 } }, { @@ -461,22 +461,22 @@ "warmup": false }, "stats": { - "min": 4.490138962864876e-07, - "max": 5.552999209612608e-05, - "mean": 4.85281754471304e-07, - "stddev": 1.8088943191581917e-07, - "rounds": 158229, - "median": 4.700850695371628e-07, - "iqr": 1.0011717677116394e-08, - "q1": 4.6996865421533585e-07, - "q3": 4.799803718924522e-07, - "iqr_outliers": 6768, - "stddev_outliers": 2464, - "outliers": "2464;6768", - "ld15iqr": 4.5902561396360397e-07, - "hd15iqr": 4.989560693502426e-07, - "ops": 2060658.5571910942, - "total": 0.07678564672823995, + "min": 4.4994521886110306e-07, + "max": 5.99490012973547e-05, + "mean": 4.997973695114113e-07, + "stddev": 1.9661595195578896e-07, + "rounds": 149925, + "median": 4.899920895695686e-07, + "iqr": 3.9814040064811707e-08, + "q1": 4.700850695371628e-07, + "q3": 5.098991096019745e-07, + "iqr_outliers": 4099, + "stddev_outliers": 1535, + "outliers": "1535;4099", + "ld15iqr": 4.4994521886110306e-07, + "hd15iqr": 5.699694156646729e-07, + "ops": 2000810.850560445, + "total": 0.07493212062399834, "iterations": 1 } }, @@ -496,23 +496,23 @@ "warmup": false }, "stats": { - "min": 1.9988510757684708e-07, - "max": 5.139969289302826e-06, - "mean": 2.2681721178572217e-07, - "stddev": 5.020622684915522e-08, - "rounds": 184842, - "median": 2.200249582529068e-07, - "iqr": 1.0011717677116394e-08, - "q1": 2.200249582529068e-07, - "q3": 2.300366759300232e-07, - "iqr_outliers": 8620, - "stddev_outliers": 2750, - "outliers": "2750;8620", - "ld15iqr": 2.08965502679348e-07, - "hd15iqr": 2.4994369596242905e-07, - "ops": 4408836.490524872, - "total": 0.041925347060896456, - "iterations": 1 + "min": 1.567738851712596e-07, + "max": 1.403064497055546e-05, + "mean": 1.6506310570564836e-07, + "stddev": 3.4999582115629985e-08, + "rounds": 196082, + "median": 1.6322554720024908e-07, + "iqr": 1.937751808474152e-09, + "q1": 1.6225667129601201e-07, + "q3": 1.6419442310448617e-07, + "iqr_outliers": 11504, + "stddev_outliers": 3249, + "outliers": "3249;11504", + "ld15iqr": 1.5935379891626296e-07, + "hd15iqr": 1.6738645612232146e-07, + "ops": 6058289.014525556, + "total": 0.03236590389297494, + "iterations": 31 } }, { @@ -531,22 +531,22 @@ "warmup": false }, "stats": { - "min": 1.637998502701521e-07, - "max": 6.628001574426889e-07, - "mean": 1.751958236487764e-07, - "stddev": 2.020776505937668e-08, - "rounds": 58279, - "median": 1.7099897377192973e-07, - "iqr": 2.500601112842562e-09, - "q1": 1.697998959571123e-07, - "q3": 1.7230049706995487e-07, - "iqr_outliers": 5666, - "stddev_outliers": 2970, - "outliers": "2970;5666", - "ld15iqr": 1.660897396504879e-07, - "hd15iqr": 1.760898157954216e-07, - "ops": 5707898.619802426, - "total": 0.01021023740642704, + "min": 1.7300015315413474e-07, + "max": 5.746900569647551e-07, + "mean": 1.8009804110252565e-07, + "stddev": 6.7639479585228705e-09, + "rounds": 55618, + "median": 1.792993862181902e-07, + "iqr": 2.0000152289867475e-09, + "q1": 1.7829937860369681e-07, + "q3": 1.8029939383268356e-07, + "iqr_outliers": 2142, + "stddev_outliers": 1315, + "outliers": "1315;2142", + "ld15iqr": 1.752993557602167e-07, + "hd15iqr": 1.8330058082938194e-07, + "ops": 5552531.242861899, + "total": 0.010016692850040271, "iterations": 100 } }, @@ -566,23 +566,23 @@ "warmup": false }, "stats": { - "min": 2.0318177782676436e-07, - "max": 4.1019457223063164e-05, - "mean": 2.2789509404673646e-07, - "stddev": 1.0343380489261818e-07, - "rounds": 197625, - "median": 2.1591338075020097e-07, - "iqr": 7.731035690415999e-09, - "q1": 2.131829122928056e-07, - "q3": 2.2091394798322159e-07, - "iqr_outliers": 19610, - "stddev_outliers": 4836, - "outliers": "4836;19610", - "ld15iqr": 2.0318177782676436e-07, - "hd15iqr": 2.3267718709327959e-07, - "ops": 4387983.8843521625, - "total": 0.04503776796098629, - "iterations": 22 + "min": 1.974985934793949e-07, + "max": 1.2000833521597087e-05, + "mean": 2.105730723877986e-07, + "stddev": 3.294005306439554e-08, + "rounds": 193799, + "median": 2.075006098796924e-07, + "iqr": 4.588703935345023e-09, + "q1": 2.0583199026683965e-07, + "q3": 2.1042069420218468e-07, + "iqr_outliers": 11045, + "stddev_outliers": 3374, + "outliers": "3374;11045", + "ld15iqr": 1.991623624538382e-07, + "hd15iqr": 2.1745897053430477e-07, + "ops": 4748945.288495225, + "total": 0.04080885085568298, + "iterations": 24 } }, { @@ -601,22 +601,22 @@ "warmup": false }, "stats": { - "min": 9.514507837593555e-08, - "max": 6.185949314385652e-07, - "mean": 1.0080823934608823e-07, - "stddev": 1.2268695698472242e-08, - "rounds": 48240, - "median": 9.795010555535555e-08, - "iqr": 1.8003629520535374e-09, - "q1": 9.719980880618096e-08, - "q3": 9.90001717582345e-08, - "iqr_outliers": 5246, - "stddev_outliers": 2396, - "outliers": "2396;5246", - "ld15iqr": 9.514507837593555e-08, - "hd15iqr": 1.0174524504691363e-07, - "ops": 9919824.078732945, - "total": 0.004862989466055297, + "min": 9.495008271187544e-08, + "max": 8.562498260289431e-07, + "mean": 9.819238487790007e-08, + "stddev": 8.207935436892014e-09, + "rounds": 51335, + "median": 9.700015652924776e-08, + "iqr": 8.993083611130741e-10, + "q1": 9.660026989877224e-08, + "q3": 9.749957825988531e-08, + "iqr_outliers": 2476, + "stddev_outliers": 1709, + "outliers": "1709;2476", + "ld15iqr": 9.529991075396538e-08, + "hd15iqr": 9.884999599307776e-08, + "ops": 10184089.135257041, + "total": 0.005040706077707, "iterations": 200 } }, @@ -636,22 +636,22 @@ "warmup": false }, "stats": { - "min": 4.100147634744644e-07, - "max": 5.970010533928871e-06, - "mean": 4.6323939803563367e-07, - "stddev": 6.556051113021353e-08, - "rounds": 109290, + "min": 4.200264811515808e-07, + "max": 6.139976903796196e-06, + "mean": 4.615799622473153e-07, + "stddev": 8.453654720518955e-08, + "rounds": 126742, "median": 4.5995693653821945e-07, - "iqr": 2.0023435354232788e-08, + "iqr": 1.0128132998943329e-08, "q1": 4.4994521886110306e-07, - "q3": 4.6996865421533585e-07, - "iqr_outliers": 2764, - "stddev_outliers": 2699, - "outliers": "2699;2764", - "ld15iqr": 4.199100658297539e-07, - "hd15iqr": 5.098991096019745e-07, - "ops": 2158711.0341661335, - "total": 0.050627433811314404, + "q3": 4.600733518600464e-07, + "iqr_outliers": 8787, + "stddev_outliers": 2251, + "outliers": "2251;8787", + "ld15iqr": 4.390021786093712e-07, + "hd15iqr": 4.789326339960098e-07, + "ops": 2166471.861411086, + "total": 0.05850156757514924, "iterations": 1 } }, @@ -671,22 +671,22 @@ "warmup": false }, "stats": { - "min": 3.7400168366730214e-07, - "max": 1.9949977286159994e-06, - "mean": 4.096341411204851e-07, - "stddev": 4.776310741259248e-08, - "rounds": 123456, - "median": 3.930006641894579e-07, - "iqr": 1.0995427146553972e-08, - "q1": 3.890017978847027e-07, - "q3": 3.9999722503125665e-07, - "iqr_outliers": 15020, - "stddev_outliers": 14329, - "outliers": "14329;15020", - "ld15iqr": 3.7400168366730214e-07, - "hd15iqr": 4.1649909690022466e-07, - "ops": 2441202.770024658, - "total": 0.05057179252617061, + "min": 3.7949648685753344e-07, + "max": 4.142004763707519e-06, + "mean": 4.0244505908328793e-07, + "stddev": 3.517347358384737e-08, + "rounds": 119047, + "median": 3.964989446103573e-07, + "iqr": 8.99890437722203e-09, + "q1": 3.925000783056021e-07, + "q3": 4.014989826828241e-07, + "iqr_outliers": 4942, + "stddev_outliers": 3931, + "outliers": "3931;4942", + "ld15iqr": 3.7949648685753344e-07, + "hd15iqr": 4.1499733924865725e-07, + "ops": 2484811.224364032, + "total": 0.04790987694868818, "iterations": 20 } }, @@ -706,23 +706,23 @@ "warmup": false }, "stats": { - "min": 1.0244955774396658e-07, - "max": 3.0219496693462134e-07, - "mean": 1.0629697690939662e-07, - "stddev": 8.48821369424721e-09, - "rounds": 45683, - "median": 1.0469986591488123e-07, - "iqr": 1.2497184798121468e-09, - "q1": 1.0410032700747252e-07, - "q3": 1.0535004548728467e-07, - "iqr_outliers": 2679, - "stddev_outliers": 2144, - "outliers": "2144;2679", - "ld15iqr": 1.0244955774396658e-07, - "hd15iqr": 1.0724470485001803e-07, - "ops": 9407605.268514466, - "total": 0.0048559647961519655, - "iterations": 200 + "min": 1.2991949915885925e-07, + "max": 4.639965482056141e-06, + "mean": 1.5344735326122993e-07, + "stddev": 3.9628501777315876e-08, + "rounds": 199206, + "median": 1.5005934983491898e-07, + "iqr": 1.1641532182693481e-10, + "q1": 1.4994293451309204e-07, + "q3": 1.5005934983491898e-07, + "iqr_outliers": 78209, + "stddev_outliers": 3670, + "outliers": "3670;78209", + "ld15iqr": 1.4994293451309204e-07, + "hd15iqr": 1.5890691429376602e-07, + "ops": 6516893.115110252, + "total": 0.03056763345375657, + "iterations": 1 } }, { @@ -741,22 +741,22 @@ "warmup": false }, "stats": { - "min": 2.1990854293107986e-07, - "max": 7.010065019130707e-06, - "mean": 2.4848519403796424e-07, - "stddev": 6.42611731375951e-08, - "rounds": 165564, - "median": 2.400483936071396e-07, + "min": 2.200249582529068e-07, + "max": 1.8549966625869274e-05, + "mean": 2.495290070327044e-07, + "stddev": 6.700914428761149e-08, + "rounds": 175750, + "median": 2.4994369596242905e-07, "iqr": 1.0011717677116394e-08, - "q1": 2.3993197828531265e-07, - "q3": 2.4994369596242905e-07, - "iqr_outliers": 11491, - "stddev_outliers": 5860, - "outliers": "5860;11491", - "ld15iqr": 2.2898893803358078e-07, + "q1": 2.400483936071396e-07, + "q3": 2.50060111284256e-07, + "iqr_outliers": 10436, + "stddev_outliers": 1755, + "outliers": "1755;10436", + "ld15iqr": 2.2992026060819626e-07, "hd15iqr": 2.689193934202194e-07, - "ops": 4024384.6474298076, - "total": 0.04114020266570151, + "ops": 4007550.1116747344, + "total": 0.043854722985997796, "iterations": 1 } }, @@ -776,22 +776,22 @@ "warmup": false }, "stats": { - "min": 1.8400605767965317e-06, - "max": 1.8540071323513985e-05, - "mean": 1.959436897679786e-06, - "stddev": 3.0188304181613786e-07, - "rounds": 54231, - "median": 1.9100261852145195e-06, - "iqr": 3.993045538663864e-08, - "q1": 1.8900027498602867e-06, - "q3": 1.9299332052469254e-06, - "iqr_outliers": 3016, - "stddev_outliers": 2698, - "outliers": "2698;3016", - "ld15iqr": 1.8400605767965317e-06, - "hd15iqr": 1.9898870959877968e-06, - "ops": 510350.70391096687, - "total": 0.10626222239807248, + "min": 1.8599675968289375e-06, + "max": 1.343991607427597e-05, + "mean": 1.9514378415340784e-06, + "stddev": 2.2459184590519486e-07, + "rounds": 61501, + "median": 1.920037902891636e-06, + "iqr": 2.991873770952225e-08, + "q1": 1.9100261852145195e-06, + "q3": 1.9399449229240417e-06, + "iqr_outliers": 2196, + "stddev_outliers": 1641, + "outliers": "1641;2196", + "ld15iqr": 1.8689315766096115e-06, + "hd15iqr": 1.9889557734131813e-06, + "ops": 512442.6608504592, + "total": 0.12001537869218737, "iterations": 1 } }, @@ -811,22 +811,22 @@ "warmup": false }, "stats": { - "min": 2.6299385353922844e-06, - "max": 0.00012721994426101446, - "mean": 2.831710488308695e-06, - "stddev": 7.816406611800028e-07, - "rounds": 37065, - "median": 2.749962732195854e-06, - "iqr": 4.9942173063755035e-08, - "q1": 2.720043994486332e-06, - "q3": 2.769986167550087e-06, - "iqr_outliers": 2649, - "stddev_outliers": 1545, - "outliers": "1545;2649", + "min": 2.620043233036995e-06, + "max": 1.5789992175996304e-05, + "mean": 2.7960869296309257e-06, + "stddev": 2.9548134292988817e-07, + "rounds": 39124, + "median": 2.7599744498729706e-06, + "iqr": 5.995389074087143e-08, + "q1": 2.7300557121634483e-06, + "q3": 2.7900096029043198e-06, + "iqr_outliers": 1293, + "stddev_outliers": 1018, + "outliers": "1018;1293", "ld15iqr": 2.649961970746517e-06, - "hd15iqr": 2.8490321710705757e-06, - "ops": 353143.44602977874, - "total": 0.10495734924916178, + "hd15iqr": 2.8799986466765404e-06, + "ops": 357642.671764857, + "total": 0.10939410503488034, "iterations": 1 } }, @@ -846,22 +846,22 @@ "warmup": false }, "stats": { - "min": 2.6399502530694008e-06, - "max": 0.00027023896109312773, - "mean": 2.9332861553407637e-06, - "stddev": 1.5312859856949828e-06, - "rounds": 90581, - "median": 2.7599744498729706e-06, - "iqr": 5.005858838558197e-08, - "q1": 2.739951014518738e-06, - "q3": 2.7900096029043198e-06, - "iqr_outliers": 6430, - "stddev_outliers": 1665, - "outliers": "1665;6430", - "ld15iqr": 2.6689376682043076e-06, - "hd15iqr": 2.8689391911029816e-06, - "ops": 340914.57397678564, - "total": 0.2656999932369217, + "min": 2.620043233036995e-06, + "max": 2.8469949029386044e-05, + "mean": 2.7905499414258814e-06, + "stddev": 3.3678126483548525e-07, + "rounds": 62422, + "median": 2.749962732195854e-06, + "iqr": 5.902256816625595e-08, + "q1": 2.720043994486332e-06, + "q3": 2.779066562652588e-06, + "iqr_outliers": 2264, + "stddev_outliers": 1699, + "outliers": "1699;2264", + "ld15iqr": 2.6399502530694008e-06, + "hd15iqr": 2.869986928999424e-06, + "ops": 358352.3036642133, + "total": 0.17419170844368637, "iterations": 1 } }, @@ -881,22 +881,22 @@ "warmup": false }, "stats": { - "min": 0.0015740980161353946, - "max": 0.005370722035877407, - "mean": 0.0018782802842651379, - "stddev": 0.0002577749899895655, - "rounds": 456, - "median": 0.0018291924498043954, - "iqr": 0.00012459448771551251, - "q1": 0.0017820930224843323, - "q3": 0.0019066875101998448, - "iqr_outliers": 26, - "stddev_outliers": 22, - "outliers": "22;26", - "ld15iqr": 0.001629277947358787, - "hd15iqr": 0.002119177021086216, - "ops": 532.4019042191256, - "total": 0.8564958096249029, + "min": 0.001582358032464981, + "max": 0.0023439470678567886, + "mean": 0.0017878168699458057, + "stddev": 9.797651240016688e-05, + "rounds": 243, + "median": 0.0017855080077424645, + "iqr": 0.00012171000707894564, + "q1": 0.0017153230146504939, + "q3": 0.0018370330217294395, + "iqr_outliers": 4, + "stddev_outliers": 70, + "outliers": "70;4", + "ld15iqr": 0.001582358032464981, + "hd15iqr": 0.0020228370558470488, + "ops": 559.3414050457601, + "total": 0.4344394993968308, "iterations": 1 } }, @@ -916,22 +916,22 @@ "warmup": false }, "stats": { - "min": 0.17070704698562622, - "max": 0.21755445003509521, - "mean": 0.181204935341763, - "stddev": 0.01831974106077427, - "rounds": 6, - "median": 0.1731430985382758, - "iqr": 0.011092103901319206, - "q1": 0.17079490702599287, - "q3": 0.18188701092731208, - "iqr_outliers": 1, - "stddev_outliers": 1, - "outliers": "1;1", - "ld15iqr": 0.17070704698562622, - "hd15iqr": 0.21755445003509521, - "ops": 5.518613486514273, - "total": 1.087229612050578, + "min": 0.16832035908009857, + "max": 0.1755698589840904, + "mean": 0.17103146946257247, + "stddev": 0.0026583298272997696, + "rounds": 7, + "median": 0.17046913609374315, + "iqr": 0.003929688478820026, + "q1": 0.16898605454480276, + "q3": 0.17291574302362278, + "iqr_outliers": 0, + "stddev_outliers": 3, + "outliers": "3;0", + "ld15iqr": 0.16832035908009857, + "hd15iqr": 0.1755698589840904, + "ops": 5.846877204190976, + "total": 1.1972202862380072, "iterations": 1 } }, @@ -951,22 +951,57 @@ "warmup": false }, "stats": { - "min": 1.7284791360143572, - "max": 1.7569332029670477, - "mean": 1.7407544063171372, - "stddev": 0.014623153558453282, + "min": 1.7241007370175794, + "max": 1.8025506250560284, + "mean": 1.770115462364629, + "stddev": 0.040949964893151496, "rounds": 3, - "median": 1.7368508799700066, - "iqr": 0.02134055021451786, - "q1": 1.7305720720032696, - "q3": 1.7519126222177874, + "median": 1.783695025020279, + "iqr": 0.05883741602883674, + "q1": 1.7389993090182543, + "q3": 1.797836725047091, "iqr_outliers": 0, "stddev_outliers": 1, "outliers": "1;0", - "ld15iqr": 1.7284791360143572, - "hd15iqr": 1.7569332029670477, - "ops": 0.5744635753159865, - "total": 5.2222632189514115, + "ld15iqr": 1.7241007370175794, + "hd15iqr": 1.8025506250560284, + "ops": 0.5649348990286422, + "total": 5.310346387093887, + "iterations": 1 + } + }, + { + "group": null, + "name": "test_executemany_1000_rows_in_txn", + "fullname": "tests/benchmarks/test_insert_perf.py::test_executemany_1000_rows_in_txn", + "params": null, + "param": null, + "extra_info": {}, + "options": { + "disable_gc": false, + "timer": "perf_counter", + "min_rounds": 5, + "max_time": 1.0, + "min_time": 5e-06, + "warmup": false + }, + "stats": { + "min": 0.029487275052815676, + "max": 0.031187782995402813, + "mean": 0.03018106399880101, + "stddev": 0.0008923988615717756, + "rounds": 3, + "median": 0.02986813394818455, + "iqr": 0.001275380956940353, + "q1": 0.029582489776657894, + "q3": 0.030857870733598247, + "iqr_outliers": 0, + "stddev_outliers": 1, + "outliers": "1;0", + "ld15iqr": 0.029487275052815676, + "hd15iqr": 0.031187782995402813, + "ops": 33.133358056552495, + "total": 0.09054319199640304, "iterations": 1 } }, @@ -986,22 +1021,22 @@ "warmup": false }, "stats": { - "min": 0.010704944957979023, - "max": 0.011916692950762808, - "mean": 0.011209826008416713, - "stddev": 0.0006434438216560385, + "min": 0.010703847045078874, + "max": 0.010994946002028883, + "mean": 0.010857723001390696, + "stddev": 0.00012220593763959052, "rounds": 5, - "median": 0.010782864061184227, - "iqr": 0.0011864805710501969, - "q1": 0.010726199980126694, - "q3": 0.01191268055117689, + "median": 0.01081350794993341, + "iqr": 0.000195654749404639, + "q1": 0.010780639509903267, + "q3": 0.010976294259307906, "iqr_outliers": 0, "stddev_outliers": 2, "outliers": "2;0", - "ld15iqr": 0.010704944957979023, - "hd15iqr": 0.011916692950762808, - "ops": 89.20745061066663, - "total": 0.05604913004208356, + "ld15iqr": 0.010703847045078874, + "hd15iqr": 0.010994946002028883, + "ops": 92.10034183704231, + "total": 0.05428861500695348, "iterations": 1 } }, @@ -1021,22 +1056,22 @@ "warmup": false }, "stats": { - "min": 0.0001263599842786789, - "max": 0.0004909689305350184, - "mean": 0.0001643969374977797, - "stddev": 3.894678555971167e-05, - "rounds": 1991, - "median": 0.00014666002243757248, - "iqr": 3.866010229103267e-05, - "q1": 0.00013921994832344353, - "q3": 0.0001778800506144762, - "iqr_outliers": 130, - "stddev_outliers": 288, - "outliers": "288;130", - "ld15iqr": 0.0001263599842786789, - "hd15iqr": 0.00023589993361383677, - "ops": 6082.838374124249, - "total": 0.32731430255807936, + "min": 0.00010902993381023407, + "max": 0.002848736010491848, + "mean": 0.00021822520665460928, + "stddev": 0.0001375125435240596, + "rounds": 2183, + "median": 0.00018530897796154022, + "iqr": 9.743723785504699e-05, + "q1": 0.00014857493806630373, + "q3": 0.0002460121759213507, + "iqr_outliers": 111, + "stddev_outliers": 165, + "outliers": "165;111", + "ld15iqr": 0.00010902993381023407, + "hd15iqr": 0.00039244897197932005, + "ops": 4582.422055316121, + "total": 0.4763856261270121, "iterations": 1 } }, @@ -1056,22 +1091,22 @@ "warmup": false }, "stats": { - "min": 0.00021265994291752577, - "max": 0.0014270880492404103, - "mean": 0.0002947074022932206, - "stddev": 7.450309873414455e-05, - "rounds": 2543, - "median": 0.0002772099105641246, - "iqr": 9.807926835492253e-05, - "q1": 0.00023547248565591872, - "q3": 0.00033355175401084125, - "iqr_outliers": 51, - "stddev_outliers": 551, - "outliers": "551;51", - "ld15iqr": 0.00021265994291752577, - "hd15iqr": 0.00048129993956536055, - "ops": 3393.1960725066724, - "total": 0.74944092403166, + "min": 0.00021435902453958988, + "max": 0.0026991659542545676, + "mean": 0.00034451136280134787, + "stddev": 0.0001381192549398773, + "rounds": 1734, + "median": 0.000307839538436383, + "iqr": 0.0001273100497201085, + "q1": 0.0002589399227872491, + "q3": 0.0003862499725073576, + "iqr_outliers": 81, + "stddev_outliers": 186, + "outliers": "186;81", + "ld15iqr": 0.00021435902453958988, + "hd15iqr": 0.0005773489829152822, + "ops": 2902.661879911984, + "total": 0.5973827030975372, "iterations": 1 } }, @@ -1091,22 +1126,22 @@ "warmup": false }, "stats": { - "min": 0.0001050999853760004, - "max": 0.0007763390894979239, - "mean": 0.0001522257840901744, - "stddev": 4.6364394881986185e-05, - "rounds": 3415, - "median": 0.00013632990885525942, - "iqr": 5.4123258450999856e-05, - "q1": 0.00011928676394745708, - "q3": 0.00017341002239845693, - "iqr_outliers": 110, - "stddev_outliers": 523, - "outliers": "523;110", - "ld15iqr": 0.0001050999853760004, - "hd15iqr": 0.0002547700423747301, - "ops": 6569.1893523611425, - "total": 0.5198510526679456, + "min": 0.00010571896564215422, + "max": 0.0014337979955598712, + "mean": 0.00016123567918431363, + "stddev": 6.081706925207678e-05, + "rounds": 3602, + "median": 0.00014233950059860945, + "iqr": 6.208906415849924e-05, + "q1": 0.0001211799681186676, + "q3": 0.00018326903227716684, + "iqr_outliers": 140, + "stddev_outliers": 436, + "outliers": "436;140", + "ld15iqr": 0.00010571896564215422, + "hd15iqr": 0.00027650000993162394, + "ops": 6202.101204019913, + "total": 0.5807709164218977, "iterations": 1 } }, @@ -1126,22 +1161,22 @@ "warmup": false }, "stats": { - "min": 0.00014620996080338955, - "max": 0.0006190190324559808, - "mean": 0.00019979103793885502, - "stddev": 4.958736138507597e-05, - "rounds": 3632, - "median": 0.0001842849887907505, - "iqr": 5.90750714763999e-05, - "q1": 0.00016182998660951853, - "q3": 0.00022090505808591843, - "iqr_outliers": 138, - "stddev_outliers": 610, - "outliers": "610;138", - "ld15iqr": 0.00014620996080338955, - "hd15iqr": 0.0003099399618804455, - "ops": 5005.229515380188, - "total": 0.7256410497939214, + "min": 0.0001464600209146738, + "max": 0.0006670691072940826, + "mean": 0.00020406637762734778, + "stddev": 5.242213281716161e-05, + "rounds": 2919, + "median": 0.00018970994278788567, + "iqr": 6.735578062944114e-05, + "q1": 0.00016244172002188861, + "q3": 0.00022979750065132976, + "iqr_outliers": 85, + "stddev_outliers": 525, + "outliers": "525;85", + "ld15iqr": 0.0001464600209146738, + "hd15iqr": 0.00033088994678109884, + "ops": 4900.366300548209, + "total": 0.5956697562942281, "iterations": 1 } }, @@ -1161,22 +1196,22 @@ "warmup": false }, "stats": { - "min": 0.00134059798438102, - "max": 0.0039493340300396085, - "mean": 0.0015644898726728505, - "stddev": 0.0001888922071878221, - "rounds": 565, - "median": 0.001525858067907393, - "iqr": 0.00019056489691138268, - "q1": 0.0014467554283328354, - "q3": 0.0016373203252442181, - "iqr_outliers": 20, - "stddev_outliers": 81, - "outliers": "81;20", - "ld15iqr": 0.00134059798438102, - "hd15iqr": 0.0019280769629403949, - "ops": 639.1859848166043, - "total": 0.8839367780601606, + "min": 0.0012891979422420263, + "max": 0.002537296968512237, + "mean": 0.0014767767646464644, + "stddev": 0.00015889450439075735, + "rounds": 536, + "median": 0.0014329130062833428, + "iqr": 0.00017965061124414206, + "q1": 0.0013673279318027198, + "q3": 0.0015469785430468619, + "iqr_outliers": 23, + "stddev_outliers": 110, + "outliers": "110;23", + "ld15iqr": 0.0012891979422420263, + "hd15iqr": 0.0018201080383732915, + "ops": 677.1504156482289, + "total": 0.7915523458505049, "iterations": 1 } }, @@ -1196,26 +1231,26 @@ "warmup": false }, "stats": { - "min": 0.000909739057533443, - "max": 0.0022729469928890467, - "mean": 0.0011316901686076275, - "stddev": 0.00017006841364525387, + "min": 0.0008895490318536758, + "max": 0.001708118012174964, + "mean": 0.0010690036180604182, + "stddev": 0.00012057836413943589, "rounds": 729, - "median": 0.001088408986106515, - "iqr": 0.0001559839874971658, - "q1": 0.0010253940126858652, - "q3": 0.001181378000183031, - "iqr_outliers": 46, - "stddev_outliers": 107, - "outliers": "107;46", - "ld15iqr": 0.000909739057533443, - "hd15iqr": 0.0014172379160299897, - "ops": 883.6340791317008, - "total": 0.8250021329149604, + "median": 0.001042668940499425, + "iqr": 0.0001435203303117305, + "q1": 0.0009819729311857373, + "q3": 0.0011254932614974678, + "iqr_outliers": 28, + "stddev_outliers": 172, + "outliers": "172;28", + "ld15iqr": 0.0008895490318536758, + "hd15iqr": 0.0013541990192607045, + "ops": 935.4505289835995, + "total": 0.7793036375660449, "iterations": 1 } } ], - "datetime": "2026-05-04T23:19:03.076528+00:00", + "datetime": "2026-05-04T23:25:30.942621+00:00", "version": "5.2.3" } \ No newline at end of file diff --git a/tests/benchmarks/test_insert_perf.py b/tests/benchmarks/test_insert_perf.py index 46f9d97..6c85060 100644 --- a/tests/benchmarks/test_insert_perf.py +++ b/tests/benchmarks/test_insert_perf.py @@ -3,19 +3,47 @@ The single-row vs. executemany delta is the ``executemany`` win — we PREPARE+RELEASE once and BIND+EXECUTE per row, vs PREPARE+RELEASE per row. On any decent network this is 10-50x. + +The autocommit-True vs. autocommit-False delta is the **transaction-flush +cost** — every autocommit INSERT forces the server to flush its +transaction log per row, drowning out everything else. The benchmark +splits these so we can see protocol overhead independently. """ from __future__ import annotations import contextlib +from collections.abc import Iterator import pytest import informix_db +from tests.conftest import ConnParams pytestmark = [pytest.mark.benchmark, pytest.mark.integration] +@pytest.fixture(scope="module") +def txn_conn(conn_params: ConnParams) -> Iterator[informix_db.Connection]: + """A separate connection with autocommit=False so we can wrap an + executemany call in a single explicit transaction. Uses ``testdb`` + (the logged user DB) — autocommit-off is meaningless on unlogged DBs. + """ + conn = informix_db.connect( + host=conn_params.host, + port=conn_params.port, + user=conn_params.user, + password=conn_params.password, + database="testdb", + server=conn_params.server, + autocommit=False, + ) + try: + yield conn + finally: + conn.close() + + def _setup_temp_table(conn: informix_db.Connection, name: str) -> None: cur = conn.cursor() with contextlib.suppress(informix_db.Error): @@ -82,7 +110,9 @@ def test_executemany_100_rows( def test_executemany_1000_rows( benchmark, bench_conn: informix_db.Connection ) -> None: - """1000 INSERTs via executemany — sustained-batch throughput.""" + """1000 INSERTs via executemany under autocommit=True — every row + forces a transaction-log flush. Worst-case protocol *plus* server + storage cost.""" table = "p21_ins_emany_1000" _setup_temp_table(bench_conn, table) counter = [0] @@ -104,3 +134,37 @@ def test_executemany_1000_rows( benchmark.pedantic(run, rounds=3, iterations=1) finally: _drop_temp_table(bench_conn, table) + + +def test_executemany_1000_rows_in_txn( + benchmark, txn_conn: informix_db.Connection +) -> None: + """1000 INSERTs via executemany inside ONE transaction — single + log flush at COMMIT time. Isolates the protocol cost from the + autocommit-flush cost. The delta vs the autocommit variant is the + server-side log-flush penalty (un-fixable from the client side).""" + table = "p21_ins_emany_txn" + _setup_temp_table(txn_conn, table) + txn_conn.commit() # Land the CREATE TABLE before timing + counter = [0] + + def run() -> None: + counter[0] += 1 + base = counter[0] * 1000 + rows = [ + (base + i, f"row_{base + i}", float(base + i)) for i in range(1000) + ] + cur = txn_conn.cursor() + cur.executemany( + f"INSERT INTO {table} VALUES (?, ?, ?)", + rows, + ) + cur.close() + txn_conn.commit() + + try: + benchmark.pedantic(run, rounds=3, iterations=1) + finally: + with contextlib.suppress(informix_db.Error): + _drop_temp_table(txn_conn, table) + txn_conn.commit() diff --git a/uv.lock b/uv.lock index ba952e6..4773a08 100644 --- a/uv.lock +++ b/uv.lock @@ -34,7 +34,7 @@ wheels = [ [[package]] name = "informix-db" -version = "2026.5.4.4" +version = "2026.5.4.6" source = { editable = "." } [package.optional-dependencies]