From aea3f640ea59d91f7ca9b193acfcc3e26740318b Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 27 Sep 2026 14:46:31 +0100 Subject: Give a compacted file the zeros of its whole cycle (085) Commits grew the tree store's tail 256 KiB at a time, each growth a sync that also committed the ext4 journal for the new size and blocks. A file installed by compaction now carries written zeros up to where the next compaction will come (4 x live + slack, plus a commit's margin), capped at the size of the file it replaces, so a store never takes more space than before and shrinks when its live data does. The cycle's commits then only overwrite blocks the file has. Store commits: 317.5 -> 385.5 a second, ext4 journal commits 109 -> 45 for 4,000 commits; durable app posts +5.3%, their p99 the same within this SD card's noise. The new cycles test keeps sizes still between compactions once the live data is whole, and shows the file shrinking with it. --- store/store-tree.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) (limited to 'store/store-tree.c') diff --git a/store/store-tree.c b/store/store-tree.c index 146e919..024809b 100644 --- a/store/store-tree.c +++ b/store/store-tree.c @@ -925,15 +925,28 @@ static int create_temporary(int directory, char name[96]) { return -1; } +/* Where a fresh file's written zeros should reach: past everything the cycle + * to the next compaction will write (4 x live + slack more), so the commits + * of that cycle neither grow the file nor take new blocks, whose sync costs + * a journal commit (research 084: +22% commits, half the p99). No further + * than the file it replaces reached, so a store never takes more space than + * it did; a store whose live data shrank shrinks with it. */ +static uint64_t cycle_end(const Head *head, uint64_t replaced) { + uint64_t cycle = head->end + 4 * head->live + COMPACT_SLACK + TAIL_MIN; + uint64_t end = cycle < replaced ? cycle : replaced; + return end > tail_end(head->end) ? end : tail_end(head->end); +} + /* Publish a fresh file with both slots pointing at head. It is synced whole - * before it is renamed into place, so its head names no commit bytes. */ -static bool install(int directory, char temporary[96], int fd, Head *head) { + * before it is renamed into place, so its head names no commit bytes. + * replaced: the size of the file it replaces (0 for none). */ +static bool install(int directory, char temporary[96], int fd, Head *head, uint64_t replaced) { unsigned char slot[SLOT]; struct stat file; head->start = head->end; head->check = digest_end(&(Digest){0}); encode_head(slot, head_magic, head); - zero_fill(fd, head->end, tail_end(head->end)); + zero_fill(fd, head->end, cycle_end(head, replaced)); bool ok = write_at(fd, slot, SLOT, 0) && write_at(fd, slot, SLOT, BLOCK) && sync_fd(fd) && !fstat(fd, &file); if (close(fd)) ok = false; ok = ok && !renameat(directory, temporary, directory, tree_name); @@ -971,7 +984,7 @@ static void compact(int directory, int source, const struct stat *file, const He write_root(&tree, writer, &fresh, false) && flush(writer); if (ok) { Head compacted = {head->seq + 1, writer->at, fresh.ref, fresh.bytes, writer->at - DATA, 0, 0}; - ok = install(directory, temporary, fd, &compacted); + ok = install(directory, temporary, fd, &compacted, (uint64_t)file->st_size); fd = -1; } if (fd >= 0) close(fd); @@ -1010,7 +1023,7 @@ PureResult pure_store_init(const char *path) { char temporary[96]; int created_fd = create_temporary(directory, temporary); head = (Head){.seq = 1, .end = DATA}; - ok = created_fd >= 0 && install(directory, temporary, created_fd, &head); + ok = created_fd >= 0 && install(directory, temporary, created_fd, &head, 0); if (*temporary) unlinkat(directory, temporary, 0); } } -- cgit v1.2.3