-
Type:
Task
-
Resolution: Unresolved
-
Priority:
Major - P3
-
None
-
Affects Version/s: None
-
Component/s: Cursors
-
None
-
Storage Engines - Foundations
-
168.652
-
None
-
None
The per-session cursor cache grows without a limit. Cursor sweep runs on the session: every 40 cacheable closes, at most once a second, and from session reset, at most every 30 seconds. It closes cached cursors whose dhandles are dead. Buckets are hashed by URI. Give the sweep an oldest-cursor order and have it close from the tail once the session is over the limit.
A handle that is closed, dropped, or outdated makes reopen return with the cursor still on the cache list. close() then removes it from session->cursors, which is the open-cursor list. A cached cursor has already dropped session_inuse, so the dhandle sweep closes the tree under the entries at the tail. Those closes have to unlink the cache list and drop the dhandle reference.
sls-heavy-schema-dev, shard-00-00, 9K-table M50, about a minute before the 2026-09-21 OOM: 7.9M cached cursors, ~1.2KB each (~10,200 tables x ~770 sessions), 9.0 GiB cursor cache, 14.0 GiB WT cache, 28.3 GiB RSS on a 30.8 GiB host. Sweep closed 32/s while the cache grew 444/s. Ivan flagged that breakdown as an AI pass that still needs reconfirming (thread). Turning the cache off took that M50 from 95%+ memory to ~80%. M30 role flips tracked ~1.3M cached cursors. 50 tables per connection stayed just as heavy (thread).
cache_cursors_max, next to cache_cursors, on wiredtiger_open. open_session inherits it. open_session and session reconfigure can override it. Default 1000. 0 leaves the cache uncapped.
Scope
- cache_cursors_max on wiredtiger_open, open_session, and session reconfigure. Default 1000. 0 uncapped.
- Session sweep closes from the oldest cursor until the session is back at the cap. That includes cursors whose handles are already closed.
- close() stays as it is.
- Test: a session over the cap, one sweep, count back at the cap, oldest cursor gone.