<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Alex Tomkins - postgresql</title>
    <link>https://www.alextomkins.com/tag/postgresql/</link>
    <atom:link href="https://www.alextomkins.com/tag/postgresql/feed.xml" rel="self" type="application/rss+xml" />
    <description>Posts tagged with postgresql</description>
    <language>en</language>
    
    
    
    
    
    
    <item>
      <title>Easier PostgreSQL Extension Installations</title>
      <link>https://www.alextomkins.com/2016/09/easier-postgresql-extension-installations/</link>
      <guid>https://www.alextomkins.com/2016/09/easier-postgresql-extension-installations/</guid>
      <pubDate>Sat, 24 Sep 2016 20:32:00 +0000</pubDate>
      <description><![CDATA[<p>Postgres has a number of extensions which require superuser access for a user to install them,
<a href="http://postgis.net/">PostGIS</a> being one of them. If you're wanting to install a
<a href="https://docs.djangoproject.com/en/1.8/ref/contrib/gis/">GeoDjango</a> app, you'll probably encounter
an error such as:</p>
<pre><code>$ ./manage.py migrate
Traceback (most recent call last):
  ..
django.db.utils.ProgrammingError: permission denied to create extension "postgis"
HINT:  Must be superuser to create this extension.
</code></pre>
<p>For security reasons you don't want to give the user superuser access, and for convenience you
don't want to go and manually go into each database just to add PostGIS support.</p>
<h1>PostgreSQL Extension Whitelisting</h1>
<p>The <a href="https://github.com/dimitri/pgextwlist">pgextwlist</a> extension can allow regular users to use
the <code>CREATE EXTENSION</code> command, allowing them to install the extension without getting the
permission denied error.</p>
<p>To install on Ubuntu 16.04 (Xenial Xerus):</p>
<pre><code>$ sudo apt-get install postgresql-9.5-pgextwlist</code></pre>
<p>To install on Debian 8 (Jessie):</p>
<pre><code>$ sudo apt-get install postgresql-9.4-pgextwlist</code></pre>
<p>After installing, edit the relevant <code>postgresql.conf</code> file for your Postgres instance and add the
following lines:</p>
<pre><code># Allow PostGIS to be loaded easily
local_preload_libraries = 'pgextwlist'
extwlist.extensions = 'postgis'</code></pre>
<p>Then run:</p>
<pre><code>$ sudo service postgresql restart</code></pre>
<p>Now you should be able to use a GeoDjango app/database without any errors:</p>
<pre><code>$ ./manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  ..
</code></pre>
]]></description>
    </item>
    
    
    
    
    
    
    
    <item>
      <title>Be careful with Django&#39;s .create_or_update()!</title>
      <link>https://www.alextomkins.com/2016/08/be-careful-with-djangos-create-or-update/</link>
      <guid>https://www.alextomkins.com/2016/08/be-careful-with-djangos-create-or-update/</guid>
      <pubDate>Sun, 21 Aug 2016 18:44:00 +0000</pubDate>
      <description><![CDATA[<p>Although using <code>Model.objects.create_or_update()</code> with a Django model is extremely convenient,
sometimes you might want to consider using it carefully with certain usage patterns.</p>
<h1>Excessive Postgres WAL files</h1>
<p>To allow point-in-time recovery (PITR), I usually setup <a href="http://www.pgbarman.org/">Barman</a>. Set it
up on a remote server, get your Postgres instance to rsync files over to the Barman server - and
you've got a set of backups which should allow you to recover your database from an earlier point
in time.</p>
<p>For every UPDATE/INSERT, Postgres will write data to the WAL (Write-Ahead Log). This isn't a
problem if you're not doing anything else with the finished WAL files, although you'll have
increased disk I/O - it's probably just a minor increase you won't notice too much.</p>
<p>However if you're keeping the WAL and archiving it for backups, every update to a table row will be
backed up:</p>
<pre><code>$ barman list-backup all
golestandt 20160613T063007 - Mon Jun 13 06:36:06 2016 - Size: 4.5 GiB - WAL Size: 12.7 GiB
</code></pre>
<p>Ouch.</p>
<h1>Finding the problem databases</h1>
<p>Assuming you've got statistics enabled, use <code>psql</code> to show the statistics for each database:</p>
<pre><code>postgres=# SELECT datname,tup_updated,tup_inserted,tup_deleted FROM pg_stat_database ORDER BY tup_updated DESC;
                datname                | tup_updated | tup_inserted | tup_deleted
---------------------------------------+-------------+--------------+-------------
 site1                                 |     1138475 |       191605 |      136569
 site2                                 |      153224 |        46650 |       12385</code></pre>
<p>Now we've got a list of databases which are excessively updating and inserting new rows.</p>
<h1>How did we get here?</h1>
<p>By being too lazy with <code>.create_or_update()</code> in applications doing regular syncs.</p>
<p>If you've got Django code syncing with a third party service, it's easy to write code similar to:</p>
<pre><code>for tweet in tweet_list:
    Tweet.objects.create_or_update(tweet_id=tweet['id'], defaults={
        'user': tweet['user']['screen_name'],
        'text': tweet['text'],
        'retweet_count': tweet['retweet_count'],
    })</code></pre>
<p>If you're only updating a few objects occasionally - this is fine and does the job. However if
you're updating hundreds of objects every hour - you could end up with hundreds of thousands of
rows being updated on a weekly basis, even though most of the data is likely to stay the same.</p>
<p>Every field for the object gets updated every time the object gets saved. If you've got a model
with more fields, this will add up very quickly to additional WAL data.</p>
<h1>Avoiding updates</h1>
<p>If only some of the synced data is actually being updated - just update the fields which will
receive any updates:</p>
<pre><code>for tweet in tweet_list:
    obj, created = Tweet.objects.get_or_create(tweet_id=tweet['id'], defaults={
        'user': tweet['user']['screen_name'],
        'text': tweet['text'],
        'retweet_count': tweet['retweet_count'],
    })

    if not created:
        # Update counts, but try to avoid excessive updates
        update_fields = []

        if obj.retweet_count != tweet['retweet_count']:
            obj.retweet_count = tweet['retweet_count']
            update_fields.append('retweet_count')

        if update_fields:
            obj.save(update_fields=update_fields)</code></pre>
<p>In this example we're assuming tweet IDs or usernames won't ever change. This is probably a safe
assumption for IDs, and slightly less so for usernames.</p>
<p>By switching to <code>.get_or_create()</code>, and using <code>.save(update_fields=list)</code> - we've significantly
reduced the number of updates. If the object already exists and none of the fields have been
changed - <code>update_fields</code> will be an empty list and we don't even bother with <code>.save()</code>.</p>
<p>The only downside using this method is that it can be tedious having to check every single field
for updates. We could probably improve this with a more generic solution - but at this point it's
easier to use a third party package which solves the problem.</p>
<h1>Django Dirty Fields</h1>
<p><a href="https://github.com/romgar/django-dirtyfields">Django Dirty Fields</a> is an easy alternative to
writing your own code to check all of the fields of an object. Just add a <code>DirtyFieldsMixin</code> to
your model and you can simplify your code:</p>
<pre><code>for tweet in tweet_list:
    try:
        obj = Tweet.objects.get(tweet_id=tweet['id'])
    except Tweet.DoesNotExist:
        obj = Tweet(tweet_id=tweet['id'])

    obj.user = tweet['user']['screen_name']
    obj.text = tweet['text']
    obj.retweet_count = tweet['retweet_count']

    # Only save if needed
    if not obj.id:
        obj.save()
    elif obj.is_dirty():
        obj.save_dirty_fields()</code></pre>
<p>Considerably easier to deal with!</p>
<p>The one huge advantage of this approach is that you can add as many fields as you want, and Django
Dirty Fields will do the job of figuring out which fields to update for you. As before if none of
the fields have been updated - the object won't be saved.</p>
<h1>The Results</h1>
<p>After trying to fix several projects:</p>
<pre><code>$ barman list-backup all
golestandt 20160704T063009 - Mon Jul  4 06:33:29 2016 - Size: 4.6 GiB - WAL Size: 822.0 MiB
</code></pre>
<p>There's still a few old projects which haven't been optimised - however we've now managed to reduce
the size of the weekly WAL to be smaller than the weekly snapshot.</p>
]]></description>
    </item>
    
    
    
    
    
    
    
    
    
    
  </channel>
</rss>
