points by pengaru 22 hours ago

I'm probably the main person responsible for making journald usable at all.

But I never really made any effort to change the on-disk structure or how writes were performed. My focus was more on the read performance for journalctl and stability of the daemon.

Back when I was paid to fix things in journald at CoreOS ages ago, it couldn't even avoid getting killed by its own service watchdog.

My impression back then was the on-disk format dispersed the information too much within the same file, and those individual datums being written at discontiguous offsets were quite small, far smaller than an IO block size or even a disk sector size.

Seemed like a write amplification problem due to the file format. If you write a few bytes into some arbitrary position within a file, the storage has to write back the whole block, despite your only changing a tiny fraction of it. If those few bytes happened to cross a block boundary, guess what? two blocks get written.

The format had no consideration for these block-oriented storage details, then doing the IO via mmap rubs salt into the wound since the kernel has to try guess what to prefetch asynchronously... but I don't think that aspect amplifies the writes above what plain buffered IO would do - maybe I'm wrong. I'd expect the mmap aspect to be causing more/mispredicted reads, and polluting the page cache with unrelated contents (you tend to end up with the entire journal cached IIRC, if you have enough memory). I suppose there's probably compounding of the write amplification problem since the kernel will be dirtying pages at page size granularity vs. 512b sectors, and you have the same issue of small writes landing on page boundaries dirtying two pages. So that aspect of using mmap for the writes probably is exacerbating the problem.

otterley 21 hours ago

> I'm probably the main person responsible for making journald usable at all.

Thank you for your service!

crabbone 9 hours ago

I've met this unwarranted love for mmap() many times in the developers who never professionally worked on storage projects. Especially common with C++ programmers for some reason. There are people who think they found a "trick" to make I/O go faster and never consider why filesystems or databases don't use it... Like, obviously, those losers who wrote eg. Ext4 never bothered to look at the system interface, right?

On the other hand, if I was ever to advise anyone on how to do I/O when they are working with an (unknown) filesystem... It's really hard. And I'd probably default to saying "do as few tricks as possible" because filesystems today are very elaborate, with a lot of optimizations that are very difficult to predict from user-space. It's quite possible that someone trying to outsmart a filesystem will end up harming themselves in the process.

Doing as few tricks as possible would allow the administrator to configure the filesystem independently of the program writing to it to match the nature of the workload instead of locking the program into a specific pattern of operation that might be impossible to rectify with administrative tools. Not an ideal situation by any means: storage-heavy user-space applications s.a. databases usually do the opposite: they try to optimize for the specific filesystem, its version and quirks... but it takes a lot of effort, obviously.

  • ValdikSS 9 hours ago

    Libtorrent 2.0 switched exclusively to mmaped read/writes for torrent downloads, which resulted in various performance and especially memory consumption issues on ALL platforms.

    For some reason Windows handled increased memory consumption the least gracefully.

    Many people continued to use v1.2 which use regular files.

    V2.1 ended up using pread/pwrite nowz it's fine now.

    The issue continued for 3 years more or less.

    https://github.com/arvidn/libtorrent/issues/6667

  • pineapplepizza6 6 hours ago

    The principled excuse for mmap is when you're reading all over a file at high performance and you want to avoid either excessive syscalls or double caching. Which sounds like what a torrent program does but evidently it doesn't even work well for them.

cloudie78 22 hours ago

Why not just have a SQLite file and call it a day?

Also, why mmaped file?

  • pengaru 22 hours ago

    I'm not the architect of journald and wasn't really around when these decisions were made, so I can't really speak authoritatively on that particular topic.

    There was mailing list discussion at the time journald was conceived though, you can find it if you look.

    https://0pointer.de/blog/projects/the-journal.html might be a good entry-point.

    • marginalia_nu 21 hours ago

      Well there was an ambition, apparently.

      > Performance: journal operations for appending and browsing should be fast in terms of complexity. O(log n) or better is highly advisable, in order to provide for organization-wide log monitoring with good performance

      > Minimal Footprint: journal data files should be small in disk size, especially in the light that the amount of data generated might be substantially bigger than on classic syslog.

    • otterley 21 hours ago

      The mailing list archives are here: https://lists.freedesktop.org/archives/systemd-devel/

      It doesn't look like there was an open design review; Lennart Poettering just dropped it in in v38. https://lists.freedesktop.org/archives/systemd-devel/2012-Ja...

      • pengaru 21 hours ago

        FWIW the journal file signature is "LPKSHHRH" for Lennart, Kay Sievers, Harald Hoyer, Red Hat... I presumed it was at least Lennart, Kay, and Harald who collaborated on the design.

        • p_l 9 hours ago

          ... Sounds like a signature on a patch that triggers an epic Linus rant on LKML[1]

          [1] Happened few times, I think RedHat as a whole even got banned from sending changes for a short while

      • giov4 8 hours ago

        I think this also explains a lot and should not be ignored. https://github.com/systemd/systemd/issues/15292#issuecomment...

        It seems a recurring (handling) issue but unfortunately it affects multiple linux distro defaults. This is the worse that can collaboratively happen for FOSS in general imho.

        • brohee 7 hours ago

          Ah, the Ulrich Drepper school of dealing with reported issues. Time for esystemd ;)

        • pineapplepizza6 6 hours ago

          systemd is not a collaborative project. It is Lennart's personal cathedral project and you can take it or leave it. That's fine for Lennart, the question is if it's so bad then why are the rest of us taking it instead of leaving it?

          • otterley 6 hours ago

            Probably because the overall impact is not as bad as extremely vocal people on GitHub and HN would have you believe, and more people like systemd than dislike it.

  • quotemstr 22 hours ago

    SQLite here is okay, but DuckDB or LevelDB would be better. Either way, no need to invent a new storage format.

    • otterley 21 hours ago

      Neither DuckDB nor LevelDB existed when journald was created. Not to say it couldn't be done today, but just some historical context.

      • actionfromafar 9 hours ago

        LevelDB was released in 2011, so it existed but was very new.

    • ElectricalUnion 21 hours ago

      No duckdb (or parquet). If you want to avoid writes and write amplification, you really want to avoid re-writing all 122880 rows of a row group every time a single insert happens.

      • quotemstr 21 hours ago

        Uh, who said anything about writing 122880 rows every time you do a single insert into DuckDB? There's a WAL. Consolidation happens in big chunks. (And it's not like journald log rotation is somehow better than WAL consolidation.)

        We shouldn't be making momentus choices of data format based on vague and incorrect understandings of data formats.

        • dchest 14 hours ago

          Write-Ahead Log for... logs?

          WAL means it will write the same data at least twice. Similar issue, but even worse, with LevelDB -- it will just delay the inevitable huge rewrites for later. Funny to hear those proposals in the write amplification thread.

          I believe journald log rotation is basically: close file - open a new one. How is it not completely different?

          • quotemstr 13 hours ago

            journald does do a rewrite of the log file on rotation, so you're paying that IO anyway even if you ignore the dumb hash table updates.

            https://github.com/systemd/systemd/blob/8f4cd7de43d1e6e94687...

            WAL writeback is at least principled and efficient. It works out to being equivalent to the custom Parquet-rotation things others mention, but already implemented and working.

            So, yes, WAL for logs, because LSM is the design everyone converges on and a WAL is LSM. Better to use the LSM already implemented and debugged in a database than write some random new one in terms of Parquet that's going to have to do the same stuff in the end anyway, just with novel bugs and no tool support.

            (And look, I don't give a damn what "DB" people say, a WAL writing back to a DB IS log... structured... merge under any fucking sensible definition of what LSM means.)

            • pengaru 12 hours ago

              > journald does do a rewrite of the log file on rotation, so you're paying that IO anyway even if you ignore the dumb hash table updates.

              You linked copy_file_atomic_at_full(), why? That function is not in the normal rotation path for journald, it's only used in a workaround when clearing FS_NOCOW_FL fails.

              Rotation does not rewrite the log file normally, but there is a hole-punching operation though for reclaiming unused space.

              • quotemstr 12 hours ago

                > it's only used in a workaround when clearing FS_NOCOW_FL fails.

                Clearing FS_NOCOW_FL doesn't work on btrfs for non-empty files. So what do you think journald is doing when it notices that it can't clear the flag?

                • pengaru 11 hours ago

                  When did this become a discussion limited to journald on btrfs?

                  and that seems like something btrfs should fix at some point

                  • quotemstr 11 hours ago

                    So, yes, journald does in fact do bulk copies of log files on rotate. btrfs is hardly some fringe FS and its COW-flag behavior is documented and well-known. I'd expect extensive work on journald's storage engine to have uncovered this behavior at some point.

                    > When did this become a discussion limited to journald on btrfs?

                    btrfs is in the HN thread title.

                    > and that seems like something btrfs should fix at some point

                    Amazing. The Linux kernel should change to work around journald's inflexibility?

                    What someone should fix at some point is journald's strange IO patterns and hard-coded "helpful" attribute changes. I'd rather it just rename the file and let me do any defrag/compression/flag-setting I want than do anything with chattr behind my back in ways I can't even configure.

                    • pengaru 4 hours ago

                      > btrfs is in the HN thread title.

                      as is ext4

                      • quotemstr 19 minutes ago

                        So write amplification on btrfs doesn't matter?

    • e2le 20 hours ago

      Sqlite3 is present in the default installation of most Linux distributions. It has proven itself from years of battle testing in many different environments. To use DuckDB or LevelDB would probably require pulling in an additional dependency.

  • dmitrygr 14 hours ago

    Because Poettering didn’t invent SQLite.

quotemstr 22 hours ago

Thank you for your work. ISTM the workload is naturally LSM-shaped.

> If you write a few bytes into some arbitrary position within a file, the storage has to write back the whole block, despite your only changing a tiny fraction of it. If those few bytes happened to cross a block boundary, guess what? two blocks get written.

Exactly. So either make the format append-only or make it append-mostly with occasional writebacks from the append-only log to the main data structure. Nice and simple.

> I'd expect the mmap aspect to be causing more/mispredicted reads, and polluting the page cache with unrelated contents (you tend to end up with the entire journal cached IIRC, if you have enough memory).

If you used an LSM or append-only approach, you could MADV_DONTNEED the pages behind your write cursor pretty easily.

  • amluto 21 hours ago

    Append-only -> Parquet -> bigger Parquet would do the trick. Sadly Parquet is useless for the append-only layer. Feather would work but is quite inefficient with a batch size of 1.

    • quotemstr 21 hours ago

      Once you solve enough problems using raw Parquet or Feather or whatever and you end up with something that looks like a DB anyway, so you might as well use a DB.

      • hedora 19 hours ago

        Or, you could write a plain text file.

        Yes, that means the FS will sometimes punch nulls towards the tail of the log. However, it is the lowest latency / write amplification way to get stuff on disk (other than a blocked compression format, which would be a small change to syslog), so if the text file gets holes punched in it, the journalctl file would be truncated before the hole anyway in practice.

        If you really care about nulls in logs for ideological reasons, you could write a few lines of code that finds the first stream of nulls in the text file, then truncates there.

        In practice, no one wants that. It is strictly worse than returning partial entries after the hole, and by the time you are hitting this corner case, you are debugging a kernel crash.

        • p_l 9 hours ago

          Honestly, I would go append-only blocks that contain binary/compressed format with synchronizing marks so worst case you get some nulls but every reader can synchronize where they are in the stream without blocking anyone.

          Might take more space on disk than theoretical best of journald storage format with its absurd hashtables, but it fulfills the job of system log better and more complex format should be done in log aggregation layer.

          • pineapplepizza6 6 hours ago

            You could even generate a bloom filter for every block, and when you have a full block of bloom filters, write them out to an index file.

            • p_l 6 hours ago

              My personal idle walking-with-dog kind of design was a linear binary record file with regular marks letting you resynchronize where you are (and stamp cryptographically) with minimal seeks, and separate indexing files with bloom filters and the like. If the indexes are corrupted or deleted, they can be reconstructed from the main log, main log is single-writer/multiple-readers with no locking in any form necessary, and easier to survive kernel/hw failure

      • amluto 19 hours ago

        The journald schema is surprisingly wide and has a bunch of boilerplate, and one of the goals is to keep the on-disk size under control (and an efficient format directly reduces write amplification). And you kind of want a format that allows a reader to just read the file without blocking concurrent writes. And the ability to use third-party tools to easily read the format is quite nice.

        SQLite gets the last one but misses on the first two (although WAL and the improved read-only support in 3.20+ mostly gets #1). DuckDB might be decent except that you would need to connect through the daemon to read if the daemon is running. If a daemon that coordinates everything is okay, something like Clickhouse might work.

        An LSM-style layer over Parquet gets all of this fairly naturally as long as readers using third party tools understand the LSM scheme. (In general there is a lack of consensus as to exactly how to correctly and efficiently use multiple Parquet files together.)

        • quotemstr 16 hours ago

          SQLite has the problem of a malicious reader being able to hold up writers. Maybe that's fine in most cases, but in a system log, I don't think that's acceptable. IMHO, options are to indirect through a daemon anyway (e.g. using Quack) or do a lot of engineering to make it possible for an unprivileged reader to open() the log file and read it in such a way that it can't interfere with privileged writers.

          Your LSM compaction strategy is going to have to solve the same problem anyway, isn't it? DuckDB is an LSM compaction strategy of this form, already done.

          • amluto 14 hours ago

            I don’t think there’s any hard work here. Other than the append-only part, all files would be either immutable (the Parquet parts) or maybe mutated by wholesale atomic replacement of the inode (the catalog, although the directory itself, via its contained filenames) could maybe do that. Readers might have to retry sometimes, but readers would neither have boy expect any write privileges.

            • quotemstr 13 hours ago

              How do readers get log entries that haven't made their way into one of the parquet archive files? If the tip is some kind of live-update DB, that DB has to support concurrent readers who can't block writers. Or would you just make log messages invisible to readers until they made their way into a stable Parquet file?

              Forget about DB terminology and look at what's happening ON THE DISK. ON THE DISK, is what DuckDB doing any less efficient than what your custom Parquet thing would be doing?

              • amluto 5 hours ago

                On the disk the live update part would be either a circular buffer (and readers would need to double-check the start/end marks after reading) or just literal append-only streams. In the latter case, reading would be barely more complex than tail -f.

                • quotemstr 17 minutes ago

                  Sure, but you have to take pains to make sure that journalctl -f doesn't skip events or print some twice. It can be made to work, for sure, but it just seems easier to print logs via a daemon instead, especially because if you go through a deamon, you turn the disk format from an interface into an implementation detail.