<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
 xmlns:content="http://purl.org/rss/1.0/modules/content/"
 xmlns:wfw="http://wellformedweb.org/CommentAPI/"
 xmlns:dc="http://purl.org/dc/elements/1.1/"
 xmlns:atom="http://www.w3.org/2005/Atom"
 xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
 xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
 xmlns:georss="http://www.georss.org/georss"
 xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
 xmlns:media="http://search.yahoo.com/mrss/">
<channel>
<atom:link href="https://rausch.io/rss.xml" rel="self" type="application/rss+xml" />
<title>rausch.io</title>
<link>https://rausch.io/</link>
<description><![CDATA[]]></description>
<language>en</language>
<lastBuildDate>Mon, 14 Sep 2026 10:34:05 +0200</lastBuildDate>
<generator>Emacs 30.1 org-publish-rss.el 0.9</generator>
<item>
<title>Upgrading/restoring a large PostgreSQL database</title>
<link>https://rausch.io/2018-02-27-opensnp-db-migration.html</link>
<pubDate>Tue, 27 Feb 2018 00:00:00 +0100</pubDate>
<guid>https://rausch.io/2018-02-27-opensnp-db-migration.html</guid>
<description>
<![CDATA[<p>
<b>TL;DR: Restoring large tables in Postgres is much faster, if you add
the indexes and constraints after the data.</b>
</p>

<p>
In my spare time I'm trying to help out at a project called <a href="https://opensnp.org/" target="_blank">OpenSNP</a>, which is an
<a href="http://github.com/OpenSNP/snpr" target="_blank">open-source platform</a> that lets you upload your genetic data, downloaded from
certain proprietary platforms, connects it to the relevant research and provides
it to other researchers, not connected to said platforms. Each of those uploaded
files, called a <i>genotype</i>, contains between 0.5M and 1M rows, each of which we
parse and store in Postgres. Each of the rows contains a so called <i>SNP</i> ("snip"),
or <a href="https://en.wikipedia.org/wiki/Single-nucleotide_polymorphism" target="_blank"><i>single-nucleotide polymorphism</i></a>, which you can imagine as your genetic
configuration parameters, the values of which, if you like me are not a
biologist, may recognize from biology class: the <a href="https://en.wikipedia.org/wiki/Base_pair" target="_blank">base pairs</a> made up of adenine,
cytosine, guanine and thymine. Also, the documentation for that configuration
was never written and researchers are only slowly trying to reverse-engineer it
with the help of <a href="https://en.wikipedia.org/wiki/Genome-wide_association_study" target="_blank">genome-wide association studies</a>.
</p>
<div id="outline-container-the-data" class="outline-2">
<h2 id="the-data">The Data</h2>
<div class="outline-text-2" id="text-the-data">
<p>
In Postgres this data is kept in three tables: <code>genotypes</code>, which
contain the references to the files and to the users, <code>snps</code>, which
contains information related to each of the SNPs, and <code>user_snps</code>, which
contains references to the <code>genotypes</code>, the <code>snps</code> and a two-letter
string for the base-pairs, one row for each of the rows in each of the
genotype files.
</p>

<pre class="example" id="orga51bf1e">
+-----------+   +-----------------+   +------+
| genotypes |--&lt;| user_snps       |&gt;--| snps |
+-----------+   +-----------------+   +------+
| user_id   |   | snp_name        |   | name |
| file      |   | genotype_id     |   | ...  |
| ...       |   | local_genotype* |   +------+
+-----------+   +-----------------+

* a.k.a. the base pair
</pre>

<p>
As of this writing, the database contains 4118 genotypes and 1.3B
user-SNPs, which is by far the largest table and the only one that ever
creates problems in terms of time it takes to insert data into it.
Importing a new data set (the 0.5M to 1M rows mentioned earlier),
currently takes about 2 hours on average. Most of that time is spent
updating the indexes, without indexes inserts are near-instantanious.
The whole database amounts to about 210 GB, including indexes. There is
a primary key on the <code>user_snps</code> on <code>genotype_id</code> and <code>snp_name</code> and an
additional index on <code>snp_name</code> as well as a primary key constraint on
the <code>genotype_id</code>.
</p>

<pre class="example" id="org1e0ea36">
snpr=# \d user_snps
              Table "public.user_snps"
     Column     |         Type          | Modifiers
----------------+-----------------------+-----------
 snp_name       | character varying(32) | not null
 genotype_id    | integer               | not null
 local_genotype | bpchar                |
Indexes:
    "user_snps_new_pkey" PRIMARY KEY, btree (genotype_id, snp_name)
    "idx_user_snps_snp_name" btree (snp_name)
Foreign-key constraints:
    "user_snps_genotype_id_fk" FOREIGN KEY (genotype_id) REFERENCES genotypes(id)
</pre>
</div>
</div>
<div id="outline-container-migrating-the-data" class="outline-2">
<h2 id="migrating-the-data">Migrating the data</h2>
<div class="outline-text-2" id="text-migrating-the-data">
<p>
When migrating the database to a new machine, we decided to migrate from
Postgres 9.3 to 9.5, as this is the version that ships with the latest
LTS release of Ubuntu. I tried migrating the data using <code>pg_upgrade</code> at
first, but after a few days it became clear, that this would take longer
than expected. It slowed down quite a bit over time. I manually kept
track of the size of Postgres' data directory now and then, using a
Google Sheet.
</p>


<div id="orge816253" class="figure">
<p><img src="https://rausch.io/../images/opensnp-db-migration-chart.png" alt="opensnp-db-migration-chart.png" />
</p>
<p><span class="figure-number">Figure 1: </span>chart: size of PostgreSQL data directory over time</p>
</div>

<p>
For what it's worth, Google Sheet's <code>FORECAST</code> function estimated it to
finish in just under a year. 😬
</p>

<p>
The only reason I could come up with, for it to get slower over time,
was that it must be updating the indexes as it inserts into the
<code>user_snps</code> table. I vaguely hoped Postgres' COPY function would copy
the data first and re-index afterwards instead, but evidently it
doesn't. Since we didn't want to wait a whole year, I aborted the
mission and started over. This time, in order to avoid this problem, I
took separate dumps of the original database, one in text-format for the
schema, one in <i>custom</i> format for the data:
</p>

<pre class="example" id="orgcd10392">
pg_dump --schema-only -Fp snpr &gt; snpr-schema.psql
pg_dump --data-only -Fc snpr &gt; snpr-data.psql
</pre>

<p>
I opened the <code>snpr-schema.psql</code> and commented out the indexes, and while
I was at it, the foreign key constraints, of the <code>user_snps</code> table. I
restored the schema and the data on the new machine and ran the
commented out bits after the data was imported. The whole process only
took a few hours. Unfortunately, I don't have a graph or an exact time
for that. I ran it overnight and it was done the next morning.
</p>
</div>
</div>
<div id="outline-container-conclusion" class="outline-2">
<h2 id="conclusion">Conclusion</h2>
<div class="outline-text-2" id="text-conclusion">
<p>
When restoring a large Postgres database, import the data before the
indexes. The next time I'll try writing a script for that, unless
someone else does it first (*hint*) or it already exists. Additionally,
always keeping track of long running processes is a very good idea.
Without keeping track of the progress of the import, we wouldn't have
been able to make an informed decision on whether to abort or not. Even
better is having a script in place doing that for you, e.g. logging the
size of Postgres' data directory to a file every minute, or have
monitoring in place on the machine, keeping track of the disk usage.
</p>
</div>
</div>
]]>
</description></item>
<item>
<title>Closing the Loop</title>
<link>https://rausch.io/2023-01-23-closing-the-loop.html</link>
<pubDate>Mon, 23 Jan 2023 00:00:00 +0100</pubDate>
<guid>https://rausch.io/2023-01-23-closing-the-loop.html</guid>
<description>
<![CDATA[<p>
If you are old enough to remember the microblogging website
<a href="https://web.archive.org/web/20230501230456/https://twitterisgoinggreat.com/" target="_blank">Twitter.com</a>,
you may also remember how people always complained about one another commenting
on or retweeting tweets without having read the articles shared within them.
Twitter even tried to mitigate this by adding a dialog that would pop up if you
tried to retweet a tweet, asking whether you wanted to read the article first,
maybe? If you really took this seriously, though, unless you read the article
immediately, you would have to work your way through all the open tabs you
accumulated scrolling through Twitter, first, until you finally made it to the
article, after which you'd have to find your way back to the tweet again. By
then you'd have scrolled on, of course, leaving the people who read immediately
or not at all to comment. And we know what that ratio looks like. Not ideal, if
you want to foster <i>quality public debate</i>.
</p>

<p>
Enter <a href="https://getpocket.com/" target="_blank">Pocket</a>&#x2026; One of many services that solved the
problem of the million open tabs, also solved the issue of finding back to the
tweet by displaying it along with the article, at least in the app. A feature
they call
<a href="https://help.getpocket.com/article/1125-saving-to-pocket-from-twitter" target="_blank">Tweet Attribution</a>.
I didn't even consciously notice how useful this actually was
until I fled Twitter, along with everybody else, during the great Twitter Exodus
of 2022. Unfortunately, the feature only works for tweets, so at Mastodon, I
started using bookmarks to save interesting links, in order to keep track of the
source. Consequently, I created two places to catch up on reading those. This
was quite a disruption to my previous process. That's why I came up with a
Firefox extension that fixes this (for me):
<a href="https://addons.mozilla.org/de/firefox/addon/pockettoots/" target="_blank">PocketToots</a>
(<a href="https://github.com/tsujigiri/PocketToots" target="_blank">source</a>)! It syncs Mastodon
bookmarks to Pocket and adds a link back to the source toot. Maybe there are
others who happen to use Mastodon, Firefox, and Pocket, who would find this
useful!
</p>
]]>
</description></item>
</channel>
</rss>
