You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
perf(btree): decide page-reuse eligibility at free time, not per allocation
allocate_page evaluated the in-session reuse rule by scanning the whole
`freed` list and, for each entry, all of `free_page_consumed` — both plain
Vecs, so `contains` is linear. Because `reuse_threshold` is the session-start
`next_page_id`, every page freed during the txn sits below it and the cheap
first disjunct is false for nearly every entry, so the full O(|freed| x
|consumed|) scan ran on every single page allocation, with both vectors
growing monotonically through the flush.
The result is unbounded work rather than a deadlock: a server was found
pinning one core at 99.4% for 84 minutes inside a single batch_write, 5048s
of user CPU against 2.9s of system time and 104 read syscalls for the whole
process lifetime, with 99.51% of perf samples in allocate_page.
Both inputs to the eligibility decision are monotonic — reuse_threshold is
fixed for the session and free_page_consumed only grows — so deciding once in
free_page is equivalent to deciding per allocation. Eligible pages go to a
second `freed_reusable` list that allocate_page pops in O(1).
set_reuse_threshold re-partitions, for callers that set it after a free
(normally a walk over two empty vectors), and drain_freed drains both lists
so an eligible page no allocation claimed still reaches the deferred-free
queue.
Three tests cover the eligibility rule, post-hoc threshold setting, and a
hang detector for the scan's return: with the old logic that test does not
finish in 300s, with this change it completes in 0.00s.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,12 @@
2
2
3
3
All notable changes to this project are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
4
5
+
## [Unreleased]
6
+
7
+
### Fixed
8
+
9
+
-**Page allocation no longer rescans the freed list.**`allocate_page` decided in-session reuse eligibility per call, scanning every page freed so far and, for each, every page drawn from the shared free-page cache — both `Vec<u64>`, so the membership test was linear. Since the reuse threshold is the session-start `next_page_id`, every page freed during a transaction fell below it and took the slow arm, making allocation quadratic in the pages a flush touches: a flush large enough would occupy a core indefinitely without completing. Eligibility is now decided once, when the page is freed, and allocation is a pop. Behaviour is unchanged — both inputs to the decision are monotonic within a session, so deciding early is equivalent.
10
+
5
11
## [0.1.0] - 2026-07-28
6
12
7
13
The first release. Pre-releases were published as `0.1.0-beta.N`; the entries below describe `0.1.0` as a whole rather than deltas against a shipped version, since none exists yet.
0 commit comments