<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Mark Hansen's Blog]]></title><description><![CDATA[I'm a Software Engineering Manager working on Google Maps in Sydney, Australia. I write about software {engineering, management, profiling}, data visualisation, and transport.]]></description><link>https://www.markhansen.co.nz/</link><image><url>https://www.markhansen.co.nz/favicon.png</url><title>Mark Hansen&apos;s Blog</title><link>https://www.markhansen.co.nz/</link></image><generator>Ghost 6.44</generator><lastBuildDate>Tue, 09 Jun 2026 08:17:50 GMT</lastBuildDate><atom:link href="https://www.markhansen.co.nz/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Glorious Parramatta Autumn, 2024]]></title><description><![CDATA[<p>A stunning day to head out and play with a new wide lens.</p><p>This churchyard tree had such vibrant contrast with the blue sky:</p>
<!--kg-card-begin: html-->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/200517901@N03/53783658801/in/dateposted-public/" title="2024-06-09-DSC04839"><img src="https://live.staticflickr.com/65535/53783658801_b2a28f1770_b.jpg" width="1024" height="683" alt="2024-06-09-DSC04839"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<!--kg-card-end: html-->
<p>Parramatta has seen a lot of construction. I love the scale here of the tree, the building, the cirrus.</p>
<!--kg-card-begin: html-->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/200517901@N03/53784084320/in/dateposted-public/" title="2024-06-09-DSC04840"><img src="https://live.staticflickr.com/65535/53784084320_366ef40c59_b.jpg" width="683" height="1024" alt="2024-06-09-DSC04840"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<!--kg-card-end: html-->
<p>Let&apos;s mix in St John&</p>]]></description><link>https://www.markhansen.co.nz/parramatta-autumn/</link><guid isPermaLink="false">6667df6ca3d8d800015884ee</guid><category><![CDATA[Photography]]></category><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Tue, 11 Jun 2024 05:42:24 GMT</pubDate><media:content url="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/06/2024-06-09-DSC04839-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/06/2024-06-09-DSC04839-1.jpg" alt="Glorious Parramatta Autumn, 2024"><p>A stunning day to head out and play with a new wide lens.</p><p>This churchyard tree had such vibrant contrast with the blue sky:</p>
<!--kg-card-begin: html-->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/200517901@N03/53783658801/in/dateposted-public/" title="2024-06-09-DSC04839"><img src="https://live.staticflickr.com/65535/53783658801_b2a28f1770_b.jpg" width="1024" height="683" alt="Glorious Parramatta Autumn, 2024"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<!--kg-card-end: html-->
<p>Parramatta has seen a lot of construction. I love the scale here of the tree, the building, the cirrus.</p>
<!--kg-card-begin: html-->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/200517901@N03/53784084320/in/dateposted-public/" title="2024-06-09-DSC04840"><img src="https://live.staticflickr.com/65535/53784084320_366ef40c59_b.jpg" width="683" height="1024" alt="Glorious Parramatta Autumn, 2024"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<!--kg-card-end: html-->
<p>Let&apos;s mix in St John&apos;s Anglican Cathedral:</p>
<!--kg-card-begin: html-->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/200517901@N03/53784084325/in/dateposted-public/" title="2024-06-09-DSC04838"><img src="https://live.staticflickr.com/65535/53784084325_5bb500135c_b.jpg" width="683" height="1024" alt="Glorious Parramatta Autumn, 2024"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<!--kg-card-end: html-->
<p>The wonderful clock glows bright gold in the noon sun. I learned how to un-distort this image to get the spire roughly rectilinear. Very happy with the result.</p>
<!--kg-card-begin: html-->
<a data-flickr-embed="true" href="https://www.flickr.com/photos/200517901@N03/53783984824/in/dateposted-public/" title="2024-06-09-DSC04834"><img src="https://live.staticflickr.com/65535/53783984824_fc54a367e7_b.jpg" width="1024" height="683" alt="Glorious Parramatta Autumn, 2024"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>
<!--kg-card-end: html-->
]]></content:encoded></item><item><title><![CDATA[Profiling apps installed from apt-get]]></title><description><![CDATA[How to take performance profiles of Ubuntu/Debian apps (the ones from apt-get) so you can see full stack symbols, even in the kernel, without root.]]></description><link>https://www.markhansen.co.nz/profiling-ubuntu-debian-programs/</link><guid isPermaLink="false">6623652aa1923800018dc190</guid><category><![CDATA[Profiling]]></category><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Tue, 23 Apr 2024 10:57:03 GMT</pubDate><media:content url="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/04/stackframes-unknown.png" medium="image"/><content:encoded><![CDATA[<img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/04/stackframes-unknown.png" alt="Profiling apps installed from apt-get"><p>Do you ever want to profile some Ubuntu/Debian apps installed with <code>apt-get</code>, to see why they&apos;re slow, but all your symbols come out as <code>[unknown]</code>, or there are stack frames missing? I&apos;ll explain how to fix these issues.</p>
<!--kg-card-begin: html-->
<iframe src="https://hachyderm.io/@markhansen/112297586395235027/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe><script src="https://hachyderm.io/embed.js" async="async"></script>
<!--kg-card-end: html-->
<p>As an example, have you ever run <code>top</code> and seen that one of the top users of CPU is... <code>top</code> itself? It seems suboptimal, that the CPU usage debugger, uses so much CPU itself.</p><p>I&apos;d like to profile <code>top</code> to find out why it&apos;s slow. There&apos;s lots of programs like <code>top</code>: prebuilt programs from <code>apt</code>.  I&apos;ve run into many roadblocks trying to profile these prebuilt binaries many times, and I&apos;m not sure anyone&apos;s written up a guide yet.</p><p>My examples are from Ubuntu 22.04, but will probably work on Debian.</p><h2 id="tldr">TL;DR</h2><pre><code class="language-shell-session"># Install perf
$ sudo apt install --yes linux-tools-common

# Install debug symbols for package you are profiling
$ sudo apt install --yes $FOO-dbgsym

# Allow profiling by non-root users, and visibility to kernel stacks.
$ sudo sh -c &apos;echo 1 &gt;/proc/sys/kernel/perf_event_paranoid&apos;

# Allow visibility of kernel stack traces
$ sudo sh -c &apos;echo 0 &gt; /proc/sys/kernel/kptr_restrict&apos;

# Profile an app, using the -dbgsym dwarf information.
$ perf record --call-graph dwarf $(pidof $FOO)

# Convert to a text format
$ perf script --input perf.data -F +pid &gt; perf.txt
</code></pre><p>Then drag and drop <code>perf.txt</code> into <a href="https://profiler.firefox.com" rel="noreferrer">https://profiler.firefox.com</a>. </p><h2 id="install-perf">Install Perf</h2><p>We&apos;ll use <code>perf</code> to profile Linux applications. Install it:</p><pre><code class="language-shell-session">$ sudo apt install --yes linux-tools-common
$ perf version
perf version 5.15.148
</code></pre><h2 id="install-debug-symbols-for-the-profiled-app">Install Debug Symbols for the Profiled App</h2><p>Debian/Ubuntu packages don&apos;t come with debug symbols by default, but we&apos;ll need them. They come in debug symbols packages, which conventionally have the same name as the base package, but ending with <code>-dbgsym</code>.</p><p>First, we have to find which package <code>top</code> is in, there is no <code>top</code> package. What package installs <code>top</code>? We can see with <code>dpkg --search &lt;path&gt;</code>.</p><p>What&apos;s the path we should be searching for? I don&apos;t know if <code>top</code> is in <code>/bin</code> or somewhere else. <code>which</code> will tell us:</p><pre><code class="language-shell-session">$ which top
/usr/bin/top</code></pre><p>Putting it together, we find the package:</p><pre><code class="language-shell-session">$ dkpg --search /usr/bin/top
procps: /usr/bin/top</code></pre><p>So <code>top</code> is installed from package <code>procps</code>, therefore the debug symbols will be in <code>procps-dbgsym</code>. Let&apos;s install that:</p><pre><code class="language-shell-session">$ sudo apt install --yes procps-dbgsym
The following NEW packages will be installed:
  procps-dbgsym
After this operation, 664 kB of additional disk space will be used.
Setting up procps-dbgsym (2:3.3.17-6ubuntu2.1) ...
</code></pre><h2 id="less-paranoid-perf">Less Paranoid Perf</h2><p>We could profile as <code>root</code>, but:</p><ul><li>I&apos;m often profiling short-lived apps I need the profiler to run, and I don&apos;t trust them to run as <code>root</code>.</li><li>It&apos;s a faff having the output files be owned by <code>root</code>.</li></ul><p>If you try profiling on Ubuntu as a non-privileged user, you get this long and incorrect error:</p><pre><code class="language-shell-session">$ perf record -p $(pidof top) --call-graph dwarf
Error:
Access to performance monitoring and observability operations is limited.
Consider adjusting /proc/sys/kernel/perf_event_paranoid setting to open
access to performance monitoring and observability operations for processes
without CAP_PERFMON, CAP_SYS_PTRACE or CAP_SYS_ADMIN Linux capability.
More information can be found at &apos;Perf events and tool security&apos; document:
https://www.kernel.org/doc/html/latest/admin-guide/perf-security.html
perf_event_paranoid setting is 4:
  -1: Allow use of (almost) all events by all users
      Ignore mlock limit after perf_event_mlock_kb without CAP_IPC_LOCK
&gt;= 0: Disallow raw and ftrace function tracepoint access
&gt;= 1: Disallow CPU event access
&gt;= 2: Disallow kernel profiling
To make the adjusted perf_event_paranoid setting permanent preserve it
in /etc/sysctl.conf (e.g. kernel.perf_event_paranoid = &lt;setting&gt;)
</code></pre><p>The docs talk about -1, 0, 1, and 2. But the <code>perf_event_paranoid setting is 4</code> ? Huh? The <a href="https://www.kernel.org/doc/Documentation/sysctl/kernel.txt#:~:text=CPU%20is%20consumed.%0A%0A%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D-,perf_event_paranoid,-%3A%0A%0AControls%20use%20of" rel="noreferrer">kernel documentation</a> only describes up to level 2. What is 4? Well, Debian patched in an extra level 3, and Ubuntu changed it to level 4, which means: &quot;disallow all unpriv perf event use&quot;. See <a href="https://askubuntu.com/questions/1400874/what-does-perf-paranoia-level-four-do" rel="noreferrer">AskUbuntu</a>, and the <a href="https://git.launchpad.net/%7Eubuntu-kernel/ubuntu/+source/linux/+git/focal/commit/?id=2986a639d3181e4686c5b88303a54f439d33c34a" rel="noreferrer">commit</a> adding this.</p><p>Let&apos;s lower (open) this to <code>1</code>, which is the highest level that allows kernel profiling.</p><pre><code class="language-shell-session">$ sudo sh -c &apos;echo 1 &gt;/proc/sys/kernel/perf_event_paranoid&apos;</code></pre><p>I don&apos;t really understand the security ramifications here. The LKML thread, <a href="https://lkml.org/lkml/2016/6/17/696" rel="noreferrer">where an Android developer tries to upstream it</a>, talks about information leaks and local privilege escalations via perf-events. Maybe reset it once you&apos;re done?</p><h2 id="allow-profiling-kernel-symbols">Allow Profiling Kernel Symbols</h2><p>The next hurdle is seeing what functions we are calling in kernel space. You may get this warning:</p><pre><code class="language-shell-session">$ perf record -p $(pidof top) --call-graph dwarf
WARNING: Kernel address maps (/proc/{kallsyms,modules}) are restricted,
check /proc/sys/kernel/kptr_restrict and /proc/sys/kernel/perf_event_paranoid.

Samples in kernel functions may not be resolved if a suitable vmlinux
file is not found in the buildid cache or in the vmlinux path.

Samples in kernel modules won&apos;t be resolved at all.

If some relocation was applied (e.g. kexec) symbols may be misresolved
even with a suitable vmlinux or kallsyms file.

Couldn&apos;t record kernel reference relocation symbol
Symbol resolution may be skewed if relocation was used (e.g. kexec).
Check /proc/kallsyms permission or run as root.</code></pre><p>By default, Linux disallows unprivileged users from seeing the locations of kernel function symbols. Symbol locations are randomised to make attacks on these structures harder.</p><p>But I just want to profile, and this is a system that only I&apos;m running code on. Disable this with:</p><pre><code class="language-shell-session">$ sudo sh -c &apos;echo 0 &gt; /proc/sys/kernel/kptr_restrict&apos;</code></pre><p>Set it to <code>1</code> once you&apos;re done if you like.</p><h2 id="profile-the-program">Profile the Program</h2><p>Finally! Let&apos;s run <code>top</code> in one terminal, then in another, profile it with <code>perf record</code>. Ctrl-C when done:</p><pre><code class="language-shell-session">$ perf record --call-graph dwarf $(pidof top)
^C[ perf record: Woken up 6 times to write data ]
[ perf record: Captured and wrote 1.322 MB perf.data (163 samples) ]
</code></pre><h2 id="visualise-the-output">Visualise the Output</h2><p>My favourite way to look at the output of <code>perf</code> is with <a href="https://profiler.firefox.com/" rel="noreferrer">Firefox Profiler</a>. Despite being named after the browser, it&apos;s a tremendous general-purpose profile analysis UI.</p><p>Follow their <a href="https://github.com/firefox-devtools/profiler/blob/main/docs-user/guide-perf-profiling.md#step-2-convert-the-profile" rel="noreferrer">instructions</a> for loading <code>perf</code> profiles:</p><pre><code class="language-shell-session">$ perf script --input perf.data -F +pid &gt; perf.txt
</code></pre><p>Then drag and drop <code>perf.txt</code> into <a href="https://profiler.firefox.com" rel="noreferrer">https://profiler.firefox.com</a>. All going well, you should see a profile like this:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/04/image.png" class="kg-image" alt="Profiling apps installed from apt-get" loading="lazy" width="2000" height="1292" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2024/04/image.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2024/04/image.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1600/2024/04/image.png 1600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/04/image.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><a href="https://share.firefox.dev/3UrzvpR"><span style="white-space: pre-wrap;">https://share.firefox.dev/3UrzvpR</span></a></figcaption></figure><p>As it turns out, <code>top</code> is using so much CPU because it&apos;s spending most of its time inside <code>close</code>, <code>open</code>, <code>fstat</code>, <code>opendir</code>, and <code>getdirents</code> system calls reading thousands of files in <code>/proc</code>.</p><h2 id="resolving-unknown-stack-frames">Resolving [unknown] Stack Frames</h2><p>I still have some missing <code>[unknown]</code> symbol stack frames. Hovering over the frame, Firefox Profiler tells me these are in file <code>/usr/lib/x86_64-linux-gnu/libprocps.so.8.0.3</code>. Let&apos;s install debug symbols for those, too. We can find what package with the same <code>dpkg</code> command:</p><pre><code class="language-shell-session">$ dpkg --search /usr/lib/x86_64-linux-gnu/libprocps.so.8.0.3
dpkg-query: no path found matching pattern /usr/lib/x86_64-linux-gnu/libprocps.so.8.0.3</code></pre><p>Huh, I don&apos;t know why that doesn&apos;t work. Let&apos;s try without the path:</p><pre><code class="language-shell-session">$ dpkg --search libprocps.so.8.0.3
libprocps8:amd64: /lib/x86_64-linux-gnu/libprocps.so.8.0.3
</code></pre><p>OK, weird, <code>dpkg</code> is reporting the file as in <code>/lib</code>, and <code>perf</code> is reporting it&apos;s in <code>/usr/lib</code>. Both files exist and have the same hash.</p><pre><code class="language-shell-session">$ sha1sum {/lib,/usr/lib}/x86_64-linux-gnu/libprocps.so.8.0.3
a2a2cd0dc5c0d88282a15e27742bac42a1e550d5  /lib/x86_64-linux-gnu/libprocps.so.8.0.3
a2a2cd0dc5c0d88282a15e27742bac42a1e550d5  /usr/lib/x86_64-linux-gnu/libprocps.so.8.0.3
</code></pre><p>Maybe it&apos;s a bug that dpkg can&apos;t find this? If anyone knows, leave a comment?</p><p>Anyway, let&apos;s guess that <code>libprocps8</code>&apos;s debug symbols are in <code>libprocps8-dbgsym</code>:</p><pre><code class="language-shell-session">$ sudo apt install --yes libprocps8-dbgsym
Setting up libprocps8-dbgsym:amd64 (2:3.3.17-6ubuntu2.1) ...
</code></pre><p>Excellent. Re-profiling, the profile looks complete. We can see the previously-unknown symbols in the <code>libprocps.so.8.0.3</code> frames. Here, <code>simple_readtask</code>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/04/image-1.png" class="kg-image" alt="Profiling apps installed from apt-get" loading="lazy" width="2000" height="1300" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2024/04/image-1.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2024/04/image-1.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1600/2024/04/image-1.png 1600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/04/image-1.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">A perfect profile. Userland and kernel, all symbolised. </span><a href="https://share.firefox.dev/3w0qU3N"><span style="white-space: pre-wrap;">https://share.firefox.dev/3w0qU3N</span></a></figcaption></figure><h2 id="common-problems-no-kernel-stack-frames">Common Problems: No Kernel Stack Frames</h2><p>If your profile is only yellow (userland) frames with no orange (kernel) frames, you may be missing permission to profile the kernel. Check the &quot;Less Paranoid Perf&quot; section above.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/04/image-2.png" class="kg-image" alt="Profiling apps installed from apt-get" loading="lazy" width="2000" height="1292" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2024/04/image-2.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2024/04/image-2.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1600/2024/04/image-2.png 1600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/04/image-2.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">A profile with no kernel stack frames. </span><a href="https://share.firefox.dev/3U6cFCG"><span style="white-space: pre-wrap;">https://share.firefox.dev/3U6cFCG</span></a></figcaption></figure><h2 id="common-problems-no-kernel-stack-symbols">Common Problems: No Kernel Stack Symbols</h2><p>If you have kernel stack frames, but they all say [unknown], check the &quot;Allow Profiling Kernel Symbols&quot; section above.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/04/image-3.png" class="kg-image" alt="Profiling apps installed from apt-get" loading="lazy" width="2000" height="1300" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2024/04/image-3.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2024/04/image-3.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1600/2024/04/image-3.png 1600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/04/image-3.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Large [unknown] towers of kernel symbols. </span><a href="https://share.firefox.dev/3Ur1Axi"><span style="white-space: pre-wrap;">https://share.firefox.dev/3Ur1Axi</span></a><span style="white-space: pre-wrap;">A reA re</span></figcaption></figure><h2 id="conclusion">Conclusion</h2><p>Well, this is a bit of a faff! Can&apos;t we have nice things? No wonder hardly anybody bothers to profile, and so much of our software is still so slow.</p><p>Maybe one day, perf can be security-hardened enough that these settings could be enabled by default?</p><p>Until then, I hope this checklist can help lower the bar to understanding software performance. Go, profile, and make code faster!</p>]]></content:encoded></item><item><title><![CDATA[Backing up Google Photos]]></title><description><![CDATA[How I backup my Google Photos, at full image quality, preserving GPS and timestamps, deduplicating identical images to save disk space.]]></description><link>https://www.markhansen.co.nz/google-photos-backup/</link><guid isPermaLink="false">65d53aa88bf63a0001a34987</guid><category><![CDATA[Backup]]></category><category><![CDATA[Photography]]></category><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Wed, 21 Feb 2024 03:47:03 GMT</pubDate><content:encoded><![CDATA[<p>Do you want to back up your Google Photos outside Google, and keep them backed up continuously, in full original quality, while minimising disk space? Here&apos;s how I do it.</p><p>I ended up with many photos only stored in Google Photos. Worryingly, these photos weren&apos;t backed up outside of Google.</p><p>I was really worried about how to set up backups; I&apos;d heard a lot of people complain about how hard it is to get your photos out of Google Photos. I particularly heard many complaints about Takeout not exporting all the metadata; this is complicated; more on that below.</p><p>This put me off doing it for a long time. I read MANY blogposts &amp; Reddit threads before choosing my strategy. It was a lot of faff, so I&apos;m keen to write it up to save you the hassle.</p><h2 id="my-strategy-tldr">My Strategy TL;DR</h2><ul><li>I schedule a Google Takeout of my entire Photos library for every two months, using the largest (50 GB) archive size.</li><li>I manually click download the archives to an external drive.</li><li>I extract the archives with a script.</li><li>I back up and deduplicate the photos and metadata with <a href="https://restic.net/" rel="noreferrer">&apos;restic&apos; backup</a> software, but you could use any deduplicating backup software.</li></ul><h2 id="problems-to-solve">Problems to solve</h2><p>These are my tradeoffs; yours may be different.</p><ul><li><strong>Image Quality: </strong>I want it best available quality please.</li><li><strong>Metadata: </strong>Many people complain that the Google Photos API doesn&apos;t include metadata like date or GPS. I want this backed up, though I don&apos;t need it stored in the photo&apos;s EXIF information: this is a backup for emergencies, not a migration to another tool.</li><li><strong>Low-overhead: </strong>I want to automate where possible. I don&apos;t want my technique to break when Google changes their Takeout format.</li><li><strong>Deduplication</strong>: Storage is expensive. I want to deduplicate data. I don&apos;t want two backups to take up 2x the data.</li><li><strong>Retention</strong>: I eventually want to &apos;age out&apos; old backups and not pay for their data forever.</li></ul><h2 id="where-to-get-the-photos-from">Where to get the photos from</h2><p>This is why it&apos;s so hard to figure out. You have many options for getting your photos out:</p><p><strong>Google Takeout</strong> is the natural option. Takeout exports all the photos in their original quality chosen when uploaded (e.g. if you chose full quality, you get the original bytes with EXIF, if you chose space saver, you get recompressed photos and most of the EXIF data stripped).</p><p>The original uploaded EXIF data where it was present when uploaded at full quality, and includes date and GPS data in sidecar JSON files alongside the images.</p><p><strong>Google Photos API</strong> might be expected to be a good place to go, and would running (say) nightly continuous backups. But the API doesn&apos;t return the original quality images with original EXIF metadata, and the API doesn&apos;t expose GPS location.</p><p><em>Aside: The API&apos;s EXIF stripping is frustrating but understandable: the EXIF information contains GPS, I&apos;m not sure it&apos;s a good idea to expose that to any website that can convince the user to click &apos;accept&apos; on an OAuth prompt.</em></p><p>I discounted options that just use the Google Photos API: <a href="https://rclone.org/googlephotos/#limitations" rel="noreferrer">RClone</a>, <a href="https://photovaultone.com/" rel="noreferrer">Photo Vault One</a>, <a href="https://www.2brightsparks.com/syncback/help/googlephotos.htm" rel="noreferrer">SyncBackPro</a>, <a href="https://github.com/gilesknap/gphotos-sync" rel="noreferrer">gphotos-sync</a>, <a href="https://photos.chrisesplin.com/content/quiver-photos-vs-google-takeout" rel="noreferrer">Quiver Photos</a>, because I want full quality exports.</p><p>The <strong>Google Photos Web App</strong> offers a download option: either photo-by-photo, or selecting an album and downloading as a .zip. The <a href="https://github.com/vikas5914/google-photos-backup" rel="noreferrer">google-photos-backup</a> tool scripts this with a headless browser API. It&apos;s a neat hack, enabling continuous backups, but I wasn&apos;t comfortable handing over my credentials to it.</p><p><strong>Google Drive</strong> used to have an integration with Google Photos that exposed your photos as files, making it easy to back them up, but it was shut down. I imagine it&apos;s hard to maintain a filesystem API to Google Photos as the product evolves, and you can edit photos and metadata in Google Photos. I&apos;ve still got a &quot;Google Photos&quot; folder in Google Drive, but it doesn&apos;t sync to Google Photos any more; I haven&apos;t figured out what to do with it yet.</p><p>Some tools use a <strong>Combination: API + Takeout</strong>. e.g. <a href="https://github.com/mholt/timeliner/tree/master" rel="noreferrer">Timeliner</a> merges data from <a href="https://github.com/mholt/timeliner/blob/master/datasources/googlephotos/takeoutarchive.go" rel="noreferrer">Takeout</a> and the <a href="https://github.com/mholt/timeliner/blob/master/datasources/googlephotos/googlephotos.go" rel="noreferrer">Google Photos API</a>, to get full GPS information from Takeout and merge that with continuous backups from the API. You can see why, but gosh, what an unfortunate situation.</p><p><strong>I chose Google Takeout</strong>, as it has the original uploaded quality, and the full GPS metadata in sidecar .json files. I can live with waiting two months between backups.</p><h2 id="takeout-options">Takeout Options</h2><p>Google Takeout gives you choices to make. Here&apos;s mine:</p><p><strong>Transfer to</strong>: Send download link via email. </p><p><strong>Frequency</strong>: Export every 2 months for 1 year. I want this to be continuous.</p><p><strong>Albums to export</strong>: all of them. This is a backup, I want it all. Some people just download the &quot;Photos from &lt;year&gt;&quot; albums, but I&apos;m worried that might not download everything. Give me everything; I will deduplicate later.</p><p><strong>File type</strong>: <code>.zip</code>. I think I heard one of these formats had a more precise timestamp format? Whatever, the timestamp is in the JSON sidecar anyway.</p><p><strong>File size</strong>: 50 GB. Choose the largest you can, because you have to click each download link manually. The first time I did this, I chose 10 GB, and had to manually download 37 files.</p><p>Download your Takeout before the files expire in 7 days! The first time I did this, I missed downloading one file, and it expired, and I had to start over! I almost cried.</p><h2 id="takeout-larger-than-expected">Takeout Larger Than Expected</h2><p>Despite Google Photos saying &quot;174.5 GB used&quot;, my Takeout download was surprisingly 7 x 50GB = 350GB. What gives?</p><ul><li>Photos in albums show up in Takeout under a folder for the album name, and a folder for the year.</li><li>Some photos &quot;don&apos;t count for storage&quot; &#x2013; either they were uploaded with &quot;Storage Saver&quot; mode, or perhaps there was a discontinued promotion where Pixel phones got free storage.</li></ul><p>After deduplication, my photos take up 289GB. So I guess I have a lot of photos where Google isn&apos;t charging me for storage?</p><p>This was a sad surprise: my laptop didn&apos;t have enough free space to store all these photos!</p><p>Instead, I used a spare 512GB SD card as the space to do this backup. It&apos;s not fast, but it&apos;s faster than my internet connection. I&apos;m sensitive about storing photos unencrypted &#x2013; when I bought this SD card second hand, it still had the photos of the previous owner on it &#x2013; so I formatted the card with an encrypted APFS partition first in macOS Disk Utility. Now no traces will remain, even after deleting the files. </p><h2 id="downloading-takeout-archives">Downloading Takeout Archives</h2><p>Sadly, I don&apos;t know a way to automate downloading these archives. The URLs are auth&apos;d, you have to click them in a real browser, you can&apos;t just copy them to cURL.</p><p>My steps:</p><ul><li>Open Chrome</li><li>In Chrome Settings, change my default download folder to the external disk</li><li>Click all the links</li><li>Leave my computer awake till they finish</li></ul><p>Took about a day.</p><h2 id="extracting">Extracting</h2><p>My 512GB drive is too small to contain both the 350GB archives and their extracted versions. Extracting takes a long time for each file; so I didn&apos;t want to manually extract one, delete one, extract one, delete one, it would have taken a lot of time.</p><p>So I scripted extracting and then deleting the archive with this <a href="https://fishshell.com/" rel="noreferrer"><code>fish</code></a> script:</p><figure class="kg-card kg-code-card"><pre><code class="language-fish">for f in *.zip
  echo unzip $f
  echo rm $f
end
</code></pre><figcaption><p><span style="white-space: pre-wrap;">If you use </span><code spellcheck="false" style="white-space: pre-wrap;"><span>bash</span></code><span style="white-space: pre-wrap;">, you have to write your own loop, sorry.</span></p></figcaption></figure><p>Remove the <code>echo</code> once you have verified the commands look reasonable. Again, leave the laptop on for a while.</p><h2 id="weird-stuff-in-the-takeout-archive">Weird Stuff in the Takeout Archive</h2><p><strong>Errors: </strong>I got &apos;missing files&apos; every time I took a takeout. I&apos;m ignoring this; I guess if I&apos;m requesting tens of thousands of photos, sometimes there will be transient errors. I hope the photo will be in the next archive.</p><p><strong>Untitled(270)</strong>: I had over 200 Untitled folders. A lot of these are anonymous share albums. I don&apos;t think these numbered folders are stable between takeouts, making it hard to just &apos;extract over the top of the last archive&apos;.</p><p><strong>Failed Videos</strong>: This folder contains videos that Google Photos couldn&apos;t transcoded. These all seemed to be sub-second videos or corrupted videos. You can see them (and delete them) in Google Photos Web UI: Settings, Unsupported videos, View. Or go to <a href="https://photos.google.com/unsupportedvideos">https://photos.google.com/unsupportedvideos</a>. I deleted all of these.</p><h2 id="deduplication">Deduplication</h2><p>I have the same photos duplicated in the &quot;Photos from 2024&quot; folder and named album folders. I don&apos;t want to pay for double storage.</p><p>There&apos;s many ways to solve this problem:</p><p><a href="https://github.com/mholt/timeliner" rel="noreferrer"><strong>Timeliner</strong></a> stores the Google Photos in sqlite database and deduplicates images. It looks very smart, and was <a href="https://twitter.com/addyosmani/status/1459577015837741056" rel="noreferrer">recommended</a>, but is deprecated, and the new version is a <a href="https://timelinize.com/">&quot;not ready yet&quot; early-stage startup</a>. Doesn&apos;t feel like a stable foundation to build backups on yet.</p><p><a href="https://github.com/jbruchon/jdupes" rel="noreferrer"><strong>jdupes</strong></a> and <a href="https://github.com/pauldreik/rdfind" rel="noreferrer"><strong>rdfind</strong></a> look for duplicates and turn them into hardlinks or symlinks. But hard-link deduplication isn&apos;t preserved when backing up to other computers / cloud storage.</p><p><strong>Git</strong> is a content-addressable filesystem, implicitly deduping things. But Git has a repuation of not working well with large files.</p><p><a href="https://git-lfs.com/" rel="noreferrer"><strong>Git Large File Storage</strong></a> replaces large files in Git with pointers, but then I still have to store the pointed-to data somewhere.</p><p><a href="https://perkeep.org/" rel="noreferrer"><strong>Perkeep</strong></a> (aka Camlistore) is content-addressable storage too aimed at preserving your digital life forever. Sounds perfect?</p><p>I tried Perkeep, I found it&apos;s datamodel a bit hard to understand &#x2013; e.g. <a href="https://perkeep.org/doc/files-and-permanodes" rel="noreferrer">File Nodes or Permanodes?</a> 5GB of photos were stored on disk as 10GB  <a href="https://groups.google.com/g/perkeep/c/dRzBcZD2JiI/m/jOGYSjA0EQAJ" rel="noreferrer">because of a caching bug</a>. I respect Perkeep as an innovator in this area, but it seems stalled: the last release was in <a href="https://perkeep.org/doc/release/0.11" rel="noreferrer">2020</a>.</p><p><strong>Deduping Backup Software: </strong>Most modern backup software uses content-addressable-storage, which automatically dedupes. I have previously <a href="https://www.markhansen.co.nz/cloud-backup-software/" rel="noreferrer">blogged about Cloud Backup Software</a>.</p><p><strong>Conclusion: </strong>I think using a normal, deduping, snapshotting backup software is a good idea! Good backup software:</p><ul><li>Solves deduplication.</li><li>Gives you snapshots you can set retention policies on (so you aren&apos;t paying for old backups forever).</li><li>Encrypts the data.</li><li>Works with offsite storage, including cloud storage.</li><li>Is automatically robust to new Google Takeout data format changes (i.e. there&apos;s no parsing of the JSON, it&apos;s all just dumb files).</li></ul><h2 id="backup-software">Backup Software</h2><p>You could use whatever backup software you want. Backblaze is a common commercial answer.</p><p>I usually use <a href="https://www.markhansen.co.nz/cloud-backup-software/#arq-its-ok" rel="noreferrer">Arq to backup data from my laptop</a>, but as I don&apos;t have room on my laptop disk for all these photos, I thought I&apos;d try something different.</p><p>I tried <a href="https://restic.net/" rel="noreferrer">Restic backup</a>. Restic is open-source, written in Go (so it probably handles errors), dedupes data, is seeing <a href="https://github.com/restic/restic/releases/tag/v0.16.4" rel="noreferrer">very active development</a> (last release two weeks ago), has a <a href="https://github.com/rubiojr/awesome-restic" rel="noreferrer">large ecosystem</a>, and can <a href="https://restic.net/" rel="noreferrer">backup to nearly any storage service via RClone</a>.</p><p>I followed Restic&apos;s docs to <a href="https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html" rel="noreferrer">prepare a new backup repository</a> and <a href="https://restic.readthedocs.io/en/latest/040_backup.html" rel="noreferrer">run a backup</a>:</p><figure class="kg-card kg-code-card"><pre><code>$ restic -r sftp:mark-restic@nas:/home init
$ restic -r sftp:mark-restic@nas:/home --verbose --read-concurrency 8 backup Takeout/
</code></pre><figcaption><p><span style="white-space: pre-wrap;">I created a Restic repo over SFTP on my NAS, and backed up the Takeout/ folder.</span></p></figcaption></figure><p>Restic&apos;s deduplication was effective: I took two backup snapshots a few days apart, and the size of the restic repo barely grew.</p><p>Every two months, I rerun the last command, and restic deduplicates all the new data against the old data.</p><p>If I could get a good GUI for Restic, maybe I&apos;d consider using it for my laptop backup too.</p><h2 id="metadata-google-photos-data-model">Metadata: Google Photos&apos; Data Model</h2><p>Photo files often (but not always) have EXIF data inside containing timestamps and GPS location.</p><p>I&apos;ll go over some of Photos&apos; data model, because I think it makes clearer the tradeoffs around EXIF data.</p><p>Google Photos stores photos and videos (I&apos;ll refer to both as just &quot;Photos&quot;).</p><p>Photos are uploaded from devices to Google Photos, and stored with some Quality:</p><ul><li><a href="Photos and videos are uploaded from devices to Google Photos." rel="noreferrer">Original Quality</a>: same resolution.</li><li><a href="https://support.google.com/photos/answer/6220791" rel="noreferrer">Storage Saver</a> (originally &quot;High Quality&quot;): slightly reduced quality. Resized to &lt;16MP or 1080p.</li><li><a href="https://support.google.com/photos/answer/6220791" rel="noreferrer">Express Quality</a>: resized to &lt;3MP or 480p.</li></ul><p>I believe that for Original Quality, the downloaded file is the same, including all EXIF information, as when originally uploaded. For Storage Saver &amp; Express Quality, that may not be true.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/02/image.png" class="kg-image" alt="A photo of a dog labrador in a jacket at a train station. Date is editable, location is editable." loading="lazy" width="1696" height="1148" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2024/02/image.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2024/02/image.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1600/2024/02/image.png 1600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/02/image.png 1696w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">An example photo I took in Tokyo. Note that Date and Location are editable.</span></figcaption></figure><p><strong>Date</strong>: Google Photos parses the date from the EXIF when uploaded.</p><p>If there&apos;s no EXIF data, some sometimes the time of the upload or the timestamp on the file. You can also edit the photo&apos;s date post-upload. In these situations, the date is not &quot;inside&quot; the file&apos;s bytes, and the new date will not be present as an EXIF tag when you re-download the photo.</p><p>But Google Takeout will include the date in a JSON sidecar.</p><p><strong>GPS Location</strong>: Google Photos parses the location from the photo EXIF.</p><p>Like date, you can also edit the GPS location in Google Photos post-upload. In this situation, the new GPS location is also not inside the file&apos;s EXIF.</p><p>Date and Location are inferred and editable. Google Photos doesn&apos;t overwrite the original information in the photo, but rather chooses to put this information in a &quot;json sidecar&quot; file alongside the image, instead of re-encoding information into EXIF.</p><figure class="kg-card kg-code-card"><pre><code class="language-json">$ cat Takeout/Google Photos/Photos from 2018/IMG_2085.HEIC.json
{
  &quot;title&quot;: &quot;IMG_2085.HEIC&quot;,
  &quot;description&quot;: &quot;&quot;,
  &quot;imageViews&quot;: &quot;6&quot;,
  &quot;creationTime&quot;: {
    &quot;timestamp&quot;: &quot;1524354063&quot;,
    &quot;formatted&quot;: &quot;21 Apr 2018, 23:41:03 UTC&quot;
  },
  &quot;photoTakenTime&quot;: {
    &quot;timestamp&quot;: &quot;1524279700&quot;,
    &quot;formatted&quot;: &quot;21 Apr 2018, 03:01:40 UTC&quot;
  },
  &quot;geoData&quot;: {
    &quot;latitude&quot;: 35.7035,
    &quot;longitude&quot;: 139.57989999999998,
    &quot;altitude&quot;: 67.02,
    &quot;latitudeSpan&quot;: 0.0,
    &quot;longitudeSpan&quot;: 0.0
  },
  &quot;geoDataExif&quot;: {
    &quot;latitude&quot;: 35.7035,
    &quot;longitude&quot;: 139.57989999999998,
    &quot;altitude&quot;: 67.02,
    &quot;latitudeSpan&quot;: 0.0,
    &quot;longitudeSpan&quot;: 0.0
  },
  &quot;url&quot;: &quot;https://photos.google.com/photo/AF1QipM23VLBvQ2A9UqFSIwFz40VTsCCxAcd1gk8akyj&quot;,
  &quot;googlePhotosOrigin&quot;: {
    &quot;mobileUpload&quot;: {
      &quot;deviceType&quot;: &quot;IOS_PHONE&quot;
    }
  }
}</code></pre><figcaption><p><span style="white-space: pre-wrap;">The JSON sidecar for my photo of a dog above. It includes date, location.</span></p></figcaption></figure><p>I think putting the data into JSON sidecars is a reasonable conservative tradeoff: EXIF is very complicated (e.g. there are many ways to specify a timestamp), overwriting EXIF would potentially lose data.</p><h2 id="do-you-lose-metadata-exporting-in-takeout">Do you lose Metadata exporting in Takeout?</h2><p>There are a lot of complaints about this.</p><p>Some metadata may be removed from files when uploading not at &quot;Original Quality&quot;; but you find the most-important metadata in the JSON sidecar.</p><p>If Google Photos edits or infers the GPS or Date, that information isn&apos;t overwritten in the EXIF, but it is in the JSON sidecar.</p><p>So: I think I&apos;m happy with the output. All the data I see in the Google Photos UI is in Takeout.</p><h2 id="metadata-repair-tools">Metadata Repair Tools</h2><p>If you&apos;re moving from Google Photos to another tool, it&apos;s natural to want to take these JSON sidecars and turn it into EXIF the other tool can understand.</p><p>This isn&apos;t straightforward: EXIF is pretty complicated. There are tools for this: <a href="https://github.com/TheLastGimbus/GooglePhotosTakeoutHelper">GooglePhotosTakeoutHelper</a> is recommended universally. <a href="https://metadatafixer.com/">Metadata Fixer</a> ($24) is <a href="https://photos.chrisesplin.com/content/metadata-fixer" rel="noreferrer">not recommended</a>. <a href="https://github.com/pwatk/google-photos-takeout/blob/main/google-photos-takeout.sh">google-photos-takeout.sh</a> and <a href="https://github.com/Alamantus/GooglePhotosExportOrganizer" rel="noreferrer">GooglePhotosExportOrganizer</a> are discontinued. You could always roll your own script with <code>exiftool</code>.</p><h2 id="restoring-the-backup">Restoring The Backup</h2><p>Everyone says: Backups are only as good as restores!</p><p>But I confess I haven&apos;t done a &quot;full restore&quot;. Getting this back into Google Photos would be a huge undertaking, and I can&apos;t be bothered.</p><p>I reckon it would be a disaster trying to reimport. I&apos;d have to use Google Drive&apos;s Photo uploader, and the timestamps would all be wrong, unless I fix them first. I&apos;d probably cry. I&apos;d have to use a Metadata Repair tool above.</p><p>I&apos;ve got peace of mind from having the photo &amp; JSON files saved, and that&apos;s all I want.</p><h2 id="wishlist-future">Wishlist / Future</h2><p>I&apos;ll watch <a href="https://timelinize.com/" rel="noreferrer">Timelinize</a> to see if they can solve online Photos backup commercially.</p><p>I&apos;m hoping that Google will make this easier, somehow &#x2013; maybe by adding an option to overwrite EXIF data on export, or setting modified-time on the archive from the metadata?</p><p>Maybe we could make a standard format for photo libraries.</p><p>I wish we had better archive formats that automatically dedupe files across folders; tar and zip don&apos;t.</p><h2 id="is-there-an-easier-way">Is there an easier way?</h2><p>Please let me know! It&apos;s still a huge pain to do this.</p>]]></content:encoded></item><item><title><![CDATA[Low Floor Bed for Cosleeping]]></title><description><![CDATA[How we built a safer, lower floor bed for cosleeping with baby with the IKEA LÖNSET bed base]]></description><link>https://www.markhansen.co.nz/cosleeping-floor-bed/</link><guid isPermaLink="false">6539a0be30efe100019d4f08</guid><category><![CDATA[Baby]]></category><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Thu, 26 Oct 2023 23:27:35 GMT</pubDate><media:content url="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/C7589F25-C66D-4E70-AE12-1DE356CAE83D_1_102.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/C7589F25-C66D-4E70-AE12-1DE356CAE83D_1_102.jpeg" alt="Low Floor Bed for Cosleeping"><p>How we built a low bed, for co-sleeping with baby.</p><p>We sleep in the same bed as our baby, and we like it. To reduce risk with a young baby, we slept with baby on the side of the bed, next to mum.</p><p>But on the side of the bed, particularly once baby starts rolling, they can fall out of bed. Higher falls hurt more. You can put bed rails up, but rails have their own risks: it seemed simpler to lower the bed.</p><h2 id="pre-baby-a-very-high-mattress">Pre-Baby: A very high mattress</h2><p>We had a queen mattress, on a box spring base, elevated on legs. Probably over 50cm over the ground. We put a camping mattress next to the bed to dampen falls.</p><p>What do people do? We found lots of opinions online:</p><ul><li>Put a <a href="https://www.reddit.com/r/AttachmentParenting/comments/us96ai/comment/i91ztj7/?utm_source=share&amp;utm_medium=web2x&amp;context=3" rel="noreferrer">mattress on the floor</a></li><li>Place the mattress on the <a href="https://www.reddit.com/r/cosleeping/comments/yyb5au/comment/iwtq29c/?utm_source=share&amp;utm_medium=web2x&amp;context=3" rel="noreferrer">bedslats</a> on the floor (or just planks of wood)</li><li>Sleep on a <a href="https://www.reddit.com/r/cosleeping/comments/pmxliq/comment/hclxs97/?utm_source=share&amp;utm_medium=web2x&amp;context=3" rel="noreferrer">camping mattress</a> / tatami mat</li><li>Saw the legs off the bed</li><li>Buy a <a href="https://www.reddit.com/r/cosleeping/comments/pmxliq/comment/hcmvgp8/?utm_source=share&amp;utm_medium=web2x&amp;context=3" rel="noreferrer">short bedframe</a></li><li>Build a <a href="https://www.reddit.com/r/cosleeping/comments/pmxliq/comment/hcp30qd/?utm_source=share&amp;utm_medium=web2x&amp;context=3" rel="noreferrer">custom pallet bed</a></li></ul><p>For how popular co-sleeping is, this is surprisingly unclear! Many of these options are uncomfortable, annoying, or require you to be handy.</p><h2 id="first-try-mattress-on-the-floor">First Try: Mattress on the floor</h2><p>Our first attempt was to just put the mattress on the floor. Height: 25cm. The obvious problem is mould growth. <a href="https://www.reddit.com/r/AttachmentParenting/comments/us96ai/mattress_on_floor_floor_bed/" rel="noreferrer">Reddit threads</a> said it&apos;d be fine.</p><blockquote>You&#x2019;ll want to prop up your mattress against a wall every now and then to air it out and prevent mold from growing underneath. &#x2013; <a href="https://cosleepy.com/toolkit/#:~:text=You%E2%80%99ll%20want%20to%20prop%20up%20your%20mattress%20against%20a%20wall%20every%20now%20and%20then%20to%20air%20it%20out%20and%20prevent%20mold%20from%20growing%20underneath." rel="noreferrer">cosleepy</a></blockquote><p>So we dutifully propped up our mattress on the wall. A huge mattress, this was very annoying, so we didn&#x2019;t do it often. Perhaps predictably, we found mould on the bottom after a few months. Perhaps Sydney is more humid? Maybe we didn&apos;t do it enough? I don&apos;t think we&apos;d have the energy to do this after every naptime.</p><p>We bought a new mattress, and researched what to put under it.</p><h2 id="option-wood-slats-under-the-mattress">Option: Wood slats under the mattress</h2><p>A common opinion:</p><blockquote>go to the hardware store and ask them to cut some boards to put under the mattress so it&#x2019;s not right on the ground</blockquote><blockquote>So we just took the bed slats from our bed frame and put them underneath the mattress</blockquote><p>But I&apos;m pretty sure the slats will move under the bed and scratch the floor. Slats need a frame to hold/stretch them in place!</p><h2 id="option-buy-a-short-bed-frame">Option: Buy a short bed frame</h2><p>I tried to search for a short bed frame. They aren&apos;t very short! Most are at least 20cm high:</p><ul><li>21cm legs: $69: <a href="https://www.ikea.com/au/en/p/grimsbu-bed-frame-grey-80458759/">GRIMSBU Bed frame, grey, Queen - IKEA</a></li><li>20cm legs: $199: <a href="https://www.ikea.com/au/en/p/vevelstad-bed-frame-white-50506436/">VEVELSTAD Bed frame, white, Queen - IKEA</a>.  same style as single bed we have</li><li>20cm legs: $229: <a href="https://www.ikea.com/au/en/p/tarva-bed-frame-pine-s19930111/">TARVA Bed frame, pine, Queen - IKEA</a>&#xA0; + $70 slats</li><li>20cm legs: $279: <a href="https://www.ikea.com/au/en/p/askvoll-bed-frame-white-s29019730/">ASKVOLL Bed frame, white, Queen - IKEA</a></li><li>20cm legs: $299: <a href="https://www.ikea.com/au/en/p/kleppstad-bed-frame-white-vissle-beige-00492682/">KLEPPSTAD Bed frame, white/Vissle beige, Queen - IKEA</a></li><li>15cm high: $179: <a href="https://www.templeandwebster.com.au/Harmony-Low-Rise-H15cm-Bed-ZINU1024.html">Studio Home Harmony Low Rise H15cm Bed | Temple &amp; Webster</a>. has slats.&#xA0;</li><li>15cm high: $189: <a href="https://www.fantasticfurniture.com.au/Categories/Bedroom-%26-Mattresses/Beds/Queen-Beds/Tori-Low-Queen-Bed/p/TOIBAEQNNOOOSTEBLK">Tori Low Queen Bed | Fantastic Furniture</a></li><li>24cm high: $1,445: <a href="https://eva.com.au/products/eva-timber-bed-frame">Eva Timber Bed Frame</a> </li></ul><p>Some people advised to &quot;get a wood ikea bed frame and cut the leg off with 1cm clearance&quot; which I guess would work, but I&apos;m not that handy.</p><h2 id="solution-ikea-l%C3%B6nset-slatted-bed-base">Solution: IKEA L&#xD6;NSET Slatted bed base</h2><p>IKEA&apos;s L&#xD6;NSET bed slats are just 9cm high: truly short. The slats bend, offering more flexibility vs mattress directly on the floor.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.ikea.com/au/en/p/loenset-slatted-bed-base-80278715/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">L&#xD6;NSET Slatted bed base, Queen - IKEA</div><div class="kg-bookmark-description">L&#xD6;NSET Slatted bed base, Queen. 30 slats adjust to your body weight and increase the suppleness of the mattress. Comfort zones adjust to your body.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.ikea.com/au/en/static/ikea-logo.f7d9229f806b59ec64cb.svg" alt="Low Floor Bed for Cosleeping"><span class="kg-bookmark-author">IKEA</span><span class="kg-bookmark-publisher">IKEA</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.ikea.com/au/en/images/products/loenset-slatted-bed-base__0373754_pe553510_s5.jpg" alt="Low Floor Bed for Cosleeping" onerror="this.style.display = &apos;none&apos;"></div></a></figure><p>They come with their own rectangular frame to keep them in place. IKEA intends for them to be used nested inside another bed base, but they work perfectly fine standalone.</p><p>To make queen-size, they come in two units. We tied them together with some gardening ties so they don&apos;t skew apart:</p><p>We were a little concerned about the wood slats scratching the wood floor, so we bought some cheap non-slip pads:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/C3AD113A-EF78-456B-9F87-D0C95B784F8A_1_105_c-1.jpeg" class="kg-image" alt="Low Floor Bed for Cosleeping" loading="lazy" width="768" height="950" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/10/C3AD113A-EF78-456B-9F87-D0C95B784F8A_1_105_c-1.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/C3AD113A-EF78-456B-9F87-D0C95B784F8A_1_105_c-1.jpeg 768w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Tied together and non-slip padded.</span></figcaption></figure><p>I kept stubbing my toe on the ends so we added babyproofing (well, daddy-proofing) foam edge/corner protectors. See bottom right:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/C7589F25-C66D-4E70-AE12-1DE356CAE83D_1_102-1.jpeg" class="kg-image" alt="Low Floor Bed for Cosleeping" loading="lazy" width="2000" height="1500" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/10/C7589F25-C66D-4E70-AE12-1DE356CAE83D_1_102-1.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/10/C7589F25-C66D-4E70-AE12-1DE356CAE83D_1_102-1.jpeg 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1600/2023/10/C7589F25-C66D-4E70-AE12-1DE356CAE83D_1_102-1.jpeg 1600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/C7589F25-C66D-4E70-AE12-1DE356CAE83D_1_102-1.jpeg 2048w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Babyproofing on the toe-hurting corners.</span></figcaption></figure><p>The end result is about 32cm high, and almost all of that height is the mattress itself. The slats flex, giving significantly more comfort than having the mattress directly on the floor. At the start, it&apos;s annoying bending down to the floor, but your muscles adapt and get used to it.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/A4924CBB-10E9-4EDB-83B3-836057C0C15C_1_102-2-1.jpeg" class="kg-image" alt="Low Floor Bed for Cosleeping" loading="lazy" width="2000" height="815" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/10/A4924CBB-10E9-4EDB-83B3-836057C0C15C_1_102-2-1.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/10/A4924CBB-10E9-4EDB-83B3-836057C0C15C_1_102-2-1.jpeg 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1600/2023/10/A4924CBB-10E9-4EDB-83B3-836057C0C15C_1_102-2-1.jpeg 1600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/A4924CBB-10E9-4EDB-83B3-836057C0C15C_1_102-2-1.jpeg 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The foot of the bed.</span></figcaption></figure><p>An unexpected benefit: the baby can climb in and out of bed by himself. Saves us getting out of bed.</p><p>I&apos;m pretty happy with how this turned out!</p><p><strong>P.S. (2024-12-27): </strong>A reader asked what happens when our child falls out of bed.</p><p>We had carpet next to the bed in old last apartment and that was OK for him to fall onto: a few cries, but no injuries. We are a bit more worried about these wood floors, so we put our camping mattress next to the bed.</p><p>Partially inflated, not fully inflated, so it cushions falls better. In our small bedroom, it wedges in between the wall, bed and drawers.</p><p>Our child rolls out of bed onto the mat often. And, astonishingly, almost always stays asleep! It&apos;s a short drop and he loves sleeping down there. Maybe there&apos;s more room and less disturbance from us.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/12/IMG_0928.jpeg" class="kg-image" alt="Low Floor Bed for Cosleeping" loading="lazy" width="960" height="1280" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2024/12/IMG_0928.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2024/12/IMG_0928.jpeg 960w" sizes="(min-width: 720px) 720px"></figure><p></p>]]></content:encoded></item><item><title><![CDATA[Fixing Humidifier Red Light: Beurer LB37]]></title><description><![CDATA[Debugging broken water sensor in my Beurer LB37 Humidifier, after it won't turn on, showing a red LED.]]></description><link>https://www.markhansen.co.nz/beurer-lb37-humidifier-red-light/</link><guid isPermaLink="false">652cb4fb26d9f80001a78134</guid><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Mon, 16 Oct 2023 04:26:50 GMT</pubDate><content:encoded><![CDATA[<p>Our dehumidifier stopped working recently: you&apos;d fill it up with water, and it would not turn on, showing a red LED indicating &quot;water level too low&quot;. Here&apos;s how I debugged the problem, though, sadly, I couldn&apos;t fix it.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.appliancesonline.com.au/ak/6/6/e/3/66e3307a604b3d1579c91690311c6e2217392c38_beurer_air_humidifieraroma_diffuser_lb37_1_b43bd9a2_high-high.jpeg" class="kg-image" alt="White cone on coffee table, mist is coming out the top. The blue light is on." loading="lazy"><figcaption><span style="white-space: pre-wrap;">Happier days. A promotional image of a (working!) Beurer LB37 Humidifier</span></figcaption></figure><p>Four years ago, we bought a Beurer LB37 humidifier. We&apos;d fill it with water, and it had a ultrasonic speaker that turned the water into mist. It had a sensor that turned off the speaker when the water ran out.</p><p>With the red LED showing, I guessed something must be wrong with the water-level detection. There are a few ways you can detect water level: weight sensor, conductivity sensor. I wasn&apos;t sure what was happening. Opening it up, the only moving part I can see is this button that&apos;s pressed in by the top half (water tank).</p><p>I suppose you can use a button to detect weight &#x2013; maybe put a spring inside and use Hooke&apos;s law to measure the force?</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/FA63A559-7C24-4BD8-A855-34D348A7DCE6_1_105_c-2-1.jpeg" class="kg-image" alt="Lower half of humidifier; showing a button the water tank can depress, and the ultrasonic speaker membrane." loading="lazy" width="1024" height="768" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/10/FA63A559-7C24-4BD8-A855-34D348A7DCE6_1_105_c-2-1.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/10/FA63A559-7C24-4BD8-A855-34D348A7DCE6_1_105_c-2-1.jpeg 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/FA63A559-7C24-4BD8-A855-34D348A7DCE6_1_105_c-2-1.jpeg 1024w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Button that gets depressed when the water tank is heavy enough.</span></figcaption></figure><p>Underneath each of the four corners of the humidifier, there are screws to open it up. Open it up, and I see this:</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/IMG_9464.jpeg" class="kg-image" alt="Pulled apart lower part of humidifier. Has AC coming in, chip boards at the top and left, wires connecting them." loading="lazy" width="2000" height="1500" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/10/IMG_9464.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/10/IMG_9464.jpeg 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1600/2023/10/IMG_9464.jpeg 1600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w2400/2023/10/IMG_9464.jpeg 2400w" sizes="(min-width: 720px) 720px"></figure><p>On the right side is the transformer (AC in, 24V DC out x2). This supplies power to  to the fan (middle, black), and to the ultrasonic speaker circuit (middle left, gray). It&apos;s all controlled by the push-button (left green board) and weight sensor (top, gray).</p><p>I pulled apart the weight sensor; it&apos;s just a switch: Jufond SW312.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/7A592E22-4EB5-4699-B570-3D1A4A10127F_1_105_c-1.jpeg" class="kg-image" alt="Black box switch with tiny button at top; says JUFOND SW312, two wires coming out bottom" loading="lazy" width="768" height="589" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/10/7A592E22-4EB5-4699-B570-3D1A4A10127F_1_105_c-1.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/7A592E22-4EB5-4699-B570-3D1A4A10127F_1_105_c-1.jpeg 768w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">JUFOND SW312 Switch</span></figcaption></figure><p>I thought, maybe the switch is broken? That would be consistent with the error. To test this, I attached my multimeter in resistance mode to the terminals and depressed the switch: it still read infinity ohms. I guess the switch is the problem?</p><p>I doubt I can fix it, but let&apos;s try to see what&apos;s going on. I opened up the switch (it has a clip at the left hand side). It&apos;s surprisingly intricate inside, like a watch:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/IMG_9462-1.jpeg" class="kg-image" alt loading="lazy" width="1129" height="714" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/10/IMG_9462-1.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/10/IMG_9462-1.jpeg 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/10/IMG_9462-1.jpeg 1129w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Switch internals.</span></figcaption></figure><p>The switch button (top) presses into the spring, which pulls down the top right-hand side contact, to (hopefully) press into the bottom contact, completing the circuit. The spring adds some hysteresis, ensuring the switch stays on or off and avoiding &apos;bouncing&apos;.</p><p>I pressed in my multimeter (and recruited a second set of hands), and yeah, the pads weren&apos;t making contact enough to connect the circuit. The multimeter still read infinity ohms.</p><p>I thought, maybe the pads are slightly corroded? But I didn&apos;t see any rust on them when I pulled out the bottom probe.</p><p>If I applied a little more pressure on the top with the multimeter probe, then the pads connect to form a circuit. Maybe the spring just doesn&apos;t have enough tension to force the pads together any more?</p><p><em>Can I replace it?</em> On a whim, I Googled [JUFOND SW312] and found some places selling replacements on eBay for USD 8.00, but they take a month to ship, and I&apos;d have to solder it on.</p><p>Unfortunately, I think this is the end for my humidifier. It&apos;s in the e-waste now. At least we had some fun pulling it apart. Maybe this story can help you debug your humidifier?</p>]]></content:encoded></item><item><title><![CDATA[Pram/Stroller Camera Mount]]></title><description><![CDATA[How to mount a heavy-ish mirrorless camera to a pram/stroller with a cheap clamp and ball mount.]]></description><link>https://www.markhansen.co.nz/pramera/</link><guid isPermaLink="false">6445c9372470f1003db0fa0a</guid><category><![CDATA[Photography]]></category><category><![CDATA[Baby]]></category><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Mon, 24 Apr 2023 01:48:55 GMT</pubDate><media:content url="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/04/IMG_8961.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/04/IMG_8961.jpeg" alt="Pram/Stroller Camera Mount"><p>Before <a href="https://www.markhansen.co.nz/blended-surname/">having a baby</a>, I canvassed all the dads I know for &quot;new dad tips&quot;. The most common advice was &quot;get a good camera, because they <em>grow up so fast</em>&quot;. I wasn&apos;t sure I&apos;d be into this whole photography thing, so I picked up a second-hand entry-level mirrorless Sony camera off eBay. It&apos;s fun, and yes: it&apos;s better than the phone camera.</p><p>After the baby arrived, we had lots of family visits, leading to family photos. I wanted us all to be in the family photos, so some<em>thing</em> needs to hold the camera.</p><p>The normal answer is &quot;get a tripod&quot;, but don&apos;t we already have a rigid, stable, movable frame: our pram! Could I use that?</p><p>I first tried zipping up the pram top and resting the camera on that. But the camera was too low; the crotch-height angle was unflattering.</p><p>Could I attach to the pram handle instead, a bit higher? With our <a href="https://uppababy.com/au/cruz-v2/">Uppababy Cruz V2</a> pram, the handle telescopes, allowing some height control.</p><p>Researching camera mounts (definition: &quot;things that attach cameras to other things&quot;) was more complicated than I expected. There exist mounts for attaching cameras to all sorts of objects.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/04/image.png" class="kg-image" alt="Pram/Stroller Camera Mount" loading="lazy" width="1500" height="797" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/04/image.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/04/image.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/04/image.png 1500w" sizes="(min-width: 720px) 720px"><figcaption>My favourite: <a href="https://gopro.com/en/us/shop/mounts-accessories/fetch-dog-harness/ADOGM-001.html">camera dog mounts</a></figcaption></figure><p>But even with all the upselling accessories of modern prams ($100 cup-holders, anyone?), I couldn&apos;t find specifically-marketed pram/stroller mounts.</p><p>I worried about screw incompatibility, but cameras seem to have standardised on the 1/4&quot; threaded screw. Thank goodness.</p><h2 id="mounting-the-pram-camera-pramera">Mounting the Pram-Camera (Pramera)</h2><p>I bought the <a href="https://www.amazon.com.au/gp/product/B00CSMCPKQ/">SmallRig model 1124: &quot;Super Clamp Mount with Ball Head Mount&quot;</a> for $21AUD, and found I can clamp it to the top of the pram: it particularly grips well on the pram&apos;s hard plastic handle, not the soft leather.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/04/image-1.png" class="kg-image" alt="Pram/Stroller Camera Mount" loading="lazy" width="905" height="1185" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/04/image-1.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/04/image-1.png 905w" sizes="(min-width: 720px) 720px"></figure><p>You must clamp the narrow axis of the handle, otherwise it rotates around. The narrow axis points diagonally, so the clamp points diagonally, but the ball joint lets you correct for this and angle the camera flat.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/04/IMG_8967.jpg" class="kg-image" alt="Pram/Stroller Camera Mount" loading="lazy" width="2000" height="1500" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/04/IMG_8967.jpg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/04/IMG_8967.jpg 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1600/2023/04/IMG_8967.jpg 1600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w2400/2023/04/IMG_8967.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>The centre of mass is... lopsided</figcaption></figure><p>I worried about the camera falling off and smashing the lens. The camera&apos;s centre of mass is lopsided with the heavy lens putting torque on the screw.</p><p>And the diagonal axis puts a bit of torque on the clamp. You can help this a little by aiming the camera sideways, or backwards toward the mount, as shown in the picture above. You have to aim the camera backwards anyway to avoid getting the pram in your photos.</p><p>I probably wouldn&apos;t keep the camera screwed there while moving over uneven terrain; but it&apos;s been perfectly stable stationary while taking photos.</p><p>It&apos;s a little slow to screw on the camera and adjust the ball to get things level; but no more difficult than a tripod. The pram wheels help positioning for framing.</p><p>Baby can&apos;t reach the handle, so I&apos;m not worried about small parts/choke hazards. Even so, I think you&apos;d struggle to disassemble this to small-enough parts to worry.</p><h2 id="conclusion">Conclusion</h2><p>Overall, the pram-mounted camera has been good for our family, and I&apos;d recommend it. The pram is a natural tripod. The height is good. We keep the clamp on the pram handle long-term, and dangle toys from it. The clamp doesn&apos;t get in the way. And we get nice family photos, with all of us in them.</p><p>Walking around, I don&apos;t see anyone else doing this. I think the Pramera should be more of a thing!</p>]]></content:encoded></item><item><title><![CDATA[Mastodon Account Verification with Ghost Blog]]></title><description><![CDATA[How to get a green verification checkmark on Mastodon from your link to your Ghost blog. Inject a link tag in your site header.]]></description><link>https://www.markhansen.co.nz/verifying-mastodon/</link><guid isPermaLink="false">64267d2e7ac7ed003dae4152</guid><category><![CDATA[Programming]]></category><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Fri, 31 Mar 2023 06:47:32 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1603899122361-e99b4f6fecf5?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fHRpY2t8ZW58MHx8fHwxNjgwMTc1MjE1&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1603899122361-e99b4f6fecf5?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fHRpY2t8ZW58MHx8fHwxNjgwMTc1MjE1&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Mastodon Account Verification with Ghost Blog"><p>People are flocking to Mastodon. The cool users are &quot;verified&quot;: the links in their profile are ticked and green, so you can be more confident they aren&apos;t imposters. e.g. here&apos;s <a href="https://mastodon.social/@shanselman">Scott Hanselman&apos;s profile</a>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/03/image.png" class="kg-image" alt="Mastodon Account Verification with Ghost Blog" loading="lazy" width="1190" height="1082" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/03/image.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/03/image.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/03/image.png 1190w" sizes="(min-width: 720px) 720px"><figcaption>Website, Podcast, GitHub all ticked... I think we can be sure this is the real Scott</figcaption></figure><p>In contrast, <a href="https://hachyderm.io/@markhansen">my profile</a> looks decidedly less &quot;legit&quot;:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/03/image-1.png" class="kg-image" alt="Mastodon Account Verification with Ghost Blog" loading="lazy" width="1190" height="1082" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/03/image-1.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/03/image-1.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/03/image-1.png 1190w" sizes="(min-width: 720px) 720px"><figcaption>No green, no ticks</figcaption></figure><p> I would like my profile to be clear that it&apos;s the real me, not some spammer pretending to be me. You achieve this with <a href="https://docs.joinmastodon.org/user/profile/#verification">Mastodon verification</a>:</p><blockquote>Document-based verification and blue ticks are not possible without a central authority. However, Mastodon can cross-reference the links you put on your profile to prove that you are the real owner of those links. In case one of those links is your personal homepage that is known and trusted, it can serve as the next-best-thing to identity document verification.</blockquote><blockquote>If you put an HTTPS link in your profile metadata, Mastodon checks if that link resolves to a web page that links back to your Mastodon profile with a special <code>rel=me</code> attribute. If so, you get a verification checkmark next to that link, since you are confirmed as the owner.</blockquote><p>So I need to change this site. I host this site, markhansen.co.nz, on the <a href="https://ghost.org/">Ghost</a> blog engine. How do you add such a link with <code>rel=me</code>? There&apos;s <a href="https://forum.ghost.org/t/verifying-mastodon-account-with-rel-me/34227">this forum thread</a>, proposing two solutions and ultimately floundering:</p><ol><li>Update the theme&apos;s HTML: I don&apos;t want to <a href="https://github.com/TryGhost/Alto">fork the theme</a> and add custom code, and constantly rebase vs upstream. I pay Ghost to avoid that maintenance pain.</li><li>Add an <code>&lt;a rel=&quot;me&quot; href=&quot;...&quot;&gt;</code> link. Where? My theme doesn&apos;t support extra links with custom <code>rel</code>. Dead-end.</li></ol><p>I took a different approach, using the <code>&lt;link ref=&quot;me&quot; href=&quot;...&quot;&gt;</code> syntax in the site-wide header with code injection. Visit your blog, Settings, Code Injection, and add:</p><pre><code>&lt;link href=&quot;https://hachyderm.io/@markhansen&quot; rel=&quot;me&quot;&gt;
</code></pre><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/03/image-2.png" class="kg-image" alt="Mastodon Account Verification with Ghost Blog" loading="lazy" width="1072" height="696" srcset="https://www.markhansen.co.nz/content/images/size/w600/2023/03/image-2.png 600w, https://www.markhansen.co.nz/content/images/size/w1000/2023/03/image-2.png 1000w, https://www.markhansen.co.nz/content/images/2023/03/image-2.png 1072w" sizes="(min-width: 720px) 720px"></figure><p>Save, reload and view-source on your blog to check the source is now just before your <code>&lt;/head&gt;</code> tag closes.</p><p>Editing your Mastodon profile and saving will trigger validation:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/03/image-3.png" class="kg-image" alt="Mastodon Account Verification with Ghost Blog" loading="lazy" width="1154" height="1048" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/03/image-3.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/03/image-3.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/03/image-3.png 1154w" sizes="(min-width: 720px) 720px"><figcaption>Sweet verification</figcaption></figure><p>I blogged this because I couldn&apos;t find a quick answer searching [ghost blog validate mastodon]. Hope this helps.</p>]]></content:encoded></item><item><title><![CDATA[CAP Theorem & UI Programming]]></title><description><![CDATA[When the network doesn't respond, will you wait forever, blocking the app (and your users), sacrificing availability? Or will you make up an answer (show inconsistent data)?]]></description><link>https://www.markhansen.co.nz/cap-theorem-ui/</link><guid isPermaLink="false">63f4695e96d890003d84bf06</guid><category><![CDATA[Android]]></category><category><![CDATA[Programming]]></category><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Sat, 04 Mar 2023 05:01:07 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1584385002340-d886f3a0f097?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDIyfHxjb25uZWN0aW9ufGVufDB8fHx8MTY3NzkwNTM2OQ&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1584385002340-d886f3a0f097?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDIyfHxjb25uZWN0aW9ufGVufDB8fHx8MTY3NzkwNTM2OQ&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="CAP Theorem &amp; UI Programming"><p>I had a familiar conversation at work recently. A colleague asked: &quot;There&apos;s a feature I only want to show to users if some hardware&apos;s present. The Android API for hardware presence says the API must be called on a background thread. But I need it from the UI thread, and on application startup, at the first frame. How can I do this?&quot;</p><p>I asked:</p><ul><li>Where does the API get the data from? From some driver implemented by the device manufacturer.</li><li>How long could the API call take? Up to 2 seconds, with retries.</li><li>Does the data ever change? No, the same device will always return the same value.</li></ul><p>If we call this API on the UI thread, we might block user interaction for two seconds. That&#x2019;s probably long enough to cause &quot;<a href="https://developer.android.com/topic/performance/vitals/anr">Application Not Responding</a>&quot; timeouts (crashes!) on some devices. And it&apos;s definitely long enough to annoy users.</p><p>It&#x2019;s a shame that this logically static data is fetched from a general-purpose asynchronous API, backed by the manufacturer&apos;s hardware-abstraction-layer drivers, which could be arbitrary code.</p><p>The call is at least interprocess communication, which can be slow even if the other side is implemented well. But this app was running was in a car (Android Automotive), so it&apos;s possible the driver might even fetch the data from another vehicle computer via the in-vehicle Ethernet. This might be even slower; the other computer might not even reply at all!</p><h2 id="what-can-we-do">What can we do?</h2><p>So we have a choice, either:</p><ol><li>Fetch the data synchronously at app startup, and risk app downtime while the app waits for the response, or:</li><li>Fetch the data asynchronously. This may need a reworking of the code to support async loading. The code will have to handle the case where the data isn&#x2019;t ready yet, then update later on once the data is ready; even if the user&apos;s in the middle of doing something with the UI. A bunch more complexity.</li></ol><h2 id="please-dont-block-the-app">Please don&apos;t block the app</h2><p>I told my colleague, you have to change your code to be asynchronous; we can&apos;t risk app downtime if the API is slow, because of your one feature.</p><p>You have to show <em>something</em> to the user while you&apos;re waiting for this data; perhaps you should hide your feature until API responds? &quot;Pessimistic UI&quot; and &quot;Optimistic UI&quot; are opposite ways of how to handle this.</p><h2 id="cap-theorem">CAP Theorem</h2><p>This is an everyday example of the CAP Theorem. The CAP theorem (loosely) states that out of:</p><ul><li>Consistency (serving the right answer)</li><li>Availability (always serving data)</li><li>Partition-Tolerance (robustness in face of network disconnections)</li></ul><p>You can choose max 2 of 3.</p><p>Our Android system can experience network partitions (between computers in the car), and we some data we want to read across the network... should we either:</p><ol><li>Block waiting for the data: choosing consistency, waiting, possibly for a long time, and blocking the user from doing their work while you wait? or</li><li>Code asynchronously, &apos;guessing&apos; until the data arrives with a default answer (maybe: &quot;the hardware doesn&apos;t exist, disable the feature&quot;). We lose consistency, but maintain availability.</li></ol><p>Most modern applications are networked, so have to deal with network partitions. Usually, availability correlates with business outcomes: users being able to do the work they need to, and ultimately pay the business. So, for many applications, consistency is given up first.</p><h2 id="whats-acceptable-to-block-on">What&apos;s Acceptable to Block On?</h2><p>There&apos;s always a choice. Often, your app needs to make many async calls: over the network; or inter-processes; or to disk. These connections can fail. In our app, we generally accept blocking on resources where there&apos;s no other way:</p><ul><li>Blocking on disk reads to load our application&apos;s code.</li><li>Making inter-process calls to known-mostly-reliable components, e.g. calling SurfaceFlinger to render frames to the screen.</li></ul><p>We generally don&apos;t accept blocking on:</p><ul><li>Network calls.</li><li>Inter-process calls backed by arbitrary unknown code.</li></ul><h2 id="spotting-cap-problems">Spotting CAP Problems</h2><p>It&apos;s useful to spot when you&apos;re facing a CAP problem. CAP is stereotyped as mostly applying to server-side databases, but it comes up a lot in networked client applications.</p><p>Ask: when making a request that could take a long time, should our app wait a long time for the response (choosing consistency)?, or assume an answer until we get the response (choosing availability)?</p>]]></content:encoded></item><item><title><![CDATA[Should you inflate Android Views on a background thread?]]></title><description><![CDATA[TL;DR: No, it's not safe. I show race conditions that could happen to any View, and consider AsyncLayoutInflater.]]></description><link>https://www.markhansen.co.nz/android-view-thread/</link><guid isPermaLink="false">63ec746961092f003dbbb66e</guid><category><![CDATA[Programming]]></category><category><![CDATA[Profiling]]></category><category><![CDATA[Android]]></category><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Sat, 18 Feb 2023 04:18:37 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1496355723323-30286a0b340d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGluZmxhdGV8ZW58MHx8fHwxNjc2NjkzMjAy&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1496355723323-30286a0b340d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGluZmxhdGV8ZW58MHx8fHwxNjc2NjkzMjAy&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Should you inflate Android Views on a background thread?"><p><strong>TL;DR: No.</strong></p><p>Android code can be slow. Profiling will often reveal the cause as&quot;View inflation&quot;, which just means &quot;constructing a <code>View</code>&quot;, e.g. calling <code>new TextView(Context)</code>, or <code>new ImageView(Context)</code>, or <code>new LinearLayout(Context)</code>. These constructors often do a lot of work, slowly (for reasons out of scope to this post), and they block the UI thread.</p><p>When people find UI-thread-blocking jank, they naturally want to speed it up. In Android, the usual advice is to move janky slow stuff to a background thread. <strong>But it&apos;s unsafe to move View inflation to a background thread.</strong> You may make your code faster, but you risk undefined behaviour: very annoying, impossible-to-debug failures that will waste your team&apos;s time.</p><p><a href="https://developer.android.com/guide/components/processes-and-threads#:~:text=the%20Android%20UI%20toolkit%20is%20not%20thread%2Dsafe">Android is pretty clear</a> on this:</p><blockquote>the Android UI toolkit is <em><em><strong>not</strong></em></em> thread-safe. So, you must not manipulate your UI from a worker thread&#x2014;you must do all manipulation to your user interface from the UI thread.</blockquote><h2 id="the-problems">The Problems</h2><p><code>View</code> constructors access their <code>Context</code>, their parent <code>View</code>, and static variables. All of this data is mutable, and written and read without synchronization. And if you do these reads and writes from different threads, you have data races, you may see both old and new data. You&apos;ll get undefined behaviour.</p><p>Here is a (non-exhaustive) list of examples:</p><ol><li><strong>Mutable Context</strong>. <code>Context</code> (really, <code><a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ContextImpl.java">ContextImpl</a></code> behind the scenes) is very mutable. Some data is protected by mutexes, but not all. For example, <code><a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ContextImpl.java;l=243;drc=f6ca5e5fbfcd28f3989d78e4482d00c37acc8409">ContextImpl.mDisplay</a></code> is non-final and mutated. Without synchronization, there&apos;s no <a href="https://shipilev.net/blog/2014/safe-public-construction/#_safe_publication">safe publication</a> across threads. Views may access the Display to decide what UI to show.</li><li><strong>Parent LayoutParams</strong>. Views often access their parent&apos;s <code>LayoutParams</code> to decide how to render. The parent could be mutated from the UI thread (perhaps in the middle of a layout pass) while you&apos;re reading their params.</li><li><strong>Static Variables.</strong> Android often puts data in mutable static datastructures, to save allocations. If you&apos;re read these concurrently with a write, you&apos;ll see corrupted data.</li></ol><p>For example, every View subclass must eventually call the super-constructor <a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/View.java;l=5366;drc=dcc45bdd207eab7ea9d4184671f4dcd9681012a3"><code>android.view.View(Context)</code>, which calls</a> <code><a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/ViewConfiguration.java;l=525;drc=2a6ce367fe5d53194a3de2f034cf2770f8979edc">ViewConfiguration.get(Context)</a></code>, which <a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/ViewConfiguration.java;l=531;drc=2a6ce367fe5d53194a3de2f034cf2770f8979edc">reads and writes a static <code>SparseMap</code> cache variable</a>:</p><figure class="kg-card kg-code-card"><pre><code class="language-java">static final SparseArray&lt;ViewConfiguration&gt; sConfigurations =
    new SparseArray&lt;ViewConfiguration&gt;(2);

public static ViewConfiguration get(@NonNull @UiContext Context context) {
  ...
  ViewConfiguration configuration = sConfigurations.get(density);
  if (configuration == null) {
    configuration = new ViewConfiguration(context);
    sConfigurations.put(density, configuration);
  }
  ...
}
</code></pre><figcaption>Reading and writing a static (global) <code>SparseArray sConfigurations</code>. No synchronization!</figcaption></figure><p><code>SparseArray</code> is a compound data structure, backed by an <code>Object[]</code> and an <code>int</code> length. Interleaved calls to this method from different threads are a data race, and could (for example) result in an <code>int</code> length longer than the <code>Object[]</code>&apos;s size.</p><p>This is just one data race, which affects all Views. There are probably many more races. Custom Views can have arbitrary logic.</p><h2 id="what-about-asynclayoutinflater">What about AsyncLayoutInflater?</h2><p>What about <code><a href="https://developer.android.com/reference/androidx/asynclayoutinflater/view/AsyncLayoutInflater">AsyncLayoutInflater</a></code>, the official Android support library which &quot;<a href="https://developer.android.com/reference/androidx/asynclayoutinflater/view/AsyncLayoutInflater#inflate(int,android.view.ViewGroup,androidx.asynclayoutinflater.view.AsyncLayoutInflater.OnInflateFinishedListener)">Triggers view inflation on background thread</a>&quot;?</p><p><code>AsyncLayoutInflater</code> <a href="https://developer.android.com/reference/androidx/asynclayoutinflater/view/AsyncLayoutInflater#:~:text=For%20a%20layout%20to%20be%20inflated%20asynchronously%20it%20needs%20to%20have%20a%20parent%20whose%20generateLayoutParams%20is%20thread%2Dsafe%20and%20all%20the%20Views%20being%20constructed%20as%20part%20of%20inflation%20must%20not%20create%20any%20Handlers%20or%20otherwise%20call%20myLooper">notes some requirements</a> about where it should be used:</p><blockquote>For a layout to be inflated asynchronously it needs to have a parent whose generateLayoutParams is thread-safe and all the Views being constructed as part of inflation must not create any Handlers or otherwise call myLooper.</blockquote><p>This addresses problem #2 above... but not #1 and #3.</p><p>So is AsyncLayoutInflater safe? I don&apos;t think so.</p>]]></content:encoded></item><item><title><![CDATA[Choosing a Portmanteau/Blended Baby Surname]]></title><description><![CDATA[How we chose a surname for our new baby: options, tradeoffs, and how to search for blends of your & your partner's surnames]]></description><link>https://www.markhansen.co.nz/blended-surname/</link><guid isPermaLink="false">63ec06cb61092f003dbbb2df</guid><category><![CDATA[Baby]]></category><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Wed, 15 Feb 2023 05:30:18 GMT</pubDate><media:content url="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/Lowe---Hansen---.....png" medium="image"/><content:encoded><![CDATA[<img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/Lowe---Hansen---.....png" alt="Choosing a Portmanteau/Blended Baby Surname"><p>My partner &amp; I recently had a baby. As part of having a baby, the state of New South Wales demands you name them, with (at least) a first name and a surname. First names are mostly anything-goes, but surnames present difficult tradeoffs around gender equality, recognition of history, convention, social ease, verbosity, convenience, spelling, stigma, future-proofing, and the politics of parents and grandparents.</p><p>We eventually decided on a portmanteau surname for our child. Here&apos;s how.</p><h2 id="background-surname-history">Background: Surname History</h2><p>It&apos;s complex. Surnames were usually mandated by state bureaucrats, to help disambiguate citizens, making it easier to tax and conscript them.</p><p>My surname (Hansen) started as a <em>patronym</em> (son-of-Hans); when my great-great-great-grandfather Hans Peterson had a child Hans Roby Hansen, sometime in the 1800s. This became a permanent/fixed <em>patrilineal</em> name sometime around Denmark&apos;s mid-1800s naming laws, passing down to children who&apos;s father was not Hans. European nobility had long used surnames to indicate legitimacy of children, but mandatory European commoner surnames are relatively recent: many countries only required surnames from the 1800s.</p><p>My partner&apos;s surname &quot;Lowe&quot; is an Anglicised version of the Cantonese &quot;Lau&quot;, which traces back at least hundreds of years. Her parents&apos; generation chose the Anglo-Saxon-sounding &quot;Lowe&quot; when immigrating to Australia, likely to reduce the racism their family would face. China, with its strong bureaucratic central government, has had widespread surnames for thousands of years.</p><p>Both names have significant history for us. But, so do our mother&apos;s maiden names, and we don&apos;t carry those. We can recognise history without carrying a surname. Genealogy is getting easier with better record-keeping, even without matching surnames.</p><h2 id="surname-options">Surname Options</h2><p>We weren&apos;t sure what to do. We wanted to be intentional about choosing a name, not just &quot;go with the default&quot;.</p><p>I didn&apos;t really like the patriarchy of choosing my surname over my partner&apos;s. I was a little worried about &quot;giving up the history&quot; of my name and choosing my partner&apos;s surname. Double-barrelled names are getting more popular. &quot;Lowe-Hansen&quot; rolls off the tongue nicely (like Johansen), but both our names have spelling difficulty, with &quot;e&quot;s that you always have to spell out over the phone; would our child resent spelling out &quot;Lowe-Hansen&quot; for the rest of their life?</p><p>I read some interviews on the <a href="https://flnp.wordpress.com/interviews-organized-by-topic/">Feminist Last Naming Practices Project</a>. And I was grateful to find a book that explores the topic, by an Australia-based author:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-23.png" class="kg-image" alt="Choosing a Portmanteau/Blended Baby Surname" loading="lazy" width="375" height="500"><figcaption>Lorelai Vashti&apos;s <em>How to Choose Your Baby&apos;s Last Name: A Handbook for New Parents. </em><a href="https://www.amazon.com/Choose-Your-Babys-Last-Name-ebook/dp/B081779TXK/">$3 on Kindle</a>.</figcaption></figure><p>The book explores the tradeoffs by interviewing parents who have chosen from the following options:</p><ol><li>Father&apos;s surname</li><li>Mother&apos;s surname</li><li>Hyphenation, or a double-barrelled surname (without a hyphen)</li><li>Alternating the two parents&apos; surnames between siblings</li><li>Combining the two surnames into a portmanteau or blended surname</li><li>Making up a completely new surname</li></ol><p>All present tradeoffs, there&apos;s no right or wrong answer.</p><h2 id="a-portmanteaublend">A Portmanteau/Blend?</h2><p>It was tempting. We&apos;d heard rumours of portmanteau names. But didn&apos;t know anyone that had actually done it. Lowe + Hansen... &quot;Hanslow&quot;? &quot;Lohan&quot;?</p><p>We initially discounted it as too &quot;out there&quot;. But we kept thinking about it. The interviews in the book with happy parents who&apos;d chosen it made us more confident we&apos;d be happy with one.</p><p>The book helped mitigate our main concerns: trouble at borders? Not really, and if there is ever any, show the birth certificate. Are there other bureaucratic challenges? Not really, apparently. It&apos;s pretty common for children to have different names from their parents anyway, with divorces and combined families. Systems generally already have to handle this.</p><h2 id="a-tool-for-finding-portmanteau-surnames">A Tool for Finding Portmanteau Surnames</h2><p>There are surname-history websites, and portmanteau-search websites. But no surname-portmanteau-search websites. So I made my own.</p><p>I looked for a dataset of surnames &#x2013; although we could choose any word, probably limiting it to &apos;actual surnames&apos; seemed like a reasonable place to start. The most detailed surname dataset I could find was the <a href="https://www.census.gov/topics/population/genealogy/data/2010_surnames.html">US Census&apos; list of surnames with over 100 occurrences</a>: 162,254 surnames.</p><p>I loaded this into a Jupyter Notebook. I <a href="https://colab.research.google.com/drive/1sOcrbY3a-jUMO4qNeHV2Avg9mX51GcgR#scrollTo=imN7GBBENXx4">put the code online into a Colab Notebook</a> so you can copy and edit the code in-browser.</p><pre><code class="language-python">import io
import pandas as pd
import requests
import zipfile

r = requests.get(&quot;https://www2.census.gov/topics/genealogy/2010surnames/names.zip&quot;)
with zipfile.ZipFile(io.BytesIO(r.content)) as z:
  with z.open(&quot;Names_2010Census.csv&quot;) as f:
    df = pd.read_csv(f, na_filter=False)
df</code></pre><p>First looking at anagrams: with all characters drawn from our names:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">def anagram(s):
    return all([c in &quot;HANSEN LOWE&quot; for c in s])
df[df[&quot;name&quot;].map(anagram)]</code></pre><figcaption>Anagrams: all letters must be from our combined names.</figcaption></figure><p>This didn&apos;t work well. It kept giving options with letters drawn from just one of our names (e.g. LEE, HESS). So I constrained it to require at least two characters from each person&apos;s name:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">def anagram(s):
    return (all([c in &quot;HANSENLOWE &quot; for c in s])
            and sum([c in &quot;HANSEN &quot; for c in s]) &gt; 2
            and sum([c in &quot;LOWE &quot; for c in s]) &gt; 2)
df[df[&quot;name&quot;].map(anagram)]</code></pre><figcaption>Anagram finder: at least two characters from each parent&apos;s name</figcaption></figure><p>The results weren&apos;t great: ALLEN, NELSON, OWENS, LAWSON, OLSEN... these didn&apos;t really feel like a blend of our names.</p><p>So I tried another approach, looking for names that had some part of each of our names:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">def bits(s):
    return (&quot;LO&quot; in s or &quot;LAU&quot; in s) and (&quot;HAN&quot; in s or &quot;SEN&quot; in s)
df[df[&quot;name&quot;].map(bits)]</code></pre><figcaption>Surnames with LO/LAU and HA</figcaption></figure><p>This turned up much better options with a blended sound: HARLOW (fun?), HANLON (of razor fame?), HALLOWELL (that&apos;s a lot of L&apos;s), HALLOWAY (if you think of my partner&apos;s name as pronounced Low&#xE9; it almost makes sense), SHALLOW (hmm, not sure of the implications), HALLOW/HALLOWS (fun, but sounds like a greeting), LOHAN, HANSELL.</p><p><a href="https://colab.research.google.com/drive/1sOcrbY3a-jUMO4qNeHV2Avg9mX51GcgR#scrollTo=imN7GBBENXx4">Copy and edit the notebook online</a>.</p><p>Using the code, we didn&apos;t find any names we liked more than the initial guesses of Lohan and Hanslow. But the process reassured me that we weren&apos;t missing a better option.</p><h2 id="lock-it-in">Lock it in</h2><p>We tried-out the name Lohan for a week. It felt good. Easy to spell, easy to say.</p><p>It feels egalitarian: half of my name, half of my partner&apos;s name. While I get more letters of my name in (3 letters to 2), my partner gets the whole pronounced syllable of her name in.</p><p>It removes our surname&apos;s hard-to-spell suffixes. It&apos;s a mix, just like the baby is. It has Celtic history, just like I do.</p><p>We put Lohan on the birth registration, and the state of New South Wales was happy to ink it on the birth certificate.</p><p>The parent&apos;s surnames haven&apos;t changed: we didn&apos;t think &quot;having the same surname&quot; is very important for family cohesion.</p><p>I&apos;m glad that we have this choice. We found a name that&apos;s true to our values. Blended surnames are a real option.</p>]]></content:encoded></item><item><title><![CDATA[Cloud Backup Storage Options]]></title><description><![CDATA[What Cloud Storage should I upload my backups to? Amazon S3? Azure Blob Storage? Google Cloud Storage? More niche players?]]></description><link>https://www.markhansen.co.nz/cloud-backup-storage/</link><guid isPermaLink="false">63de478b5b1992003d88fbd8</guid><category><![CDATA[Backup]]></category><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Mon, 06 Feb 2023 03:40:14 GMT</pubDate><media:content url="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/cloud_storages.png" medium="image"/><content:encoded><![CDATA[<img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/cloud_storages.png" alt="Cloud Backup Storage Options"><p>After I <a href="https://www.markhansen.co.nz/short-circuited-external-disk-recovery/">lost a hard drive to (possibly) a lightning storm</a>, I re-evaluated my backup strategy. I chose which <a href="https://www.markhansen.co.nz/cloud-backup-software/">cloud backup software</a> I&apos;d use (winner: <a href="https://duplicacy.com/">Duplicacy</a>), but where should I upload my backups to? There are lots of blob storage providers.</p><p>The <strong>pricing</strong> is fairly tricky to work out. There&apos;s usually storage fees (per GB per month), sometimes &apos;restore fees&apos; if it&apos;s in deep storage, and sometimes network egress fees when you want to get your data back out onto the public internet. Some providers have &apos;minimum retentions&apos; where you have to pay for at least 1, 3, 6, or 12 months of storage.</p><p><strong>What I care about</strong>: I have about 1TB to back up. I want backups to be quick, so they should probably have an Australian region. I&apos;m happy to keep the backups for at least 90 days. But I probably want to start thinning backups out after 90 days, and delete altogether after a year. I don&apos;t really want minimum retention period of a year because then I&apos;ll feel very locked-in.</p><p><strong>Egress fees</strong>: I don&apos;t care so much about network egress fees because I think the risk of me losing this data locally is pretty low, about &lt;10% each year. If my local data fails, I&apos;ll probably be happy to pay a bit to restore. Or maybe I can poke around the data on a VM in their cloud, and only egress the really interesting stuff. So my expected value of egress fees is maybe one tenth what&apos;s quoted. Egress fees are generally high, so you really don&apos;t want to use a backup software that has to read back data as part of normal operation.</p><p><strong>Your mileage may vary</strong>: This is a pretty rough, not very rigorous, somewhat &quot;vibes-based&quot; comparison. I didn&apos;t bother considering restore fees. You should probably read <a href="https://blog.decryption.net.au/t/cloud-archive-storage-comparison/91/1">Anthony Agius&apos; Cloud Archive Storage Comparison</a> in addition to mine, because he goes deeper into niche players that I couldn&apos;t be bothered evaluating, and considers restore fees. Also this <a href="https://forum.duplicacy.com/t/supported-storage-backends/1107">great comparison on the Duplicacy Forum</a>.</p><p>I chose <strong>Google Cloud Storage Coldline</strong>. This was frankly a bit disappointing, as I was hoping to find something better than what I was already using!</p><h2 id="options">Options</h2><p>Here&apos;s the spreadsheet I made to evaluate (<a href="https://docs.google.com/spreadsheets/d/1_xjHphGVLaKxtHho5OXJpP5eUXiQnxPFWkaRtX1804c/edit#gid=0">original in Google Sheets</a>):</p><!--kg-card-begin: html--><table xmlns="http://www.w3.org/1999/xhtml" cellspacing="0" cellpadding="0" dir="ltr" border="1" style="table-layout:fixed;font-size:10pt;font-family:Arial;width:0px;border-collapse:collapse;border:none"><colgroup><col width="194"><col width="180"><col width="63"><col width="60"><col width="66"><col width="71"><col width="72"><col width="63"><col width="63"><col width="63"><col width="298"></colgroup><tbody><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;wrap-strategy:4;white-space:normal;word-wrap:break-word;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Name&quot;}">Name</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;wrap-strategy:4;white-space:normal;word-wrap:break-word;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Storage Class&quot;}">Storage Class</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;wrap-strategy:4;white-space:normal;word-wrap:break-word;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Region&quot;}">Region</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;wrap-strategy:4;white-space:normal;word-wrap:break-word;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Storage USD / GB / month&quot;}">Storage USD / GB / month</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;wrap-strategy:4;white-space:normal;word-wrap:break-word;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Storage USD / TB / year&quot;}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}">Storage USD / TB / year</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;wrap-strategy:4;white-space:normal;word-wrap:break-word;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing Page&quot;}">Pricing Page</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;wrap-strategy:4;white-space:normal;word-wrap:break-word;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Minimum Storage Duration Days&quot;}">Minimum Storage Duration Days</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;wrap-strategy:4;white-space:normal;word-wrap:break-word;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Read Latency&quot;}">Read Latency</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;wrap-strategy:4;white-space:normal;word-wrap:break-word;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Egress USD / GB&quot;}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}">Egress USD / GB</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;wrap-strategy:4;white-space:normal;word-wrap:break-word;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Egress USD / TB&quot;}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}">Egress USD / TB</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;wrap-strategy:4;white-space:normal;word-wrap:break-word;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Egress Policy&quot;}">Egress Policy</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Google Cloud Storage&quot;}">Google Cloud Storage</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Archive&quot;}" data-sheets-hyperlink="https://cloud.google.com/storage/docs/storage-classes#archive"><a class="in-cell-link" href="https://cloud.google.com/storage/docs/storage-classes#archive" target="_blank">Archive</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Sydney&quot;}">Sydney</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.0025}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0025</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:30}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$30</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://cloud.google.com/storage/pricing"><a class="in-cell-link" href="https://cloud.google.com/storage/pricing" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:365}">365</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;milliseconds&quot;}" data-sheets-hyperlink="https://cloud.google.com/storage/docs/storage-classes#descriptions"><a class="in-cell-link" href="https://cloud.google.com/storage/docs/storage-classes#descriptions" target="_blank">milliseconds</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.19}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://cloud.google.com/vpc/network-pricing#premium-tier"><a class="in-cell-link" href="https://cloud.google.com/vpc/network-pricing#premium-tier" target="_blank">$0.190</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:190}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$190</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Premium Tier Australia Egress&quot;}" data-sheets-hyperlink="https://cloud.google.com/vpc/network-pricing#premium-tier"><a class="in-cell-link" href="https://cloud.google.com/vpc/network-pricing#premium-tier" target="_blank">Premium Tier Australia Egress</a></td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Google Cloud Storage&quot;}">Google Cloud Storage</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Coldline&quot;}" data-sheets-hyperlink="https://cloud.google.com/storage/docs/storage-classes#coldline"><a class="in-cell-link" href="https://cloud.google.com/storage/docs/storage-classes#coldline" target="_blank">Coldline</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Sydney&quot;}">Sydney</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.006}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0060</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:72}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$72</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://cloud.google.com/storage/pricing"><a class="in-cell-link" href="https://cloud.google.com/storage/pricing" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:90}">90</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;milliseconds&quot;}" data-sheets-hyperlink="https://cloud.google.com/storage/docs/storage-classes#descriptions"><a class="in-cell-link" href="https://cloud.google.com/storage/docs/storage-classes#descriptions" target="_blank">milliseconds</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.19}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://cloud.google.com/vpc/network-pricing#premium-tier"><a class="in-cell-link" href="https://cloud.google.com/vpc/network-pricing#premium-tier" target="_blank">$0.190</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:190}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$190</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Premium Tier Australia Egress&quot;}" data-sheets-hyperlink="https://cloud.google.com/vpc/network-pricing#premium-tier"><a class="in-cell-link" href="https://cloud.google.com/vpc/network-pricing#premium-tier" target="_blank">Premium Tier Australia Egress</a></td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Google Cloud Storage&quot;}">Google Cloud Storage</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Nearline&quot;}" data-sheets-hyperlink="https://cloud.google.com/storage/docs/storage-classes#nearline"><a class="in-cell-link" href="https://cloud.google.com/storage/docs/storage-classes#nearline" target="_blank">Nearline</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Sydney&quot;}">Sydney</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.016}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0160</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:192}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$192</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://cloud.google.com/storage/pricing"><a class="in-cell-link" href="https://cloud.google.com/storage/pricing" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:30}">30</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;milliseconds&quot;}" data-sheets-hyperlink="https://cloud.google.com/storage/docs/storage-classes#descriptions"><a class="in-cell-link" href="https://cloud.google.com/storage/docs/storage-classes#descriptions" target="_blank">milliseconds</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.19}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://cloud.google.com/vpc/network-pricing#premium-tier"><a class="in-cell-link" href="https://cloud.google.com/vpc/network-pricing#premium-tier" target="_blank">$0.190</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:190}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$190</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Premium Tier Australia Egress&quot;}" data-sheets-hyperlink="https://cloud.google.com/vpc/network-pricing#premium-tier"><a class="in-cell-link" href="https://cloud.google.com/vpc/network-pricing#premium-tier" target="_blank">Premium Tier Australia Egress</a></td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Google Cloud Storage&quot;}">Google Cloud Storage</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Standard&quot;}" data-sheets-hyperlink="https://cloud.google.com/storage/docs/storage-classes#standard"><a class="in-cell-link" href="https://cloud.google.com/storage/docs/storage-classes#standard" target="_blank">Standard</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Sydney&quot;}">Sydney</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.023}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0230</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:276}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$276</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://cloud.google.com/storage/pricing"><a class="in-cell-link" href="https://cloud.google.com/storage/pricing" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}">0</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;milliseconds&quot;}" data-sheets-hyperlink="https://cloud.google.com/storage/docs/storage-classes#descriptions"><a class="in-cell-link" href="https://cloud.google.com/storage/docs/storage-classes#descriptions" target="_blank">milliseconds</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.19}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://cloud.google.com/vpc/network-pricing#premium-tier"><a class="in-cell-link" href="https://cloud.google.com/vpc/network-pricing#premium-tier" target="_blank">$0.190</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:190}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$190</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Premium Tier Australia Egress&quot;}" data-sheets-hyperlink="https://cloud.google.com/vpc/network-pricing#premium-tier"><a class="in-cell-link" href="https://cloud.google.com/vpc/network-pricing#premium-tier" target="_blank">Premium Tier Australia Egress</a></td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Backblaze B2&quot;}">Backblaze B2</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;-&quot;}">-</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;us-west&quot;}">us-west</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.005}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0050</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:60}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$60</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://www.backblaze.com/b2/cloud-storage-pricing.html"><a class="in-cell-link" href="https://www.backblaze.com/b2/cloud-storage-pricing.html" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}">0</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;"></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.01}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://www.backblaze.com/b2/cloud-storage-pricing.html"><a class="in-cell-link" href="https://www.backblaze.com/b2/cloud-storage-pricing.html" target="_blank">$0.010</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:10}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$10</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Consumption-Based Rate&quot;}">Consumption-Based Rate</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Synology C2&quot;}">Synology C2</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;-&quot;}">-</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Taiwan&quot;}">Taiwan</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.005832499999999999}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}" data-sheets-formula="=69.99/12/1000">$0.0058</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:69.99}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$70</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://c2.synology.com/en-us/pricing/object-storage"><a class="in-cell-link" href="https://c2.synology.com/en-us/pricing/object-storage" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}">0</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;"></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://c2.synology.com/en-us/pricing/storage"><a class="in-cell-link" href="https://c2.synology.com/en-us/pricing/storage" target="_blank">$0.000</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$0</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;"></td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Wasabi&quot;}">Wasabi</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;-&quot;}">-</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Sydney&quot;}" data-sheets-hyperlink="https://wasabi.com/press-releases/wasabi-technologies-opens-storage-region-in-sydney/"><a class="in-cell-link" href="https://wasabi.com/press-releases/wasabi-technologies-opens-storage-region-in-sydney/" target="_blank">Sydney</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.0059900000000000005}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}" data-sheets-formula="=5.99/1000">$0.0060</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:71.88}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$72</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://wasabi.com/"><a class="in-cell-link" href="https://wasabi.com/" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}">0</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;"></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://wasabi.com/glossary/egress-charges-definition/"><a class="in-cell-link" href="https://wasabi.com/glossary/egress-charges-definition/" target="_blank">$0.000</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$0</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;"></td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Cloudflare R2&quot;}">Cloudflare R2</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;-&quot;}">-</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;auto&quot;}" data-sheets-hyperlink="https://blog.cloudflare.com/r2-ga/"><a class="in-cell-link" href="https://blog.cloudflare.com/r2-ga/" target="_blank">auto</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.015}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0150</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:180}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$180</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://www.cloudflare.com/en-gb/products/r2/"><a class="in-cell-link" href="https://www.cloudflare.com/en-gb/products/r2/" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}">0</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;"></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://www.cloudflare.com/en-gb/products/r2/"><a class="in-cell-link" href="https://www.cloudflare.com/en-gb/products/r2/" target="_blank">$0.000</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$0</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;"></td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Amazon S3 &quot;}">Amazon S3 </td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Standard&quot;}" data-sheets-hyperlink="https://aws.amazon.com/s3/storage-classes/"><a class="in-cell-link" href="https://aws.amazon.com/s3/storage-classes/" target="_blank">Standard</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Sydney&quot;}">Sydney</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.025}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0250</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:300}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$300</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://aws.amazon.com/s3/pricing/"><a class="in-cell-link" href="https://aws.amazon.com/s3/pricing/" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}">0</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;millisecond&quot;}">millisecond</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.114}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://aws.amazon.com/cloudfront/pricing/"><a class="in-cell-link" href="https://aws.amazon.com/cloudfront/pricing/" target="_blank">$0.114</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:114}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$114</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Regional Data Transfer Out to Internet (Australia)&quot;}">Regional Data Transfer Out to Internet (Australia)</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Amazon S3&quot;}">Amazon S3</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Standard Infrequent Access&quot;}" data-sheets-hyperlink="https://aws.amazon.com/s3/storage-classes/"><a class="in-cell-link" href="https://aws.amazon.com/s3/storage-classes/" target="_blank">Standard Infrequent Access</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Sydney&quot;}">Sydney</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.025}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0250</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:300}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$300</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://aws.amazon.com/s3/pricing/"><a class="in-cell-link" href="https://aws.amazon.com/s3/pricing/" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:30}">30</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;millisecond&quot;}">millisecond</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.114}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://aws.amazon.com/cloudfront/pricing/"><a class="in-cell-link" href="https://aws.amazon.com/cloudfront/pricing/" target="_blank">$0.114</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:114}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$114</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Regional Data Transfer Out to Internet (Australia)&quot;}">Regional Data Transfer Out to Internet (Australia)</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Amazon S3&quot;}">Amazon S3</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Glacier Instant Retrieval&quot;}" data-sheets-hyperlink="https://aws.amazon.com/s3/storage-classes/glacier/"><a class="in-cell-link" href="https://aws.amazon.com/s3/storage-classes/glacier/" target="_blank">Glacier Instant Retrieval</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Sydney&quot;}">Sydney</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.0138}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0138</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:165.6}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$166</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://aws.amazon.com/s3/pricing/"><a class="in-cell-link" href="https://aws.amazon.com/s3/pricing/" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:90}">90</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;milliseconds&quot;}">milliseconds</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.114}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://aws.amazon.com/cloudfront/pricing/"><a class="in-cell-link" href="https://aws.amazon.com/cloudfront/pricing/" target="_blank">$0.114</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:114}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$114</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Regional Data Transfer Out to Internet (Australia)&quot;}">Regional Data Transfer Out to Internet (Australia)</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Amazon S3&quot;}">Amazon S3</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Glacier Flexible Retrieval&quot;}" data-sheets-hyperlink="https://aws.amazon.com/s3/storage-classes/glacier/"><a class="in-cell-link" href="https://aws.amazon.com/s3/storage-classes/glacier/" target="_blank">Glacier Flexible Retrieval</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Sydney&quot;}">Sydney</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.0036}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0036</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:43.2}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$43</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://aws.amazon.com/s3/pricing/"><a class="in-cell-link" href="https://aws.amazon.com/s3/pricing/" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:90}">90</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;1min-12h&quot;}">1min-12h</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.114}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://aws.amazon.com/cloudfront/pricing/"><a class="in-cell-link" href="https://aws.amazon.com/cloudfront/pricing/" target="_blank">$0.114</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:114}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$114</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Regional Data Transfer Out to Internet (Australia)&quot;}">Regional Data Transfer Out to Internet (Australia)</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Amazon S3&quot;}">Amazon S3</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Glacier Deep Archive&quot;}" data-sheets-hyperlink="https://aws.amazon.com/s3/storage-classes/glacier/"><a class="in-cell-link" href="https://aws.amazon.com/s3/storage-classes/glacier/" target="_blank">Glacier Deep Archive</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Sydney&quot;}">Sydney</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.00099}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0010</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:11.879999999999999}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$12</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://aws.amazon.com/s3/pricing/"><a class="in-cell-link" href="https://aws.amazon.com/s3/pricing/" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:180}">180</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;12h&quot;}">12h</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.114}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://aws.amazon.com/cloudfront/pricing/"><a class="in-cell-link" href="https://aws.amazon.com/cloudfront/pricing/" target="_blank">$0.114</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:114}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$114</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Regional Data Transfer Out to Internet (Australia)&quot;}">Regional Data Transfer Out to Internet (Australia)</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Microsoft Azure Blob Storage&quot;}">Microsoft Azure Blob Storage</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Hot (Locally Redundant)&quot;}" data-sheets-hyperlink="https://azure.microsoft.com/en-au/pricing/details/storage/blobs/#pricing"><a class="in-cell-link" href="https://azure.microsoft.com/en-au/pricing/details/storage/blobs/#pricing" target="_blank">Hot (Locally Redundant)</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Sydney&quot;}">Sydney</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.02}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0200</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:240}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$240</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://azure.microsoft.com/en-au/pricing/details/storage/blobs/#overview"><a class="in-cell-link" href="https://azure.microsoft.com/en-au/pricing/details/storage/blobs/#overview" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}">0</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;milliseconds&quot;}">milliseconds</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.12}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://azure.microsoft.com/en-au/pricing/details/bandwidth/#pricing"><a class="in-cell-link" href="https://azure.microsoft.com/en-au/pricing/details/bandwidth/#pricing" target="_blank">$0.120</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:120}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$120</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Premium Internet egress from Australia&quot;}">Premium Internet egress from Australia</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Microsoft Azure Blob Storage&quot;}">Microsoft Azure Blob Storage</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Cool (Locally Redundant)&quot;}" data-sheets-textstyleruns="{&quot;1&quot;:0,&quot;2&quot;:{&quot;2&quot;:{&quot;1&quot;:2,&quot;2&quot;:1136076},&quot;9&quot;:1}}&#xEE10;{&quot;1&quot;:4,&quot;2&quot;:{&quot;2&quot;:{&quot;1&quot;:2,&quot;2&quot;:0},&quot;9&quot;:0}}" data-sheets-hyperlinkruns="{&quot;1&quot;:0,&quot;2&quot;:&quot;https://learn.microsoft.com/en-GB/azure/storage/blobs/access-tiers-overview&quot;}&#xEE10;{&quot;1&quot;:4}"><span style="font-size:10pt;font-family:Arial;font-style:normal;text-decoration:underline;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;color:#1155cc;"><a class="in-cell-link" target="_blank" href="https://learn.microsoft.com/en-GB/azure/storage/blobs/access-tiers-overview">Cool</a></span><span style="font-size:10pt;font-family:Arial;font-style:normal;"> (Locally Redundant)</span></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Sydney&quot;}">Sydney</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.011}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0110</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:132}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$132</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://azure.microsoft.com/en-au/pricing/details/storage/blobs/#overview"><a class="in-cell-link" href="https://azure.microsoft.com/en-au/pricing/details/storage/blobs/#overview" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:30}">30</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;milliseconds&quot;}">milliseconds</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.12}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://azure.microsoft.com/en-au/pricing/details/bandwidth/#pricing"><a class="in-cell-link" href="https://azure.microsoft.com/en-au/pricing/details/bandwidth/#pricing" target="_blank">$0.120</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:120}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$120</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Premium Internet egress from Australia&quot;}">Premium Internet egress from Australia</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Microsoft Azure Blob Storage&quot;}">Microsoft Azure Blob Storage</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Archive (Locally Redunant)&quot;}" data-sheets-hyperlink="https://azure.microsoft.com/en-au/pricing/details/storage/blobs/#pricing"><a class="in-cell-link" href="https://azure.microsoft.com/en-au/pricing/details/storage/blobs/#pricing" target="_blank">Archive (Locally Redunant)</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Sydney&quot;}">Sydney</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.002}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0020</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:24}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$24</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://azure.microsoft.com/en-au/pricing/details/storage/blobs/#overview"><a class="in-cell-link" href="https://azure.microsoft.com/en-au/pricing/details/storage/blobs/#overview" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:180}">180</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;hours&quot;}">hours</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.12}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://azure.microsoft.com/en-au/pricing/details/bandwidth/#pricing"><a class="in-cell-link" href="https://azure.microsoft.com/en-au/pricing/details/bandwidth/#pricing" target="_blank">$0.120</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:120}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$120</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Premium Internet egress from Australia&quot;}">Premium Internet egress from Australia</td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;iDrive e2&quot;}">iDrive e2</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;-&quot;}">-</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;San Jose&quot;}" data-sheets-hyperlink="https://www.idrive.com/e2/locations"><a class="in-cell-link" href="https://www.idrive.com/e2/locations" target="_blank">San Jose</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.004}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}">$0.0040</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:48}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$48</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://www.idrive.com/e2/pricing"><a class="in-cell-link" href="https://www.idrive.com/e2/pricing" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}">0</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;"></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://www.idrive.com/e2/pricing"><a class="in-cell-link" href="https://www.idrive.com/e2/pricing" target="_blank">$0.000</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$0</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;"></td></tr><tr style="height:21px;"><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Storj&quot;}">Storj</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;-&quot;}">-</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Random&quot;}">Random</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.004}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.0000&quot;,&quot;3&quot;:1}" data-sheets-formula="=4/1000">$0.0040</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:48}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2*12">$48</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;" data-sheets-value="{&quot;1&quot;:2,&quot;2&quot;:&quot;Pricing&quot;}" data-sheets-hyperlink="https://www.storj.io/pricing"><a class="in-cell-link" href="https://www.storj.io/pricing" target="_blank">Pricing</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0}">0</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;"></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline;color:#1155cc;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:0.007}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0.000&quot;,&quot;3&quot;:1}" data-sheets-hyperlink="https://www.storj.io/pricing"><a class="in-cell-link" href="https://www.storj.io/pricing" target="_blank">$0.007</a></td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:7}" data-sheets-numberformat="{&quot;1&quot;:4,&quot;2&quot;:&quot;\&quot;$\&quot;#,##0&quot;,&quot;3&quot;:1}" data-sheets-formula="=R[0]C[-1]*R21C2">$7</td><td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;"></td></tr></tbody></table><!--kg-card-end: html--><h3 id="google-cloud-storage">Google Cloud Storage</h3><p>Disclosure: I&apos;m employed by Google, and use Google Cloud Storage at work.</p><p>I was already using Google Cloud Storage Coldline for Arq backups from my laptop. It&apos;s a good fit for backups, with decent storage pricing ($72/TB/year) and still instant retrieval when needed. There&apos;s a high-speed Sydney region, the security posture is great, I&apos;m very familiar with the platform, the tool ecosystem is large, but the egress pricing, particularly in Australia, is extremely expensive.</p><h3 id="amazon-web-services-s3">Amazon Web Services S3</h3><p>The market leader and inventor of this segment. Amazon has 5(!) classes of blob storage, from &quot;Standard&quot; to &quot;Glacier Deep Archive&quot;.</p><p>Amazon has a Sydney region, which is probably fast to upload to. Their security posture is great. But their instant-access 90-day retention offering, &quot;Glacier Instant Retrieval&quot; was much more expensive ($166/TB/year storage) than Google&apos;s. And I didn&apos;t really feel like wrestling with the tradeoffs of &quot;Glacier Flexible Retrieval&quot; ($43/TB/year storage). Would each API request take an hour to return? What if I need to restore many files, do I need to rewrite my backup client to work in parallel? Or do I copy to other storage first? There&apos;s probably good answers to these questions but I don&apos;t have them.</p><p>AWS&apos; egress fees are famously expensive and opaque, though it looks like it&apos;s cheaper than Google in Australia. It was pretty hard to find the egress pricing and I&apos;m not totally sure I&apos;m reading the right price?</p><h3 id="microsoft-azure-blob-storage">Microsoft Azure Blob Storage</h3><p>Azure is the #2 cloud. They have a Sydney region, and 30-day (&quot;Cool&quot;) and 180-day retention options (&quot;Archive&quot;). They have lots of different pricing depending how much level of replication you want. I just looked at their cheapest option. Their 30-day retention plan &quot;Cold&quot; costs $132/TB/year storage. The 180-day &quot;Archive&quot; storage is cheaper than Google, but might take hours to restore, and I don&apos;t want to figure that out.</p><h3 id="backblaze-b2">Backblaze B2</h3><p>A lot of people on backup forums recommend <a href="https://www.backblaze.com/b2/cloud-storage.html">Backblaze B2</a>. The price is great: $60/TB/year storage, the egress fees are &lt;10x cheaper than Google/Amazon/Microsoft, and it&apos;s backed by a big backup company. I thought it was promising, so I tried it.</p><p>B2 was <strong>slow</strong>. I&apos;m not sure exactly why; maybe it&apos;s the latency from Australia to their closest datacenter (US-West)? Or maybe their computers or disks just can&apos;t sink the data fast enough. My home internet is 40Mbps up, but with Duplicacy backing up to Backblaze B2, I struggled to maintain 10Mbps, even when I cranked it up to 20 parallel upload threads.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-22.png" class="kg-image" alt="Cloud Backup Storage Options" loading="lazy" width="1556" height="710" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-22.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-22.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-22.png 1556w" sizes="(min-width: 720px) 720px"><figcaption>Backblaze R2 was 2-3x slower than Google Cloud Storage</figcaption></figure><p>This made the backup take around 3x as long as it should (around a week, rather than two-ish days), so I cancelled the upload midway and moved to Google Cloud Storage. It&apos;s a shame! I would have loved a cheaper option with no egress/retention fees.</p><h3 id="wasabi">Wasabi</h3><p>Backup forum people also recommended <a href="https://wasabi.com/">Wasabi</a>. Their price is the same as Google Cloud Storage Coldline ($72/TB/year), and they have no egress fees. I signed up to try them.</p><p>I like that they have a Sydney datacenter, which might make them faster. But it turned out you can only list buckets by sending requests to the US?</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-9.png" class="kg-image" alt="Cloud Backup Storage Options" loading="lazy" width="1268" height="124" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-9.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-9.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-9.png 1268w" sizes="(min-width: 720px) 720px"></figure><p>What? So I couldn&apos;t set up Duplicacy to use the local datacenter. This seems to have been <a href="https://forum.duplicacy.com/t/wasabi-in-web-version-working-only-with-us-east-1/2224">broken since 2019</a>, so I don&apos;t think it&apos;ll be fixed anytime soon.</p><p>I find it shady that Wasabi don&apos;t explain on their pricing page that they will charge you for minimum 90 days storage, while all other competitors I surveyed were up-front about this, <a href="https://wasabi.com/paygo-pricing-faq/#minimum-storage-duration">Wasabi buries this information in their FAQ</a>. I only found out about this because of people warning about it on backup forums! It would have been very annoying if I&apos;d deleted some data and charged for 90 days of storage.</p><p>On Wasabi&apos;s pricing page, they directly compare their offering (90day min retention) against Google Cloud Storage Standard (0 day retention), when Coldline would be the comparable offering, and cost the same price. </p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-10.png" class="kg-image" alt="Cloud Backup Storage Options" loading="lazy" width="1230" height="400" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-10.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-10.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-10.png 1230w" sizes="(min-width: 720px) 720px"></figure><p>This seems, at best, misinformed about their competitors, and dishonest at worst.</p><p>With data storage, trust is so important! You&apos;re paying for the trust that you&apos;ll be able to read your data back. Underhanded-at-best price comparisons, and hiding retention fees, hardly inspires trust. So I closed my Wasabi account and moved on.</p><h3 id="synology-c2">Synology C2</h3><p>I was backing up my Synology Network-Attached-Storage device. And <a href="https://c2.synology.com/en-us">Synology has an integrated cloud storage offering</a>. They have no retention fees, but the closest datacenter they have is Taiwan, which is still a while away. And the price was pretty similar to Google Cloud Storage Coldline. In the end, <a href="https://www.markhansen.co.nz/cloud-backup-software/#synology-hyper-backup-slow">I was pretty unimpressed with their software&apos;s speed</a>, so I moved on.</p><h3 id="cloudflare-r2">Cloudflare R2</h3><p><a href="https://www.cloudflare.com/en-gb/products/r2/">Cloudflare R2</a> made a big splash recently with object storage with $0 egress fees from a large CDN. It&apos;s unclear whether they have storage close to Sydney: <a href="https://blog.cloudflare.com/r2-ga/#region-automatic">they only support the <code>auto</code> zone, where they decide for you where your data lives</a>. The pricing didn&apos;t really make sense for me though, at $180/TB/year. So I didn&apos;t look further into it.</p><h3 id="idrive-e2">iDrive e2</h3><p><a href="https://www.idrive.com/e2/">iDrive e2</a> has rock-bottom pricing ($48/TB/year), no egress fees. Their storage is &quot;S3-compatible&quot;. No Australian region however, the closest is San Jose, CA.</p><p>But <em>the vibes seem off</em> &#x2013; their pricing lights up the &quot;too good to be true&quot; scam-detection part of my brain. iDrive offer 90% off the first year, which seems... steeper of a discount than a reputable company would do.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-20.png" class="kg-image" alt="Cloud Backup Storage Options" loading="lazy" width="1226" height="748" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-20.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-20.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-20.png 1226w" sizes="(min-width: 720px) 720px"><figcaption>Unsustainable pricing?</figcaption></figure><p>Obviously it&apos;s a customer acquisition strategy and they hope to lock you in. But I&apos;m skeptical of storage businesses that sell unsustainable products.</p><p>There are a lot of bargain-basement storage providers that offer &quot;lifetime deals&quot; or &quot;unlimited storage&quot;. These are unsustainable and smell like pyramid schemes. iDrive isn&apos;t quite doing this, but it&apos;s close. I want to pay them a sustainable price to store my data. If not, why wouldn&apos;t they cut corners, and lose my data?</p><h2 id="storj">Storj</h2><p>Some backup forum people talked of <a href="https://www.storj.io/">Storj</a>, which claims to &quot;Store data securely on over 16k nodes worldwide instead of just a few vulnerable data centers with privacy and CDN-like performance by design&quot;. The price is really low: $48/TB/year, with very cheap egress: $7/TB/year.</p><p>Random people get paid for their disk space on Ethereum. Regular audits are carried out to ensure nodes are actually storing the data.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-21.png" class="kg-image" alt="Cloud Backup Storage Options" loading="lazy" width="758" height="674" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-21.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-21.png 758w" sizes="(min-width: 720px) 720px"><figcaption>https://www.storj.io/node</figcaption></figure><p>It&apos;s interesting. But, again! Backup storage is about trust, and I don&apos;t trust blockchain grifters with my backups.</p><h2 id="conclusion">Conclusion</h2><p>I went with Google Cloud Storage Coldline. The price is transparent and decent, the data is stored in Sydney, the upload speed is good, and the security posture is strong. I will pay a bunch in egress fees if I have to restore, but there&apos;s low probability of my disks failing, so I&apos;m discounting that.</p><p>Hope this helps if you&apos;re trying to set up your own backups.</p>]]></content:encoded></item><item><title><![CDATA[Send Duplicacy Backup Notifications with Mailgun]]></title><description><![CDATA[How to send schedule backup success and failure emails from Duplicacy with Mailgun]]></description><link>https://www.markhansen.co.nz/duplicacy-mailgun/</link><guid isPermaLink="false">63e043215b1992003d88fd41</guid><category><![CDATA[Backup]]></category><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Mon, 06 Feb 2023 01:00:20 GMT</pubDate><media:content url="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/Duplicacy-Mailgun.png" medium="image"/><content:encoded><![CDATA[<img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/Duplicacy-Mailgun.png" alt="Send Duplicacy Backup Notifications with Mailgun"><p>I&apos;m setting up <a href="https://duplicacy.com/">Duplicacy Backup</a> after <a href="https://www.markhansen.co.nz/short-circuited-external-disk-recovery/">a drive failed during a lightning storm</a>, and after <a href="https://www.markhansen.co.nz/cloud-backup-software/">evaluating backup software options</a>. Backups are no good if they fail without telling you!</p><p>Here&apos;s how I set it up <a href="https://www.mailgun.com/">Mailgun</a> to send the notifications of backup success and failure.</p><p>Duplicacy lets you send SMTP notifications after scheduled backups, by ticking the &apos;Send email after completion&apos; checkbox.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-11.png" class="kg-image" alt="Send Duplicacy Backup Notifications with Mailgun" loading="lazy" width="1520" height="810" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-11.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-11.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-11.png 1520w" sizes="(min-width: 720px) 720px"><figcaption>Screenshot of Duplicacy Web UI</figcaption></figure><p>Usually I&apos;d register a throwaway GMail account for sending email from automated bots, but <a href="https://forum.duplicacy.com/t/using-gmail-smtp-server/649/7?u=mark3">saspus@ on the Duplicacy forums suggested</a>:</p><blockquote>It&#x2019;s best not to use Gmail for sending automated email; especially if it involves exporting credentials that provide full access to your google account and storing them pretty much plaintext within the service that has no business accessing your gmail account in the first place. This not only defeats any MFA you might have there but is also likely against googles TOS.</blockquote><blockquote>Especially when dedicated services for sending this type of transactional emails are readily available (E.g. mail gun, Amazon SES, etc).</blockquote><p>They&apos;re right, it&apos;s annoying registering throwaway GMail accounts: you have to set up security questions and backup accounts and the account might get closed as spam. So let&apos;s try Mailgun instead.</p><p>Mailgun has a free indefinitely-long trial tier, which looks sufficient:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-12.png" class="kg-image" alt="Send Duplicacy Backup Notifications with Mailgun" loading="lazy" width="634" height="654" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-12.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-12.png 634w"><figcaption>5k should be enough for me sending one email a day.</figcaption></figure><p>Sign up, verify your email, validate your phone number by SMS.</p><p>Then you have to add yourself as an &apos;authorized recipient&apos;. I had trouble finding this, it&apos;s under &quot;Sending &gt; Overview &gt; Authorized Recipients&quot;.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-13.png" class="kg-image" alt="Send Duplicacy Backup Notifications with Mailgun" loading="lazy" width="942" height="640" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-13.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-13.png 942w" sizes="(min-width: 720px) 720px"><figcaption>Sending &gt; Overview &gt; Authorized Recipients</figcaption></figure><p>Free trial accounts can only send to these verified recipients. You get 5 free recipients; I only need one.</p><p>Then click &apos;SMTP&apos;, as Duplicacy only does SMTP sending, not HTTP-with-API-key sending:</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-14.png" class="kg-image" alt="Send Duplicacy Backup Notifications with Mailgun" loading="lazy" width="768" height="536" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-14.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-14.png 768w" sizes="(min-width: 720px) 720px"></figure><p>Then create credentials for Duplicacy. You could just use the default sender credentials, but just in case I use this Mailgun account for more than one thing, I don&apos;t want Duplicacy to use the default (postmaster?) credentials. Click &quot;Manage SMTP credentials&quot;, and &quot;Add new SMTP user&quot;. I called mine &quot;duplicacy&quot;:</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-15.png" class="kg-image" alt="Send Duplicacy Backup Notifications with Mailgun" loading="lazy" width="1218" height="580" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-15.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-15.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-15.png 1218w" sizes="(min-width: 720px) 720px"></figure><p>The password isn&apos;t shown on screen, instead you copy it to clipboard:</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-16.png" class="kg-image" alt="Send Duplicacy Backup Notifications with Mailgun" loading="lazy" width="616" height="346" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-16.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-16.png 616w"></figure><p>Now go back to Duplicacy, and click &quot;Send email after completion&quot; and set it up like this, pasting in the password:</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-17.png" class="kg-image" alt="Send Duplicacy Backup Notifications with Mailgun" loading="lazy" width="1222" height="1202" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-17.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-17.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-17.png 1222w" sizes="(min-width: 720px) 720px"></figure><p>There were a few ports you can use, but only Server: <code>smtp.mailgun.org:587</code> worked for me. Trying a different port actually <a href="https://forum.duplicacy.com/t/duplicacy-web-ui-hangs-while-trying-to-test-email/7261/4">hung the Duplicacy Web UI bugreport; the email sending blocked forever while holding a mutex</a>.</p><p>Use the same &apos;From&apos; and Username` field as your SMTP credentials. Put your email in &apos;To&apos;.</p><p>Put <code>{{Status}}</code> into the Subject line which will be replaced with whether the backup succeeded or failed.</p><p>I then clicked &apos;Test&apos;. My first few tests failed, I looked in the <code>duplicacy_web.log</code> file to see why. My test email ended up in Spam, and I clicked &apos;Not Spam&apos;. Hopefully that will train GMail not to put this in Spam? But to be sure, in the message kebab-menu, select &apos;Filter messages like this&apos;:</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-18.png" class="kg-image" alt="Send Duplicacy Backup Notifications with Mailgun" loading="lazy" width="1376" height="376" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-18.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-18.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-18.png 1376w" sizes="(min-width: 720px) 720px"></figure><p>Then &quot;Never send it to Spam&quot;:</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-19.png" class="kg-image" alt="Send Duplicacy Backup Notifications with Mailgun" loading="lazy" width="1110" height="740" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-19.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-19.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-19.png 1110w" sizes="(min-width: 720px) 720px"></figure><p>And you should be getting backup messages! Now, if only Duplicacy would <a href="https://forum.duplicacy.com/t/send-mail-only-when-backup-fails/3008">allow you to only send messages on failure</a>, I&apos;d be very happy.</p>]]></content:encoded></item><item><title><![CDATA[Cloud Backup Software]]></title><description><![CDATA[What software can incrementally backup 1TB+, e2e-encrypted, to cloud object storage, reliably, cheaply?]]></description><link>https://www.markhansen.co.nz/cloud-backup-software/</link><guid isPermaLink="false">63ddd80b5b1992003d88f987</guid><category><![CDATA[Backup]]></category><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Sat, 04 Feb 2023 06:17:42 GMT</pubDate><media:content url="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/IMG_8643.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/IMG_8643.jpg" alt="Cloud Backup Software"><p>After my last blogpost, where one of my hard drives was <a href="https://www.markhansen.co.nz/short-circuited-external-disk-recovery/">maybe taken out by lightning</a>, I was reminded to reevaluate my backup strategy.</p><p>I have data on my macbook, and on my Synology NAS. My macbook&apos;s backups are all good, having revisioned daily backups on Arq.</p><p>But my big files that live only on the Synology NAS (mostly disk images taken from disks inherited from a deceased estate) were only &quot;synced&quot; to Google Cloud Storage, by <a href="https://www.synology.com/en-au/dsm/feature/cloud_sync">Synology Cloud Sync</a>. Cloud Sync is not revisioned &#x2013; only the latest version is kept, so a corrupted source would overwrite the sync. So, not really backups! Time to re-evaluate cloud backup software. I&apos;m looking for software that will run on the NAS (Linux, Synology), at a reasonable cost for 1TB of backups, client-side encrypted, maybe with a year&apos;s worth of version retention.</p><p><strong>Spoiler</strong>: Ultimately, I use <a href="https://www.arqbackup.com/">Arq</a> to backup my Macs, and <a href="https://duplicacy.com/">Duplicacy</a> for my Synology Network-Attached-Storage (NAS) device. See below.</p><h2 id="arq-its-ok">Arq: It&apos;s OK</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-8.png" class="kg-image" alt="Cloud Backup Software" loading="lazy" width="1540" height="876" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-8.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-8.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-8.png 1540w" sizes="(min-width: 720px) 720px"><figcaption>Arq screenshot, from my mac.</figcaption></figure><p><a href="https://www.arqbackup.com/">Arq</a> is decent, and I use it for my Macbook. It uses a modern, content-addressed storage format. It mostly works, although I do worry when I hear about the developer breaking backwards compatibility between versions. And the UX is a little awkward to find things. And I worry a little that it&apos;s closed-source, but I&apos;m reassured by them <a href="https://www.arqbackup.com/docs/arqcloudbackup/English.lproj/dataFormat.html">documenting the storage format</a>. Overall, I would weakly recommend it as the best of a bad bunch.</p><p>But Arq doesn&apos;t run on Linux. So I can&apos;t use it on the Synology.</p><h2 id="crashplan-the-good-old-days">Crashplan: The Good Old Days</h2><p>Crashplan used to be good. I used to use it. The price point was great and the software mostly just worked. But then they shut down their consumer offering, leaving only a too-expensive small business offering. A real shame.</p><h2 id="backblaze-the-market-leader">Backblaze: The Market Leader</h2><p><a href="https://www.backblaze.com/">Backblaze</a> is the best left after Crashplan exited. It&apos;s the <a href="https://www.nytimes.com/wirecutter/reviews/best-online-backup-service/">Wirecutter recommendation</a>. But I don&apos;t like Backblaze&apos;s software offering:</p><ul><li>Backblaze has no option to &apos;restore in place&apos;. Your only <a href="https://www.backblaze.com/restore.html">restore options</a> are downloading the data as .zip from a web UI, or requesting a delivery of a disk. Why not? All the other backup software offers in-place restores. Is it the challenge of a 3-way merge? Data is more than just the bytes; it&apos;s the metadata, the permissions, this is just not captured in the archives you download from the web. A dedicated restore software could do this. I don&apos;t know why they don&apos;t. <strong>Update, Aug 2023: </strong><a href="https://www.backblaze.com/blog/2023-product-announcement/">Backblaze have announced a &quot;new local restore experience&quot;</a>. Good!</li><li>By default, Backblaze will only keep 30 days of data. If it takes you longer than a month to notice data corruption, sorry! You&apos;re screwed. This is, frankly, a bonkers default to have. To their credit, they now <a href="https://www.backblaze.com/version-history.html">allow longer retention</a> (1 year for +$2/month, or pay-per-GB for &gt;1yr). <strong>Update, Aug 2023: </strong><a href="https://www.backblaze.com/blog/2023-product-announcement/">Backblaze raised prices by $2 and announced &quot;all Computer Backup licenses may add One Year Extended Version History, previously a $2 per month expense, for free&quot;</a>.</li><li>When I last used Backblaze a few years ago, they immediately started uploading my backups in plaintext, before I had a chance to say &quot;no, I want mine encrypted&quot;. While this probably makes their software easier for unsophisticated users (some growth hacker probably got promoted for it), it&apos;s a breach of trust to hoover up my personal files when I want them encrypted. I have no idea if they&apos;ve fixed this now.</li></ul><p>With these limitations, Backblaze being Wirecutter&apos;s annointed &apos;best&apos; is an indictment of the backup software industry.</p><h2 id="synology-hyper-backup-slow">Synology Hyper Backup: Slow</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-5.png" class="kg-image" alt="Cloud Backup Software" loading="lazy" width="1812" height="1172" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-5.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-5.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1600/2023/02/image-5.png 1600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-5.png 1812w" sizes="(min-width: 720px) 720px"><figcaption>Synology Hyper Backup Screenshot</figcaption></figure><p>I thought, <a href="https://www.synology.com/en-au/dsm/feature/hyper_backup">Synology&apos;s built-in backup software</a> will probably be decent, right? Commercial company behind it that sells lots of data backup boxes, hopefully they put some effort into this? A lot of people on <a href="https://www.reddit.com/r/synology/">/r/synology</a> recommended it.</p><p>In positives, it&apos;s well-integrated with Synology, with emails alerts on failure, and can backup Synology software configuration. It has compression, and scheduling. It has an active user base. And it&apos;s built-in.</p><p>But unfortunately, <a href="https://twitter.com/MarkHnsn/status/1621070888421715968">I was disappointed</a>, and gave up on Hyper Backup.</p><h3 id="synology-hyper-backups-format-is-secret">Synology Hyper Backup&apos;s Format is Secret</h3><p>Having a secret data format is, I think, such an own-goal for backup software. It helps trust so much to be able to say &quot;it doesn&apos;t matter if we disappear, here&apos;s how to read the data&quot;. You have to document it anyway for your own engineers, just make the docs public!</p><p>The closest we get to an explanation of the data format is this <a href="https://www.synology.com/en-au/dsm/6.2/software_spec/hyper_backup">bullet-point-heavy marketing page</a>.</p><p>To their credit, Synology have a software package that can read the backup files, <a href="https://kb.synology.com/en-ph/DSM/help/HyperBackupExplorer/hyperbackupexplorer?version=7">Synology Hyper Backup Explorer</a>, but this is still cold comfort if the backups get corrupted, as some people report happening in forums.</p><h3 id="synology-hyper-backup-is-slow">Synology Hyper Backup is Slow</h3><p>It&apos;s extremely slow. Here&apos;s a representative progress bar: 0.00 B/s. It would jump between 0 and 1MB/s, mostly staying on 0.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-1.png" class="kg-image" alt="Cloud Backup Software" loading="lazy" width="1360" height="430" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-1.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-1.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-1.png 1360w" sizes="(min-width: 720px) 720px"><figcaption>It&apos;s fun that they&apos;re giving me fractional bytes. Like maybe sometimes it does 0.01 B/s ?</figcaption></figure><p>I don&apos;t know why it was so slow. If you google [synology hyper backup slow], as you might expect, there&apos;s a lot of laypeople on the forum confidently being wrong about what makes it slow. Compression? Network bandwidth? Disk I/O are all given as explanations. Let&apos;s consider these.</p><p>Many people say compression will slow down the backup. I had compression enabled. But compression is usually very fast, and CPU-bound, and the CPU stayed at &lt;10%. Even if it was &apos;nice&apos;d, you&apos;d expect it to be able to burst to use idle CPU. So we can immediately discount compression as a cause.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-2.png" class="kg-image" alt="Cloud Backup Software" loading="lazy" width="638" height="430" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-2.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-2.png 638w"><figcaption>Not a very-CPU intensive job</figcaption></figure><p>And <code>htop</code> confirms, the <code>HyperBackup</code> jobs are using 0-1% CPU.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-3.png" class="kg-image" alt="Cloud Backup Software" loading="lazy" width="2000" height="346" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-3.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-3.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1600/2023/02/image-3.png 1600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-3.png 2360w" sizes="(min-width: 720px) 720px"><figcaption>The &apos;1&apos; and &apos;0&apos; column is CPU%. HyperBackup is in the middle.</figcaption></figure><p>My hard drive can read at 10MB/s, if sequential. Seeks will reduce this, but not to zero.</p><p>And my home internet upload pipe is 40Mbps (or about 4MB/s, after overhead). And I can regularly hit that 40Mbps, whenever Synology Cloud Sync runs.</p><p>So I wonder, what the heck is Synology Hyper Backup doing? I don&apos;t have the <code>linux-perf-tools</code> profiler installed, but I have &quot;poor man&apos;s perf&quot;, looking at the kernel stack of the threads: <code>cat /proc/$pid/stack</code>. First I find the pid using <code>pstree</code>, with <code>-p</code> to show pids, we see that <code>synobackupd</code> is the root job, spawning <code>img_backup</code> which spawns <code>img_worker</code> which spawns <code>img_backup</code> and <code>php</code>.</p><p>You could expect the leaf nodes to be doing work, but here the <code>img_backup</code> tool is sleeping, calling the <a href="https://man7.org/linux/man-pages/man2/nanosleep.2.html"><code>nanosleep</code> system call</a>. I can&apos;t tell exactly how long it&apos;s sleeping for (<a href="https://twitter.com/MarkHnsn/status/1621075960228610053">though I tried</a>), but I&apos;m guessing it&apos;s a long time, and that that&apos;s probably why my backup is slow.</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-4.png" class="kg-image" alt="Cloud Backup Software" loading="lazy" width="1268" height="608" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-4.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-4.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-4.png 1268w" sizes="(min-width: 720px) 720px"></figure><p>The PHP process is at least polling a file descriptor for work to do, rather than sleeping. I looked at the PHP source &#x2013; it&apos;s the plugin for uploading to S3. I found about 400 lines of uncommented PHP.</p><p>It seemed kind of a bizarre choice to use PHP as a backup software adapter. AWS S3 has bindings for all languages. Is it language-chauvinist to say it didn&apos;t inspire confidence? Well, backup software is all about trust, and uncommented PHP doesn&apos;t inspire trust.</p><p>Between the slowness (it would have taken weeks to upload), the lack of transparency into what it&apos;s doing, and the closed-sourceness, I wasn&apos;t keen. So I looked into other stuff.</p><h2 id="duplicity-obsolete">Duplicity: Obsolete</h2><p><a href="https://duplicity.us/">Duplicity</a> bills itself as &quot;encrypted bandwidth-efficient backup using the rsync algorithm&quot;. It&apos;s webpage quaintly declares a &quot;A standards (CSS) compliant web browser will display this page correctly&quot;. It&apos;s command-line, it&apos;s mature.</p><p>It&apos;s format is open: full backups are tarballs, and incremental backups are tarfiles containing new files and diffs. It relies on librsync to find diffs.</p><p>Managing full and incremental backups are a pain. Long incremental chains make it slow to recover data, and prevent garbage-collection of any file in the full backup (costing you extra storage). </p><p>To work around this, <a href="https://duplicity.us/FAQ.html">their advice is</a>:</p><blockquote>Keep the number of incrementals for a maximum of one month, then do a full backup.</blockquote><p>But if I want a year&apos;s worth of backups, this means I&apos;m redundantly storing 12x my dataset. That&apos;s going to cost me real money.</p><p>And rsync is fast if you have ssh access to the remote to run a rolling checksum there, but not so fast with modern cloud object storage where you don&apos;t have that. I&apos;m not sure exactly if Duplicity downloads the full blob, but it certainly has to download the checksum header in the tar file. And with cloud object storage, that&apos;ll cost you exorbitant egress fees.</p><p>The full/incremental backup strategy is obsolete in 2023. Really, I&apos;d argue it&apos;s been obsolete ever since Git popularised content-addressable-storage and immutable data structures plus garbage collection.</p><h2 id="duplicati-unmaintained">Duplicati: Unmaintained?</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-6.png" class="kg-image" alt="Cloud Backup Software" loading="lazy" width="936" height="531" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-6.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-6.png 936w" sizes="(min-width: 720px) 720px"><figcaption>Duplicati web interface. From <a href="https://www.duplicati.com/screenshots/#lg=1&amp;slide=0">their website</a>.</figcaption></figure><p><a href="https://www.duplicati.com/">Duplicati</a> is &quot;Free backup software to store encrypted backups online&quot;. It&apos;s open source, written in C#, with a nice web interface. It looks really nice!</p><p>The system is completely open-source, even the web UI. C# is a language that inspires confidence. And the data format is modern, playing well with cloud object storage: <a href="https://duplicati.readthedocs.io/en/latest/appendix-a-how-the-backup-process-works/">garbage-collected content-addressed chunk storage.</a></p><p>But the <a href="https://github.com/duplicati/duplicati/commit/666b2281032460254839fdc3b6e1055fdf7ce1db">last commit was merged over 6 months ago</a>. It sounds like the <a href="https://forum.duplicati.com/t/was-worried-due-to-the-lack-of-releases/14422/2?u=ts678">maintainer doesn&apos;t have time, and there are insufficient volunteers</a>. While I could volunteer, I already have open source commitments. I&apos;d like a backup software that&apos;s actively developed, if only to get security fixes.</p><h2 id="duplicacy-good-enough">Duplicacy: Good enough?</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-7.png" class="kg-image" alt="Cloud Backup Software" loading="lazy" width="2000" height="882" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image-7.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image-7.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1600/2023/02/image-7.png 1600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image-7.png 2018w" sizes="(min-width: 720px) 720px"><figcaption>Duplicacy screenshot. From my Synology.</figcaption></figure><p>There are too many backup tools that start with &quot;Dupli-&quot;. It&apos;s very confusing.</p><p><a href="https://duplicacy.com/">Duplicacy</a> says it &quot;backs up your files to many cloud storages with client-side encryption and the highest level of deduplication&quot;. It&apos;s open-core, with an open-source free-for-personal-use command line tool, and a paid web UI to manage it. It&apos;s written in Go, which is probably the language that inspires the most confidence for me (they&apos;re probably not ignoring errors!).</p><p>The data format is very modern: content-addressed chunks. Just like Git. There&apos;s no need to read the chunk content back from the cloud to see what&apos;s inside it. And they have a clever two-step garbage collection process, which was neat enough that they got an <a href="https://ieeexplore.ieee.org/document/9310668">IEEE Transactions on Cloud Computing paper</a> out of it.</p><p>Duplicacy was able to max out my upload bandwidth. There&apos;s a way to install it on Synology. There are things I&apos;m worried about: I was able to wedge the web UI behind a lock on first use, and leak goroutines (<a href="https://forum.duplicacy.com/t/duplicacy-web-ui-hangs-forever/">bug report</a>), the developer has limited time between this and other projects, and email alerting seems limited or unimplemented. But it&apos;s seeing regular development (<a href="https://github.com/gilbertchen/duplicacy/commit/24c2ea76b90f2b755c3447344688c42db9e578f7">MR merged 2 weeks ago</a>) and <a href="https://github.com/gilbertchen/duplicacy/releases">releases</a> (last Dec 2022).</p><p>I&apos;m impressed with the data format, and the open core means I&apos;ll be able to recover or move my data easily even if I don&apos;t pay for the web UI.</p><p>Duplicacy is the winner, for now. I forked out the money for a personal license for the web UI.</p><h2 id="software-i-havent-looked-into">Software I haven&apos;t looked into </h2><ul><li><a href="https://www.borgbackup.org/">Borg Backup</a> looks like it has a modern data format, and is seeing active development. Although it doesn&apos;t natively back up to cloud, so would double my local disk space.</li><li><a href="https://restic.net/">Restic</a> backup was recommended by a few people, and seems to see active development.</li><li><a href="https://kopia.io/">Kopia</a> backup is open-source, backs up to clouds, deduplicates, and has a GUI. Looks like a good option, but I found out about it too late.</li></ul><h2 id="conclusion">Conclusion</h2><p>If you&apos;re on a Mac or Windows, I recommend Arq. I don&apos;t recommend Backblaze.</p><p>If you&apos;re on Synology, I recommend Duplicacy. I don&apos;t recommend Synology Hyper Backup. Duplicity is obsolete. Duplicati&apos;s development has stalled.</p><p>All backup software has tradeoffs and bugs. I don&apos;t feel great about anything I&apos;ve tried, really. I wonder if backup software is a <a href="https://en.wikipedia.org/wiki/The_Market_for_Lemons">Market for Lemons</a>, where most people don&apos;t have to restore, and so they don&apos;t even know if their backups work, and the market sinks to dodgy players. That&apos;s another post, though.</p><p>Next in this series: What location should I backup to? <a href="https://www.markhansen.co.nz/cloud-backup-storage/">I evaluate where cloud backup storage providers</a>, and how to <a href="https://www.markhansen.co.nz/duplicacy-mailgun/">setup Duplicacy email notifications with Mailgun</a>.</p><p><a href="https://reddit.com/r/synology/comments/10wmxay/evaluation_of_cloud_backup_software_options_for/">Reddit discussion</a> of this post.</p>]]></content:encoded></item><item><title><![CDATA[Short-Circuited External Disk Recovery]]></title><description><![CDATA[How I tried (and failed) to recover an external hard drive that failed after a lightning storm.]]></description><link>https://www.markhansen.co.nz/short-circuited-external-disk-recovery/</link><guid isPermaLink="false">63dda6e95b1992003d88f895</guid><category><![CDATA[Forensics]]></category><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Sat, 04 Feb 2023 01:11:20 GMT</pubDate><media:content url="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/IMG_8603.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/IMG_8603.jpeg" alt="Short-Circuited External Disk Recovery"><p>A few mornings ago, we woke up to the power out. The fuse had tripped, we flicked the switch to re-set it. We weren&apos;t sure what had tripped the fuse. My <a href="https://www.markhansen.co.nz/monitoring-electricity-with-tasmota/">home electricity monitoring</a> logged its last event at 02:38am, so the power must have failed just after.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image.png" class="kg-image" alt="Short-Circuited External Disk Recovery" loading="lazy" width="1596" height="1300" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/image.png 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/image.png 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/image.png 1596w" sizes="(min-width: 720px) 720px"><figcaption>Prometheus graph of whether my home servers are &quot;up&quot;.</figcaption></figure><p>Later, I tried to read some data off the Western Digital My Book external hard disk I&apos;d plugged in the night before.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/IMG_8599.jpeg" class="kg-image" alt="Short-Circuited External Disk Recovery" loading="lazy" width="1280" height="960" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/IMG_8599.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/IMG_8599.jpeg 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/IMG_8599.jpeg 1280w" sizes="(min-width: 720px) 720px"><figcaption>The Western Digital Drive (black, right), plugged into my NAS (left).</figcaption></figure><p>The drive had some data from a relative who passed away, so I was keen to read it. But the disk wasn&apos;t showing up on the computer any more. Why?</p><h2 id="is-the-power-supply-broken">Is the power supply broken?</h2><p>I couldn&apos;t hear the disk spin up, unless I plugged in another power supply. So I tested the power supply with a multimeter. It should output 12V but was outputting 0V:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/IMG_8598--1-.jpeg" class="kg-image" alt="Short-Circuited External Disk Recovery" loading="lazy" width="960" height="1280" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/IMG_8598--1-.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/IMG_8598--1-.jpeg 960w" sizes="(min-width: 720px) 720px"><figcaption>-0.00 volts on the external drive power supply. Dead!</figcaption></figure><h2 id="plug-it-into-other-machines">Plug it into other machines?</h2><p>I was hoping it was &apos;just the power supply&apos; that shorted, that the disk was undamaged. However, with an alternative power supply, the disk still didn&apos;t mount. macOS said the disk was &apos;uninitialized&apos; (unformatted), but further tries to read the first few bytes of the disk (where the partition table is stored) failed:</p><pre><code>$ sudo cat /dev/disk4 | xxd | less
error: resource busy</code></pre><p>That seems to say we can&apos;t even read any bytes off the disk at all! Plugging the disk into my NAS and running <code>sudo dmesg -w</code>, I see the kernel having errors reading the disk:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/FnrcDxXaUAA4CCr.jpeg" class="kg-image" alt="Short-Circuited External Disk Recovery" loading="lazy" width="1404" height="838" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/FnrcDxXaUAA4CCr.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/FnrcDxXaUAA4CCr.jpeg 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/FnrcDxXaUAA4CCr.jpeg 1404w" sizes="(min-width: 720px) 720px"><figcaption>USB disk detected, then... two &quot;Buffer I/O error&quot; errors trying to read the drive (&quot;async page read&quot;).</figcaption></figure><p>I hoped: maybe it&apos;s just the enclosure that&apos;s fried, and maybe the disk inside is still good? This was a last-resort because you have to snap the disk enclosure to open it up. So I tried plugging it into one more Linux machine, but I got much the same errors. This time, so many that <code>buffer_io_error</code> said <code>103 callbacks suppressed</code>. I guess there were over 100 errors?</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/FnrdoxUaYAA64nK.jpeg" class="kg-image" alt="Short-Circuited External Disk Recovery" loading="lazy" width="1512" height="550" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/FnrdoxUaYAA64nK.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/FnrdoxUaYAA64nK.jpeg 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/FnrdoxUaYAA64nK.jpeg 1512w" sizes="(min-width: 720px) 720px"><figcaption>Too many errors in <code>dmesg</code>.</figcaption></figure><h2 id="bypass-the-usb-controller">Bypass the USB controller?</h2><p>I prised the case off with a screwdriver, breaking the clips:</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/IMG_8600.jpeg" class="kg-image" alt="Short-Circuited External Disk Recovery" loading="lazy" width="1280" height="996" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/IMG_8600.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/IMG_8600.jpeg 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/IMG_8600.jpeg 1280w" sizes="(min-width: 720px) 720px"></figure><p>And I found the internal disk. A cheap Western Digital disk inside an expensive Western Digital case, that&apos;s vertical integration!</p><figure class="kg-card kg-image-card"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/IMG_8602.jpeg" class="kg-image" alt="Short-Circuited External Disk Recovery" loading="lazy" width="957" height="1280" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/IMG_8602.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/IMG_8602.jpeg 957w" sizes="(min-width: 720px) 720px"></figure><p>I popped the raw disk into my Synology NAS. Its spare disk slot has been handy for reading many disks as I&apos;ve been processing these inherited hard drives. Now Linux is talking directly to the disk, without a USB controller. Maybe it&apos;ll work?</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/FnrhOExaQAAxvML.jpeg" class="kg-image" alt="Short-Circuited External Disk Recovery" loading="lazy" width="1512" height="834" srcset="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w600/2023/02/FnrhOExaQAAxvML.jpeg 600w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/size/w1000/2023/02/FnrhOExaQAAxvML.jpeg 1000w, https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/FnrhOExaQAAxvML.jpeg 1512w" sizes="(min-width: 720px) 720px"><figcaption>Drive detected, then read errors, then a &apos;hard reset&apos; loop.</figcaption></figure><p>The drive is throwing exceptions when we send read ATA commands to it, and Linux tries to reset the drive to fix it, but the reset doesn&apos;t fix it. Presumably the USB controller was trying to reset the drive too, and returning <code>resource busy</code>.</p><h2 id="conclusion">Conclusion</h2><p>Unfortunately, I think this drive is toast. I e-wasted it, labelling it &quot;shorted&quot; with a sharpie, just in case any recycler tries to use it.</p><p>I failed to recover the data, but I hope that sharing the steps I followed might help others in this situation.</p><p>I&apos;m not sure why the disk failed. The disk was maybe 10 years old. I&apos;m not sure if drives can just &apos;short out&apos; themselves? I hadn&apos;t heard of this failure mode.</p><p>The most fun explanation is: there were a few lightning storms in Sydney around the time this failed. Could it have been a lightning surge that ruined this? But no other electronics in the house seem to be broken. Is it really likely that lightning hit our power, and only one hard drive fails?</p><p>Next in this series: <a href="https://www.markhansen.co.nz/cloud-backup-software/">I evaluate cloud backup software options</a>.</p>]]></content:encoded></item><item><title><![CDATA[Snoo Smart Sleeper Repair in Sydney, Australia]]></title><description><![CDATA[I recommend "The Snoo Lady" for in-home Snoo bassinet repair in Sydney]]></description><link>https://www.markhansen.co.nz/snoo-repair-in-sydney/</link><guid isPermaLink="false">63d8babc5b1992003d88f77f</guid><category><![CDATA[Baby]]></category><dc:creator><![CDATA[Mark Hansen]]></dc:creator><pubDate>Tue, 31 Jan 2023 07:28:46 GMT</pubDate><media:content url="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/snoo-smart-sleeper_grande.webp" medium="image"/><content:encoded><![CDATA[<img src="https://storage.ghost.io/c/c8/43/c84367ab-a86b-4985-9838-402ea83c1481/content/images/2023/02/snoo-smart-sleeper_grande.webp" alt="Snoo Smart Sleeper Repair in Sydney, Australia"><p>The Snoo is a fancy baby bassinet, that rocks babies to sleep, with motors that respond to baby&apos;s stirring by rocking the more. It&apos;s neat, although expensive. Unfortunately, the moving parts fail, and, shamefully, the manufacturer doesn&apos;t offer repairs, and encourages you to buy a new device instead.</p><p>To save money, we bought a third-hand Snoo online. It worked great, but just three weeks in, the motor started making grinding noises, and a few days later stopped moving altogether.</p><p>I was really worried, as our newborn was used to rocking to sleep. I searched for [Snoo Repair Sydney] and nothing came up. My options were pretty awful:</p><ol><li>Replace it. Should I pay $2000 new to get a warranty? Or $900 second-hand and risk it breaking again? Either way: ouch.</li><li>Try to repair it myself: watch long YouTube videos and do multiple trips to Bunnings to get the right parts (all the while trying to take care of a newborn and fighting sleep deprivation). I would probably fail at this.</li></ol><p>My partner (being much smarter than me) jumped on social media and found Ashleigh, aka &quot;<a href="https://www.facebook.com/people/The-SNOO-Lady/100087286617220/">The Snoo Lady</a>&quot;.</p><p>The very next day, Ashleigh visited us in our home, with a huge collection of Snoo parts (well beyond what I&apos;d be able to source), and stripped, repaired, dusted, and thoroughly cleaned our bassinet. Unsurprisingly, the O-rings had failed (they crumbled to dust as she removed them). Ashleigh not only replaced the broken O-rings, but replaced all the other worn parts. She&apos;d done this a lot of times before and did it very quickly.</p><p>I was very impressed. I&apos;d strongly recommend her to anyone staring at a broken Snoo. She does a much more thorough job than you&apos;ll probably do yourself, will save you precious newborn time, and her repair price was extremely reasonable &#x2013; if anything, I think she should raise her prices.</p><p>I&apos;m writing this post because, today, when I Google for [Snoo Repair Sydney], nothing useful comes up. I didn&apos;t get any discount or anything for writing this recommendation. It&apos;s a disgrace that Snoo&apos;s manufacturer sells expensive devices with these known failure modes but doesn&apos;t offer repair. I hope this can help other parents can find repair help, and keep their devices out of landfill.</p>]]></content:encoded></item></channel></rss>