<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Mike</title>
    <description>The latest articles on Forem by Mike (@ypkuby).</description>
    <link>https://forem.com/ypkuby</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F157781%2Ff40b458e-25a9-4375-b89d-8b445d32d5a3.png</url>
      <title>Forem: Mike</title>
      <link>https://forem.com/ypkuby</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ypkuby"/>
    <language>en</language>
    <item>
      <title>Thinking of self-hosting? Here's some tips.</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Wed, 24 Apr 2019 01:49:19 +0000</pubDate>
      <link>https://forem.com/ypkuby/thinking-of-self-hosting-here-s-some-tips-51dh</link>
      <guid>https://forem.com/ypkuby/thinking-of-self-hosting-here-s-some-tips-51dh</guid>
      <description>&lt;p&gt;&lt;a href="https://kuby.ca/"&gt;This was originally published on my blog, where I regularly post tech tutorials, and other awesome content.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was inspired by a &lt;a href="https://www.reddit.com/r/selfhosted/comments/bgegz4/how_to_learn_about_self_hosting/"&gt;Reddit Post&lt;/a&gt;, in this post the author asks for advice on self-hosting things. I weighed in with some points on locking down your self-hosted services.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why self-host?
&lt;/h1&gt;

&lt;p&gt;It's fun, it helps give you real world experience. On top of this, if you care for privacy and security, it helps you lock the environment down so you feel more comfortable. Remember, nothing is bulletproof. If a determined threat actor wants in, it only takes one tiny mistake to open the flood gates.&lt;/p&gt;

&lt;h1&gt;
  
  
  OK, so I'll host. What next?
&lt;/h1&gt;

&lt;p&gt;Welcome to the exciting world of self-hosting! It's a great hobby. System administration rocks.&lt;/p&gt;

&lt;p&gt;Let's assume you'll be using a flavour of linux, such as Debian or Ubuntu. First, I always pick the most recent LTS, in the case of Ubuntu, I'll prefer 18.04 LTS. &lt;a href="https://m.do.co/c/d77690d747e5"&gt;I use DigitalOcean (ref link)&lt;/a&gt; for hosting most personal things, including this very own blog along with my wife's. They've done a great job at making both developers and new-comers life super simple.&lt;/p&gt;

&lt;p&gt;Go ahead and make an account at DigitalOcean, get some free credit, spin up a droplet for yourself with Ubuntu 18.04. You'll need an SSH key, you can generate one on linux or mac by doing the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-b&lt;/span&gt; 4096
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To retrieve the public key, just run this command and then paste it into the ssh key slot on DigitalOcean (like shown below):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.ssh/id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7QdNUYg1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kuby.ca/content/images/2019/04/image-6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7QdNUYg1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kuby.ca/content/images/2019/04/image-6.png" alt="DO SSH Keys"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, finalize and create your droplet! Within about 1 minute your droplet is online and ready to use.&lt;/p&gt;

&lt;h1&gt;
  
  
  Accessing your droplet
&lt;/h1&gt;

&lt;p&gt;You'll need to use ssh, just type in your terminal (linux/mac) or Putty/other-ssh client on Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@&lt;span class="nv"&gt;$IP_ADDRESS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Replace $IP_ADDRESS with your droplets IP address. You're in!&lt;/p&gt;

&lt;h1&gt;
  
  
  Securing your droplet
&lt;/h1&gt;

&lt;p&gt;Immediately, the first things you'll want to do is start locking down your droplet. Start by running a simple apt update and installing fail2ban:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install &lt;/span&gt;fail2ban &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will update your packet repository, install and enable fail2ban. Fail2ban works wonders for SSH and helps ban brute-force attackers. &lt;em&gt;But, we really shouldn't keep our ssh port 22 as the default.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It is always advisable to either (a) change the SSH port, (b) limit access to SSH via firewall, or, (c) do both. I always pick option C.&lt;/p&gt;

&lt;p&gt;Let's change the ssh port! I use vim for this (yes, I know) - but nano is a good alternative. If you don't have nano, run apt-get install nano and you'll have it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;vim /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the first few lines you should see "Port 22" - go ahead and change this to any memorable port you know, pick something that isn't simple to guess (eg. 2222). I'll use in this example port 9132.&lt;/p&gt;

&lt;p&gt;Go ahead and save this file, then restart ssh - you may have to reconnect once this is done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;service ssh restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To reconnect with the new port, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@&lt;span class="nv"&gt;$IP_ADDRESS&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$PORT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now this is getting more locked down, but let's imagine we have an apache2 server running, and we use Cloudflare. We want to make sure that only Cloudflare and our IP can access the web server directly, let's introduce a &lt;a href="https://www.digitalocean.com/products/cloud-firewalls/"&gt;Cloud Firewall from DigitalOcean.&lt;/a&gt; Go ahead and create one, tag it appropriately.&lt;/p&gt;

&lt;p&gt;Here is what your basic configuration should look like (IP list for cloudflare is available here: &lt;a href="https://www.cloudflare.com/ips/"&gt;https://www.cloudflare.com/ips/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hVyJlzjt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kuby.ca/content/images/2019/04/image-7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hVyJlzjt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kuby.ca/content/images/2019/04/image-7.png" alt="DO Cloud Firewall Configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, HTTP is open to Cloudflare only. Perfect. Now if you try to access it you should get rejected by the firewall (unless you whitelisted yourself). I opened 10/8 for this demonstration so if you have multiple droplets they can communicate properly with each-other (I'd suggest just whitelisting IPs manually, not entire subnets, however). YMMV.&lt;/p&gt;

&lt;h1&gt;
  
  
  Always take backups!
&lt;/h1&gt;

&lt;p&gt;DigitalOcean charges peanuts to do them automatically, this blog costs me $1/mo to backup. I'd recommend you always keep a personal backup as well, just in case.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Tml4Wfj---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kuby.ca/content/images/2019/04/image-8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Tml4Wfj---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kuby.ca/content/images/2019/04/image-8.png" alt="Backups Picture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  That's it!
&lt;/h1&gt;

&lt;p&gt;Now you can experiment and install your own software, like NextCloud, etc. For further reading, checkout &lt;a href="https://reddit.com/r/homelab"&gt;/r/homelab&lt;/a&gt; and &lt;a href="https://reddit.com/r/selfhosted"&gt;/r/selfhosted&lt;/a&gt; - it's quite addictive.&lt;/p&gt;

&lt;p&gt;If you have any questions, feel free to give me a shout!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>selfhosting</category>
      <category>digitalocean</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>CD: Configuring my own ISP network to reach 2Gbit+ speeds</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Thu, 18 Apr 2019 09:52:37 +0000</pubDate>
      <link>https://forem.com/ypkuby/cd-configuring-my-own-isp-network-to-reach-2gbit-speeds-4m4b</link>
      <guid>https://forem.com/ypkuby/cd-configuring-my-own-isp-network-to-reach-2gbit-speeds-4m4b</guid>
      <description>&lt;p&gt;This was &lt;a href="https://kuby.ca/cd-configuring-my-isp-network-to-reach-2gbit-speeds/" rel="noopener noreferrer"&gt;originally posted on my blog, where you'll from everything from breakfast to servers caught on fire, and spaghetti code in production, come check it out!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've never blogged about this directly before, but I operate my own ISP locally. I've got stable customers, and I'm profitable! I recently started getting bogged down on port speed however, we managed to exhaust an entire 10G port. Wowsers.&lt;/p&gt;

&lt;p&gt;I've spun up new nodes to my cluster for higher bandwidth, and migrated some hosts over this evening. I use Grafana, Observium, and a few other tools to catalog + monitor everything in production - it's perfect for this sort of thing. Right now the new cluster node is averaging 2G consistently, which is pretty awesome. I didn't have to spend hundreds to spin it up either, due to my extensive work at scaling my business, it took me about 45 minutes and pushing some configuration files to customers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fkuby.ca%2Fcontent%2Fimages%2F2019%2F04%2Fimage-5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fkuby.ca%2Fcontent%2Fimages%2F2019%2F04%2Fimage-5.png" alt="2G consistently"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  How did I manage to burst over 10G?
&lt;/h1&gt;

&lt;p&gt;Being an ISP, my customers have a variety of needs, some stream consistently, some are heavy data hoarders and download tonnes. As you can see from the above screenshot, there's a lot of data being transferred here. I do extensive monitoring on what is being used to ensure there is no abuse.&lt;/p&gt;

&lt;h1&gt;
  
  
  How'd I start my own ISP?
&lt;/h1&gt;

&lt;p&gt;It started in 2016, I was fed up with the prices the local ISPs were offering, and they had shotty service at best. If you paid for a 1G pipe, you'd get at best 500Mbit down, 150Mbit up. This wasn't due to any congestion, they just didn't want to allot more bandwidth, despite selling it with more.&lt;/p&gt;

&lt;p&gt;I entered into an agreement to lease lines, and colocated gear. I bought bandwidth from transit browsers, acquired an ASN + IP range. Bingo, I began. Selling was easy due to my area of town, I was the best option - I costed about $25/mo more than my competitors, but instead of 1G "symmetrical" with 5TB cap, I offered 1GB symmetrical with 50TB cap. That's a lot of bandwidth.&lt;/p&gt;

&lt;p&gt;Automating this wasn't easy, a mixture of freeredius, ansible scripts to push config files. It works, it communicates back with my billing appliance, and my bandwidth calculator pumps the data into the billing appliance as well. At the end of the month, my customers get a detailed invoice stating amount used, average speed, and bandwidth credit (if applicable).&lt;/p&gt;

&lt;p&gt;And, as of early this year, I am beginning to become profitable. This is a big milestone, it's been my dream to run an ISP, and it's happening every single day.&lt;/p&gt;

&lt;h1&gt;
  
  
  Any tips for people who want to run their own ISP?
&lt;/h1&gt;

&lt;p&gt;First, it's hard - the logistics of this all is complex. Before you consider it, get a list of at least 10-20 people who would sign up with you immediately. This helps bring in money, possibly startup funds to acquire equipment.&lt;/p&gt;

&lt;p&gt;Start small, don't immediately buy 50G of transit, start with 1G, sell slowly, upgrade as you need. If you jump into it big, you'll lose big time.&lt;/p&gt;

&lt;p&gt;Don't fold - even if your competitor is cheaper, keep your same rates - focus on quality not quantity.&lt;/p&gt;

&lt;p&gt;Have an actual support panel and reply to tickets actively, engage customers.&lt;/p&gt;

&lt;p&gt;Don't focus on selling your own hardware, it's OK to resell and put your own scripts on it.&lt;/p&gt;

&lt;p&gt;Most importantly, have fun - this is an experience you'll never forget. You don't need lots of money, you just need passion.&lt;/p&gt;

&lt;p&gt;Good luck!&lt;/p&gt;

</description>
      <category>production</category>
      <category>engineering</category>
      <category>isp</category>
      <category>blog</category>
    </item>
    <item>
      <title>Recursion is difficult: PostgreSQL Edition</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Thu, 18 Apr 2019 09:45:48 +0000</pubDate>
      <link>https://forem.com/ypkuby/recursion-is-difficult-postgresql-edition-2o6m</link>
      <guid>https://forem.com/ypkuby/recursion-is-difficult-postgresql-edition-2o6m</guid>
      <description>&lt;p&gt;This was &lt;a href="https://kuby.ca/recursion-is-difficult-in-postgresql/" rel="noopener noreferrer"&gt;originally posted on my blog, where you'll find me discussing everything from lunch to engineering, system administration, and running my own ISP!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Arguably one of the most difficult things to get a grasp on, no matter the experience level the developer has, is recursion. When implemented properly, recursion is beautiful, simple, and can be entirely stateless.&lt;/p&gt;

&lt;p&gt;Now imagine implementing recursive functions successfully in PostgreSQL that are capable of maintaining themselves via various triggers. This premise is beautiful, a one-deploy function and trigger that can detect updates in tables, and trigger a function. This function accepts a parameter of the OLD state, and NEW state – seems simple enough, or is it?&lt;/p&gt;

&lt;h1&gt;
  
  
  Mocking up recursive functions
&lt;/h1&gt;

&lt;p&gt;I've always found mocking the intended recursive function on paper is the best way to get the full picture, in most cases you can't reliably see recursion in your head. I usually default to my most-used language PHP when writing the function on paper, for example if I wanted to take the OLD state, and NEW state, and check if the id_link number changed, it would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;onUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;\psqltable&lt;/span&gt; &lt;span class="nv"&gt;$previous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;\psqltable&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$previous&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id_link'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id_link'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="c1"&gt;// this is now linked to a new parent element, let's rebuild&lt;/span&gt;
         &lt;span class="nf"&gt;rebuild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$previous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our end goal is, if we're linked to a new "id_link" tag, we want to rebuild an entire tree structure, deleting and re-inserting new nodes as things change, all automatically via triggers. Let's dive into how this may look...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;rebuild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$previous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$next&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$children_prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$previous&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;children&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$children_prev&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;rebuild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$children_prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// recursion one&lt;/span&gt;
    &lt;span class="nv"&gt;$previous&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCurrent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;reinsert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id_link'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id_link'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'last_updated'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we begin to pull apart this function, we can see we get all our children for our current node, if we have any, we call rebuild again for n children, until we've reached the bottom of the stack. We then get our current index in the object, change our id_link, last_updated fields to the parents id_link and time respectively.&lt;/p&gt;

&lt;p&gt;On the surface this seems pretty simple, so let's give it a kick at the can in PSQL this time!&lt;/p&gt;

&lt;h1&gt;
  
  
  PSQL Mockup
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Disclosure: By no means a PSQL expert!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Starting simple with our most basic triggers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;  &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;trigger_for_changes&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;reference_detail&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;trigger_for_changes&lt;/span&gt; &lt;span class="k"&gt;BEFORE&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;reference_detail&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;PROCEDURE&lt;/span&gt; &lt;span class="n"&gt;reference_detail_changed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;reference_detail_changed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt; &lt;span class="k"&gt;language&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
    &lt;span class="k"&gt;BEGIN&lt;/span&gt;
        &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;TG_OP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'UPDATE'&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;TG_OP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'INSERT'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
            &lt;span class="n"&gt;PERFORM&lt;/span&gt; &lt;span class="n"&gt;adapt_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_link&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;ELSE&lt;/span&gt;
            &lt;span class="n"&gt;PERFORM&lt;/span&gt; &lt;span class="n"&gt;adapt_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_link&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'd decided in implementation to change it around, instead just pass our ID_LINK to search in the header table, which contains metadata of all current objects. Let's give our function an attempt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;adapt_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;old_id_link&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_id_link&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt;
  &lt;span class="k"&gt;language&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;
  &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
    &lt;span class="k"&gt;DECLARE&lt;/span&gt;
      &lt;span class="n"&gt;meta_row&lt;/span&gt; &lt;span class="n"&gt;RECORD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;ref_detail&lt;/span&gt; &lt;span class="n"&gt;RECORD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;BEGIN&lt;/span&gt;
      &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;old_id_link&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;new_id_link&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
        &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;reference_detail&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_id_link&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;meta_row&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id_curr_link&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;reference_meta&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id_meta_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_id_link&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;
          &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;reference_meta&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id_meta_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_id_link&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;-- clear out meta record&lt;/span&gt;
          &lt;span class="n"&gt;PERFORM&lt;/span&gt; &lt;span class="n"&gt;adapt_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;ref_detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ref_detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id_link&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;-- parent was destroyed, time to delete ourselves!&lt;/span&gt;
        &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;ELSE&lt;/span&gt;
        &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;reference_detail&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;id_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_id_link&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_id_link&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;reference_meta&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;id_curr_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_id_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id_meta_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_id_link&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_id_link&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;meta_row&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;rowid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id_curr_link&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;reference_meta&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id_meta_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_id_link&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;
          &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;reference_detail&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;id_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_id_link&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id_curr_link&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;reference_meta&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;id_curr_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_id_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id_meta_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_id_link&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id_curr_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id_curr_link&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;rowid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;meta_row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rowid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fkuby.ca%2Fcontent%2Fimages%2F2019%2F02%2Fimage-7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fkuby.ca%2Fcontent%2Fimages%2F2019%2F02%2Fimage-7.png" alt="Wowsers!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a mess, but it's fully recursive, let me break it down some, starting with our first IF condition, we check if our old id matches our new id, if that's the case, we're a DELETE operation, so we'll delete ourselves, and our child tree as we no longer need them.&lt;/p&gt;

&lt;p&gt;Our ELSE condition gets much more interesting, let's take an isolated look at it here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;reference_detail&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;id_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_id_link&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_id_link&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;reference_meta&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;id_curr_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_id_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id_meta_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_id_link&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_id_link&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;meta_row&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;rowid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id_curr_link&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;reference_meta&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id_meta_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_id_link&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;
  &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;reference_detail&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;id_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_id_link&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id_curr_link&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;reference_meta&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;id_curr_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_id_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id_meta_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_id_link&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id_curr_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id_curr_link&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;rowid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;meta_row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rowid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: For those who are unaware, PERFORM is used as we don't care for the returned value in this case.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;First, we update our record in the detail and meta table to match our new parent. Then we begin iterating on our meta_row object, getting the current (id_curr_link) from the meta table, when our id_meta_link matches the ID we just were a child node – then we begin recursion again, and begin to alter that record and all it's children.&lt;/p&gt;

&lt;p&gt;It's by no means a perfect function, but for the use case I have currently, which is wanting to delete/update trees of data, it seems to just fit the bill. Performance wise, it's pretty snappy, on average takes less than one second from start to finish.&lt;/p&gt;

&lt;p&gt;This is a basic implementation of a self-managing database table, ran by INSERT/UPDATE/DELETE triggers, and recursive functions. Pretty neat, if you ask me – it's nice to have the database maintain itself to a degree, it takes the headway off of using another language (eg, PHP) to maintain it. PostgreSQL is incredibly fast if done right – it's just getting it right which takes work, but it's worth it!&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>showdev</category>
      <category>postgres</category>
      <category>recursion</category>
    </item>
    <item>
      <title>Engineering yourself to become a better Engineer</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Thu, 18 Apr 2019 09:38:08 +0000</pubDate>
      <link>https://forem.com/ypkuby/engineering-yourself-to-become-a-better-engineer-4910</link>
      <guid>https://forem.com/ypkuby/engineering-yourself-to-become-a-better-engineer-4910</guid>
      <description>&lt;p&gt;This was &lt;a href="https://kuby.ca/engineering-yourself-to-become-a-better-engineer/"&gt;originally posted on my blog, where I discuss all things engineering, sysadmin, labbing!&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There's this paradox that you need to be as fast as possible when programming - just ship it they say. I'd like to argue the counter - perfect what you can, then ship it. It's important to get a MVP out there, but wha tis equally as important is making the MVP suitable enough for potential customers to want to remain on the platform. When I find a MVP, I make a point of spending 10 minutes on it - in 10 minutes if I don't understand the ultimate goal - or it's not possible to do as advertised within reason - it's lacking engineering, not features.&lt;/p&gt;

&lt;p&gt;This goes hand in hand when you want to improve yourself, in order to understand yourself better - make yourself a MVP. You're the customer of your own skillset - how do you face a task? A question I strike often is, "if I showed up at your desk, gave you 20 things to do on your first day, and went 'oh by the way, I have a meeting so good luck see you tomorrow!', how would you react?" - You wouldn't be surprised how many hopefuls tell me, "I'd dive right in and get it all done to perfection before you get back in, I'd go the extra mile and ensure to ..." - Not the answer we've wanted to hear. Let's be real here, you're only going to get a few things done, personally, I'd spend the day introducing myself and getting to know everyone first, then get the tasks done the next day.&lt;/p&gt;

&lt;p&gt;This is just one example out of many, I've often found that during interviews, the average person overstates their confidence and ability. I'd rather you aim high and miss, then to shoot low and hit. Tell me straight up with no sugarcoating your abilities, it won't mean you can't work - in fact, it shows you're willing to be honest with yourself. Everyone wants a 10 year junior developer, and unfortunately most people give into it, even if they have excellent experience.&lt;/p&gt;

&lt;p&gt;My takeaway for all those who are seeking careers in computer science-related fields:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No is a perfectly acceptable answer&lt;/li&gt;
&lt;li&gt;Find the right job for you, don't listen to the people around you - make the decision based on where you want to be in a few years down the road&lt;/li&gt;
&lt;li&gt;Don't jump ship because it's tough&lt;/li&gt;
&lt;li&gt;Be nice to everyone, despite their attitude towards you&lt;/li&gt;
&lt;li&gt;Finally, to err is human - to blame others shows management potential./s&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>showdev</category>
      <category>blog</category>
      <category>engineering</category>
      <category>advice</category>
    </item>
    <item>
      <title>Super fast full text search with PostgreSQL</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Thu, 18 Apr 2019 02:30:24 +0000</pubDate>
      <link>https://forem.com/ypkuby/super-fast-full-text-search-with-postgresql-58pi</link>
      <guid>https://forem.com/ypkuby/super-fast-full-text-search-with-postgresql-58pi</guid>
      <description>&lt;p&gt;This was &lt;a href="https://kuby.ca/fast-full-text-search-using-postgresql/"&gt;originally posted oh my blog where I post all things tech and sysadmin and the like!&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;What if I told you that searching millions of records didn't have to be complex, and take minutes to complete? Most organizations jump ship to a dedicated searching engine like Elastic Search, but they greatly underestimate the power of PostgreSQL.&lt;/p&gt;

&lt;p&gt;Let's suppose we have a table with 1.1 million records, and we want to apply a text search vector to it. We care about the author and publisher names. In order to create the vectors, we need to take out old data, and put it into a new column that has a vector.&lt;/p&gt;

&lt;p&gt;Time to break it down! Our author and publisher names are just strings, for example author is "Jack Sparrow", publisher is "A Movie Corporation" - we want to be able to start typing "jac" or "mov cor" and have it pickup Jack Sparrow and/or A Movie Corporation. Makes sense! The issue we face is we don't want to have two columns, we need an index so we can rapidly search, so we make a new column in the database table, let's call this column "tsv" for short.&lt;/p&gt;

&lt;p&gt;First, add the column, then apply a GIN index on it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;searchable&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;tsv&lt;/span&gt; &lt;span class="n"&gt;tsvector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;searchable_gin_idx&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;searchable&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tsv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, let's update our table records accordingly, you can use this SQL to create an English TSVector and instantly apply it to the TSV field.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;UPDATE searchable SET tsv = ( to_tsvector('english', author) || to_tsvector('english', publisher ) );&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;This will begin to build a simple GIN index using our concatenated author and publisher. We will now be able to perform both simple and complex fast search queries. It's the same data, without compromising speed. For example, if we want to find all items containing 'jac parr' as the author, we can do:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT * FROM searchable WHERE tsv @@ to_tsquery('jac:* parr:*');&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Instantly, we're presented with a ton of results. It's interesting to note, that even though we've attached a wildcard to the query, PostgreSQL is still able to rapidly search and get our result set. We could further apply a ts_rank_cd in the ORDER BY clause to rank them according to weights.&lt;/p&gt;

&lt;p&gt;Speed and consistency matters.&lt;br&gt;
If we run explain analyze on it, we can see that the query took 0.013ms to plan, and 0.067ms to execute.&lt;/p&gt;

&lt;p&gt;If we were to run the same query without a GIN index, we see it took 0.121ms to plan, and 132.14ms to execute.&lt;/p&gt;

&lt;p&gt;As you can see, this is a major performance boost, and we get the same results we did previously, much faster this time around. Thus, another reason you may not need a searching product - if you optimize your queries and database table with the proper indexes you can leverage the full power of PostgreSQL.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>blog</category>
    </item>
    <item>
      <title>The beauty in simplicity: Migration in real-time, without data loss.</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Thu, 18 Apr 2019 02:26:19 +0000</pubDate>
      <link>https://forem.com/ypkuby/the-beauty-in-simplicity-migration-in-real-time-without-data-loss-35me</link>
      <guid>https://forem.com/ypkuby/the-beauty-in-simplicity-migration-in-real-time-without-data-loss-35me</guid>
      <description>&lt;p&gt;This was &lt;a href="https://kuby.ca/"&gt;originally posted on my blog, where I publish all things tech, sysadmin, and more&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've all heard the stories - to be able to migrate in real time, without any data loss. I've done it once before for this blog, and I've done it again for my wife and I. In the middle of the day, about 1k visitors online. Here's a breakdown of how I did it.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Goal
&lt;/h1&gt;

&lt;p&gt;Not only do I want to migrate providers, I want to migrate my content to a new country, literally continents apart. Moving from my old blog host on Hetzner, to DigitalOcean. Luckily, both sides have 1G port (Hetzner unmetered, DO has a few TB cap) - more than enough.&lt;/p&gt;

&lt;p&gt;File Size isn't an issue, about 2.4 GB of file size, the transfer of data took about 1 minute in total (which isn't bad, considering distance, it could be improved however).&lt;/p&gt;

&lt;p&gt;The issue that is happening is real-time movement, behind the scenes there's analytic engines, few headless APIs, etc. that all interact with data live. This is the issue.&lt;/p&gt;

&lt;p&gt;In order to fix this, we'll need to setup some master/slave replication on PostgreSQL, then go fully fledged master/master replication. Next, I imagine we'll have to slowly pull traffic off the old host to the new one. Finally, turn off the old box.&lt;/p&gt;

&lt;h1&gt;
  
  
  Explanation
&lt;/h1&gt;

&lt;p&gt;With our files transferred, I've setup both DO VMs as 1GB / 25GB SSD / 2TB bandwidth instances. I've enabled SSH keys.&lt;/p&gt;

&lt;p&gt;While browsing on DO interface, I noticed they have their Cloud Firewalls, this is great - the interface is better than Hetzners Firewall interface (for dedicated servers). I started by creating one that is filtered by tags (production, cloudflare, blogging) - so this firewall will be applied to virtual machines with those tags.&lt;/p&gt;

&lt;p&gt;I want only SSH from my jump servers (OVH BHS VM or my home servers), HTTP/HTTPS only from Cloudflare, and deny the rest of the world for this instance. Because Cloud Firewalls are so simple, this is how the rules ended up looking:&lt;/p&gt;

&lt;p&gt;Outbound, I've allowed to all sources (for good or bad). I should look at tightening this up in the future, but for right now I'll let it be as-is. I want to make sure everything gets up for production first.&lt;/p&gt;

&lt;p&gt;I've enabled private networking on my two VMs for our blogs, this helps save on bandwidth count, and I've linked them together in same DC. Sweet.&lt;/p&gt;

&lt;p&gt;Now, for the SQL instance, I created a firewall called "int-production" - this will reject ALL communications on it's public IPv4 and IPv6 addresses, but can speak to the connected VMs that need access (eg. blogs). I've enabled private networking so I can connect to it direct over private network, yet again saving bandwidth (as it's metered on DigitalOcean).&lt;/p&gt;

&lt;p&gt;I setup master/slave replication, allowed the Firewall to accept from Hetzner IP, allowed outbound to Hetzner IP only as well. After 20 minutes, we were synced up and ready to motor. I flipped it over to master/master, and started slowly redirecting the domains over.&lt;/p&gt;

&lt;p&gt;15 minutes later, I was confident the hosts were identical, and shut off replication, turned off the Hetzner node. I ran over to my monitoring on UptimeRobot, and saw exactly what I was looking for - 100% uptime.&lt;/p&gt;

&lt;p&gt;But wait, all my error logs have CF's IP in them, before I had setup some rules on the host dedicated server, now that these are individual VMs, I need to re-configure them, I found this article + script for nginx that worked like a charm, and setup a crontab on it. I set it to once every week (Sunday, 11 PM GMT), which is more than sufficient for my needs.&lt;/p&gt;

&lt;p&gt;No data was lost, we were online the whole time, and this scales for my growing needs. My wife and I's blogs combined have hit well over 50,000 requests per day, and the old system didn't scale really well and would constantly drop legitimate connections, the energy and time lost debugging this outweighed the cost of staying at the old provider.&lt;/p&gt;

&lt;p&gt;Thanks to better flexibility with DigitalOcean, I've been able to add some same rules for all my VMs with tags, instead of how I used to - individually and replicate them across N virtual machines. Alert Policies for Memory, Disk, Inbound/Outbound bandwidth help.&lt;/p&gt;

&lt;h1&gt;
  
  
  Future ideas
&lt;/h1&gt;

&lt;p&gt;In the event of traffic spikes, and as my side project network grows, I'll spin up a DO load balancer, it's only $10 per month and seems very easy to use. Throwing my VMs for blogs, apps behind it. I'd love to get more oversight, possibly migrate my Grafana + Prometheus setup to DO, use internal networking to save on bandwidth.&lt;/p&gt;

&lt;p&gt;Let's see how it holds up. I'd love to be in a position to migrate all my production off my own colocated gear, and revert the colocated gear back to a lab. I'm going to keep testing DO for a few months, and hope to make a full switch. The price point is reasonable, the location is good (20ms or less from where I live),&lt;/p&gt;

&lt;p&gt;That's all for now! I'll keep updating on my experience with DO as I dive into more features. Maybe next I'll try their k8 services.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>article</category>
    </item>
  </channel>
</rss>
