<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://markhenrick.site/feed.xml" rel="self" type="application/atom+xml" /><link href="https://markhenrick.site/" rel="alternate" type="text/html" /><updated>2026-04-01T11:07:58+00:00</updated><id>https://markhenrick.site/feed.xml</id><title type="html">Mark Henrick</title><entry><title type="html">systemd as a CI system</title><link href="https://markhenrick.site/2021/09/09/systemd-ci.html" rel="alternate" type="text/html" title="systemd as a CI system" /><published>2021-09-09T20:00:00+00:00</published><updated>2021-09-09T20:00:00+00:00</updated><id>https://markhenrick.site/2021/09/09/systemd-ci</id><content type="html" xml:base="https://markhenrick.site/2021/09/09/systemd-ci.html"><![CDATA[<p>I make fairly extensive use of systemd for scripting tasks which I need to run repeatedly and possibly on schedule (or based on other triggers), and recording a log of the output. At work and among friends, people use Jenkins for the same purpose. This gave me a stupid idea - can I turn systemd into Jenkins, and use it to build Git codebases?</p>

<!--more-->

<p><em>Disclaimer: This is purely a bit of “what if?” fun, not serious advice</em></p>

<p>The basic idea is quite simple</p>

<ol>
  <li>Write a timer to pull a git repo</li>
  <li>If a new change is found, trigger a oneshot service to build the project</li>
</ol>

<p>Writing this for each project would get quite repetitive, so I’d like to generify it as follows</p>

<ol>
  <li>The timer will be parameterised with the git URL, so I can do <code class="language-plaintext highlighter-rouge">systemctl enable systemd-ci-poll@$REPO</code></li>
  <li>The builder service will expect an executable at <code class="language-plaintext highlighter-rouge">$REPO/.systemd-ci/build</code></li>
</ol>

<h1 id="assumptions">Assumptions</h1>

<p>Since this is just a proof of concept, I make the followingg assumptions</p>

<ol>
  <li>Every project has a name marching <code class="language-plaintext highlighter-rouge">[a-Z0-9]+</code>, to saves character escaping headaches</li>
  <li>Every project is already cloned in the workspace dir, and we’re only building the branch that’s been checked out</li>
  <li>I’m not including any sort of artefact archiving or workspace cleaning. That could be implemented if needed, but for this PoC I’ll take the <em>laissez-faire</em> approach of leaving that to the build script</li>
  <li>We can always fast-forward. No force pushes!</li>
</ol>

<h1 id="system-setup">System setup</h1>

<p>I deployed a fresh Ubuntu 20.04 LTS Linux container on Proxmox and made the following changes</p>

<ul>
  <li>Installed <code class="language-plaintext highlighter-rouge">git</code></li>
  <li>Add a <code class="language-plaintext highlighter-rouge">systemd-ci</code> user and group with the normal minimal permissions expected of a service user</li>
  <li>Create the workspace dir <code class="language-plaintext highlighter-rouge">/var/lib/systemd-ci</code> with owner <code class="language-plaintext highlighter-rouge">systemd-ci:systemd-ci</code> and mode <code class="language-plaintext highlighter-rouge">770</code></li>
</ul>

<h1 id="the-builder-service">The builder service</h1>

<p>This service is quite simple - just run an executable</p>

<p><code class="language-plaintext highlighter-rouge">/etc/systemd/system/systemd-ci-build@.service</code></p>

<figure class="highlight"><pre><code class="language-systemd" data-lang="systemd"><span class="k">[Unit]</span>
<span class="nt">Description</span><span class="p">=</span>Build a systemd-ci project
<span class="nt">After</span><span class="p">=</span>multi-user.target

<span class="k">[Service]</span>
<span class="nt">Type</span><span class="p">=</span>oneshot
<span class="nt">WorkingDirectory</span><span class="p">=</span>/var/lib/systemd-ci/%i
<span class="nt">ExecStart</span><span class="p">=</span>/var/lib/systemd-ci/%i/.systemd-ci/build
<span class="nt">User</span><span class="p">=</span>systemd-ci</code></pre></figure>

<p>We can test this in isolation, by just creating</p>

<p><code class="language-plaintext highlighter-rouge">/var/lib/systemd-ci/test/.systemd-ci/build</code> (mode: <code class="language-plaintext highlighter-rouge">+x</code>)</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c">#!/bin/bash -eux</span>
<span class="nb">touch </span>success
<span class="nb">echo </span>it works</code></pre></figure>

<p>Then execute <code class="language-plaintext highlighter-rouge">systemctl start systemd-ci-build@test</code> (remembering to do a <code class="language-plaintext highlighter-rouge">daemon-reload</code> first). Logs can be found with <code class="language-plaintext highlighter-rouge">journalctl -u systemd-ci-build@test</code></p>

<h1 id="the-trigger-service">The trigger service</h1>

<p>So for the timer, we need to repeatedly pull the repository, and take note of when it changes. In accordance with assumption 4, let’s be really hacky and just <code class="language-plaintext highlighter-rouge">grep Fast-forward</code> in the git output</p>

<p><code class="language-plaintext highlighter-rouge">/etc/systemd/system/systemd-ci-poll@.service</code></p>

<figure class="highlight"><pre><code class="language-systemd" data-lang="systemd"><span class="k">[Unit]</span>
<span class="nt">Description</span><span class="p">=</span>Poll a systemd-ci project repo and trigger a build if necessary
<span class="nt">After</span><span class="p">=</span>multi-user.target
<span class="nt">OnSuccess</span><span class="p">=</span>systemd-ci-build@%i

<span class="k">[Service]</span>
<span class="nt">Type</span><span class="p">=</span>oneshot
<span class="nt">WorkingDirectory</span><span class="p">=</span>/var/lib/systemd-ci/%i
<span class="nt">ExecStart</span><span class="p">=</span>/bin/bash -c "git pull | grep Fast-forward"
<span class="nt">User</span><span class="p">=</span>systemd-ci</code></pre></figure>

<p>And here we run into a problem. <code class="language-plaintext highlighter-rouge">OnSuccess</code> was added in systemd 249, but this version of Ubuntu has 245. Luckily, bash has a negation operator, so I can use <code class="language-plaintext highlighter-rouge">OnFailure</code> and <code class="language-plaintext highlighter-rouge">ExecStart=/bin/bash -c "! git...</code></p>

<h1 id="the-polling-timer">The polling timer</h1>

<p><code class="language-plaintext highlighter-rouge">/etc/systemd/system/systemd-ci-poll@.timer</code></p>

<figure class="highlight"><pre><code class="language-systemd" data-lang="systemd"><span class="k">[Unit]</span>
<span class="nt">Description</span><span class="p">=</span>Run a systemd-ci poll every 15m

<span class="k">[Timer]</span>
<span class="nt">OnUnitInactiveSec</span><span class="p">=</span>15m

<span class="k">[Install]</span>
<span class="nt">WantedBy</span><span class="p">=</span>timers.target</code></pre></figure>

<p>As far as I understand you need to enable <code class="language-plaintext highlighter-rouge">systemd-ci-poll@.timer</code> at least once, in addition to <code class="language-plaintext highlighter-rouge">systemd-ci-poll@$REPO.timer</code>. Well, I guess that provides an easy way to turn the whole thing off if needed</p>

<h1 id="project-creation-script">Project creation script</h1>

<p><code class="language-plaintext highlighter-rouge">systemd-ci-init</code> (mode <code class="language-plaintext highlighter-rouge">+x</code>)</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c">#!/bin/bash -eux</span>

<span class="c"># Syntax: sudo systemd-ci-init &lt;repo url&gt; &lt;project name&gt;</span>

<span class="nb">cd</span> /var/lib/systemd-ci
git clone <span class="nv">$1</span> <span class="nv">$2</span>
<span class="nb">chown</span> <span class="nt">-R</span> systemd-ci:systemd-ci <span class="nv">$2</span>
systemctl <span class="nb">enable</span> <span class="nt">--now</span> systemd-ci-poll@.timer
systemctl <span class="nb">enable</span> <span class="nt">--now</span> systemd-ci-poll@<span class="nv">$2</span>.timer</code></pre></figure>

<h1 id="conclusion">Conclusion</h1>

<p>Yes, you can do it, no, you probably shouldn’t. The main problem is that you pollute the logs with “failed” polls, but I imagine this can be fixed if one is determined enough, but not on version 245</p>]]></content><author><name></name></author><category term="tech" /><category term="coding" /><summary type="html"><![CDATA[I make fairly extensive use of systemd for scripting tasks which I need to run repeatedly and possibly on schedule (or based on other triggers), and recording a log of the output. At work and among friends, people use Jenkins for the same purpose. This gave me a stupid idea - can I turn systemd into Jenkins, and use it to build Git codebases?]]></summary></entry><entry><title type="html">Dark theme is now live!</title><link href="https://markhenrick.site/2021/06/05/prefers-media-scheme.html" rel="alternate" type="text/html" title="Dark theme is now live!" /><published>2021-06-05T21:35:00+00:00</published><updated>2021-06-05T21:35:00+00:00</updated><id>https://markhenrick.site/2021/06/05/prefers-media-scheme</id><content type="html" xml:base="https://markhenrick.site/2021/06/05/prefers-media-scheme.html"><![CDATA[<p>This site should now use <code class="language-plaintext highlighter-rouge">prefers-color-scheme</code> to correctly switch to dark mode. Apologies for blinding you</p>

<!--more-->

<p><a href="https://github.com/jekyll/minima">Minima</a> partially supported this. There’s an official dark scheme, but it uses SCSS variables and hence has to be specified at compile time. I’ve converted those to native CSS variables/custom properties, but I did have to inline the use of <code class="language-plaintext highlighter-rouge">lighten</code>/<code class="language-plaintext highlighter-rouge">darken</code>. It would have been possible to work around this by indirecting the variables, but I didn’t see it as worth it</p>

<p>Here’s a code block to test syntax highlighting, since I don’t have any yet</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">foo</span>
  <span class="nb">puts</span> <span class="s1">'foo'</span>
<span class="k">end</span></code></pre></figure>]]></content><author><name></name></author><category term="tech" /><category term="coding" /><category term="meta" /><summary type="html"><![CDATA[This site should now use prefers-color-scheme to correctly switch to dark mode. Apologies for blinding you]]></summary></entry><entry><title type="html">Advent of Code</title><link href="https://markhenrick.site/2020/12/01/aoc.html" rel="alternate" type="text/html" title="Advent of Code" /><published>2020-12-01T18:30:00+00:00</published><updated>2020-12-01T18:30:00+00:00</updated><id>https://markhenrick.site/2020/12/01/aoc</id><content type="html" xml:base="https://markhenrick.site/2020/12/01/aoc.html"><![CDATA[<p>So the <a href="https://adventofcode.com/">Advent of Code</a> is running again this year. I’ll be trying to solve each day in Java, Haskell, and Python. All my code will be in <a href="https://github.com/markhenrick/adventofcode">this repo</a> and my commentary on the solutions will be in Markdowns in the repo rather than on this site.</p>

<!--more-->]]></content><author><name></name></author><category term="tech" /><category term="coding" /><summary type="html"><![CDATA[So the Advent of Code is running again this year. I’ll be trying to solve each day in Java, Haskell, and Python. All my code will be in this repo and my commentary on the solutions will be in Markdowns in the repo rather than on this site.]]></summary></entry><entry><title type="html">Unraid vs Snapraid</title><link href="https://markhenrick.site/2020/11/04/unraid-snapraid.html" rel="alternate" type="text/html" title="Unraid vs Snapraid" /><published>2020-11-04T18:00:00+00:00</published><updated>2021-07-04T16:30:00+00:00</updated><id>https://markhenrick.site/2020/11/04/unraid-snapraid</id><content type="html" xml:base="https://markhenrick.site/2020/11/04/unraid-snapraid.html"><![CDATA[<p>Following on from my <a href="/2020/11/03/binpacking.html">previous post</a>, I’ve been experimenting with different setups for storing media on an old computer. The characteristics of this use case is that the files are relatively large, write-once-read-many, do not require high-speed or highly-parallel access, and the disks are a highly heterogenous jumble of spare drives, subject to frequent change. The requirements are just that I have a directory to dump files in, spread them across drives in some manner, and provide protection against up to two disk failures. This makes something like ZFS or Btrfs overkill for my setup, so the two solutions that I looked at were Unraid and a custom SnapRAID + mergerfs setup.</p>

<!--more-->

<h1 id="tldr">TL;DR</h1>

<p>See the sections beneath the table for discussion of these points. Summary at the bottom of the article.</p>

<table>
  <thead>
    <tr>
      <th>Unraid</th>
      <th>SnapRAID</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Closed source, but decently priced and OS is very transparent. Your data is <em>not</em> locked in</strong></td>
      <td><strong>Open source</strong></td>
    </tr>
    <tr>
      <td><strong>Complete package</strong></td>
      <td><strong>Just redundancy</strong></td>
    </tr>
    <tr>
      <td>Union mounting built-in</td>
      <td>Basic read-only symlink-snapshot union view. Try mergerfs</td>
    </tr>
    <tr>
      <td>Sharing built-in</td>
      <td>Manually set-up SMB or NFS or whatever</td>
    </tr>
    <tr>
      <td>SMART monitoring built-in</td>
      <td>Use smartd</td>
    </tr>
    <tr>
      <td>Email and webhook notifications built-in</td>
      <td>Manual setup</td>
    </tr>
    <tr>
      <td>Web GUI built-in</td>
      <td><a href="https://michaelxander.com/diy-nas/">Try OpenMediaVault</a></td>
    </tr>
    <tr>
      <td>Task scheduling built-in</td>
      <td>No task scheduling, and remember that syncs and checks have to be run manually. Community systemd units exist</td>
    </tr>
    <tr>
      <td>Easy UI for VMs and Docker</td>
      <td>Too many alternatives to list. Maybe Proxmox?</td>
    </tr>
    <tr>
      <td>Opinionated with a big community, good for beginners or people who want to “set it and forget it”</td>
      <td>Go your own way, good for people who want more control or to integrate it with an existing server setup</td>
    </tr>
    <tr>
      <td><strong>Works on blocks</strong></td>
      <td><strong>Works on files</strong></td>
    </tr>
    <tr>
      <td>Have to clear disks before adding</td>
      <td>Can start with populated disks</td>
    </tr>
    <tr>
      <td>Supports XFS or Btrfs (+ LUKS)</td>
      <td>Supports virtually any mountpoint, even Windows hosts</td>
    </tr>
    <tr>
      <td>Parity sync duration proportional to raw block size</td>
      <td>Parity sync duration proportional to actual usage. Partial checks supported OOTB</td>
    </tr>
    <tr>
      <td>Consumes entire block device for parity</td>
      <td>Parity stored as plain files</td>
    </tr>
    <tr>
      <td>No fragmentation</td>
      <td>Small files can lead to fragmentation which lead to the parity disk filling prematurely</td>
    </tr>
    <tr>
      <td>Low RAM usage</td>
      <td>High RAM usage. Tradeoff between RAM usage and fragmentation</td>
    </tr>
    <tr>
      <td><strong>Live parity</strong></td>
      <td><strong>Snapshot parity</strong></td>
    </tr>
    <tr>
      <td>Data is protected immediately</td>
      <td>Unsynced data is unprotected. Deleted or changed data can remove protection from files on other drives until the next sync. Cannot sync whilst writing</td>
    </tr>
    <tr>
      <td>Automatically simulates failed disks from parity</td>
      <td>Must mount a replacement disk and wait for lost files to be recovered one-by-one</td>
    </tr>
    <tr>
      <td>Write speed bottlenecked by parity disks (without mover)</td>
      <td>Parity sync can be run during quiet hours</td>
    </tr>
    <tr>
      <td>No protection against accidental deletion</td>
      <td>Snapshot parity gives you a grace period to recover from stupid mistakes (affecting no more data disks than you have parity)</td>
    </tr>
    <tr>
      <td>Not a substitute for a proper offsite backup</td>
      <td>Also not a substitute for a proper offsite backup</td>
    </tr>
    <tr>
      <td><strong>“Mover” built-in</strong></td>
      <td><strong>rsync + cron?</strong></td>
    </tr>
    <tr>
      <td><strong>No built-in integrity checks</strong> (community plugins available)</td>
      <td><strong>Checksums all files</strong></td>
    </tr>
    <tr>
      <td>Has (small) potential to silently restore corrupted data</td>
      <td>Verifies all restored data with checksums</td>
    </tr>
    <tr>
      <td>Power loss during write can leave array in ambiguous state</td>
      <td>Syncs are transactional</td>
    </tr>
  </tbody>
</table>

<h1 id="licensing">Licensing</h1>

<p>Let’s start with the elephant in the room. SnapRAID is an open source project while Unraid costs between $60 and $130 depending on how many drives you use. Unless you have ethical objections to proprietary software I don’t think this is a big difference. The Unraid licenses are fairly cheap when you consider how much hardware costs, and they’re lifetime licenses too.</p>

<p>Some people are concerned that Unraid locks in your data - this isn’t true; I moved from Unraid to SnapRAID with just a parity sync. Your data drives are plain XFS or Btrfs, perhaps with LUKS. The parity drive format is undocumented afaik, but I’m sure someone’s reverse engineered it. Presumably the first parity drive is plain xor.</p>

<p>The Unraid system is just Slackware and you can easily SSH in and do whatever you want to it. The Unraid developers interact a lot with the community and often promote community plugins and tutorials, so they’re very open to hacking and experimenting on the OS.</p>

<p>The main disadvantage of Unraid is that it’s “all-or-nothing”, as discussed in the next section. I’m not sure how comfortable Unraid is running in a VM, or if you always need to dedicate a physical host.</p>

<h1 id="complete-package-vs-just-redundancy">Complete package vs just redundancy</h1>

<p>The first difference to understand is that “Unraid” refers to a whole operating system that provides redundancy, union mounting, networked file access, monitoring, scheduling, notifications, VM and Docker management, and a web GUI to configure it all. On the other hand SnapRAID is <em>just</em> a utility for adding redundancy to a set of existing mountpoints, and everything else must be provided elsewhere. OpenMediaVault with plugins can offer an Unraid-like experience for SnapRAID+mergerfs - <a href="https://michaelxander.com/diy-nas/">see here</a>.</p>

<p>Almost everyone using SnapRAID will want union mounting (the ability to view all the disks as one pool). SnapRAID comes with built-in support for a read-only union view, in the form of a command which will populate a pool directory with symlinks to the actual files. Most users will want to replace this with something more powerful that can offer live updates and write support. The most popular solution is mergerfs, though I think Windows users often go with Stablebit Drivepool. I wrote <a href="/2020/11/03/binpacking.html">a post comparing the write strategies of mergerfs and Unraid</a>, but the executive summary is that union mounting is a fairly solved problem and both Unraid and mergerfs do it well. Mergerfs is more powerful, but assumes more expertise from users.</p>

<p>Another thing that you’ll really want to setup is SMART monitoring of your drives with email alerts. Unraid has this out-of-the-box. For custom SnapRAID systems you’ll probably want to use smartd and your preferred MTA or other notification dispatcher.</p>

<p>The flexibility of being able to add SnapRAID to an existing system allows me to use one machine as both a NAS and a media centre connected straight to my TV. When I was on Unraid I had to use a separate Raspberry Pi as the media client, which added the complication of network sharing to Kodi on the Pi. If I had had a spare discrete graphics card I may have been able to pass it through to a media client VM on Unraid, and kept the integrated graphics for the root console, but that is quite a bit of extra time and money to spend.</p>

<p><a href="/assets/2020-11-04-unraid-snapraid/dank_meem.jpg">TL;DR</a></p>

<h1 id="blocks-vs-files">Blocks vs files</h1>

<p>Another key difference is that Unraid works on block devices while SnapRAID works on files. This makes SnapRAID more convenient, but requires somewhat more resources.</p>

<p>Unraid is more like mdadm, where you take some raw block devices, create virtual RAID devices, and then work on top of those. The Unraid OS only supports XFS or Btrfs with optional LUKS, but in theory I expect the storage virtualisation layer could support any FS. This also means that adding a new disk requires a parity sync; the way this is usually done is by “pre-clearing” the new disk to all zeroes, since <code class="language-plaintext highlighter-rouge">a xor 0 = a</code>, meaning that the existing disks do not need updating. Unfortunately this means you can’t officially add a full disk to Unraid, though I expect it should be theoretically possible if you do a full parity sync afterwards. The other disadvantage of this is that a parity sync or check time is proportional to the size of your largest data drive, regardless of how much actual space is used.</p>

<p>SnapRAID OTOH does not really care about filesystems or block devices; it just works on directories, and is so relaxed about semantics that it even runs fine on Windows. You can start off with full drives, as I did when I moved from Unraid to SnapRAID. Even the parity itself is just plain files rather than requiring a raw block device, as Unraid does, and will be proportional to actual usage, meaning you can take a risk and temporarily overcommit a parity drive smaller than a data drive, as long as you don’t use more storage on a single drive than the parity drive can hold. Technically the parity file cannot use the whole disk, due to the FS overhead, but the data disks also have that “issue”, and in any case it should be a minor overhead.</p>

<p>The downside of the file parity is less efficient use of resources. SnapRAID can use quite a lot of RAM when performing a sync, as it has to hold data structures for the more complicated way its parity works, compared to <code class="language-plaintext highlighter-rouge">A[x] xor B[x] = P[x]</code>. The other major issue is that of fragmentation. Having files smaller than the parity <code class="language-plaintext highlighter-rouge">block_size</code> results in more space being allocated for parity than the file actually uses. If you have a lot of small files (e.g. photos), this can add up to a significant amount of space wasted. You can decrease the block size, but that increases the RAM used (see the <a href="https://www.snapraid.it/manual">manual</a>, section 7.8). When I ran into this, I calculated that it was cheaper to have to spend a bit more on disks than upgrade my RAM. It’s also generally ill-advised to fill disks to the brim on any system, but keep in mind that just filling one data disk can induce this problem.</p>

<h1 id="live-vs-snapshot-parity">Live vs snapshot parity</h1>

<p>The main difference between Unraid’s storage system and SnapRAID is that Unraid runs as a daemon, constantly maintaining the virtual devices discussed previously, while SnapRAID is actually just a set of terminating commands that you run manually or on a schedule. You add/delete/update files and run <code class="language-plaintext highlighter-rouge">snapraid sync</code>. If a disk fails, you install, format, and mount a new one, then tell SnapRAID to start restoring the files onto it. You won’t have access to all your files until this finishes. This also means your new files are unprotected until you run a sync, and if you delete files from one drive, you remove protection from <em>some</em> files on other drives until you sync again, though the integrity features described later mean it should never result in silent corruption of restored files.</p>

<p>Unraid is more like a traditional RAID setup. Parity is written before returning “success” to the writing program. Normal reads on a healthy array only use one data disk, but when a disk fails the system will immediately start using the other disks to emulate it from parity, allowing you to continue using the whole array without interruption. Ironically this means that <em>Un</em>raid is more “real” RAID than Snap<em>RAID</em> IMO, since I consider high-availability to be the main purpose of RAID.</p>

<p>This does mean that a file deleted from Unraid is deleted immediately, while SnapRAID allows you until the next sync to notice your mistake and restore it, although this is only certain to work if the mistake was confined to one disk (otherwise you may have deleted files from other disks which were providing each other with parity). This should be considered a little bonus rather than something to rely on. If you really need that, use a filesystem with proper snapshots like ZFS, and remember that snapshots and disk redundancy are still not a substitute for offsite backup.</p>

<h1 id="the-mover">The mover</h1>

<p>Writing to hard drives can be slow, especially since Unraid array writes are limited by the speed of the parity disk. Unraid provides a built in utility to use an SSD as a write cache. Files are initially written to the SSD, and on a schedule (by default, in the early hours of the morning), they are moved to the array proper. Unlike the rest of Unraid, this works at a file, rather than block, level. The SSD is included in the union mount so it’s essentially transparent to the user. Since files on the cache are not covered by array parity, there is built in support for using two SSDs in RAID1 using Btrfs.</p>

<p>SnapRAID doesn’t have anything like this, but the concept is fairly simple, so it shouldn’t be too hard to write your own script using rsync and cron or systemd timers, or just do it by hand. Your writes aren’t bottlenecked by parity drives in the first place, due to the snapshot model.</p>

<h1 id="scrubbing-and-integrity">Scrubbing and integrity</h1>

<p>Both systems can perform scrubs/parity checks, however Unraid is more limited.</p>

<p>On a base install, Unraid parity checks are all-or-nothing, while SnapRAID defaults to scanning 8% of the files with the oldest scrub date, meaning that every file is checked every three months if you run the command weekly. As discussed before, Unraid has to scan the whole block device while SnapRAID just scans actual files.</p>

<p>Unraid suffers from the same problem as traditional RAID setups: if the calculated parity does not match the stored parity, is it the data or the parity which is wrong? SnapRAID is more similar to ZFS in that it stores actual file checksums, so can always answer correctly (beyond reasonable doubt). Presumably using dual parity on Unraid should help here.</p>

<p>Silent data corruption on hard disks is often considered to be virtually a myth, since it would be very unlikely to happen in such a way that the hard disk’s own error correction doesn’t detect it and return a media error, however it’s not the only way that the parity can become desynced. I believe Unraid is vulnerable to a “write hole” if power is lost during a write, whereas SnapRAID performs its syncs in a transactional manner, so can safely resume them.</p>

<p>As a bonus, SnapRAID also provides a <code class="language-plaintext highlighter-rouge">dup</code> command to list duplicate files, and also supports hard links, so it may be possible to leverage these for some limited deduplication support.</p>

<h1 id="summary">Summary</h1>

<p>Both are great solutions. Unraid is more opinionated while SnapRAID is more flexible. There are tools to simplify SnapRAID setup, giving it an Unraid-like UX, but the reverse - using Unraid as a facet of an existing Linux install - is not possible. SnapRAID’s unique advantage is the file-based model, while Unraid’s is the fact that it provides high availability like a traditional RAID system. The checksumming built into SnapRAID is nice, but there are Unraid plugins that do similar things.</p>]]></content><author><name></name></author><category term="tech" /><category term="storage" /><category term="comparison" /><summary type="html"><![CDATA[Following on from my previous post, I’ve been experimenting with different setups for storing media on an old computer. The characteristics of this use case is that the files are relatively large, write-once-read-many, do not require high-speed or highly-parallel access, and the disks are a highly heterogenous jumble of spare drives, subject to frequent change. The requirements are just that I have a directory to dump files in, spread them across drives in some manner, and provide protection against up to two disk failures. This makes something like ZFS or Btrfs overkill for my setup, so the two solutions that I looked at were Unraid and a custom SnapRAID + mergerfs setup.]]></summary></entry><entry><title type="html">Bin packing strategies for union mounts</title><link href="https://markhenrick.site/2020/11/03/binpacking.html" rel="alternate" type="text/html" title="Bin packing strategies for union mounts" /><published>2020-11-03T20:34:00+00:00</published><updated>2021-07-04T16:30:00+00:00</updated><id>https://markhenrick.site/2020/11/03/binpacking</id><content type="html" xml:base="https://markhenrick.site/2020/11/03/binpacking.html"><![CDATA[<p>Recently I’ve been configuring an old desktop as a NAS/media centre. I settled on using a “JBOD plus redundancy” system like Unraid or SnapRAID+mergerfs for the flexibility that it provides, as opposed to something more enterprise like FreeNAS/ZFS or a traditional RAID setup.</p>

<p>I intend to write a more thorough comparison of these two solutions in the future, but the short summary relevant to this article is that they use plain old filesystems like XFS on each disk and mount them independently, then provide a writeable <a href="https://en.wikipedia.org/wiki/Union_mount">union mount</a> view. This isn’t as performant as “real” RAID, but for the scenario of media storage that’s usually not an issue, and in fact has the advantage of only spinning one disk at a time.</p>

<p>Creating a read-only union mount is simple enough, but when you add writeability into the equation you have to decide on a strategy to select a drive to write new files to. In theory this shouldn’t matter, but in practice I want to colocate whole TV shows onto one drive, so that a data loss scenario does not result in me having half of 10 shows rather than 5 complete and 5 completely missing shows. I also wanted to write some code and draw some pretty graphs.</p>

<!--more-->

<p>Each solution offers a different unique feature that can help with this. Unraid has a “maximum split level”. For example if your schema is <code class="language-plaintext highlighter-rouge">share root/TV/Show Name/Season 1/episode.mkv</code>, then you could set the maximum split to two to colocate entire series, or three to colocate only seasons. Mergerfs includes “most-shared path” variants of most strategies, which will try to put new files in an existing directory on the same drive, then move up a level and try again if they won’t fit.</p>

<p>When using SnapRAID, one potential issue with strategies that fill disks unevenly is increasing the likelihood of wasted parity space. See <a href="/2020/11/04/unraid-snapraid.html">this post</a>, CTRL+F “fragmentation”.</p>

<p>In either case, the raw storage devices are exposed too, so you can always manually manage where things are put. Unraid comes with a web interface to show you where files are allocated, and the third-party “Unbalance” plugin makes it easy to relocate them. Mergerfs is a little more manual. I execute <a href="https://gist.github.com/markhenrick/cfc9ba9ed78344ab58cdff88381bfdc2">this script</a> that I wrote in the root of the TV shows directory, and then manually use Midnight Commander to move things. I’m planning to work on a more automated solution.</p>

<h1 id="bin-packing">Bin packing</h1>

<p>The problem of selecting a bin (drive) to put an item (file) in is known as the <a href="https://en.wikipedia.org/wiki/Bin_packing">bin-packing problem</a>; specifically, the online variant, meaning we don’t have foresight of the files that will come in the future and we’d rather not move them once allocated.</p>

<p>The formal problem described in that article assumes all bins are the same size, which is not the case with my setup. It also considers items of radically different sizes and tries to optimise for the maximum total storage. This is not something I’m concerned with, since the drive are usually so much bigger than the files, and you’re ideally leaving them at &lt;90% usage, so efficiency differences should be negligible.</p>

<p>The strategies are known by different names, so here’s a terminology table. Remember that lus and mfs are equivalent if all bins are the same size.</p>

<table>
  <thead>
    <tr>
      <th>Name on Wikipedia</th>
      <th>Name in Unraid</th>
      <th>Name in mergerfs</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>First-Fit?</td>
      <td>Fill-up</td>
      <td><code class="language-plaintext highlighter-rouge">ff</code> (first found)</td>
    </tr>
    <tr>
      <td>-</td>
      <td>High-water</td>
      <td>-</td>
    </tr>
    <tr>
      <td>Best-Fit?</td>
      <td>-</td>
      <td><code class="language-plaintext highlighter-rouge">lfs</code> (least free space)</td>
    </tr>
    <tr>
      <td>Worst-Fit?</td>
      <td>-</td>
      <td><code class="language-plaintext highlighter-rouge">lus</code> (least used space)</td>
    </tr>
    <tr>
      <td>Worst-Fit?</td>
      <td>Most-free</td>
      <td><code class="language-plaintext highlighter-rouge">mfs</code> (most free space)</td>
    </tr>
    <tr>
      <td>-</td>
      <td>-</td>
      <td><code class="language-plaintext highlighter-rouge">pfrd</code> (percentage free random distribution)</td>
    </tr>
    <tr>
      <td>-</td>
      <td>-</td>
      <td><code class="language-plaintext highlighter-rouge">rand</code> (random)</td>
    </tr>
  </tbody>
</table>

<p>I wrote a <a href="https://github.com/markhenrick/binpackingsim">little utility</a> to simulate the different strategies of Unraid and mergerfs. Its main flaw is that it does not test the path-preservation feature of mergerfs, since it simulates all the files being dropped in the same directory.</p>

<p>I tested it using the scenario of storing 4GB files into drives with sizes (1, 4, 4, 2)TB, until they’re full. The key thing I was looking for in these plots is the separation of the lines. In many strategies the orange and green, and red and blue, lines follow almost the same trajectory, meaning that files are being alternatively assigned between the two during some periods, which would manifest as a fragmented TV show.</p>

<p><a href="https://www.youtube.com/watch?v=NOmzX3bFpZ8">Now then</a></p>

<h2 id="first">First</h2>

<p><img src="/assets/2020-11-03-binpacking/first.png" alt="First strategy plot" /></p>

<p>The simplest strategy, and does a decent job, as long as you don’t mind your drives being used somewhat disproportionally.</p>

<h2 id="least-free-space">Least-free space</h2>

<p><img src="/assets/2020-11-03-binpacking/lfs.png" alt="LFS strategy plot" /></p>

<p>This is what I’m currently using on my setup.</p>

<h2 id="least-used-space">Least-used space</h2>

<p><img src="/assets/2020-11-03-binpacking/lus.png" alt="LUS strategy plot" /></p>

<p>This makes good proportionate use of the drives, but has poor separation of drives 1 and 2.</p>

<h2 id="most-free-space">Most-free space</h2>

<p><img src="/assets/2020-11-03-binpacking/mfs.png" alt="MFS strategy plot" /></p>

<p>Good proportionate use, but pretty much the worst strategy for colocation.</p>

<h2 id="high-water">High-water</h2>

<p>This is an Unraid-specific strategy. Quoting from the UI</p>

<blockquote>
  <p>High-water Choose the lowest numbered disk with free space still above the current high water mark. The high water mark is initialized with the size of the largest data disk divided by 2. If no disk has free space above the current high water mark, divide the high water mark by 2 and choose again.</p>

  <p>The goal of High-water is to write as much data as possible to each disk (in order to minimize how often disks need to be spun up), while at the same time, try to keep the same amount of free space on each disk (in order to distribute data evenly across the array).</p>
</blockquote>

<p><img src="/assets/2020-11-03-binpacking/high_water.png" alt="High-water strategy plot" /></p>

<p>This appears to fulfill its stated goal, making approximately equal use of drives <em>and</em> having good separation of lines. I’d probably use this if I were on Unraid.</p>

<h2 id="random">Random</h2>

<p><img src="/assets/2020-11-03-binpacking/random.png" alt="Random strategy plot" /></p>

<p>Basically a non-deterministic version of LUS.</p>

<h2 id="percentage-free-random-distribution">Percentage-Free Random Distribution</h2>

<p><img src="/assets/2020-11-03-binpacking/pfrd.png" alt="pfrd strategy plot" /></p>

<p>An interesting strategy, but not good for my use case.</p>

<h1 id="conclusion">Conclusion</h1>

<p>So in short, I would recommend</p>

<ul>
  <li>If you’re on Unraid just use high-water tbh. I think it’s the default too</li>
  <li>If you want colocation, first off make use of the features like maximum split or path preservation that I described earlier, then use LFS or first</li>
  <li>If you want equal usage of drives (to reduce fragmentation), use LUS</li>
</ul>]]></content><author><name></name></author><category term="tech" /><category term="storage" /><category term="comparison" /><category term="coding" /><summary type="html"><![CDATA[Recently I’ve been configuring an old desktop as a NAS/media centre. I settled on using a “JBOD plus redundancy” system like Unraid or SnapRAID+mergerfs for the flexibility that it provides, as opposed to something more enterprise like FreeNAS/ZFS or a traditional RAID setup. I intend to write a more thorough comparison of these two solutions in the future, but the short summary relevant to this article is that they use plain old filesystems like XFS on each disk and mount them independently, then provide a writeable union mount view. This isn’t as performant as “real” RAID, but for the scenario of media storage that’s usually not an issue, and in fact has the advantage of only spinning one disk at a time. Creating a read-only union mount is simple enough, but when you add writeability into the equation you have to decide on a strategy to select a drive to write new files to. In theory this shouldn’t matter, but in practice I want to colocate whole TV shows onto one drive, so that a data loss scenario does not result in me having half of 10 shows rather than 5 complete and 5 completely missing shows. I also wanted to write some code and draw some pretty graphs.]]></summary></entry></feed>