From 29646b6f1cfc252532162b790775f4097cda2513 Mon Sep 17 00:00:00 2001 From: SecretLUL <18047775+SecretLUL@users.noreply.github.com> Date: Tue, 29 Sep 2026 22:33:52 +0200 Subject: [PATCH] journal-file: don't zero freshly allocated hash tables The hash tables are only ever set up in newly created journal files, in space that was just allocated via posix_fallocate() and hence reads as zeroes anyway, so zeroing the items explicitly is unnecessary. It is also harmful: the sequential write faults of the memzero() make the kernel's mmap readahead instantiate the table in ever larger folios, up to the size of the readahead window, on file systems that support large folios in the page cache (e.g. XFS, and ext4 since kernel 6.16). And since write faults on shared file mappings dirty the whole folio, each later update of a single 16 byte hash bucket then has the whole folio written back. Without the explicit zeroing, the table's pages are only faulted in when buckets are first accessed, in random order, and hence mostly as order-0 folios. Measured on Linux 6.18 with a new journal file (SystemMaxFileSize=50M), appending 15 syslog-like entries with 23 fields each per second for 120s, while writing back the file's dirty pages every second, as happens on machines under page cache pressure, where reclaim keeps waking up the flusher threads. Bytes written to the (loop) device, median and range of 4-5 runs: before after ext4 182.8M (178.4M-185.7M) 41.2M (31.6M-41.9M) xfs 172.7M (161.0M-182.4M) 40.1M (39.7M-44.9M) This also avoids writing out the whole data hash table once for every new journal file, i.e. ~2.8% of the maximum file size. Related: #40262 (cherry picked from commit 6db7cf38d40e8fe6a11392d2f0cc044ed5acf812) --- src/libsystemd/sd-journal/journal-file.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index bbfe3631c3..950b22f067 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -1298,7 +1298,6 @@ int journal_file_append_object( static int journal_file_setup_data_hash_table(JournalFile *f) { uint64_t s, p; - Object *o; int r; assert(f); @@ -1314,15 +1313,21 @@ static int journal_file_setup_data_hash_table(JournalFile *f) { log_debug("Reserving %"PRIu64" entries in data hash table.", s); + /* We deliberately don't zero the hash table items: the table is placed in a newly created file, in + * space that was just allocated via posix_fallocate() and hence reads as zeroes anyway. Writing the + * zeroes through the memory map would not only write out the whole table once for no reason: the + * sequential write faults would also make the kernel's mmap readahead instantiate most of the table + * in large folios, on file systems that support them. And since write faults on shared file mappings + * dirty the whole folio, each later update of a single hash bucket would then cause the whole folio + * to be written back. See #40262. */ + r = journal_file_append_object(f, OBJECT_DATA_HASH_TABLE, offsetof(Object, hash_table.items) + s * sizeof(HashItem), - &o, &p); + /* ret_object= */ NULL, &p); if (r < 0) return r; - memzero(o->hash_table.items, s * sizeof(HashItem)); - f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items)); f->header->data_hash_table_size = htole64(s * sizeof(HashItem)); @@ -1331,7 +1336,6 @@ static int journal_file_setup_data_hash_table(JournalFile *f) { static int journal_file_setup_field_hash_table(JournalFile *f) { uint64_t s, p; - Object *o; int r; assert(f); @@ -1343,15 +1347,14 @@ static int journal_file_setup_field_hash_table(JournalFile *f) { s = DEFAULT_FIELD_HASH_TABLE_SIZE; log_debug("Reserving %"PRIu64" entries in field hash table.", s); + /* No need to zero the items, see journal_file_setup_data_hash_table(). */ r = journal_file_append_object(f, OBJECT_FIELD_HASH_TABLE, offsetof(Object, hash_table.items) + s * sizeof(HashItem), - &o, &p); + /* ret_object= */ NULL, &p); if (r < 0) return r; - memzero(o->hash_table.items, s * sizeof(HashItem)); - f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items)); f->header->field_hash_table_size = htole64(s * sizeof(HashItem));