DEV Community

Cover image for Does AbortSignal Cancel a Node.js Database Query? 4 Drivers Tested

Does AbortSignal Cancel a Node.js Database Query? 4 Drivers Tested

Shubhra Pokhariya on November 02, 2026

I let 12 Node.js clients walk away from their database queries on a 4-connection pool. The abandoned queries kept every connection busy for about 6...
Collapse
 
alexshev profile image
Alex Shev •

The key operational question is what happens after the caller leaves. I’d track cancellation propagation, connection release time, server-side query lifetime, and pool saturation separately; a canceled HTTP request is not proof that the database stopped doing work. Those measurements make driver behavior actionable for capacity planning.

Collapse
 
shubhradev profile image
Shubhra Pokhariya •

Thanks, Alex. That's exactly why I wanted to look beyond what the driver reports. I used a separate observer connection to track the tagged queries in pg_stat_activity and SHOW FULL PROCESSLIST, which let me see what was actually happening on the database side. Across the cancel runs, 38 of 39 queries stopped within 136 ms.

I also measured the impact on pool availability through database-observed connection-seconds and the wait time for the first healthy select 1. I didn't separately measure connection release time from the client pool's perspective, though. A query stopping on the database side and a connection becoming available to the client pool aren't necessarily the same event.

Collapse
 
alexshev profile image
Alex Shev •

That separation between database cancellation and client-pool availability is the key operational detail. Measuring the first successful checkout after cancellation alongside the server-side stop time would expose whether the pool still has hidden cleanup work that can amplify under load.

Picked as gem Thread Thread
 
shubhradev profile image
Shubhra Pokhariya •

I did measure the first successful query after cancellation, though I didn't isolate pool checkout time from the query round trip.

In Test 1, the follow-up select 1 goes through the same one-connection pool immediately after the abort. Across the cancel runs, it completed in 5–105 ms, with one mysql2 run taking 595 ms. In that same run, the database-side query stop took 651 ms. Test 2 measured the same kind of follow-up query with a four-connection pool.

What I haven't measured separately is the time spent waiting for a connection checkout, independent of query execution. I also haven't run repeated cancellation cycles under sustained load. One case worth looking at further was postgres.js 3.4.9: four cancelled promises never settled in every warm-pool run, and the extent of the pool stall varied between runs.

Measuring checkout latency across repeated cancellations under load would be a useful next step.

Collapse
 
panthpatel profile image
Panth Patel •

Your test 2 counts connection-seconds, and there is a second cost on the Node side. With pg, a query nobody cancels still finishes, and the driver turns every row into an object for a client that already left. Our report queries at Oizom touch a million sensor readings, and held as JSON objects that was enough to run a 2 GB droplet out of memory. So an abandoned report there costs heap as well as a pool slot. Did you look at client memory, or only at the database side?

Collapse
 
shubhradev profile image
Shubhra Pokhariya •

Only the database side, so no, I didn't look at client memory. My queries were sleeps, lock waits and select 1, so each one returned a single row. A cost like the one you're describing wouldn't show up in my measurements.

My tests focused on database-side query activity and the impact on pool availability. They don't cover the additional memory cost on the Node.js side when an abandoned query returns a large result set.

Your million-reading report is a really useful example of why that matters. Measuring heap usage alongside connection-seconds with a large result set would be a good follow-up. Thanks for sharing the real-world numbers from Oizom.

Collapse
 
panthpatel profile image
Panth Patel •

Sleeps and select 1 were the right probes for pool slots. The heap half we fixed apart from cancellation: rows now go straight into two typed arrays, never a million objects, so an abandoned report at Oizom costs two flat buffers. Cancelling it still matters for the connection, which is what your test measures.

Thread Thread
 
shubhradev profile image
Shubhra Pokhariya •

Thanks. Two flat buffers are a much better place to be than a million objects.

The connection side is what my test can speak to. If nobody cancels the query, it keeps running on the server, and the connection stays occupied until it finishes. With pg ignoring the signal, the sleep kept running for about 2.5 seconds after the abort. In the pile-up test, the abandoned queries held all four connections until the lock cleared.

I haven't tried this with a real report query, though. My tests used sleeps and lock waits, not large result sets or long-running sorts and aggregates. Testing cancellation with those workloads would show whether the same behavior holds there.

Collapse
 
shubhradev profile image
Shubhra Pokhariya •

In pg 8.23.1, mysql2 3.24.6 and mariadb 3.5.4, passing an AbortSignal didn't cancel the query. What actually stopped it was a cancel sent from a second connection. postgres.js 4.0.0 was the only version I tested that reads the signal, but even there, a fast query already sent behind a running one wasn't rejected when the signal was aborted.

I haven't tested poolers beyond one PgBouncer check. In transaction mode, a stored backend pid ended up cancelling another client's query, and I haven't gone further than that.

If you've run into this behind PgBouncer or RDS Proxy, or handled cancellation through Prisma, Drizzle or Kysely, I'd really like to hear what you found.

Collapse
 
hemapriya_kanagala profile image
Hemapriya Kanagala •

Shubhra, another interesting experiment 😃 I honestly wouldn't have thought about what happens to those database queries after a client disconnects. Loved this one!

Collapse
 
shubhradev profile image
Shubhra Pokhariya •

Thank you, Hema! 😊 I actually wanted to find out what happens in exactly this situation. When a client-side request is aborted, you'd expect the database work to stop too, but that isn't always what happens. In several of the drivers I tested, queries kept running and held connections that other requests needed. I'm really glad you enjoyed reading it!