<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Posts on Robin's blog</title><link>https://kaveland.no/posts/</link><description>Recent content in Posts on Robin's blog</description><generator>Hugo -- 0.145.0</generator><language>en-us</language><lastBuildDate>Fri, 06 Jun 2025 20:30:00 +0000</lastBuildDate><atom:link href="https://kaveland.no/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>The librarian immediately attempts to sell you a vuvuzela</title><link>https://kaveland.no/posts/2025-06-06-library/</link><pubDate>Fri, 06 Jun 2025 20:30:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-06-06-library/</guid><description>&lt;p>Imagine entering the biggest library in the world. You peer down an incredibly long aisle with wooden bookshelves brimming with books. You can see multiple such corridors, all lit with a comfortable warm light. There&amp;rsquo;s a rich smell of old paper. You can hear some muted voices, perhaps arguing in a whisper. It&amp;rsquo;s perfect, but vast and difficult to make sense of. Just this day, it doesn&amp;rsquo;t feel like such a terrible ordeal to just wander for a while, see where your legs take you. Maybe you&amp;rsquo;ll find something interesting completely by accident? The prospect is both exhilarating and daunting. The library appears endless, it feels like you could potentially find all sorts of exotic and interesting book collections.&lt;/p></description></item><item><title>Using SQL to turn all the buses around</title><link>https://kaveland.no/posts/2025-05-28-turning-the-bus-sql/</link><pubDate>Wed, 28 May 2025 19:30:00 +0200</pubDate><guid>https://kaveland.no/posts/2025-05-28-turning-the-bus-sql/</guid><description>&lt;p>I have a small hobby project over at &lt;a href="https://kollektivkart.arktekk.no/">kollektivkart.arktekk.no&lt;/a> that is for visualizing changes in public transit in Norway. For some time I&amp;rsquo;ve been wanting to do some visualizations on public transit lines. For example, plot the mean delay at each stop used by a line over time.&lt;/p>
&lt;p>When trying to do some concept work on this, I discovered a puzzle in the data! Many lines go in two opposite directions. Here in Trondheim, Line 3 goes from Loholt to Hallset, but also from Hallset to Loholt. The way I can tell these apart is to look up the &lt;em>direction&lt;/em> in the data. Within a line, there can be variations in each direction. Some services might skip some stops, or depending on how you look at it, others visit extra stops. But these are variations on a theme, and it probably makes sense to group them together to preserve our sanity and not get 12 different plots for each line—2 should be plenty!&lt;/p></description></item><item><title>No-ops linux part 3: It puts the data in the pond. Nightly.</title><link>https://kaveland.no/posts/2025-05-14-fire-and-forget-linux-p3/</link><pubDate>Wed, 14 May 2025 18:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-05-14-fire-and-forget-linux-p3/</guid><description>&lt;p>This post is part of the series on no-ops linux deployment. The &lt;a href="https://kaveland.no/posts/2025-05-13-fire-and-forget-linux-p1">first post&lt;/a> covered local development of linux server configuration and essential configuration. The &lt;a href="https://kaveland.no/posts/2025-05-14-fire-and-forget-linux-p2">previous installment&lt;/a> covers a janky podman installation and configures a reverse proxy to send traffic to a simple container deployment. This is the &lt;a href="https://kaveland.no/posts/2025-05-14-fire-and-forget-linux-p3">final post&lt;/a>. It covers a more challenging deployment with jobs and rolling restarts, and discusses the strengths and weaknesses of this approach to hosting.&lt;/p>
&lt;p>After the previous post, we know how to deploy a container that requires absolutely no configuration and restarts almost instantly. Most of the applications I work on in my daytime job aren&amp;rsquo;t like that. Let&amp;rsquo;s take a look at a more complex example.&lt;/p></description></item><item><title>No-ops linux part 2: Hosting a simple container on a lean mean systemd machine</title><link>https://kaveland.no/posts/2025-05-14-fire-and-forget-linux-p2/</link><pubDate>Wed, 14 May 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-05-14-fire-and-forget-linux-p2/</guid><description>&lt;p>This post is part of the series on no-ops linux deployment. The &lt;a href="https://kaveland.no/posts/2025-05-13-fire-and-forget-linux-p1">previous post&lt;/a> covered local development of linux server configuration and essential configuration. &lt;a href="https://kaveland.no/posts/2025-05-14-fire-and-forget-linux-p2">This installment&lt;/a> covers a janky podman installation and configures a reverse proxy to send traffic to a simple container deployment. The &lt;a href="https://kaveland.no/posts/2025-05-14-fire-and-forget-linux-p3">final post&lt;/a> covers a more challenging deployment with jobs and rolling restarts, and discusses the strengths and weaknesses of this approach to hosting.&lt;/p>
&lt;p>At the completion of the previous post, we had automatic installation of a functional Ubuntu server with the bare essentials installed. We did this by writing a &lt;code>base-install&lt;/code> ansible role. There&amp;rsquo;s still a missing ingredient before we can start deploying containers, though!&lt;/p></description></item><item><title>No-ops Linux part 1: Automation, security and essentials</title><link>https://kaveland.no/posts/2025-05-13-fire-and-forget-linux-p1/</link><pubDate>Tue, 13 May 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-05-13-fire-and-forget-linux-p1/</guid><description>&lt;p>In &lt;a href="https://kaveland.no/posts/2025-04-14-running-containers-on-the-cheap">Running containers on no-ops linux in 2025&lt;/a> I wrote about moving my hobby projects to a European cloud provider. I did an initial, manual setup in &lt;a href="https://www.hetzner.com/">Hetzner&lt;/a>, which I&amp;rsquo;ve now automated. This weekend, I tested the setup. It takes me a few minutes now to get everything moved to a new host, and most of that has to do with DNS. I&amp;rsquo;ve got a reproducible setup, I can quickly provision up a machine locally or in any cloud that has Ubuntu 24.04. Reproducible infrastructure is ✨liberating✨&lt;/p></description></item><item><title>To create, or be consumed? That is the question</title><link>https://kaveland.no/posts/2025-05-06-produce-or-consume/</link><pubDate>Tue, 06 May 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-05-06-produce-or-consume/</guid><description>&lt;p>This is a note to myself about how to prioritize wisely. Or perhaps, it is an effort to commit to text a healthy mindset for myself. I have been trying to get into this mindset over a timeframe of some years now. I am making some progress, but it&amp;rsquo;s slow and not without setbacks. You can read it if you&amp;rsquo;d like, but this is my own advice for &lt;em>me&lt;/em>. It may not apply to you.&lt;/p></description></item><item><title>That join sure is a natural</title><link>https://kaveland.no/posts/2025-04-30-that-join-is-natural/</link><pubDate>Wed, 30 Apr 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-04-30-that-join-is-natural/</guid><description>&lt;p>Working with SQL can sometimes be painful, &lt;em>especially&lt;/em> when you have composite keys and many tables to join. Today I want to write a helpful tip for designing data models with such keys, to make it less painful to handwrite SQL for them.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>&lt;em>TIP:&lt;/em>&lt;/strong> Introduce a consistent naming standard for all columns that take part in a primary key, so that the column has the same name in all tables it is used, also where it&amp;rsquo;s on the referencing side of a foreign key.&lt;/p></description></item><item><title>It's okay to have no idea what you're doing and try anyway</title><link>https://kaveland.no/posts/2025-04-26-no-idea/</link><pubDate>Sat, 26 Apr 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-04-26-no-idea/</guid><description>&lt;p>When I was a kid, I thought adults knew everything there was to know. When I was a junior developer, I thought the senior developers knew everything there was to know about software.&lt;/p>
&lt;p>As anyone who has made some progression in life knows, nobody gives you the big manual with all the answers when you level up. You&amp;rsquo;ll still be you. Just with more responsibility and more experience. But the experience does not come from age, it comes from actually having to try. It&amp;rsquo;s okay to have no idea what you&amp;rsquo;re doing and try anyway. It will make you better.&lt;/p></description></item><item><title>Deploying to BunnyCDN and protecting Norway from drop bears</title><link>https://kaveland.no/posts/2025-04-20-deploying-to-bunnycdn/</link><pubDate>Sun, 20 Apr 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-04-20-deploying-to-bunnycdn/</guid><description>&lt;p>Not long ago, I wrote about &lt;a href="https://kaveland.no/posts/2025-04-14-running-containers-on-the-cheap">running containers&lt;/a> as part of moving my hobby projects to European cloud providers. That post was focused on running good old Linux servers. I briefly mentioned &lt;a href="https://bunny.net">BunnyCDN&lt;/a> but didn’t dive into the details. It&amp;rsquo;s time to dive into the details!&lt;/p>
&lt;h2 id="what-the-flark-is-a-cdn">What the flark is a CDN?&lt;/h2>
&lt;p>A &lt;a href="https://en.wikipedia.org/wiki/Content_delivery_network">content delivery network&lt;/a> is a geographically distributed network of servers that can deliver content to your users, close to where they are. It&amp;rsquo;s useful, because the speed of light isn&amp;rsquo;t fast enough to make pages load quickly across the other side of the globe. Seriously, the fastest thing in the universe cannot deliver cat pictures to people quickly enough. By using a CDN, you can geographically distribute assets like cat pictures, HTML, CSS, JavaScript, video files, fonts and much more.&lt;/p></description></item><item><title>Checking SQL migrations with eugene</title><link>https://kaveland.no/posts/2025-04-16-whats-up-eugene/</link><pubDate>Wed, 16 Apr 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-04-16-whats-up-eugene/</guid><description>&lt;p>It’s been almost a year since I last posted an update on &lt;a href="https://github.com/kaaveland/eugene/">eugene&lt;/a>, the CLI tool I’m building to help people write safer SQL migration scripts for postgres. I announced this tool in &lt;a href="https://kaveland.no/posts/2024-05-06-careful-with-that-lock-eugene-pt-2">Careful with That Lock, Eugene: Part 2&lt;/a>. At the time, &lt;code>eugene&lt;/code> would execute a single SQL script, recording all the locks acquired and warn about possible downtime due to migrations.&lt;/p>
&lt;p>It could produce JSON suitable for automated tooling and Markdown suitable for human reading and using in CI comments/checks. That version was already good enough for me to start using in real projects — but it&amp;rsquo;s improved a lot since then, it&amp;rsquo;s now easy to run with almost no setup.&lt;/p></description></item><item><title>Running containers on no-ops linux in 2025</title><link>https://kaveland.no/posts/2025-04-14-running-containers-on-the-cheap/</link><pubDate>Mon, 14 Apr 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-04-14-running-containers-on-the-cheap/</guid><description>&lt;p>Back in February, I decided that I wanted to move hosting of my hobby projects to a european cloud provider. At this time, I don&amp;rsquo;t feel like spending more energy on why, but maybe someone can learn something from the how. I have pretty simple requirements, so I figured I should be able to find simple and inexpensive hosting too. It turns out that there are many european cloud providers in 2025, but none that were really a &lt;em>perfect&lt;/em> fit for what I was looking for. Here&amp;rsquo;s what I wanted:&lt;/p></description></item><item><title>Finding foreign keys missing indexes</title><link>https://kaveland.no/posts/2025-04-04-finding-missing-indexes-in-pg-catalog/</link><pubDate>Fri, 04 Apr 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-04-04-finding-missing-indexes-in-pg-catalog/</guid><description>&lt;p>Last week I was made aware that we had some foreign keys not backed by indexes in the system we&amp;rsquo;re developing at work. Foreign keys in postgres must be backed by an index only on the side they refer &lt;em>to&lt;/em>, not necessarily the side they refer &lt;em>from&lt;/em>. Here&amp;rsquo;s an example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">create&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">author&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">generated&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">always&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">identity&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">primary&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">key&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">text&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">not&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">null&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">create&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">table&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">book&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">generated&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">always&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">as&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">identity&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">primary&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">key&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">author&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">not&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">null&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">references&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">author&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">),&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">text&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">not&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">null&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In this example, there&amp;rsquo;s a foreign key from the &lt;code>book&lt;/code> table to the &lt;code>author&lt;/code> table. Since &lt;code>author&lt;/code> refers to a primary key in the &lt;code>author&lt;/code> table, inserts into &lt;code>book&lt;/code> can validate very quickly. There&amp;rsquo;s no index on the &lt;code>author&lt;/code> column in the &lt;code>book&lt;/code> table though. The consequence of this is that &lt;code>delete&lt;/code> on &lt;code>author&lt;/code> must check every single row in &lt;code>book&lt;/code> to check if it&amp;rsquo;s safe to actually delete. The really annoying part of this is that the scan does not show up in query plans:&lt;/p></description></item><item><title>Constraint propagation: Mutual recursion for fun and profit</title><link>https://kaveland.no/posts/2025-03-10-mutual-recursion-for-fun-and-profit/</link><pubDate>Mon, 10 Mar 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-03-10-mutual-recursion-for-fun-and-profit/</guid><description>&lt;p>I&amp;rsquo;ve been wanting to write this post for a while, about what I think is an
elegant way to solve some &lt;a href="https://en.wikipedia.org/wiki/Constraint_satisfaction_problem">constraint satisfaction
problems&lt;/a>. Constraints
tend to come up fairly often in real world programs, and some times it can be
effective to treat them as constraint satisfaction problems. This post has a bit
of background on constraint satisfaction problems I&amp;rsquo;ve encountered recently,
then it goes over to develop Rust code for an algorithm that we can easily use
to solve some Advent of Code problems, and we use it to make a solver for
&lt;a href="https://en.wikipedia.org/wiki/Sudoku">sudoku&lt;/a> puzzles. Along the way, we
explain the syntax we use, it shouldn&amp;rsquo;t be too hard to understand for someone
who is unfamiliar with the language.&lt;/p></description></item><item><title>Why would I use DuckDB for that?</title><link>https://kaveland.no/posts/2025-03-02-can-i-just-use-postgres/</link><pubDate>Sun, 02 Mar 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-03-02-can-i-just-use-postgres/</guid><description>&lt;p>The past few weeks I&amp;rsquo;ve been experimenting with &lt;a href="https://duckdb.org/">DuckDB&lt;/a>, and as a consequence I&amp;rsquo;ve ended up talking about it a lot as well. I&amp;rsquo;m not going to lie, I really like it! However, experienced programmers will rightly be skeptical to add new technology that overlaps with something that already works great. So why not just use postgres?&lt;/p>
&lt;p>Well, I really like postgres too, and I think you should consider just using it! But despite both of these technologies being all about tabular data, they&amp;rsquo;re not really for the same kinds of problems. I think DuckDB is primarily an analysis or ELT tool, and it really excels in this space. postgres &lt;em>can&lt;/em> do a lot of the things that DuckDB can do, but not nearly as fast or easily. I wouldn&amp;rsquo;t want to use DuckDB for a transactional workload, so it&amp;rsquo;s not going to replace postgres for anything that I use it for.&lt;/p></description></item><item><title>🎶 These points of data make a beautiful line 🎶</title><link>https://kaveland.no/posts/2025-01-26-points-of-data/</link><pubDate>Fri, 31 Jan 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-01-26-points-of-data/</guid><description>&lt;p>One of my most vivid memories is from the day in my late teens when I first got
a contact lens for my left eye. It took a long time to discover that I had poor
vision on this eye, you see, like many people, I chose to keep both of my eyes
open when I wasn&amp;rsquo;t sleeping. It was the headaches that sent me to a doctor. I
was adamant that I could see well, but when he blocked my right eye, I had the
humbling experience of no longer being able to read the biggest letters on the
poster. It turned out that my headaches were probably due to my brain working
overtime to interpret the world using mostly only one eye. My appointment with
an optician was only a few days later, and I got to try a contact lens that same
day.&lt;/p></description></item><item><title>What if that isn't a bool?</title><link>https://kaveland.no/posts/2025-01-08-what-if-it-isnt-a-bool/</link><pubDate>Wed, 08 Jan 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-01-08-what-if-it-isnt-a-bool/</guid><description>&lt;p>A common way that code grows difficult to reason about is increasing the number
of things you must keep in your head simultaneously to understand it. Often,
this simply happens by adding one attribute, one variable, one column at a
time. Some people are gifted with a great capacity for working memory, but most
of us aren&amp;rsquo;t &amp;ndash; having to hold the state of 5 variables in your head
simultaneously to understand a piece of code may be pushing it far, according to
&lt;a href="https://en.wikipedia.org/wiki/Working_memory">this&lt;/a> article from wikipedia:&lt;/p></description></item><item><title>Exploring a webapp using psql and pg_stat_statements</title><link>https://kaveland.no/posts/2025-01-06-exploring-a-webapp-using-pg-stat/</link><pubDate>Mon, 06 Jan 2025 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2025-01-06-exploring-a-webapp-using-pg-stat/</guid><description>&lt;p>It&amp;rsquo;s always an exciting day for me when I get access to the source
code for an entirely new application I need to work on. How does it
look inside, how does it work? Sometimes, there&amp;rsquo;s some design
documentation along with it, or operational procedures, or maybe some
developer handbook in a wiki. I do check all of those, but I don&amp;rsquo;t
expect any of those things to accurately describe how the code works,
because they tend to change less frequently. It&amp;rsquo;s also fairly
low-bandwidth, it takes a ton of time to ingest technical text.&lt;/p></description></item><item><title>Consider using array operators over the SQL in operator</title><link>https://kaveland.no/posts/2024-09-21-equals-any-over-where-in/</link><pubDate>Sat, 21 Sep 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-09-21-equals-any-over-where-in/</guid><description>&lt;p>In my post about &lt;a href="https://kaveland.no/posts/2024-08-30-multi-selecting-by-composite-key/">batch operations&lt;/a>, I used the
&lt;code>where id = any(:ids)&lt;/code> pattern, with &lt;code>ids&lt;/code> bound to a JDBC array. I&amp;rsquo;ve gotten questions about that
afterwards, asking why I do it like that, instead of using &lt;code>in (:id1, :id2, ...)&lt;/code>. Many libraries
can take care of the dynamic SQL generation for you, so often you can just write &lt;code>in (:ids)&lt;/code>, just
like the array example. I would still prefer to use the &lt;code>= any(:ids)&lt;/code> pattern, and I decided to write
down my reasoning here.&lt;/p></description></item><item><title>Batch operations using composite keys in postgres over jdbc</title><link>https://kaveland.no/posts/2024-08-30-multi-selecting-by-composite-key/</link><pubDate>Fri, 30 Aug 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-08-30-multi-selecting-by-composite-key/</guid><description>&lt;p>Throughout a career as a software developer, you encounter many patterns. Some appear just often
enough to remember that they exist, but you still need to look them up every time. I&amp;rsquo;ve discovered
that writing things down helps me remember them more easily. This particular pattern is very useful
for my current project. So, it&amp;rsquo;s time to write it down and hopefully commit it to memory properly
this time. Although this post is specific to PostgreSQL, I&amp;rsquo;m sure other databases have the necessary
features to achieve the same results efficiently.&lt;/p></description></item><item><title>Norwegian Wild Salmon Fishing Ban of 2024</title><link>https://kaveland.no/posts/2024-06-27-salmon-ban/</link><pubDate>Thu, 27 Jun 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-06-27-salmon-ban/</guid><description>&lt;p>For this blog post, I&amp;rsquo;m trying something different. This is a jupyter notebook that I&amp;rsquo;m using to study some data, and just dumping my brain out in text. If I can easily export this to a format that works with &lt;a href="https://gohugo.io/">hugo&lt;/a>, this might become a common occurrence.&lt;/p>
&lt;p>For this one, I&amp;rsquo;m leaving the code in. There isn&amp;rsquo;t that much of it, but I think it&amp;rsquo;s fun to show how much visualization per
line of code you can get with &lt;a href="https://seaborn.pydata.org/">seaborn&lt;/a> and &lt;a href="https://pandas.pydata.org/">pandas&lt;/a>.&lt;/p></description></item><item><title>Using short lived postgres servers for testing</title><link>https://kaveland.no/posts/2024-05-27-shortlived-postgres-servers/</link><pubDate>Mon, 27 May 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-05-27-shortlived-postgres-servers/</guid><description>&lt;p>Database servers are usually long-lived, and important parts of the infrastructure
that we build on. We rarely set them up from scratch, because we have to take
such good care of them over time. I think this causes a lot of people to think
that setting up a database server is some mysteriously difficult ordeal. To be clear,
that&amp;rsquo;s actually true, if you need high availability and a solid recovery point objective.
But there are a lot of use cases where that&amp;rsquo;s overkill, for example short-lived
test environments, or CI/CD pipelines.&lt;/p></description></item><item><title>Building documentation for Eugene</title><link>https://kaveland.no/posts/2024-05-20-building-docsite-for-eugene/</link><pubDate>Mon, 20 May 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-05-20-building-docsite-for-eugene/</guid><description>&lt;p>I&amp;rsquo;ve been busy working on a &lt;a href="https://kaveland.no/eugene">documentation site for eugene&lt;/a>, and I
think it&amp;rsquo;s starting to look pretty good. I wanted to write down some of
my thoughts around the process so far, and some of the things I&amp;rsquo;ve learned.&lt;/p>
&lt;p>It&amp;rsquo;s just been a few days since I ported my blog to hugo, so since I was
already feeling like I was up to speed on that, I decided I&amp;rsquo;d try using it
for the eugene documentation too. I experimented with a few different
setups around the &lt;a href="https://themes.gohugo.io/themes/hugo-book/">hugo-book&lt;/a>
theme, but it ended up feeling like I was having to configure a bit
too much for my taste. I think it looked really nice, but the setup
felt a bit janky and complicated, with having the submodules and
everything in the eugene repo itself. I think my problem here is
just that doing this with hugo would require me to learn more about
hugo than I&amp;rsquo;m ready for right now.&lt;/p></description></item><item><title>Moving the blog to Hugo</title><link>https://kaveland.no/posts/2024-05-18-moving-to-hugo/</link><pubDate>Sat, 18 May 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-05-18-moving-to-hugo/</guid><description>&lt;p>I&amp;rsquo;ve been using &lt;a href="https://github.com/getpelican/pelican">pelican&lt;/a> for my blog for
a while now, and I don&amp;rsquo;t really have anything negative to say about it. But for
a while, I&amp;rsquo;ve been wanting a more minimal theme. I ended up on the front page
of hacker news a couple of times, and the old theme had my face on all the
pages, which made me feel a bit uncomfortable. I was looking at some other
themes around the web, and I found &lt;a href="https://github.com/adityatelange/hugo-PaperMod">PaperMod&lt;/a>
which I absolutely loved. I followed instructions for how to set it up, and
it turns out that it only took me an hour or two to get everything up and
running. I guess I have to give credit to where credit is due, both Hugo and
PaperMod are really well-made, and easy to use. There&amp;rsquo;s a lot more stuff that
Hugo can do, but I think I have everything I need set up for now.&lt;/p></description></item><item><title>Linting postgres migration scripts</title><link>https://kaveland.no/posts/2024-05-16-linting-postgres-migration-scripts/</link><pubDate>Thu, 16 May 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-05-16-linting-postgres-migration-scripts/</guid><description>&lt;p>I have been working quite a bit on picking up dangerous migration patterns in
migration scripts over at the &lt;a href="https://github.com/kaaveland/eugene">eugene repository&lt;/a>
lately. A major feature I&amp;rsquo;ve added is syntax tree analysis, so that we can pick
up some patterns without having to run the SQL scripts. This isn&amp;rsquo;t quite as precise
as running the scripts, but it&amp;rsquo;s a lot faster and can catch quite a few
common mistakes. So let&amp;rsquo;s take a look at how it works!&lt;/p></description></item><item><title>Porting an application from cats effects to ZIO</title><link>https://kaveland.no/posts/2024-05-16-porting-from-cats-effects-to-zio/</link><pubDate>Thu, 16 May 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-05-16-porting-from-cats-effects-to-zio/</guid><description>&lt;p>In my current project, we&amp;rsquo;re working on a large-ish code base that is written in
Scala and uses &lt;a href="https://typelevel.org/cats-effect/">cats effect&lt;/a> as an effect
system in large parts of the code base. If you&amp;rsquo;re not familiar with what an
effect system is, I think the most important detail is that it&amp;rsquo;s a tool that
gives you certain superpowers if you promise to be honest about it when your
code does things that can be considered &amp;ldquo;effectful&amp;rdquo;, such as interacting with
the network or reading files. We use the &lt;code>IO&lt;/code> monad from cats effect, which is a
wonderful way of writing async, concurrent code that looks a lot like
synchronous code.&lt;/p></description></item><item><title>Careful with That Lock, Eugene: Part 2</title><link>https://kaveland.no/posts/2024-05-06-careful-with-that-lock-eugene-pt-2/</link><pubDate>Mon, 06 May 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-05-06-careful-with-that-lock-eugene-pt-2/</guid><description>&lt;p>A while back, I wrote
&lt;a href="https://kaveland.no/posts/2024-04-12-careful-with-that-lock-eugene">Careful with That Lock, Eugene&lt;/a> about an
idea for how to check if a database migration is likely to disturb production.
That post came about after having an inspiring chat with a colleague about
the advantages of transactional migration scripts and the ability to check
the postgres system catalog views before committing a transaction.&lt;/p>
&lt;p>Over the past few weeks, I&amp;rsquo;ve been experimenting with this idea to test if I can
use it to build valuable safety checks for DDL migrations. Kind of like
&lt;a href="https://www.shellcheck.net/">shellcheck&lt;/a>, but for database DDL migrations.&lt;/p></description></item><item><title>Careful with That Lock, Eugene</title><link>https://kaveland.no/posts/2024-04-12-careful-with-that-lock-eugene/</link><pubDate>Fri, 12 Apr 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-04-12-careful-with-that-lock-eugene/</guid><description>&lt;p>It is rewarding to work on software that people care about and use all around
the clock. This constant usage means we can&amp;rsquo;t simply take the system offline for
maintenance without upsetting users. Therefore, techniques that allow us to
update the software seamlessly without downtime or compromising service
quality are incredibly valuable.&lt;/p>
&lt;p>Most projects I&amp;rsquo;ve worked on use a relational database for persistence, and have
some sort of migration tool like flyway or liquibase to make changes to the
database schema. This post is about a particular kind of migration situation
that, in my experience, most developers who work on such projects will encounter
at some point in their career. They will want to apply a simple, and seemingly
innocent migration, like adding a column to a table and it&amp;rsquo;ll cause some number
of requests to fail, or maybe even a small outage. There are some tricks we can
use here to reduce risk and automatically detect some patterns that cause this
problem.&lt;/p></description></item><item><title>How to test for missing indexes on foreign keys</title><link>https://kaveland.no/posts/2024-04-04-testcase-for-foreign-keys/</link><pubDate>Thu, 04 Apr 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-04-04-testcase-for-foreign-keys/</guid><description>&lt;p>If you&amp;rsquo;re developing a transactional application backed by postgres, there&amp;rsquo;s a
pretty cool trick you can use to check if you&amp;rsquo;re missing indexes that could
potentially cause serious performance issues or even outages. In particular, I
mean foreign keys where the referencing side of the constraint does not have an
index. The idea is very simple, we can select all of the columns that take part
in a foreign key, then remove the ones that take part in a complete index, and
the remainder should be the empty set, or possibly match a known allowlist. I
think this is a valuable addition to the test cases for your database
migrations, or if you can&amp;rsquo;t easily do that, maybe in your CI/CD pipeline.&lt;/p></description></item><item><title>Friends don't let friends export to CSV</title><link>https://kaveland.no/posts/2024-03-10-friends-dont-let-friends-export-csv/</link><pubDate>Sun, 24 Mar 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-03-10-friends-dont-let-friends-export-csv/</guid><description>&lt;p>I worked for a few years in the intersection between data science and software
engineering. On the whole, it was a really enjoyable time and I&amp;rsquo;d like to have
the chance to do so again at some point. One of the least enjoyable experiences
from that time was to deal with big CSV exports. Unfortunately, this file format
is still very common in the data science space. It is easy to understand why &amp;ndash;
it seems to be ubiquitous, present everywhere, it&amp;rsquo;s human-readable, it&amp;rsquo;s less
verbose than options like JSON and XML, it&amp;rsquo;s super easy to produce from almost
any tool. What&amp;rsquo;s not to like?&lt;/p></description></item><item><title>Isolating integration tests that commit transactions</title><link>https://kaveland.no/posts/2024-03-10-testing-transactions-that-commit/</link><pubDate>Sun, 10 Mar 2024 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2024-03-10-testing-transactions-that-commit/</guid><description>&lt;p>For tests that need to touch the database, it is generally a really good idea to
roll back transactions. That way, you can run lots of tests in parallell or in
any arbitrary order and the tests won&amp;rsquo;t interfere with each other. But
sometimes, that just isn&amp;rsquo;t possible. One reason for this could be that the code
base handles transactions in a way that makes it really hard to get a handle on
them in the right place, or it could be a legacy code base where everything is
running with auto-commit or some other explanation. Either way, it is very
important to be able to isolate effectful tests from each other both to get good
performance out of building on multi-core machines and to avoid flaky tests or
tests that break when they run in a different order.&lt;/p></description></item><item><title>Protecting your postgres server from your application</title><link>https://kaveland.no/posts/2023-05-09-configure-postgres/</link><pubDate>Tue, 09 May 2023 00:00:00 +0000</pubDate><guid>https://kaveland.no/posts/2023-05-09-configure-postgres/</guid><description>&lt;p>There are 2 configuration options that every OLTP application that uses postgres
should set, in order to protect the database from high load:&lt;/p>
&lt;ul>
&lt;li>&lt;code>statement_timeout&lt;/code>&lt;/li>
&lt;li>&lt;code>idle_in_transaction_session_timeout&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>These can both be set by client configuration and require no special
permissions to set, and are easily overridden locally for transactions that have
different requirements.&lt;/p>
&lt;p>They can be a bit scary to retrofit to existing applications, but we can
activate two postgres extensions to help us measure our queries to find safe
values to set:&lt;/p></description></item></channel></rss>