From 8cc91ffa9ee57abbafe7b3e51ec92cb0f006774d Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sun, 24 Aug 2025 23:02:19 -0500 Subject: [PATCH] Prevent oscillation when crystal_thresh < fragment_size When crystal_thresh < fragment_size, there was a risk that repeated write operations would oscillate between crystallizing and fragmenting every operation. Not only would this wreck performance, it would also violently wear down blocks as each crystallization would trigger an erase. Fortunately all we need to do to prevent this is check both fragment_size and crystal_thresh before fragmenting. Note this also affects the fragment checks in truncate/fruncate. --- crystal_thresh < fragment_size is kind of a weird configuration, to be honest we should probably just assert if configured this way (we never write fragments > crystal_thresh, because at that point we would just crystallize). But at the moment the extra leniency is useful for benchmarking. Adds a bit of code, but will probably either assert or mutably limit in the future: code stack ctx before: 37028 2352 684 after: 37076 (+0.1%) 2352 (+0.0%) 684 (+0.0%) --- lfs3.c | 18 ++++++++++++------ lfs3.h | 3 +++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/lfs3.c b/lfs3.c index ca0726ce..32aac670 100644 --- a/lfs3.c +++ b/lfs3.c @@ -13268,7 +13268,9 @@ static int lfs3_file_graft_(lfs3_t *lfs3, lfs3_file_t *file, // carve fragment? } else if (!lfs3_bptr_isbptr(&bptr_) // carve bptr into fragment? - || lfs3_bptr_size(&l) <= lfs3->cfg->fragment_size) { + || (lfs3_bptr_size(&l) <= lfs3->cfg->fragment_size + && lfs3_bptr_size(&l) + < lfs3_max(lfs3->cfg->crystal_thresh, 1))) { rattrs[rattr_count++] = LFS3_RATTR_DATA( LFS3_TAG_GROW | LFS3_TAG_MASK8 | LFS3_TAG_DATA, -(bid+1 - pos), @@ -13316,7 +13318,9 @@ static int lfs3_file_graft_(lfs3_t *lfs3, lfs3_file_t *file, // carve fragment? } else if (!lfs3_bptr_isbptr(&bptr_) // carve bptr into fragment? - || lfs3_bptr_size(&r) <= lfs3->cfg->fragment_size) { + || (lfs3_bptr_size(&r) <= lfs3->cfg->fragment_size + && lfs3_bptr_size(&r) + < lfs3_max(lfs3->cfg->crystal_thresh, 1))) { r_rattr_ = LFS3_RATTR_DATA( LFS3_TAG_DATA, bid+1 - (pos+weight), &r.d); @@ -14964,8 +14968,9 @@ int lfs3_file_truncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) { // discard if our leaf is a fragment, is fragmented, or is completed // truncated, we can't rely on any in-bshrub/btree state if (!lfs3_bptr_isbptr(&file->leaf.bptr) - || lfs3_bptr_size(&file->leaf.bptr) - <= lfs3->cfg->fragment_size) { + || (lfs3_bptr_size(&file->leaf.bptr) <= lfs3->cfg->fragment_size + && lfs3_bptr_size(&file->leaf.bptr) + < lfs3_max(lfs3->cfg->crystal_thresh, 1))) { lfs3_file_discardleaf(file); } @@ -15056,8 +15061,9 @@ int lfs3_file_fruncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) { // discard if our leaf is a fragment, is fragmented, or is completed // truncated, we can't rely on any in-bshrub/btree state if (!lfs3_bptr_isbptr(&file->leaf.bptr) - || lfs3_bptr_size(&file->leaf.bptr) - <= lfs3->cfg->fragment_size) { + || (lfs3_bptr_size(&file->leaf.bptr) <= lfs3->cfg->fragment_size + && lfs3_bptr_size(&file->leaf.bptr) + < lfs3_max(lfs3->cfg->crystal_thresh, 1))) { lfs3_file_discardleaf(file); } diff --git a/lfs3.h b/lfs3.h index 57295055..0a379e41 100644 --- a/lfs3.h +++ b/lfs3.h @@ -566,6 +566,9 @@ struct lfs3_cfg { // allow crystal_thresh=0? crystal_thresh=0 => block_size/16 or // block_size/8 is probably a better default. need to benchmark. + // TODO we should probably just assert if crystal_thresh < fragment_size, + // or if crystal_thresh < prog_size, these aren't really valid cases + // Threshold for compacting multiple fragments into a block. Smaller // values will crystallize more eagerly, reducing disk usage, but // increasing the cost of random-writes.