memory_pages is a promise about anonymous bytes. Once #757's map_settled is on, a settled page is a mapping of the file, and a mapping costs no anonymous bytes at all. Evicting one therefore gives back nothing the kernel was not already free to take, and it buys a pread on the next read of that page. The bound is doing work against itself.
2e61a2b fixed the worst version of this, which was an ordering accident: evict_settled ran the remap before the eviction, so on a bulk load, where the flusher settles pages in bursts and a whole burst lands below the floor at once, every settled page was mapped by one half of the call and unmapped by the other half of the same call. Two syscalls a page for a mapping that never served a read.
What that fix does not touch is the steady state. The floor sits at tail - memory_pages and the read-only boundary sits at tail - mutable_pages, so there is a band of memory_pages - mutable_pages pages between them where a page is settled and still above the floor. Every page transits that band as the log grows. So on a sustained write with both options set, each page is still mapped once and unmapped once, and with a tight bound the band is a page or two wide and the mapping's whole life is the time the tail takes to move two pages. That is churn with a real cost and no benefit.
The shape of the answer looks like this, and it needs deciding rather than guessing:
memory_pages counts anonymous pages only, and a mapped page is out of scope for it. evict_behind would skip a mapped victim instead of unmapping it, which means head has to be able to move past a page whose slot is still populated. Today everything below head is assumed not resident, so this is not a one line change and the invariant needs restating first.
- Or the two options are declared mutually exclusive,
Options::validate refuses a database with both set, and the documentation says a mapped tail is the bound. That is honest and cheap and it gives up the case where somebody wants a hard ceiling on total resident bytes and not just anonymous ones.
- Or eviction unmaps but only under memory pressure it can observe, which is the virtual memory assisted buffer management direction from Leis and colleagues (SIGMOD 2023) and is a much larger piece of work.
There is a measurement that decides which of the first two is right, and it is not taken yet: whether a mapped read that faults is cheaper or dearer than the pread it replaces. That is the #757 A/B, queued on server3. If the mapping wins, the bound should stop evicting mapped pages and the first option is worth its cost. If the mapping loses, map_settled is not becoming the default anyway and the second option costs nothing.
So this is blocked on #757's numbers and should not be picked up before them. Filing it now because the interaction is live in the code today for anybody who sets both options, and the only thing standing between a user and the churn is that map_settled defaults to off.
memory_pagesis a promise about anonymous bytes. Once #757'smap_settledis on, a settled page is a mapping of the file, and a mapping costs no anonymous bytes at all. Evicting one therefore gives back nothing the kernel was not already free to take, and it buys apreadon the next read of that page. The bound is doing work against itself.2e61a2bfixed the worst version of this, which was an ordering accident:evict_settledran the remap before the eviction, so on a bulk load, where the flusher settles pages in bursts and a whole burst lands below the floor at once, every settled page was mapped by one half of the call and unmapped by the other half of the same call. Two syscalls a page for a mapping that never served a read.What that fix does not touch is the steady state. The floor sits at
tail - memory_pagesand the read-only boundary sits attail - mutable_pages, so there is a band ofmemory_pages - mutable_pagespages between them where a page is settled and still above the floor. Every page transits that band as the log grows. So on a sustained write with both options set, each page is still mapped once and unmapped once, and with a tight bound the band is a page or two wide and the mapping's whole life is the time the tail takes to move two pages. That is churn with a real cost and no benefit.The shape of the answer looks like this, and it needs deciding rather than guessing:
memory_pagescounts anonymous pages only, and a mapped page is out of scope for it.evict_behindwould skip a mapped victim instead of unmapping it, which meansheadhas to be able to move past a page whose slot is still populated. Today everything belowheadis assumed not resident, so this is not a one line change and the invariant needs restating first.Options::validaterefuses a database with both set, and the documentation says a mapped tail is the bound. That is honest and cheap and it gives up the case where somebody wants a hard ceiling on total resident bytes and not just anonymous ones.There is a measurement that decides which of the first two is right, and it is not taken yet: whether a mapped read that faults is cheaper or dearer than the
preadit replaces. That is the #757 A/B, queued on server3. If the mapping wins, the bound should stop evicting mapped pages and the first option is worth its cost. If the mapping loses,map_settledis not becoming the default anyway and the second option costs nothing.So this is blocked on #757's numbers and should not be picked up before them. Filing it now because the interaction is live in the code today for anybody who sets both options, and the only thing standing between a user and the churn is that
map_settleddefaults to off.