RE:(2) [PATCH v2 2/2] f2fs: introduce gcless mount option to avoid foreground GC

Yonggil Song <[email protected]>
Newsgroups org.kernel.vger.linux-doc,net.sourceforge.lists.linux-f2fs-devel,org.kernel.vger.linux-kernel
Message-ID <20260811054736epcms2p2f8831acf8c6072e0c0104ebf554d0450@epcms2p2>
To: Chao Yu <[email protected]>
Cc: [email protected],
    [email protected],
    [email protected],
    [email protected],
    [email protected],
    Dongjin Kim <[email protected]>,
    Daejun Park <[email protected]>
Subject: Re: [PATCH v2 2/2] f2fs: introduce gcless mount option to avoid foreground GC
In-Reply-To: <[email protected]>
References: <20260707225453epcms2p4f8323f4f8b88f3ee892cddc126da51a6@epcms2p4>
 <[email protected]>

Hi Chao,

No problem at all, and thanks a lot for the careful review.

Let me answer the four points in order.  Two of them (the update timing
and the node/data typing) are real defects in v2; for the other two I
think the current code is already correct, and I explain why below so you
can tell me if I am missing something.

On 8/10/26 02:10, Chao Yu wrote:
>> 8 GiB UFS, 2 GiB random overwrite at 99% utilization:
>>    baseline: WAF 76.7, foreground GC calls 578k
>>    gcless:   WAF  1.2, foreground GC calls 504
>
> Can we mitigate this by increasing min_ssr_sections?

I tried it, and it has no effect.  Near-full random overwrite at 99%
utilization, everything fixed except min_ssr_sections: raising it from
the default 66 to 2000 left every counter identical (94 foreground GC
calls, 602823 SSR blocks, FS-WAF 1.53), while lowering it to 0 did
change things (476 GC calls, WAF 1.67) -- so the knob is live, it is
just already saturated at the default.

>> +	if (test_bit(seg, free_i->free_segmap))
>> +		return 0;
>> +	if (test_bit(seg, dirty_i->dirty_segmap[PRE]))
>> +		return 0;
>> +	if (is_curseg(sbi, seg))
>> +		return 0;
>> +	if (ckpt_v >= usable)
>> +		return 0;
>> +
>> +	return usable - ckpt_v;
>
> If SSR is enabled, there may be valid but not checkpointed blocks in section?
> such space can not be treated as free?

Such blocks exist, but they are already excluded, because
ckpt_valid_blocks is not a pure "as of the last checkpoint" counter --
allocation bumps it immediately.  In update_sit_entry_for_alloc():

	/*
	 * SSR should never reuse block which is checkpointed
	 * or newly invalidated.
	 */
	if (!is_sbi_flag_set(sbi, SBI_CP_DISABLED)) {
		if (!f2fs_test_and_set_bit(offset, se->ckpt_valid_map)) {
			se->ckpt_valid_blocks++;
			...
	}

	if (!f2fs_test_bit(offset, se->ckpt_valid_map)) {
		se->ckpt_valid_blocks += del;
		...

So every block written after the last checkpoint (LFS or SSR, and with
checkpointing disabled too, via the second hunk) is counted in
ckpt_valid_blocks right away, and update_sit_entry_for_del() only
decrements it for blocks that are *not* set in ckpt_valid_map.  As a
result ckpt_valid_blocks is exactly the population count of
ckpt_valid_map | cur_valid_map, which is precisely the target_map that
__next_free_blkoff() refuses to allocate from:

	for (i = 0; i < entries; i++)
		target_map[i] = ckpt_map[i] | cur_map[i];

	return __find_rev_next_zero_bit(target_map, BLKS_PER_SEG(sbi), start);

So "usable - ckpt_valid_blocks" is the number of blocks SSR can actually
hand out in that segment, not an upper bound that includes freshly
written blocks.  If you still see a case where the two diverge I would
very much like to know -- that would be a bug in the counter itself.

> And, only updating sbi->cib_total_blocks w/ f2fs_update_cib() in checkpoint()
> is not enough? since free space in section may change due to SSR allocation
> and deletion. right?

You are right, and this is the weaker half of the same argument.  The
per-segment counter is accurate at all times as described above, but
sbi->cib_total_blocks is only a snapshot of it taken at checkpoint time,
so between two checkpoints the aggregate goes stale: SSR allocation
consumes slack that the watermark still credits, and truncation creates
slack that it does not yet see.  The stale direction that matters is the
first one -- it can keep foreground GC skipped after the budget is
already spent.  My reasoning in v2 was that gcless checkpoints often
enough to bound the drift, but "often enough" is not a correctness
argument, and I should not have relied on it.

>> +	if (test_opt(sbi, GCLESS)) {
>> +		unsigned int sec_blks = CAP_BLKS_PER_SEC(sbi);
>> +		unsigned int add_secs = READ_ONCE(sbi->cib_total_blocks) / sec_blks;
>> +		unsigned int room = free_secs < MAIN_SECS(sbi) ?
>> +					MAIN_SECS(sbi) - free_secs : 0;
>> +
>> +		free_secs += min(add_secs, room);
>
> A free section can be reused by any DATA or NODE type write, but above GCLESS
> "free_secs" may not, e.g. all free_secs are from DATA type, then latter checkpoint
> won't write any node into DATA type section w/ SSR.

Agreed, this is a real defect.  SSR never crosses the node/data boundary:
select_policy() restricts the victim search to dirty_segmap[type], and
get_ssr_segment() only falls back among the three node types or among the
three data types, never between them.  So crediting one pooled block count
to free_secs is wrong exactly as you describe -- a budget made entirely of
data-type slack would still let has_not_enough_free_secs() claim there is
room for node writes, and the node allocation would then have to fall back
to a real free section that the watermark just pretended existed.

I will take both points into account and validate the result carefully
before sending the next version.

Thanks a lot for taking the time to review this.

Thanks,
Yonggil
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.