<?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>DEV Community: Jim Hatcher</title>
    <description>The latest articles on DEV Community by Jim Hatcher (@jhatcher9999).</description>
    <link>https://dev.to/jhatcher9999</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%2F405016%2F1cd088be-6217-49c7-9691-c9d0c34d12d3.png</url>
      <title>DEV Community: Jim Hatcher</title>
      <link>https://dev.to/jhatcher9999</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jhatcher9999"/>
    <language>en</language>
    <item>
      <title>Repaving CockroachDB in AWS EC2</title>
      <dc:creator>Jim Hatcher</dc:creator>
      <pubDate>Tue, 25 Jul 2023 17:21:51 +0000</pubDate>
      <link>https://dev.to/jhatcher9999/repaving-crdb-in-aws-ec2-2k24</link>
      <guid>https://dev.to/jhatcher9999/repaving-crdb-in-aws-ec2-2k24</guid>
      <description>&lt;h1&gt;
  
  
  Repaving CockroachDB Nodes
&lt;/h1&gt;

&lt;p&gt;"Repaving" nodes refers to the replacement of the underlying Linux/Unix nodes that are running CockroachDB -- usually in a cloud deployment.  It is a procedure followed by some companies as a way to make sure that they're using the newest (and presumably most secure) versions of O/S images.  It is thought to be easier than applying O/S patches.  Because CockroachDB is a highly available and resilient distributed database, repaving nodes where CockroachDB is running is a relatively easy process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Approaches to repaving
&lt;/h3&gt;

&lt;p&gt;There are generally two approaches to repaving  CockroachDB nodes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;decommission nodes and add them back one-at-a-time&lt;/li&gt;
&lt;li&gt;stop each node, detach the block storage, re-attach the block storage to a newly created node and start up CockroachDB again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are pros/cons to each.  Let's examine in more detail.&lt;/p&gt;

&lt;h4&gt;
  
  
  Decommissioning
&lt;/h4&gt;

&lt;p&gt;Let's assume we have a five-node cluster and that we're using the default replication factor of three.  This means we will have a replica for every data range on each node.  We can decommission a node by running &lt;code&gt;cockroach node decommission &amp;lt;nodeid&amp;gt;&lt;/code&gt;.  When we do this, the node being decommissioned will start to move its replicas to other nodes within the cluster.  This process could take several hours, depending on how much data is being stored on each node.  During this time, data streaming will be occurring which means that we will increase CPU, memory, disk and network usage on the nodes involved (both the node being decommissioned and any nodes -- likely all the others -- where data replicas are being moved).  After the node has finished off-loading its replicas, we can add a new node.  This new node will be recognized by the cluster and it will start receiving replicas as the cluster re-balances again.  The nice thing about this approach is that the cluster never runs in an "under-replicated state."  We will always have three replicas of all the nodes.  This overall process could take a while as you move through the nodes one-by-one.&lt;/p&gt;

&lt;p&gt;It's worth noting that using this decommissioning method on a three-node cluster with RF=3 is not a good idea because there are no additional nodes to be used to accept new replicas.  If you decomm a node on this topology, the replicas will not be copied to other nodes.&lt;/p&gt;

&lt;h4&gt;
  
  
  Stop and replace
&lt;/h4&gt;

&lt;p&gt;The other common approach is to stop nodes and switch out the underlying hardware.  One node at a time, you would stop the cockroach process (i.e., &lt;code&gt;kill -9 &amp;lt;cockroach pid&amp;gt;&lt;/code&gt;).  When this node is stopped, the CockroachDB cluster will consider it to be in a "suspect state" and any of the ranges for which replicas are located on that node will be considered "under-replicated."  An under-replicated state implies that a quorum is still maintained, so we can still read and write to the range.  Cockroach will do nothing to remedy the under-replicated state for a certain period of time called the "time till store dead" window (the setting is called &lt;code&gt;server.time_until_store_dead&lt;/code&gt; and the default value is 5 minutes).  If the node comes back up during this "time till dead" window, then CockroachDB will put the node back into a healthy state by catching up the range to a fully consistent state by sending RAFT logs from the leaseholder(s).&lt;/p&gt;

&lt;p&gt;So, the idea is that we have the "time till dead" timeframe in order to detach the data device from the host, replace the host, re-attach the disk and restart the CockroachDB process.  It is a common practice to temporarily extend the "time till dead" window to a more manageable timeframe like 15 or 30 mins.  The advantage of this approach is that no streaming of data replicas has to occur, so it's much quicker to cycle through the cluster nodes.  The potential risk of this approach is that during the time where some ranges are in an under-replicated state, if you were to lose another node in the cluster (due to an unexpected outage maybe), then you could potentially lose quorum on some ranges and therefore lost the ability to read and write to parts of the database.&lt;/p&gt;

&lt;p&gt;This risk, however, can be mitigated.  One mitigating factor is that this process is usually automated and therefore runs quickly.  Another step that can be taken to mitigate this risk is to adjust the replication factor of your critical data to use a replication factor (RF) of 5, rather than the default of 3.  When using RF=5, a quorum is 3 out of 5 (rather than 2 out of 3).  In our repaving scenario, some ranges will be in an under-replicated state of 4 out of 5; and to move into an unavailable state, we'd have to lose 2 more replicas which is unlikely.&lt;/p&gt;

&lt;p&gt;My personal preference is to run in Production with RF=5 and to use the stop/detach/reattach method -- hopefully in a fully-tested and automated manner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example of the "stop/detach/reattach" method in EC2
&lt;/h3&gt;

&lt;p&gt;To exemplify this method, let's spin up four EC2 instances to make a three-node CockroachDB cluster in AWS EC2.  You can follow the general documentation for deploying CockroachDB in AWS EC2 -- docs here (&lt;a href="https://www.cockroachlabs.com/docs/dev/deploy-cockroachdb-on-aws.html"&gt;https://www.cockroachlabs.com/docs/dev/deploy-cockroachdb-on-aws.html&lt;/a&gt;) -- with a few slight deviations.  One deviation we'll make is to attach an additional EBS volume to each node to be used to store all data/logs.  We can still use the normally attached EBS volume to house the CockroachDB executable and TLS certificates.&lt;/p&gt;

&lt;p&gt;I suggest you spin up four EC2 instances and for each one, record the private IP, the public IP, the public host name, the private host name, and the identifiers of the volumes of each of the drives.  All of this information is available from the AWS control plane.  For instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Node name (for all): hatcher-repave-test
Region: us-east-2a

N1
3.145.68.78
ec2-3-145-68-78.us-east-2.compute.amazonaws.com
172.31.11.189
ip-172-31-11-189.us-east-2.compute.internal
vol-0821f7fb596be5f25 (100GB)
vol-07c96d7b41612d9b7 (8GB)

N2
3.139.85.111
ec2-3-139-85-111.us-east-2.compute.internal
172.31.8.166
ip-172-31-8-166.us-east-2.compute.internal
vol-03da8d9790bc8e3ca (100GB)
vol-00a17f674809e3f40 (8GB)

N3
3.145.180.186
ec2-3-145-180-186.us-east-2.compute.internal
172.31.15.100
ip-172-31-15-100.us-east-2.compute.internal
vol-0fda8d9790bc8e2ef (100GB)
vol-0e3b89cebb10f84df (8GB)

N4 (Note: the fourth node won't need a separate data node)
3.138.181.122
ec2-3-138-181-122.us-east-2.compute.internal
172.31.13.143
ip-172-31-13-143.us-east-2.compute.internal
vol-0f2c700f0d9f9749b (100GB)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure that the EC2 instances have a network security group that allows inbound calls on TCP/26257, TCP/8080, and TCP/22 to allow SSH-ing into each node. &lt;/p&gt;

&lt;p&gt;Then, for each node, we need to run some Linux setup in order to make sure that our data drive is available for use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#Check to see the name of the device and whether it has a file system installed&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;lsblk &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;#it shouldn't have a file system at first, so let's make a file system (assuming the device is called /dev/xvdb)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mkfs &lt;span class="nt"&gt;-t&lt;/span&gt; xfs /dev/xvdb

&lt;span class="c"&gt;#verify there is a file system now&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;lsblk &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;#make the /data directory so we can mount to it&lt;/span&gt;
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /data

&lt;span class="c"&gt;#manually mount the volume&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount /dev/xvdb /data

&lt;span class="c"&gt;#backup the existing fstab file&lt;/span&gt;
&lt;span class="nb"&gt;sudo cp&lt;/span&gt; /etc/fstab /etc/fstab.orig

&lt;span class="c"&gt;#get the UUID of the volume&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;blkid

&lt;span class="c"&gt;#using the UUID of the volume captured in the previous step, edit the fstab so the volume will auto-mount again after subsequent reboots&lt;/span&gt;
&lt;span class="c"&gt;# add: UUID=b26331da-9d38-4b0a-af09-3c3808b8313e     /data       xfs    defaults,nofail   0   2&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/fstab

&lt;span class="c"&gt;#verify that the fstab edit was done correctly by unmounting and remounting the volume&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;umount /data
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-a&lt;/span&gt;

&lt;span class="c"&gt;#verify that you see the drive&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;

&lt;span class="c"&gt;#change the owner to ec2-user&lt;/span&gt;
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;ec2-user /data

&lt;span class="c"&gt;#if you need to change the permission of the drive, do that here&lt;/span&gt;
&lt;span class="c"&gt;#sudo chmod 400 /data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have our EBS-based data volume available to be used, we need to create a node cert for each node.  We can follow the instructions from the docs to create the CA (certificate authority) cert/key pair and the root user's client cert/key pair; we only need to create once of these for the whole cluster.  But, we want to create a separate node cert for each of the nodes.  The easiest way to do this is to create the CA root/key + client root cert/key and copy those files to each of the nodes.  In practice, you only need to have the ca.crt on the nodes and the ca.key should be kept in a safe place off the cluster; also in practice, you only need to copy the client root user cert/key files to locations where you will be authentication to the cluster.  But, for this exercise, I recommend copying all four files to each of the nodes.&lt;/p&gt;

&lt;p&gt;You'll need to copy the &lt;code&gt;cockroach&lt;/code&gt; executable to each of the nodes before running the next step -- this is described in the main doc.&lt;/p&gt;

&lt;p&gt;Then, run commands like the following on each node to create the node cert/key pair files.  If you're planning on exposing the nodes via an AWS-based load balancer, you can include the LB's IP and its hostname (if you made one) to each of these commands, too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# node 1 command&lt;/span&gt;
cockroach cert create-node &lt;span class="se"&gt;\&lt;/span&gt;
172.31.11.189 &lt;span class="se"&gt;\&lt;/span&gt;
3.145.68.78 &lt;span class="se"&gt;\&lt;/span&gt;
ip-172-31-11-189.us-east-2.compute.internal &lt;span class="se"&gt;\&lt;/span&gt;
ec2-3-145-68-78.us-east-2.compute.amazonaws.com &lt;span class="se"&gt;\&lt;/span&gt;
localhost &lt;span class="se"&gt;\&lt;/span&gt;
127.0.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--certs-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certs &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--ca-key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-safe-directory/ca.key

&lt;span class="c"&gt;# node 2 command&lt;/span&gt;
cockroach cert create-node &lt;span class="se"&gt;\&lt;/span&gt;
172.31.8.166 &lt;span class="se"&gt;\&lt;/span&gt;
3.139.85.111 &lt;span class="se"&gt;\&lt;/span&gt;
ip-172-31-8-166.us-east-2.compute.internal &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;.us-east-2.compute.internal &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;.us-east-2.compute.amazonaws.com &lt;span class="se"&gt;\&lt;/span&gt;
localhost &lt;span class="se"&gt;\&lt;/span&gt;
127.0.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--certs-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certs &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--ca-key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-safe-directory/ca.key

&lt;span class="c"&gt;# node 3 command&lt;/span&gt;
cockroach cert create-node &lt;span class="se"&gt;\&lt;/span&gt;
172.31.15.100 &lt;span class="se"&gt;\&lt;/span&gt;
3.145.180.186 &lt;span class="se"&gt;\&lt;/span&gt;
ip-172-31-15-100.us-east-2.compute.internal &lt;span class="se"&gt;\&lt;/span&gt;
ec2-3-145-180-186.us-east-2.compute.amazonaws.com &lt;span class="se"&gt;\&lt;/span&gt;
localhost &lt;span class="se"&gt;\&lt;/span&gt;
127.0.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--certs-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certs &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--ca-key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-safe-directory/ca.key

&lt;span class="c"&gt;# node 4 command&lt;/span&gt;
cockroach cert create-node &lt;span class="se"&gt;\&lt;/span&gt;
172.31.13.143 &lt;span class="se"&gt;\&lt;/span&gt;
3.138.181.122 &lt;span class="se"&gt;\&lt;/span&gt;
ip-172-31-13-143.us-east-2.compute.internal &lt;span class="se"&gt;\&lt;/span&gt;
ec2-3-138-181-122.us-east-2.compute.amazonaws.com &lt;span class="se"&gt;\&lt;/span&gt;
localhost &lt;span class="se"&gt;\&lt;/span&gt;
127.0.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--certs-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certs &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--ca-key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-safe-directory/ca.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're going to start a three-node CockroachDB cluster (even though we have four nodes -- we'll use that fourth node later).&lt;/p&gt;

&lt;p&gt;Start CockroachDB on nodes 1-3 by running the &lt;code&gt;start&lt;/code&gt; command.  Notice that we're explicitly specifying the &lt;code&gt;--store=/data&lt;/code&gt; directory so that CockroachDB will store all data and logs on our separately-mounted data volume (this is slightly different than the doc instructions).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#node 1 command&lt;/span&gt;
cockroach start &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--certs-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certs &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--advertise-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.31.11.189 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.31.11.189,172.31.8.166 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.25 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--max-sql-memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.25 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/data &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--background&lt;/span&gt;

&lt;span class="c"&gt;#node 2 command&lt;/span&gt;
cockroach start &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--certs-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certs &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--advertise-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.31.8.166 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.31.11.189,172.31.8.166 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.25 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--max-sql-memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.25 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/data &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--background&lt;/span&gt;

&lt;span class="c"&gt;#node 3 command&lt;/span&gt;
cockroach start &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--certs-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certs &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--advertise-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.31.15.100 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.31.11.189,172.31.8.166 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.25 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--max-sql-memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.25 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/data &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--background&lt;/span&gt;

&lt;span class="c"&gt;#node 4 command (don't run yet)&lt;/span&gt;
cockroach start &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--certs-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certs &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--advertise-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.31.13.143 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.31.11.189,172.31.8.166 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.25 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--max-sql-memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.25 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/data &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--background&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The docs will have you run an &lt;code&gt;init&lt;/code&gt; command (from any one of the nodes) and then your CockroachDB cluster will be up and running.  We could at this point initialize one of the canned CockroachDB workloads in order to put some data into the cluster.  Or we could create a simple database and table and manually insert a few records.&lt;/p&gt;

&lt;p&gt;Now that we are in a steady state, let's start the repaving part.&lt;/p&gt;

&lt;p&gt;(1) Extend the "time till dead" window from the default of 5 minutes to 30 minutes.  Execute this command from a SQL window (i.e., &lt;code&gt;cockroach sql --certs-dir=certs --host=172.31.13.143&lt;/code&gt;)&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;set&lt;/span&gt; &lt;span class="k"&gt;cluster&lt;/span&gt; &lt;span class="n"&gt;setting&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time_until_store_dead&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="s1"&gt;'30m0s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;show&lt;/span&gt; &lt;span class="k"&gt;cluster&lt;/span&gt; &lt;span class="n"&gt;setting&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time_until_store_dead&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(2) Verify that all the nodes are healthy and running.  You can also verify that there are currently no under-replicated ranges via a SQL query (below) or in the DB Console's main page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cockroach node status &lt;span class="nt"&gt;--certs-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certs &lt;span class="nt"&gt;--host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.31.13.143
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Under replicated (should be zero)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'ranges.underreplicated'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="n"&gt;INT8&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ranges_underreplicated&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;crdb_internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kv_store_status&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;crdb_internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gossip_liveness&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;node_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;node_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decommissioning&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(3) Stop N3&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#kill all the processes running CRDB&lt;/span&gt;
killall &lt;span class="nt"&gt;-9&lt;/span&gt; cockroach

&lt;span class="c"&gt;#verify it's not running anymore&lt;/span&gt;
ps aux | &lt;span class="nb"&gt;grep &lt;/span&gt;cockroach
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(4) Unmount the data drive on N3&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;umount /data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(5) Detach the volume from N3 by using the AWS Control Plane of the AWS CLI.  Having the volume identifiers is very helpful for this step and the next step.&lt;/p&gt;

&lt;p&gt;(6) Attach the volume to N4 by using the AWS Control Plane of the AWS CLI.&lt;/p&gt;

&lt;p&gt;(7) Mount the volume in N4.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#Check to see the name of the device and verify that it has a file system installed -- we expect it to already have a file system&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;lsblk &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;#make the /data directory so we can mount to it&lt;/span&gt;
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /data

&lt;span class="c"&gt;#manually mount the volume&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount /dev/xvdb /data

&lt;span class="c"&gt;#backup the existing fstab file&lt;/span&gt;
&lt;span class="nb"&gt;sudo cp&lt;/span&gt; /etc/fstab /etc/fstab.orig

&lt;span class="c"&gt;#get the UUID of the volume&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;blkid

&lt;span class="c"&gt;#edit the fstab so we will get the mount again after rebooting&lt;/span&gt;
&lt;span class="c"&gt;# add: UUID=b26331da-9d38-4b0a-af09-3c3808b8313e     /data       xfs    defaults,nofail   0   2&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/fstab

&lt;span class="c"&gt;#verify that the fstab edit was done correctly by unmounting and remounting the volume&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;umount /data
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-a&lt;/span&gt;

&lt;span class="c"&gt;#verify that you see the drive&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;

&lt;span class="c"&gt;#verify that there is data in the volume&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-alh&lt;/span&gt; /data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(8) Start N4&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cockroach start &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--certs-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certs &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--advertise-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.31.13.143 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.31.11.189,172.31.8.166 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.25 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--max-sql-memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.25 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/data &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--background&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NOTE: CockroachDB does not require that the node being replaced and the new node have the same IP.  In this example, they have different IPs.  The data associated with the node in the data drive is not associated with the node IP.&lt;/p&gt;

&lt;p&gt;(9) Verify that all nodes are up, data is available, and we have no under-replicated ranges.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cockroach node status &lt;span class="nt"&gt;--certs-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certs &lt;span class="nt"&gt;--host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.31.13.143
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Under replicated (should be zero)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'ranges.underreplicated'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="n"&gt;INT8&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ranges_underreplicated&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;crdb_internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kv_store_status&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;crdb_internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gossip_liveness&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;node_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;node_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decommissioning&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(10) Reset the "time till dead" setting.&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;set&lt;/span&gt; &lt;span class="k"&gt;cluster&lt;/span&gt; &lt;span class="n"&gt;setting&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time_until_store_dead&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(11) You can delete N3 and the main EBS volume (not the data volume) associated with N3 via the AWS control plane.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;Repaving nodes is a very automat-able activity that can be used to satisfy security requirements -- especially in security-sensitive, cloud-based organizations.  CockroachDB tolerates this type of operation well because of its distributed, resilient, and available design.&lt;/p&gt;

</description>
      <category>cockroachdb</category>
      <category>aws</category>
      <category>repaving</category>
    </item>
    <item>
      <title>Jim's Guide to CockroachDB Naming Standards</title>
      <dc:creator>Jim Hatcher</dc:creator>
      <pubDate>Thu, 30 Mar 2023 14:31:34 +0000</pubDate>
      <link>https://dev.to/jhatcher9999/jims-guide-to-cockroachdb-naming-standards-b5</link>
      <guid>https://dev.to/jhatcher9999/jims-guide-to-cockroachdb-naming-standards-b5</guid>
      <description>&lt;p&gt;I don't think CockroachDB has an official guide for naming standards, so I thought I would put out a post about what I think are the generally agreed-upon standards and claim them as my own. :)&lt;/p&gt;

&lt;p&gt;When I say "naming standards," I'm referring to the general patterns for how we should name databases, tables, columns, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Case is Handled in Identifiers
&lt;/h2&gt;

&lt;p&gt;Before I get into that, let's take a look at how case gets dealt with in CockroachDB.&lt;/p&gt;

&lt;p&gt;If we create a table that has a camel-cased name (i.e., each word is "separated" by a capital letter), it might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@localhost:26257/defaultdb&amp;gt; CREATE TABLE CamelCaseTable
(
  myKeyValue int PRIMARY KEY,
  myField varchar
);

root@localhost:26257/defaultdb&amp;gt; SELECT * FROM CamelCaseTable;
  mykeyvalue | myfield
-------------+----------
(0 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This all seems good so far.&lt;/p&gt;

&lt;p&gt;However, if you ask CRDB to show you the table you created, you'll notice that the table definition that was stored does not retain the case specified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@localhost:26257/defaultdb&amp;gt; SELECT create_statement FROM [SHOW CREATE TABLE CamelCaseTable];
                         create_statement
-------------------------------------------------------------------
  CREATE TABLE public.camelcasetable (
      mykeyvalue INT8 NOT NULL,
      myfield VARCHAR NULL,
      CONSTRAINT camelcasetable_pkey PRIMARY KEY (mykeyvalue ASC)
  )
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, if you try to SELECT from the table using a name that is double-quoted, CRDB will report that the table doesn't exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@localhost:26257/defaultdb&amp;gt; SELECT * FROM "CamelCaseTable";
ERROR: relation "CamelCaseTable" does not exist
SQLSTATE: 42P01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is strange!  What's going on here?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Any time you specify an identifier name in DDL or DML, CRDB will convert that name to lowercase.  If you want to override this behavior, you must specify the identifier in double quotes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's an example of using double quotes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE "CamelCaseTable2" ( "myKeyValue" int PRIMARY KEY, "myField" varchar );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, note that when you do this, you have to also use the double quotes any time you reference the name.&lt;/p&gt;

&lt;p&gt;For instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@localhost:26257/defaultdb&amp;gt; SELECT * FROM CamelCaseTable2;
ERROR: relation "camelcasetable2" does not exist
SQLSTATE: 42P01
root@localhost:26257/defaultdb&amp;gt; SELECT myKeyValue, myField FROM "CamelCaseTable2";
ERROR: column "mykeyvalue" does not exist
SQLSTATE: 42703
root@localhost:26257/defaultdb&amp;gt; SELECT "myKeyValue", "myField" FROM "CamelCaseTable2";
  myKeyValue | myField
-------------+----------
(0 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Snake Case
&lt;/h2&gt;

&lt;p&gt;Because of this "automatic lowercase-ification" of all identifiers, it is much simpler and easier to specify all your names in lower case.  For separation of words, use an underscore (btw, a dash is not valid in CRDB identifiers).  This is called "snake case."&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@localhost:26257/defaultdb&amp;gt; CREATE TABLE snake_case_table
(
  my_key_value int PRIMARY KEY,
  my_field varchar
);

root@localhost:26257/defaultdb&amp;gt; SELECT * FROM snake_case_table;
  my_key_value | my_field
---------------+-----------
(0 rows)

root@localhost:26257/defaultdb&amp;gt; SELECT * FROM SNAKE_CASE_TABLE; -- if you want to use caps here, go for it
  my_key_value | my_field
---------------+-----------
(0 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Naming Standards
&lt;/h2&gt;

&lt;p&gt;Because of the handling of case in identifiers, it is much simpler and easier to use snake case for naming things in CRDB.  i_hope_you_agree_that_this_is_the_way_to_go&lt;/p&gt;

</description>
      <category>cockroachdb</category>
      <category>naming</category>
      <category>standards</category>
    </item>
    <item>
      <title>Making Queries Sargable</title>
      <dc:creator>Jim Hatcher</dc:creator>
      <pubDate>Mon, 27 Mar 2023 14:05:31 +0000</pubDate>
      <link>https://dev.to/jhatcher9999/making-queries-sargable-2e4c</link>
      <guid>https://dev.to/jhatcher9999/making-queries-sargable-2e4c</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When tuning database queries, a common technique is to add indexes that are tailor made to improve the performance of that query.  However, sometimes, the query you are analyzing is written in such a way that it will not take advantage of indexes, even if they are in place.&lt;/p&gt;

&lt;p&gt;The term used to describe whether a query is written in a way where it can take advantage of indexes is "sargable" -- it is an abbreviation for "search ARGument ABLE" (&lt;a href="https://en.wikipedia.org/wiki/Sargable"&gt;definition&lt;/a&gt;).  That is, a query is said to be sargable if it can leverage an index.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;An example should help us here.  Suppose we have an &lt;code&gt;event_log&lt;/code&gt; table which stores millions of records and we want to be able to SELECT data from this log table based on a few simple predicates.&lt;/p&gt;

&lt;p&gt;Let's create a table in CockroachDB with 10 million rows and some randomized data like this:&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;DATABASE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;sarg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;sarg&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;TABLE&lt;/span&gt; &lt;span class="n"&gt;event_log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log_ts&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;log_ts&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;generate_series&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1668003200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1678003200&lt;/span&gt;&lt;span class="p"&gt;,&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;g&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- 10,000,000 records&lt;/span&gt;
&lt;span class="c1"&gt;-- between 2022-11-09 14:13:20+00 (i.e., 1668003200)&lt;/span&gt;
&lt;span class="c1"&gt;-- and 2023-03-05 08:00:00 (i.e., 1678003200)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's create a decidedly non-sargable query to find log entries within a certain day and containing a certain log message:&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;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;event_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%abc%'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_ts&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2023-02-01'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      id     |             message              |       log_ts
-------------+----------------------------------+----------------------
  1675209983 | 813cdbec6ab76abc470cc19e42975692 | 2023-02-01 00:06:23
  1675210237 | 41963851207539abc612869eb5cf2301 | 2023-02-01 00:10:37(0 rows)
-- rows omitted for brevity --
(570 rows)

Time: 10.466s total (execution 10.466s / network 0.000s)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query takes 10 seconds to run.  Not good.  Let's look at the query plan:&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;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;event_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%abc%'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_ts&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2023-02-01'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                                            info
---------------------------------------------------------------------------------------------
  distribution: full
  vectorized: true

  • filter
  │ estimated row count: 1,111,111
  │ filter: (message LIKE '%abc%') AND (log_ts::DATE = '2023-02-01')
  │
  └── • scan
        estimated row count: 10,000,001 (100% of the table; stats collected 23 minutes ago)
        table: event_log@event_log_pkey
        spans: FULL SCAN
(11 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're committing the cardinal sin on non-scalable database performance -- we're doing a full scan of the table and then we're filtering for our predicate in memory.  This means that as the size of the table grows, the query will get slower and slower because we have to read every record of the table every time the query runs.&lt;/p&gt;

&lt;p&gt;Also, it's worth noting that our explain plan didn't contain any index hints (further evidence that our query is to blame here).&lt;/p&gt;

&lt;p&gt;Just to further show that we've got a non-sargable query, let's add indexes to the &lt;code&gt;message&lt;/code&gt; and &lt;code&gt;log_ts&lt;/code&gt; fields to see if we can improve the execution.&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;INDEX&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;event_log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;message&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="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;event_log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;log_ts&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After adding these indexes, the query plan looks exactly the same.  We have a non-sargable query on our hands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Non-sargable patterns
&lt;/h2&gt;

&lt;p&gt;There are two classic anti-patterns being used by this query contributing to it being non-sargable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;applying functions to the left side of the predicate&lt;/li&gt;
&lt;li&gt;using a leading wildcard in a LIKE expression&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fixing functions on the left-side of the predicate
&lt;/h2&gt;

&lt;p&gt;Let's see how we can change our query by addressing the first anti-pattern.  In our query, we're trying to limit the results to only give us log entries from Feb 1, 2023.  Logically, our predicate gives us the right results; but anytime you run a query with a function on the left-hand side of the predicate (i.e., the field itself), you're limiting the SQL optimizer's ability to leverage indexes.  To fix this, let's employ some different logic that works without applying functions to the left-hand side of the predicate.&lt;/p&gt;

&lt;p&gt;Instead of:&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;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_ts&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2023-02-01'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;let's do:&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="n"&gt;log_ts&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2023-02-01'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;log_ts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2023-02-02'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this edited version of the query, we get our results back in ~800ms instead of 10s+.&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;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;event_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%abc%'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;log_ts&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2023-02-01'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;log_ts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2023-02-02'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      id     |             message              |       log_ts
-------------+----------------------------------+----------------------
-- rows omitted for brevity --
  1675295883 | 508472e48f1d4121d56ba36cfdabc147 | 2023-02-01 23:58:03
  1675295922 | 179a0abc7e6b8653f89be8cf875bc789 | 2023-02-01 23:58:42
(570 rows)

Time: 836ms total (execution 834ms / network 2ms)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's look at the query plan to see what's changed:&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;EXPLAIN&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;event_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%abc%'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;log_ts&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2023-02-01'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;log_ts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2023-02-02'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--------------------------------------------------------------------------------------------------------------------------------------
  distribution: local
  vectorized: true

  • filter
  │ estimated row count: 30,670
  │ filter: message LIKE '%abc%'
  │
  └── • index join
      │ estimated row count: 92,011
      │ table: event_log@event_log_pkey
      │
      └── • scan
            estimated row count: 92,011 (0.92% of the table; stats collected 19 minutes ago; using stats forecast for 7 minutes ago)
            table: event_log@event_log_log_ts_idx
            spans: [/'2023-02-01 00:00:00' - /'2023-02-01 23:59:59.999999']

  index recommendations: 1
  1. type: index replacement
     SQL commands: CREATE INDEX ON event_log (log_ts) STORING (message); DROP INDEX event_log@event_log_log_ts_idx;
(19 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is better!  We're now able to leverage the &lt;code&gt;event_log_log_ts_idx&lt;/code&gt; index.  Our plan uses this first index to filter the number of records to process, then it joins back to the primary index (i.e, the table itself) and then it does the final filter on the &lt;code&gt;message&lt;/code&gt; field.  By the time this final filter is applied, we've already narrowed down the possible result set to 30k records, so this isn't terrible, but let's see if we can improve further.&lt;/p&gt;

&lt;p&gt;NOTE: there are some further indexing recommendations in that last query plan which we could employ to help our query performance more, but they're not related to sargability, so I'm not going to follow that bunny trail in this blog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing leading wildcards
&lt;/h2&gt;

&lt;p&gt;The second non-sargable mistake we've made is to use a leading wildcard in a LIKE expression (i.e., &lt;code&gt;message LIKE '%abc%'&lt;/code&gt;).  If we had only used a wildcard at the end of the expression (i.e., &lt;code&gt;message LIKE 'abc%'&lt;/code&gt;), then the query engine could still have leveraged the index (try looking at the query plan for this query and see for yourself); but when you put a wildcard at the beginning of the expression, the index can't be leveraged because we can't seek to specific ranges of the ordered index.&lt;/p&gt;

&lt;p&gt;This is a classically hard problem to overcome in relational databases (it's exactly the kind of use cases that search engines like Solr and ElasticSearch are built to solve).  If the requirements for our query are to look in the middle of a text string, then we can't change the query to not having a leading wildcard.  We have a few choices then.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ngram indexes
&lt;/h3&gt;

&lt;p&gt;One choice is to do what we've done above and use indexes on other parts of our predicate to limit the result set so that the impact of the actual string filter is minimized.  That would be a boring solution for a blog!  So, let's remove that option by getting rid of the date/time filter in our query.&lt;/p&gt;

&lt;p&gt;If we only filter by the message field, we're back to our full table scan (and our 10sec performance).&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;EXPLAIN&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;event_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%abc%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--------------------------------------------------------------------------------------------------------------------------------------
  distribution: full
  vectorized: true

  • filter
  │ estimated row count: 3,333,334
  │ filter: message LIKE '%abc%'
  │
  └── • scan
        estimated row count: 10,000,001 (100% of the table; stats collected 30 minutes ago; using stats forecast for 18 minutes ago)
        table: event_log@event_log_pkey
        spans: FULL SCAN
(11 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next option that we have to deal with this is to leverage an n-gram index.  An n-gram is a tokenization of a piece of text into "chunks" that are of length n.  For example, if we tokenize the phrase "hello world" into tokens of size 3, we end up with the following values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"hel"&lt;/li&gt;
&lt;li&gt;"ell"&lt;/li&gt;
&lt;li&gt;"llo"&lt;/li&gt;
&lt;li&gt;"lo "&lt;/li&gt;
&lt;li&gt;"o w"&lt;/li&gt;
&lt;li&gt;" wo"&lt;/li&gt;
&lt;li&gt;"wor"&lt;/li&gt;
&lt;li&gt;"orl"&lt;/li&gt;
&lt;li&gt;"rld"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CockroachDB has a feature called &lt;a href="https://www.cockroachlabs.com/docs/stable/trigram-indexes.html"&gt;trigram indexes&lt;/a&gt; which leverages this tokenization technique.  At write time, when the index is being built, the values are broken down into 3-grams (i.e., trigrams) and these multiple values are stored.  Then, at query-time, the query optimizer is able to do a seek against these values instead of having to scan the table.  There is a trade-off at play here: we're doing more work at write time &amp;amp; storing more data in our index (potentially a lot more); but we're reducing the work that has to be done at query time.&lt;/p&gt;

&lt;p&gt;Let's create a trigram index on our &lt;code&gt;message&lt;/code&gt; field and see how our performance is impacted:&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;INDEX&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;event_log&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;message&lt;/span&gt; &lt;span class="n"&gt;gin_trgm_ops&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;event_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%abc%'&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;info
--------------------------------------------------------------------------------------------------------------------------------------------------------------
  distribution: local
  vectorized: true

  • render
  │
  └── • limit
      │ count: 500
      │
      └── • filter
          │ estimated row count: 3,333,334
          │ filter: message LIKE '%abc%'
          │
          └── • index join
              │ estimated row count: 1,111,111
              │ table: event_log@event_log_pkey
              │
              └── • scan
                    estimated row count: 167 - 1,111,112 (11% of the table; stats collected 1 minute ago; using stats forecast for 28 minutes in the future)
                    table: event_log@event_log_message_idx1
                    spans: 1 span
(20 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's run the query and see how long it takes:&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;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;event_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%abc%'&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Time: 43ms total (execution 34ms / network 9ms)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty good!&lt;/p&gt;

&lt;h3&gt;
  
  
  Computed Columns
&lt;/h3&gt;

&lt;p&gt;Another approach that we could use to handle this situation is to add a computed column to our table which calculates whether our keyword has been seen.&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;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;event_log&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;contains_abc&lt;/span&gt; &lt;span class="nb"&gt;boolean&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%abc%'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;STORED&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="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;event_log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;contains_abc&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log_ts&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;event_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;contains_abc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Time: 23ms total (execution 16ms / network 7ms)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This seems to be even a little faster which makes sense since the work done at read time is very minimal (even compared to the trigram approach).  Conceptually, it's a similar approach in that we're doing some extra work at write time in order to speed things up at read time.  It's a little more precise (i.e., it stores less data), but it's less flexible (i.e., you can't change the string you're searching on without changing the computed column).&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;We've identified a few red flags to watch out for in queries that will render them non-sargable.  And, we've looked at ways to address specific instances of this problem.&lt;/p&gt;

</description>
      <category>cockroachhdb</category>
      <category>querytuning</category>
      <category>sargable</category>
    </item>
    <item>
      <title>Efficiently deleting data</title>
      <dc:creator>Jim Hatcher</dc:creator>
      <pubDate>Wed, 15 Mar 2023 01:29:03 +0000</pubDate>
      <link>https://dev.to/jhatcher9999/efficiently-deleting-data-3el3</link>
      <guid>https://dev.to/jhatcher9999/efficiently-deleting-data-3el3</guid>
      <description>&lt;p&gt;When I delete data in CockroachDB (CRDB), the delete is slow, and how can I speed it up?&lt;/p&gt;

&lt;p&gt;This is a topic that comes up occasionally, and it's not super intuitive, so I think it would be worth explaining in more detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;To explore this, let's create some data we can play with.&lt;/p&gt;

&lt;p&gt;First let's create a database and then configure the database to only wait 60 seconds after deleting today to actually delete the data out of the storage layer in CRDB (this will be helpful if we need to delete the data multiple times and run everything again)&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;DATABASE&lt;/span&gt; &lt;span class="n"&gt;deletes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;deletes&lt;/span&gt; &lt;span class="n"&gt;CONFIGURE&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttlseconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;deletes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, let's create a table.  I'm going to create a table with a few columns of various cardinality and 100 million rows.  There is a helpful function in CRDB called &lt;code&gt;generate_series&lt;/code&gt; which is great for this kind of data creation:&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;TABLE&lt;/span&gt; &lt;span class="n"&gt;deletes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_items&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_date&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;77777&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;1000000&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;11111&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;order_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;generate_series&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1578003200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1678003200&lt;/span&gt;&lt;span class="p"&gt;,&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;g&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- 100,000,000 records&lt;/span&gt;
&lt;span class="c1"&gt;--  between 2020-01-02 22:13:20 (i.e., 1578003200)&lt;/span&gt;
&lt;span class="c1"&gt;--  and 2023-03-05 08:00:00 (i.e., 1678003200)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm leveraging a few handy techniques here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I'm using the &lt;code&gt;CREATE TABLE AS&lt;/code&gt; (or &lt;a href="https://www.cockroachlabs.com/docs/stable/create-table-as.html"&gt;CTAS&lt;/a&gt; as we call it) to generate a new table.  This is much faster in CRDB than creating the table first and then doing &lt;code&gt;INSERT INTO ... SELECT&lt;/code&gt; because it bypasses all the consistency-checking algorithms.&lt;/li&gt;
&lt;li&gt;I'm using the &lt;code&gt;g.i&lt;/code&gt; value as my "seed value" and using it directly as my id field&lt;/li&gt;
&lt;li&gt;for some of the other fields, I'm using the modulo operator (i.e., %) against the g.i feild to create some low-cardinality values.&lt;/li&gt;
&lt;li&gt;I'm generating values in the range between 1578003200 and 1678003200 so that I can easily convert these values into timestamps.&lt;/li&gt;
&lt;li&gt;Tip: To figure out which values you want to use as your start and end values, run &lt;code&gt;SELECT '2023-01-15 2:00 PM'::TIMESTAMP::INT;&lt;/code&gt; with your desired date values to figure out the values to specify.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once we have our table created, let's create indexes on our other fields in order to mimic what a realistic table might do:&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;INDEX&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;customer_id&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="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;order_id&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="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;product_id&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="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;order_date&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get a sense of the cardinality of our fields, we can run a query:&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;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rec_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cust_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ord_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;prd_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;order_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;date_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  rec_count | cust_count | ord_count | prd_count | date_count
------------+------------+-----------+-----------+-------------
  100000001 |      77777 |   1000000 |     11111 |  100000001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Looking at explain plans
&lt;/h2&gt;

&lt;p&gt;Now that we have some data to play with, we can start to understand what happens when we delete data in this table.&lt;/p&gt;

&lt;p&gt;Let's start by selecting a single record by the PK value:&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;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1600000000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="n"&gt;id&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;     &lt;span class="n"&gt;order_date&lt;/span&gt;
&lt;span class="c1"&gt;-------------+-------------+----------+------------+----------------------&lt;/span&gt;
  &lt;span class="mi"&gt;1600000000&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="mi"&gt;49333&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;        &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="mi"&gt;4889&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's prepend "EXPLAIN" to our query to look at the execution plan.&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;EXPLAIN&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1600000000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                                                        &lt;span class="n"&gt;info&lt;/span&gt;
&lt;span class="c1"&gt;---------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="n"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt;
  &lt;span class="n"&gt;vectorized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;

  &lt;span class="err"&gt;•&lt;/span&gt; &lt;span class="n"&gt;scan&lt;/span&gt;
    &lt;span class="n"&gt;estimated&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="n"&gt;forecast&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;order_items_pkey&lt;/span&gt;
    &lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1600000000&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1600000000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is as simple of an execution plan as you can get.  It's doing a scan of 1 span in the primary index (i.e., the table itself).&lt;/p&gt;

&lt;p&gt;Now, let's query by the order_date field which is also a very high-cardinality value with an index on it:&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;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;order_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1600000000&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;id&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;     &lt;span class="n"&gt;order_date&lt;/span&gt;
&lt;span class="c1"&gt;-------------+-------------+----------+------------+----------------------&lt;/span&gt;
  &lt;span class="mi"&gt;1600000000&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="mi"&gt;49333&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;        &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="mi"&gt;4889&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We get the exact same data back.  Let's look at the explain plan:&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;EXPLAIN&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;order_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1600000000&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                                                                            &lt;span class="n"&gt;info&lt;/span&gt;
&lt;span class="c1"&gt;-------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="n"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt;
  &lt;span class="n"&gt;vectorized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;

  &lt;span class="err"&gt;•&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt;
  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="n"&gt;estimated&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;order_items_pkey&lt;/span&gt;
  &lt;span class="err"&gt;│&lt;/span&gt;
  &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="err"&gt;•&lt;/span&gt; &lt;span class="n"&gt;scan&lt;/span&gt;
        &lt;span class="n"&gt;estimated&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="n"&gt;forecast&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;order_items_order_date_idx&lt;/span&gt;
        &lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s1"&gt;'2020-09-13 12:26:40'&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s1"&gt;'2020-09-13 12:26:40'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;recommendations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;replacement&lt;/span&gt;
     &lt;span class="k"&gt;SQL&lt;/span&gt; &lt;span class="n"&gt;commands&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="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;STORING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;order_items_order_date_idx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice a few differences about this explain plan from the one above:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We do a scan of the &lt;code&gt;order_items_order_date_idx&lt;/code&gt; this time&lt;/li&gt;
&lt;li&gt;We then do an &lt;code&gt;index join&lt;/code&gt; back to the primary index &lt;code&gt;order_items_pkey&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;CRDB has helpfully given us some suggestions on how we can alter our index to perform better.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's very efficient to use the order_date index since our predicate is on order_date.  But the order_date index only contains the field we're indexing (order_date) and a reference back to the PK (id).  In order to satisfy our query's &lt;code&gt;SELECT *&lt;/code&gt; clause, we need to join back to the primary index in order to have all the rest of the field values in this record.  The index suggestion is telling us that if we include/store all the other fields from the record in the order_date index that we can skip the index join since we'll already have all the data points necessary to satisfy our query.&lt;/p&gt;

&lt;p&gt;You can prove this by changing the SELECT query to only return the id and order_date fields:&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;EXPLAIN&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;order_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1600000000&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                                                        &lt;span class="n"&gt;info&lt;/span&gt;
&lt;span class="c1"&gt;---------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="n"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt;
  &lt;span class="n"&gt;vectorized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;

  &lt;span class="err"&gt;•&lt;/span&gt; &lt;span class="n"&gt;scan&lt;/span&gt;
    &lt;span class="n"&gt;estimated&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="n"&gt;forecast&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;order_items_order_date_idx&lt;/span&gt;
    &lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s1"&gt;'2020-09-13 12:26:40'&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s1"&gt;'2020-09-13 12:26:40'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Moving onto deletes
&lt;/h2&gt;

&lt;p&gt;Now that we have a sense of interpreting query plans on our table, let's think about what happens when we delete a record in CRDB.&lt;/p&gt;

&lt;p&gt;When we delete a record by the PK value, our execution plan is very simple -- find the record and delete it:&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;EXPLAIN&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;order_items&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1600000000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                                                          &lt;span class="n"&gt;info&lt;/span&gt;
&lt;span class="c1"&gt;-------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="n"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt;
  &lt;span class="n"&gt;vectorized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;

  &lt;span class="err"&gt;•&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt;
  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;
  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="n"&gt;auto&lt;/span&gt; &lt;span class="k"&gt;commit&lt;/span&gt;
  &lt;span class="err"&gt;│&lt;/span&gt;
  &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="err"&gt;•&lt;/span&gt; &lt;span class="n"&gt;scan&lt;/span&gt;
        &lt;span class="n"&gt;estimated&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="n"&gt;forecast&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;order_items_pkey&lt;/span&gt;
        &lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1600000000&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1600000000&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 do a similar DELETE query using the order_date field instead of the PK, we see that we once again have an index join:&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;EXPLAIN&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;order_items&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;order_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1600000000&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                                                                            &lt;span class="n"&gt;info&lt;/span&gt;
&lt;span class="c1"&gt;-------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="n"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt;
  &lt;span class="n"&gt;vectorized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;

  &lt;span class="err"&gt;•&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt;
  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;
  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="n"&gt;auto&lt;/span&gt; &lt;span class="k"&gt;commit&lt;/span&gt;
  &lt;span class="err"&gt;│&lt;/span&gt;
  &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="err"&gt;•&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt;
      &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="n"&gt;estimated&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;order_items_pkey&lt;/span&gt;
      &lt;span class="err"&gt;│&lt;/span&gt;
      &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="err"&gt;•&lt;/span&gt; &lt;span class="n"&gt;scan&lt;/span&gt;
            &lt;span class="n"&gt;estimated&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="n"&gt;forecast&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;order_items_order_date_idx&lt;/span&gt;
            &lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s1"&gt;'2020-09-13 12:26:40'&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s1"&gt;'2020-09-13 12:26:40'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;recommendations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;replacement&lt;/span&gt;
     &lt;span class="k"&gt;SQL&lt;/span&gt; &lt;span class="n"&gt;commands&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="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;STORING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;order_items_order_date_idx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The need for this index join is pretty obvious on the SELECT query, but why is it needed on the DELETE query?  The answer is that when CRDB deletes a record, it needs to delete the entry(ies) from the primary index but it also needs to delete the entry(ies) from all of the corresponding indexes.  And, in order to delete the index entries, it needs the index keys.&lt;/p&gt;

&lt;p&gt;Let's follow the optimizer's advice and create this new "covering" index:&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;INDEX&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;STORING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;order_items_order_date_idx&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 run our DELETE statement again with this new index in place, we can see that the need for the index join has been eliminated.&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;EXPLAIN&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;order_items&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;order_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1600000000&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                                                                                  &lt;span class="n"&gt;info&lt;/span&gt;
&lt;span class="c1"&gt;-------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="n"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt;
  &lt;span class="n"&gt;vectorized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;

  &lt;span class="err"&gt;•&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt;
  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;
  &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="n"&gt;auto&lt;/span&gt; &lt;span class="k"&gt;commit&lt;/span&gt;
  &lt;span class="err"&gt;│&lt;/span&gt;
  &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="err"&gt;•&lt;/span&gt; &lt;span class="n"&gt;scan&lt;/span&gt;
        &lt;span class="n"&gt;estimated&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="n"&gt;forecast&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;order_items_order_date_idx1&lt;/span&gt;
        &lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s1"&gt;'2020-09-13 12:26:40'&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s1"&gt;'2020-09-13 12:26:40'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Warning: This is a great technique to help make deletes efficient.  If you're doing a regular batch delete of old data or cleaning up records that have met some "deletable" state, this can help a lot -- especially on really big tables.  However, be aware that if you later add another index on the order_items table, you'll want to remember to include that newly-indexed field into the index that is going to be leveraged in your deletes -- otherwise, this index join will creep back into your plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Covering indexes are a great tool in CRDB and other DBMS systems to make specific queries more efficient.  It can be relatively easy to identify when they should be utilized to help SELECT queries, but it's a little un-intuitive to realize that they can also help with DELETEs.  But now you know, so happy deleting!!&lt;/p&gt;

</description>
      <category>cockroachdb</category>
      <category>deletes</category>
      <category>tuning</category>
    </item>
    <item>
      <title>Running Multi-region CockroachDB on k8s -- the internals</title>
      <dc:creator>Jim Hatcher</dc:creator>
      <pubDate>Mon, 13 Mar 2023 14:49:53 +0000</pubDate>
      <link>https://dev.to/jhatcher9999/running-multi-region-cockroachdb-on-k8s-the-internals-4en0</link>
      <guid>https://dev.to/jhatcher9999/running-multi-region-cockroachdb-on-k8s-the-internals-4en0</guid>
      <description>&lt;p&gt;If you are familiar with CockroachDB (CRDB), you may have heard that CRDB runs well on Kubernetes (k8s).  You may have also heard that CRDB can be deployed in either a single-region or multi-region configuration.&lt;/p&gt;

&lt;p&gt;The Cockroach Labs' docs site has a &lt;a href="https://www.cockroachlabs.com/docs/stable/orchestrate-cockroachdb-with-kubernetes-multi-cluster.html" rel="noopener noreferrer"&gt;great guide&lt;/a&gt; for deploying CRDB on k8s in a multi-region environment.&lt;/p&gt;

&lt;p&gt;If you follow the guide, you'll have a working multi-region cluster, but you may not understand what exactly you've set up!&lt;/p&gt;

&lt;p&gt;There is a &lt;a href="https://www.cockroachlabs.com/docs/stable/orchestrate-cockroachdb-with-kubernetes-multi-cluster.html#ux-differences-from-running-in-a-single-cluster" rel="noopener noreferrer"&gt;section&lt;/a&gt; that explains some of the differences that you'll see between a single-region k8s deployment and the multi-region k8s deployment described in the guide.&lt;/p&gt;

&lt;p&gt;In this blog, I want to explain things in a little more detail.  Understanding these fundamentals can be really helpful when you're troubleshooting a cluster that isn't acting quite right.&lt;/p&gt;

&lt;h2&gt;
  
  
  You gotta use the manifests!
&lt;/h2&gt;

&lt;p&gt;When you're deploying CRDB in a single-region, there are three deployment options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;k8s operator&lt;/li&gt;
&lt;li&gt;Helm Chart&lt;/li&gt;
&lt;li&gt;Manual manifests (i.e., yamls)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, when you go the &lt;em&gt;multi-region route&lt;/em&gt;, the only available option is the manual manifests.&lt;/p&gt;

&lt;p&gt;This is not really a CRDB limitation.  There is not a strong consensus in the k8s community about how federated k8s clusters should work, so we have our own way of doing it that doesn't really use any k8s primitives to provide coordination across the various clusters. &lt;/p&gt;

&lt;h2&gt;
  
  
  One CockroachDB cluster, 3 Kubernetes Clusters
&lt;/h2&gt;

&lt;p&gt;So...when you create a multi-region CRDB cluster (let's say 3 regions for the sake of this illustration), you run the CRDB resources on 3 separate k8s clusters and these 3 k8s clusters know nothing about each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpb6s64qybl8g5qlzmctm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpb6s64qybl8g5qlzmctm.png" alt="Multi-Region CRDB Deployment on k8s" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the CRDB perspective, it is one single, logical cluster; the CRDB nodes don't really know that they're running on k8s -- they just know that there are various CRDB nodes, and as long as there is network connectivity between these nodes, everything will interact as designed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cluster Maintenance
&lt;/h3&gt;

&lt;p&gt;There are some implication to this.  One implication is that if you want to make changes to your cluster across all three regions, you'll need to interact with the k8s control planes in all three k8s clusters.&lt;/p&gt;

&lt;p&gt;If you look at the guide and find the &lt;a href="https://www.cockroachlabs.com/docs/stable/orchestrate-cockroachdb-with-kubernetes-multi-cluster.html#step-6-maintain-the-cluster" rel="noopener noreferrer"&gt;section called "Maintain the Cluster"&lt;/a&gt;, you'll see steps for scaling, upgrading, and stopping the cluster.  Any of these steps necessitates doing actions on all 3 k8s control planes.  There is no one single control plane that can handle these actions for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network Planning
&lt;/h3&gt;

&lt;p&gt;Another implication is that you should not create overlapping service or pod networks in your k8s clusters.&lt;/p&gt;

&lt;p&gt;A k8s cluster has a service network with a pool of available IP addresses which it assigns to newly-created services (things like load balancers).  It also has a pod network with a pool of available IP addresses which it assigns to newly-created pods.&lt;/p&gt;

&lt;p&gt;When you create a k8s-based deployment of CRDB, a stateful set is used.  The stateful set has a number of replicas configured (&lt;a href="https://github.com/cockroachdb/cockroach/blob/master/cloud/kubernetes/multiregion/cockroachdb-statefulset-secure.yaml#L146" rel="noopener noreferrer"&gt;the default replicas in the guide&lt;/a&gt; is 3).  Each of these replicas is created as a pod and gets assigned an IP address from this pod network IP range.&lt;/p&gt;

&lt;p&gt;We also create one headless service (which is used internally and doesn't use an IP) and one non-headless service (which gets an IP assigned from the service network IP range).  You can see these IPs by running &lt;code&gt;kubectl get pods&lt;/code&gt; and &lt;code&gt;kubectl get services&lt;/code&gt;, respectively.&lt;/p&gt;

&lt;p&gt;Below is a diagram which shows the network connectivity need of every CRDB node (running as a k8s pod).&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjlm3b586rvmqosg2201.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjlm3b586rvmqosg2201.png" alt="CRDB Node Network Connectivity" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use a k8s load balancer (running on the service network) to handle the incoming SQL and DB Console access, but the CRDB node-to-node connectivity requires that every node (in any region) needs to be able to connect to every other node directly -- and in a k8s deployment, that means that every k8s pod running CRDB has to be able to talk directly to every other k8s pod running CRDB.&lt;/p&gt;

&lt;p&gt;Our docs give basic examples of creating the various k8s clusters.  For instance, in the GKE path, the following commands are given:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud container clusters create cockroachdb1 &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GCE&lt;/span&gt;&lt;span class="nt"&gt;-ZONE1&lt;/span&gt;
gcloud container clusters create cockroachdb2 &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GCE&lt;/span&gt;&lt;span class="nt"&gt;-ZONE2&lt;/span&gt;
gcloud container clusters create cockroachdb3 &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GCE&lt;/span&gt;&lt;span class="nt"&gt;-ZONE3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a simple, demo deployment, this command is fine.  But for more complex deployments (certainly production deployments), it's worth thinking through whether these clusters will end up having overlapping service and pod networks.&lt;/p&gt;

&lt;p&gt;I personally like to use a more explicit command for creating the cluster.  For instance, for the GKE path, I would issue the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud container clusters create cockroachdb1 
  &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GCE&lt;/span&gt;&lt;span class="nt"&gt;-ZONE1&lt;/span&gt; &lt;span class="nt"&gt;--machine-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$MACHINETYPE&lt;/span&gt; &lt;span class="nt"&gt;--num-nodes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3 
  &lt;span class="nt"&gt;--cluster-ipv4-cidr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.1.0.0/16
  &lt;span class="nt"&gt;--services-ipv4-cidr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.101.0.0/16
gcloud container clusters create cockroachdb2 
  &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GCE&lt;/span&gt;&lt;span class="nt"&gt;-ZONE2&lt;/span&gt; &lt;span class="nt"&gt;--machine-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$MACHINETYPE&lt;/span&gt; &lt;span class="nt"&gt;--num-nodes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3 
  &lt;span class="nt"&gt;--cluster-ipv4-cidr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.2.0.0/16
  &lt;span class="nt"&gt;--services-ipv4-cidr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.102.0.0/16
gcloud container clusters create cockroachdb3 
  &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GCE&lt;/span&gt;&lt;span class="nt"&gt;-ZONE3&lt;/span&gt; &lt;span class="nt"&gt;--machine-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$MACHINETYPE&lt;/span&gt; &lt;span class="nt"&gt;--num-nodes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3 
  &lt;span class="nt"&gt;--cluster-ipv4-cidr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.3.0.0/16
  &lt;span class="nt"&gt;--services-ipv4-cidr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.103.0.0/16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that I'm explicitly giving each cluster a unique cluster (i.e., pod) CIDR block and services CIDR block.  I'm also being explicit about the machine type and number of nodes just because it avoids ambiguity.&lt;/p&gt;

&lt;p&gt;Note: Because CRDB wants to have this direct pod-to-pod connectivity, we don't like to run CRDB on service meshes (like Istio).  Service meshes tend to rely on hiding the pod details and having everything connect through the services layer, which works great for stateless services, but not so great for highly-stateful databases with node-level replication requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  DNS Resolution
&lt;/h2&gt;

&lt;p&gt;k8s pods within a given k8s cluster are able to talk to each other by host name because DNS Resolution is handled within k8s.  For example if pod0 needs to talk to pod1, pod0 references pod1's host name which gets resolved by the DNS service (either coredns or kube-dns, depending on your flavor of k8s).  The DNS service returns the IP address mapped to the hostname and then pod0 talks to pod1 directly via this IP address.&lt;/p&gt;

&lt;p&gt;Since our CRDB cluster involves three k8s clusters, this DNS system breaks down a little bit.  It works fine for pods within the same cluster, but pods in different clusters can't find each other out of the box.&lt;/p&gt;

&lt;p&gt;To remedy this, we set up a few things in our multi-region implementation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;we create a &lt;a href="https://github.com/cockroachdb/cockroach/blob/master/cloud/kubernetes/multiregion/dns-lb.yaml" rel="noopener noreferrer"&gt;load balancer&lt;/a&gt; that exposes the DNS service of each cluster outside of that cluster.&lt;/li&gt;
&lt;li&gt;in each DNS service we create a &lt;a href="https://github.com/cockroachdb/cockroach/blob/master/cloud/kubernetes/multiregion/setup.py#L125" rel="noopener noreferrer"&gt;config map&lt;/a&gt; that tells each DNS service how to forward DNS requests to every other DNS service (using the aforementioned load balancer) based on the format of the hostname.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to see these, look in the "kube-system" namespace and run &lt;code&gt;kubectl get configmap&lt;/code&gt; and &lt;code&gt;kubectl get svc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using this system of "DNS forwarding", each pod can now resolve the name of every other pod across all the clusters.  Assuming that the pod networks are non-overlapping and routable, then we should be able to operate.&lt;/p&gt;

&lt;p&gt;Let me walk through an example of this, just to make sure it's clear:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;pod1 in region1 wants to connect to pod2 in region2&lt;/li&gt;
&lt;li&gt;pod1 reaches out to its DNS service and asks to resolve the name "pod2.region2".  The DNS service is not able to resolve that name directly but there is a forwarding rule that says for any hostname of the format "*.region2", talk to another DNS server at address 10.10.10.10.&lt;/li&gt;
&lt;li&gt;The 10.10.10.10 address is a load balancer that is exposing the DNS service of region2's k8s cluster.  The region1 DNS server talks to region2's DNS server via this link and resolves the pod2's host name as 10.2.0.2.&lt;/li&gt;
&lt;li&gt; pod1 now knows the IP address of region2.pod2 and creates a connection to 10.2.0.2 directly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're having trouble getting the pods to communicate across regions, there are few things you can look at for troubleshooting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can look at the logs of the CRDB pods and find error messages about connecting to other nodes.  Find the logs by running &lt;code&gt;kubectl logs cockroachdb-0 -c cockroach -n &amp;lt;namespace name here&amp;gt;&lt;/code&gt;.  Sometimes the error message will help you understand whether name resolution has occurred.  For instance, if you see an error like:
&lt;code&gt;addrConn.createTransport failed to connect to {cockroachdb-2.region2:}. Err :connection error: desc = "transport: Error while dialing dial tcp 10.2.2.2: connect: connection refused&lt;/code&gt;, 
then you can tell that the DNS resolution has happened correctly (since both the hostname and IP address are listed in the error).  In this case, you don't have a DNS resolution problem but some other network connectivity issue.&lt;/li&gt;
&lt;li&gt;You can also look at the logs of the DNS pods by running &lt;code&gt;kubectl get pods -n kube-system&lt;/code&gt;.  Then after you find the name of one of the dns pods, you can run &lt;code&gt;kubectl logs &amp;lt;name of dns pod&amp;gt; -n kube-system&lt;/code&gt; and look at the log messages.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Network Firewalls
&lt;/h2&gt;

&lt;p&gt;You'll want to make sure that you have firewalls opened between the clusters on TCP/53 (the port used for DNS) and also TCP/26257 (the port used for CRDB node-to-node communication).  Depending on your k8s environment (GKE, EKS, etc.), these settings can be controlled in one or more places.  For instance, in EKS (i.e., Amazon), there is an NSG (Network Security Group) for the cluster role that has firewall settings.  There is also a NACL (Network Access Control List) on each private subnet that has firewall settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;CockroachDB is complementary to k8s.  When running in multiple k8s regions, there is a little extra work (mostly done by the python script described in our docs) that creates DNS forwarding and allows k8s pods in different k8s clusters to be able to communicate.  This DNS setup, in conjunction with smart network planning is a good solution for running CRDB in multi-region k8s.&lt;/p&gt;

</description>
      <category>security</category>
      <category>systemdesign</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Mentors</title>
      <dc:creator>Jim Hatcher</dc:creator>
      <pubDate>Wed, 08 Mar 2023 16:28:57 +0000</pubDate>
      <link>https://dev.to/jhatcher9999/mentors-34j5</link>
      <guid>https://dev.to/jhatcher9999/mentors-34j5</guid>
      <description>&lt;p&gt;My oldest daughter is months away from graduating from college.  She and I have had lots of discussions about where she should apply for jobs and what attributes she should seek in an ideal employer.  I have thought about what was most valuable for me early in my career, and if I really boil it down, it is mentorship.&lt;/p&gt;

&lt;p&gt;In this blog, I want to talk about my early education and jobs and talk about how mentorship impacted me.&lt;/p&gt;

&lt;p&gt;Early in my college years at NLU (later to be renamed ULM), I met a professor named Ron Berry.  From the get-go, I knew that Dr. Berry was special.  He was the faculty sponsor for the DPMA (the student organization for Computer Information Systems students), and I remember interacting with him at a club event and realizing that he really cared about connecting with the students.  I later got a student job working in the CIS department, and Dr. Berry was assigned to be my supervisor.  He gave me some great tasks, including going through all the computers in an old computer lab and to try to make some working units out of the pile of computer parts.  Later, he taught me how to make ethernet cables (cutting, crimping, testing) and tasked me with wiring up a new computer lab.  What amazing tasks for a kid just learning how to use computers!  He also taught several of my classes and was my advisor.  Nearly 25 years later, Ron Berry is now the President of ULM and is continuing to impact the lives of students.&lt;/p&gt;

&lt;p&gt;In the summer between my junior and senior years, my longtime girlfriend and I got married.  When I returned to school in the fall, I realized that I needed more income to afford my newlywed life.  I decided to do classes part-time and I took a full-time job at the College of Business doing tech support for faculty.  Dr. Berry was my supervisor and encouraged me to write a system to track the issues that people called me about and to track what the solutions were.  I learned many things in my time with Dr. Berry -- including basic computer repair skills, networking, troubleshooting, systems analysis, but the main thing I took away from Dr. Berry is that a good man who cares about his community and his students can have an enormous impact.&lt;/p&gt;

&lt;p&gt;Roughly a year later, Dr. Berry called me and told me that there was a job opening at Chase Manhattan Mortgage Company and suggested that it might be a good fit for me.  I did accept this new role at CMMC and became a junior programmer working for a guy named Ken Robertson.  Ken taught me a lot about writing database queries and basic programming.  He also taught me what it looked like to be an IT professional -- including taking responsibility, acting with integrity, and how to interact with other departments effectively.  Ken had a way of demonstrating things without you even knowing he was teaching you -- he just modeled professionalism in his every day routines, and you couldn't help but learn from his example.&lt;/p&gt;

&lt;p&gt;A few years later, my family and I had moved to Colorado and I took at job at First Data Corporation as a full-stack developer.  One of my teammates was a guy named Mike Barlow.  It was quickly apparent to me that Mike was a really sharp guy, and that there were things I could learn from Mike.  To this point in my career, I had worked a lot with databases and had done a lot of programming in languages that used top-down programming in Visual Basic and (what is now called "classic") ASP.  Mike liked to challenge his teammates to always learn new techniques and technologies, and I remember a day where he took me and another gal on our team to a conference room and taught us the basics of object-oriented programming.  This was an eye opener for me, and I quickly started to adopt new languages and new object-oriented techniques.  Mike was always pushing those around him to do better, and he taught me that you could have teachers in your life who weren't your bosses or formally assigned mentors.  My relationship with Mike was one of the best things that ever happened to me -- we worked together at many companies over the next 15-20 years and pushed each other to be better.  Our relationship was often described by others as a "husband and wife" because we knew how to work together and be effective while challenging and even arguing with each other.  Mike passed away recently, and I think about and miss him daily.&lt;/p&gt;

&lt;p&gt;I am now 25 years into my IT career.  I have worked in a variety of companies in various roles and have worked on many different systems.  When I look back on my early career as a newly minted college graudate, I realize how little I actually knew about how to write code and interact with data.  I had learned some basic skills and knew &lt;em&gt;about&lt;/em&gt; the things one did to work on computer systems, but I had very little actual experience.  What I did have was a desire to learn. I managed to work at positions which were a series of stepping stones, picking up real-world skills.  Another realization was that I was supported and encouraged each step of the way by mentors who had seen things, knew things, and saw something in me,  leading them to believe that I had promise and was worth investing in.  I am thankful for the mentors described here and for others who had a hand in helping me.  Given the chance to take on mentoring opportunities in my current role, I jump at the chance as a way to honor those who pumped time and wisdom into me.&lt;/p&gt;

&lt;p&gt;So, when my daughter asks me, "Dad, where should I apply?," I tell her that she needs to seek out roles with good bosses, good teammates, and a culture of teamwork.  This is a hard thing to find in a job -- you certainly can't filter a list of job openings for these traits! -- but I believe that if you enter into an interview process with these goals in mind that you can discern which roles are going to provide these benefits and will therefore act as a springboard to launch you into a successful career.&lt;/p&gt;

&lt;p&gt;Here's to the mentors out there -- thank you!&lt;/p&gt;

</description>
      <category>mentorship</category>
      <category>reflection</category>
      <category>career</category>
    </item>
    <item>
      <title>Running CockroachDB on k8s - with tweaks for Production</title>
      <dc:creator>Jim Hatcher</dc:creator>
      <pubDate>Mon, 06 Mar 2023 15:56:02 +0000</pubDate>
      <link>https://dev.to/jhatcher9999/running-cockroachdb-on-k8s-with-tweaks-for-production-1gf5</link>
      <guid>https://dev.to/jhatcher9999/running-cockroachdb-on-k8s-with-tweaks-for-production-1gf5</guid>
      <description>&lt;p&gt;Running CockroachDB (CRDB) on Kubernetes (k8s) is a complementary affair.  CRDB provides high availability at the data layer, and k8s provides high availability at the infrastructure layer.&lt;/p&gt;

&lt;p&gt;For enterprises that are already leveraging k8s to run their applications &amp;amp; microservices and who have experience running and administering k8s, it can make sense to also run the database within k8s.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.cockroachlabs.com/docs/stable/kubernetes-overview.html" rel="noopener noreferrer"&gt;docs for running CRDB on k8s&lt;/a&gt; on the Cockroach Labs' documentation site have an excellent set of steps for getting CRDB up and running.  They are all you need to run a demo of CRDB on k8s.&lt;/p&gt;

&lt;p&gt;However, if you're planning on running k8s in Production, there are a few other things you'll probably want to do.  In this blog I'll explain how to go about this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expose CRDB outside of the k8s cluster
&lt;/h3&gt;

&lt;p&gt;You can follow the docs for &lt;a href="https://www.cockroachlabs.com/docs/stable/deploy-cockroachdb-with-kubernetes.html" rel="noopener noreferrer"&gt;deploying CRDB in a single-region  k8s deployment&lt;/a&gt;.  When you deploy CRDB on k8s (through any of the available methods -- operator, helm chart, or via yaml configs), there are a few services created for you.&lt;/p&gt;

&lt;p&gt;For instance, after installing CRDB via the CRDB k8s operator on GKE, I have the following services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get svc
NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;                        AGE
cockroachdb          ClusterIP   None            &amp;lt;none&amp;gt;        26258/TCP,8080/TCP,26257/TCP   59s
cockroachdb-public   ClusterIP   10.108.12.200   &amp;lt;none&amp;gt;        26258/TCP,8080/TCP,26257/TCP   59s
kubernetes           ClusterIP   10.108.0.1      &amp;lt;none&amp;gt;        443/TCP                        7m5s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In k8s, there are four potential &lt;a href="https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types" rel="noopener noreferrer"&gt;types of services&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ClusterIP&lt;/strong&gt; - this is the default if another type is not explicitly specified in the service's manifest; this type of service is internal to the k8s cluster&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nodeport&lt;/strong&gt; - this is a way to expose the service outside of the k8s cluster using a custom port on the various cluster nodes - this can be OK for development clusters but it's not typically how you want to do things in Production because you have to use non-standard ports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LoadBalancer&lt;/strong&gt; - this is another way to expose services to apps running outside of the k8s cluster; it's a better way for Production deployments because you can use the standard ports, but you need to have a process that can assign a Public IP to the load balancer; if you're running in a cloud-based k8s service (i.e., EKS, GKE, AKS, or OpenShift), this is handled for you, but if you're running on OSS k8s, you have to handle this yourself&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ExternalName&lt;/strong&gt; - this is a way of assigning an external DNS name to a k8s service and is not really applicable for what we're talking about here.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  SQL Port
&lt;/h4&gt;

&lt;p&gt;You'll notice that in the services listed in our CRDB k8s cluster that we have one called "cockroachdb" of type "ClusterIP" and which has no Cluster IP assigned.  This is called a "headless" service.  The point of this service is to be the service associated with the statefulset called "cockroach".  It is not intended to be used to access the CRDB cluster by any internal or external apps. You can see the reference to this service in the statefulset's manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get sts cockroachdb &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"serviceName"&lt;/span&gt;
  serviceName: cockroachdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other cockroach service called "cockroachdb-public" is also of type ClusterIP but has a Cluster IP assigned to it.  The point of this service is to be used by apps wanting to access CRDB that are running inside the k8s cluster.&lt;/p&gt;

&lt;p&gt;In the CRDB docs, you'll see a section called "Use the built-in SQL Client" and you can see that they leverage this service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; cockroachdb-client-secure &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--&lt;/span&gt; ./cockroach sql &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--certs-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/cockroach/cockroach-certs &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb-public
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a perfectly acceptable way to setup some basic things in the cluster via the SQL prompt (like creating the first users and to verify basic read/write capabilities are working).  However, this is not the mechanism you'd want to use in Production for your apps to access the CRDB cluster -- especially if your apps are running outside the k8s cluster.  I'll talk about the right way to do this a little later on.&lt;/p&gt;

&lt;p&gt;There is also a third service listed called "kubernetes" which is not used to access CRDB at all.&lt;/p&gt;

&lt;p&gt;When you're running CRDB, there are three access points into the CRDB nodes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the SQL client port (26257 by default)&lt;/li&gt;
&lt;li&gt;the DB Console port (8080 by default), and&lt;/li&gt;
&lt;li&gt;the port used by other nodes to do node-to-node interactions (26258 by default).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All three of these ports are exposed by the "cockroachdb-public" service that we've been looking at.&lt;/p&gt;

&lt;h4&gt;
  
  
  DB Console port
&lt;/h4&gt;

&lt;p&gt;We can technically get to the DB Console on each CRDB node from any pod running inside our k8s cluster, but that would involve running curl commands which aren't very useful.&lt;/p&gt;

&lt;p&gt;Just to illustrate what I'm talking about, you can do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; cockroachdb-0 &lt;span class="nt"&gt;--&lt;/span&gt; curl &lt;span class="nt"&gt;-Lk&lt;/span&gt; http://cockroachdb-public:8080/
Defaulted container &lt;span class="s2"&gt;"db"&lt;/span&gt; out of: db, db-init &lt;span class="o"&gt;(&lt;/span&gt;init&lt;span class="o"&gt;)&lt;/span&gt;
&amp;lt;&lt;span class="o"&gt;!&lt;/span&gt;DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;&lt;span class="nb"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &amp;lt;title&amp;gt;Cockroach Console&amp;lt;/title&amp;gt;
        &amp;lt;meta &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"UTF-8"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &amp;lt;&lt;span class="nb"&gt;link &lt;/span&gt;&lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"favicon.ico"&lt;/span&gt; &lt;span class="nv"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"shortcut icon"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;div &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"react-layout"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;lt;/div&amp;gt;
        &amp;lt;script &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bundle.js"&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text/javascript"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;lt;/script&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that we do actually get some HTML back from our curl command, but this is a lame way to interact with a website!  So, in the CRDB docs, they recommend using the &lt;code&gt;kubectl port-forward&lt;/code&gt; command to expose this service to the computer where your kubectl command is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl port-forward service/cockroachdb-public 8080
Forwarding from 127.0.0.1:8080 -&amp;gt; 8080
Forwarding from &lt;span class="o"&gt;[&lt;/span&gt;::1]:8080 -&amp;gt; 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you've run this command, you can go to the browser on your computer and get to &lt;code&gt;http://localhost:8080/&lt;/code&gt; and acccess the DB Console.  The kubectl command is proxying your input to the CRDB nodes and their output back to your browser.&lt;/p&gt;

&lt;p&gt;Again, this is a perfectly acceptable way to access the CRDB nodes for a demo and just to make sure they're running OK.  But, for Production, you don't want to have everybody on your team port-forwarding into your nodes in order to monitor what's happening in CRDB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using an external load balancer
&lt;/h3&gt;

&lt;p&gt;In order to access the SQL client and DB Console from outside the cluster, the best way to go is to create a k8s service of type &lt;code&gt;LoadBalancer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create a yaml file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# put annotations here that affect how EKS creates things&lt;/span&gt;
    &lt;span class="c1"&gt;# service.beta.kubernetes.io/aws-load-balancer-internal: "true"&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cockroachdb&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cockroachdb-lb&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# if you don't specify the type, it will default to ClusterIP which won't expose the services outside of the k8s cluster&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# this selector is the label associated with your CRDB pods&lt;/span&gt;
    &lt;span class="c1"&gt;# if you're not sure -- run this: kubectl get pods --show-labels&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cockroachdb&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;26257&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;26257&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you can create the service by applying that yaml file with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; cockroachdb-lb.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that if you get a listing of your services right after creating it that you will see a service called "cockroachdb-lb" and it will have an External IP of "pending":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get svc
NAME                 TYPE           CLUSTER-IP      EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;                          AGE
cockroachdb          ClusterIP      None            &amp;lt;none&amp;gt;        26258/TCP,8080/TCP,26257/TCP     33m
cockroachdb-lb       LoadBalancer   10.108.4.152    &amp;lt;pending&amp;gt;     8080:31016/TCP,26257:31395/TCP   4s
cockroachdb-public   ClusterIP      10.108.12.200   &amp;lt;none&amp;gt;        26258/TCP,8080/TCP,26257/TCP     33m
kubernetes           ClusterIP      10.108.0.1      &amp;lt;none&amp;gt;        443/TCP                          39m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you wait a few seconds and try again, you'll see that an External IP value is assigned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get svc
NAME                 TYPE           CLUSTER-IP      EXTERNAL-IP      PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;                          AGE
cockroachdb          ClusterIP      None            &amp;lt;none&amp;gt;           26258/TCP,8080/TCP,26257/TCP     35m
cockroachdb-lb       LoadBalancer   10.108.4.152    34.139.126.177   8080:31016/TCP,26257:31395/TCP   97s
cockroachdb-public   ClusterIP      10.108.12.200   &amp;lt;none&amp;gt;           26258/TCP,8080/TCP,26257/TCP     35m
kubernetes           ClusterIP      10.108.0.1      &amp;lt;none&amp;gt;           443/TCP                          41m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because I'm running in GKE, Google Cloud handles creating a load balancer for me.  If you look in the GCP Cloud Console, you can see the load balancer details.&lt;/p&gt;

&lt;p&gt;If I describe the LB svc, I can look at the endpoints that have been exposed by the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe svc cockroachdb-lb
Name:                     cockroachdb-lb
Namespace:                default
Labels:                   &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb
Annotations:              cloud.google.com/neg: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"ingress"&lt;/span&gt;:true&lt;span class="o"&gt;}&lt;/span&gt;
Selector:                 &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb
Type:                     LoadBalancer
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.108.4.152
IPs:                      10.108.4.152
LoadBalancer Ingress:     34.139.126.177
Port:                     https  8080/TCP
TargetPort:               8080/TCP
NodePort:                 https  31016/TCP
Endpoints:                &amp;lt;none&amp;gt;
Port:                     tcp  26257/TCP
TargetPort:               26257/TCP
NodePort:                 tcp  31395/TCP
Endpoints:                &amp;lt;none&amp;gt;
Session Affinity:         None
External Traffic Policy:  Cluster
Events:
  Type    Reason                Age    From                Message
  &lt;span class="nt"&gt;----&lt;/span&gt;    &lt;span class="nt"&gt;------&lt;/span&gt;                &lt;span class="nt"&gt;----&lt;/span&gt;   &lt;span class="nt"&gt;----&lt;/span&gt;                &lt;span class="nt"&gt;-------&lt;/span&gt;
  Normal  EnsuringLoadBalancer  3m12s  service-controller  Ensuring load balancer
  Normal  EnsuredLoadBalancer   2m36s  service-controller  Ensured load balancer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see here that there are no endpoints assigned.  That's not good!&lt;/p&gt;

&lt;p&gt;The way that the LB gets associated with pods is via the "selector" in its spec.  My current selector is looking for pods with a label of &lt;code&gt;app: cockroachdb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's see what labels our pods are actually using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods &lt;span class="nt"&gt;--show-labels&lt;/span&gt;
NAME            READY   STATUS    RESTARTS   AGE   LABELS
cockroachdb-0   1/1     Running   0          37m   app.kubernetes.io/component&lt;span class="o"&gt;=&lt;/span&gt;database,app.kubernetes.io/instance&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb,app.kubernetes.io/name&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb,controller-revision-hash&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb-7b9668cd75,crdb&lt;span class="o"&gt;=&lt;/span&gt;is-cool,statefulset.kubernetes.io/pod-name&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb-0
cockroachdb-1   1/1     Running   0          37m   app.kubernetes.io/component&lt;span class="o"&gt;=&lt;/span&gt;database,app.kubernetes.io/instance&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb,app.kubernetes.io/name&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb,controller-revision-hash&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb-7b9668cd75,crdb&lt;span class="o"&gt;=&lt;/span&gt;is-cool,statefulset.kubernetes.io/pod-name&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb-1
cockroachdb-2   1/1     Running   0          37m   app.kubernetes.io/component&lt;span class="o"&gt;=&lt;/span&gt;database,app.kubernetes.io/instance&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb,app.kubernetes.io/name&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb,controller-revision-hash&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb-7b9668cd75,crdb&lt;span class="o"&gt;=&lt;/span&gt;is-cool,statefulset.kubernetes.io/pod-name&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A better choice for the selector label would be &lt;code&gt;app.kubernetes.io/name=cockroachdb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's edit the yaml to include that value and re-apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# put annotations here that affect how EKS creates things&lt;/span&gt;
    &lt;span class="c1"&gt;# service.beta.kubernetes.io/aws-load-balancer-internal: "true"&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cockroachdb&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cockroachdb-lb&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# if you don't specify the type, it will default to ClusterIP which won't expose the services outside of the k8s cluster&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# this selector is the label associated with your CRDB pods&lt;/span&gt;
    &lt;span class="c1"&gt;# if you're not sure -- run this: kubectl get pods --show-labels&lt;/span&gt;
    &lt;span class="na"&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cockroachdb&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;26257&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;26257&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that I have to enter &lt;code&gt;app.kubernetes.io/name: cockroachdb&lt;/code&gt; instead of &lt;code&gt;app.kubernetes.io/name=cockroachdb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; cockroachdb-lb.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's look at our endpoints again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe svc cockroachdb-lb
Name:                     cockroachdb-lb
Namespace:                default
Labels:                   &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb
Annotations:              cloud.google.com/neg: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"ingress"&lt;/span&gt;:true&lt;span class="o"&gt;}&lt;/span&gt;
Selector:                 app.kubernetes.io/name&lt;span class="o"&gt;=&lt;/span&gt;cockroachdb
Type:                     LoadBalancer
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.108.4.152
IPs:                      10.108.4.152
LoadBalancer Ingress:     34.139.126.177
Port:                     https  8080/TCP
TargetPort:               8080/TCP
NodePort:                 https  31016/TCP
Endpoints:                10.104.0.4:8080,10.104.1.8:8080,10.104.2.7:8080
Port:                     tcp  26257/TCP
TargetPort:               26257/TCP
NodePort:                 tcp  31395/TCP
Endpoints:                10.104.0.4:26257,10.104.1.8:26257,10.104.2.7:26257
Session Affinity:         None
External Traffic Policy:  Cluster
Events:
  Type    Reason                Age                  From                Message
  &lt;span class="nt"&gt;----&lt;/span&gt;    &lt;span class="nt"&gt;------&lt;/span&gt;                &lt;span class="nt"&gt;----&lt;/span&gt;                 &lt;span class="nt"&gt;----&lt;/span&gt;                &lt;span class="nt"&gt;-------&lt;/span&gt;
  Normal  EnsuringLoadBalancer  49s &lt;span class="o"&gt;(&lt;/span&gt;x2 over 8m52s&lt;span class="o"&gt;)&lt;/span&gt;  service-controller  Ensuring load balancer
  Normal  EnsuredLoadBalancer   44s &lt;span class="o"&gt;(&lt;/span&gt;x2 over 8m16s&lt;span class="o"&gt;)&lt;/span&gt;  service-controller  Ensured load balancer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yay!  We have endpoints.&lt;/p&gt;

&lt;p&gt;Now, if I try to access the nodes via the SQL port or via the DB Console, I should be able to do so from outside the cluster.&lt;/p&gt;

&lt;p&gt;I can go to a browser and access &lt;code&gt;https://34.139.126.177:8080/&lt;/code&gt; and it works.  (You'll want to substitute the actual value of your LB's External IP here.)&lt;/p&gt;

&lt;p&gt;Also, I can access the nodes on the SQL port.  For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cockroach sql &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s1"&gt;'postgres://roach:Q7gc8rEdS@34.139.126.177:26257/defaultdb?sslmode=require'&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Welcome to the CockroachDB SQL shell.&lt;/span&gt;
&lt;span class="c"&gt;# All statements must be terminated by a semicolon.&lt;/span&gt;
&lt;span class="c"&gt;# To exit, type: \q.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Client version: CockroachDB CCL v22.2.5 (aarch64-apple-darwin21.2, built 2023/02/16 16:37:38, go1.19.4)&lt;/span&gt;
&lt;span class="c"&gt;# Server version: CockroachDB CCL v22.2.2 (x86_64-pc-linux-gnu, built 2023/01/04 17:23:00, go1.19.1)&lt;/span&gt;

warning: server version older than client! proceed with caution&lt;span class="p"&gt;;&lt;/span&gt; some features may not be available.

&lt;span class="c"&gt;# Cluster ID: 7539a31a-fc44-4f89-a154-cc60f8aaeddd&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Enter \? for a brief introduction.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
roach@34.139.126.177:26257/defaultdb&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SQL port in CRDB needs to be exposed by a L4/TCP load balancer (which is what we're doing above).  The DB Console port can also be exposed this way (as we've demonstrated), but since it's an HTTP/HTTPS access point, it could also be exposed through an L7 endpoint, like k8s' Ingress.  I'm not going to demonstrate that here in this blog, but it can certainly be done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixing the Node Certs
&lt;/h3&gt;

&lt;p&gt;Another thing to note here.  In my example above, I'm able to connect to my server using the IP address because I specified &lt;code&gt;sslmode=require&lt;/code&gt;.  This tells my Postgres driver/client that I want to use TLS to encrypt traffic to/from the cluster, but I don't want to do any hostname verification checks.  &lt;a href="https://www.cockroachlabs.com/docs/cockroachcloud/authentication.html#ssl-mode-settings" rel="noopener noreferrer"&gt;We don't recommend&lt;/a&gt; connecting this way because it leave your cluster susceptible to man-in-the-middle (MITM) attacks.&lt;/p&gt;

&lt;p&gt;In order to connect the "right way", I need to connect using &lt;code&gt;sslmode=verify-full&lt;/code&gt; and specify the ca.crt used to sign all the certs used in the CRDB cluster.&lt;/p&gt;

&lt;p&gt;I can get a list of the certs used by my cluster by asking k8s to list out all the secrets being used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get secrets
NAME               TYPE     DATA   AGE
cockroachdb-ca     Opaque   1      57m
cockroachdb-node   Opaque   3      57m
cockroachdb-root   Opaque   3      57m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I look into the details of the cockroachdb-node secret, I can see the various cert and key files that it contains:&lt;br&gt;
(note that I'm using jq which you can install on your Mac using &lt;code&gt;brew install jq&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get secrets cockroachdb-node &lt;span class="nt"&gt;-o&lt;/span&gt; json | jq &lt;span class="s1"&gt;'.data | map_values(@base64d)'&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{gsub(/\\n/,"\n")}1'&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"ca.crt"&lt;/span&gt;: &lt;span class="s2"&gt;"-----BEGIN CERTIFICATE-----
MIIDJTCCAg2gAwIBAgIQC+85luldQT9+ctIxQ1BitjANBgkqhkiG9w0BAQsFADAr
MRIwEAYDVQQKEwlDb2Nrcm9hY2gxFTATBgNVBAMTDENvY2tyb2FjaCBDQTAeFw0y
MzAyMjUyMDEzMzhaFw0zMzAzMDUyMDEzMzhaMCsxEjAQBgNVBAoTCUNvY2tyb2Fj
aDEVMBMGA1UEAxMMQ29ja3JvYWNoIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAp7fVT7JMbrzC0J9UeqN3r5YeW1FyYpVfpGWRiQICK8ZPv8NnzsaQ
SgOig83c9wax/wHP+xK4ISoTPMLc75eM+YKoN5fU17Ki28iopJwgIakCjSXJxcAv
cN0H6cn6BemL+qb9RS7Pffu8ohJKyLsNk7a/8xMNKUAPhgmBAYws4SOhG68/f1je
Lk8hsPrqVlCDBGPwVQdhCYkKvavLA7qG0D/+F+FfNI7a/qldqn/u74DN69gie5w4
37bB1IecleX3Ks0Ype+AiNzcdllUBC22ttVREpymVj7K24ti5DeyGPeHND5F/q6F
o8a/apYMPr+hbbPgsMjoreHlcCwgxk/zEwIDAQABo0UwQzAOBgNVHQ8BAf8EBAMC
AuQwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUabb2eIdtS1cn3QY/pNrk
v9Kyz8swDQYJKoZIhvcNAQELBQADggEBAI29Fz3SBzkSYdvhRWsXVjRL3XnteIvJ
GwwdIXgEx/Uxc+QXnOGRF6yKqMAzJhU15qP0u1LgqHq56tkeTmQAeApMg6VTa2wj
HibW77O8w8anukv5ThXeGs52FYTVzzQ/ao+y3R9cfyHQleoecohiFXYJ0RLKmj7n
ywZ9CocP6VnRklMyegpNBp9VWnnKTsMOs+lEaGzPDiJBdPJ0Ym9946jwaojb1st3
pnApAgN/32Ak9bTrBVf6Zl2zj6n6rLD294+EMScpvVqqIqA4iJh9cpGbIEu2TO4x
QrjTl5aBbP7e4VWQnVOSZgmeTJUnFm4L2kR53yFonmys0ZJ/14z0acw=
-----END CERTIFICATE-----
"&lt;/span&gt;,
  &lt;span class="s2"&gt;"tls.crt"&lt;/span&gt;: &lt;span class="s2"&gt;"-----BEGIN CERTIFICATE-----
MIID+jCCAuKgAwIBAgIQCjaSSuwS1yLAEuoysn7ZUDANBgkqhkiG9w0BAQsFADAr
MRIwEAYDVQQKEwlDb2Nrcm9hY2gxFTATBgNVBAMTDENvY2tyb2FjaCBDQTAeFw0y
MzAyMjUyMDEzMzhaFw0yODAzMDEyMDEzMzhaMCMxEjAQBgNVBAoTCUNvY2tyb2Fj
aDENMAsGA1UEAxMEbm9kZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AKT4igRxUZE5p7NDkDeSWqQjENX7W3tOTXoON1GyIjf8/j1xyQN2i/AMMFAdb5P9
f8mBFzsYes/WLgXWlPZQOal2MKJAOKJ1AYywKeZ+AqCYftIJlqm/1A/EdNn74Mv1
ykNU5f2YxdBAnl8MOIrIvWeghwzKv1PSYTiUDBFti9TNsQAvrwtXC8vrfir9rnz3
8j8QP1RMzQkySRUSsik0GGD/YMW5leTsEQKYxI+clkH7YM1pOUhw6b3SHbkZlYkO
arsgv2qlnjMUN4j/6HqtOyzu5wjyOBXKxccGwNtIJB3Xq0w3wYN1E3TWDmi9jY1c
T64w9KGgXLC8NR46MqjvfM0CAwEAAaOCASAwggEcMA4GA1UdDwEB/wQEAwIFoDAd
BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHwYDVR0jBBgwFoAUabb2eIdt
S1cn3QY/pNrkv9Kyz8swgckGA1UdEQSBwTCBvoIJbG9jYWxob3N0ghJjb2Nrcm9h
Y2hkYi1wdWJsaWOCGmNvY2tyb2FjaGRiLXB1YmxpYy5kZWZhdWx0gixjb2Nrcm9h
Y2hkYi1wdWJsaWMuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbIINKi5jb2Nrcm9h
Y2hkYoIVKi5jb2Nrcm9hY2hkYi5kZWZhdWx0gicqLmNvY2tyb2FjaGRiLmRlZmF1
bHQuc3ZjLmNsdXN0ZXIubG9jYWyHBH8AAAEwDQYJKoZIhvcNAQELBQADggEBACAr
6MQL8dGjbhufGRcGjpKE/ctmwpoARvfFIvCg1S5/ZXPJTz4A9fp1B0bxKoHopaOO
6F2EH9B6qH6g3cFbD6au+QXc5f/kgxVJVJOewCOUDLRjH1i3Fcnd/zxvywQU6cIs
ArfwWW+XoifirNQ6MwqxtPVzjMectGQUs1IpdDwLReO6eS9pFo4kHMZiJi5XTgjJ
krDFMbFUW8qnul1w3UrxgikXeLKnuIDnegPpX4Xk0yYF1ycxA46ZORV+DybP3DG8
F6lH6wA3uF2E62Z/52XH7UUvtUaAIK937vbxXosufD8KwbXCNEcojlSDYtuKhtCq
KcywMKGrVgdtd/nwxy4=
-----END CERTIFICATE-----
"&lt;/span&gt;,
  &lt;span class="s2"&gt;"tls.key"&lt;/span&gt;: &lt;span class="s2"&gt;"-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEApPiKBHFRkTmns0OQN5JapCMQ1ftbe05Neg43UbIiN/z+PXHJ
A3aL8AwwUB1vk/1/yYEXOxh6z9YuBdaU9lA5qXYwokA4onUBjLAp5n4CoJh+0gmW
qb/UD8R02fvgy/XKQ1Tl/ZjF0ECeXww4isi9Z6CHDMq/U9JhOJQMEW2L1M2xAC+v
C1cLy+t+Kv2ufPfyPxA/VEzNCTJJFRKyKTQYYP9gxbmV5OwRApjEj5yWQftgzWk5
SHDpvdIduRmViQ5quyC/aqWeMxQ3iP/oeq07LO7nCPI4FcrFxwbA20gkHderTDfB
g3UTdNYOaL2NjVxPrjD0oaBcsLw1HjoyqO98zQIDAQABAoIBAQCGBtY6ncXi8rBo
V6/HNkQlrcdz0W6VUxxm2T3gRZS/X+8+BD+HbLxsHbrym7eWyBEVqKcy/8RnLl7d
p2QGaU8vejIw33QjqGPF5SlldWK1Dq+Z/OhGqO6kkLtOjfAoRFw7L7Jawc+UTatd
FRSqzEP0+No/bkja1MTfrofPcOx1ygiTHsSm3JHy+rh/bxRxeU9J5JBWUD1KeRS4
FRsYqf7tgv6KzBktRRs29q/HeU4up0S9HyjbE9emc99g6ZfX2dpmqoDW0kBjo729
x0XP2KxmSGeAogTmpVBz6RjoDuCUAbtUjMAbpbDRJJqnm6R8fIj1e+mDpSwOS4QN
dikzHQiBAoGBANacPfviPU81ddowy1HjPEko4C4Qy6brmPWuaeA6QUUL/MR+QrYN
Usp4B7d8lsLnZEdHyeszDnxPaAzj4rE7uDhSSMJmfflNqQVmWR6jByQ8GgzDFS5/
Re3LYR26DJMHBNGZqCxQ7us7Aqc0+YeDT8/wlniOAlndvXQ7l1Tt7nGZAoGBAMTJ
fk7Cs81SaalQQ05O7jfjwvi6kX+ISbBnBB6LyDCNkBoCwRKIHsiDIkKlFhGgwvim
+K/Ugsg8GuqYb5qd1ag+Kb8ykQpbMjkvr6Mh1bArN3KWSQTaiFko7nJLLd7P2H0V
WzrD/OUD0J2NKkzQLJcxuS8hc5YRj0DGWqzCVw1VAoGAAmj+yTVhOuJ+0FR79A95
PdkXq2zE3LsInLm4tqvwz7WywQIp/aForJ1seMMNbmLq3WIRAnMwVnUN1hc5FIR3
LSq/Zm+AOqyEmWrs1Us/aUjDgiEuu7byMhl2nb7ZJU2O4Eu5d8Xw6PNgtEAEDWGM
I+mvxurRW/EBj6ybpniFlQECgYB/DXzQSyMdeI0htOGPyKRDT3lNb9+K0KqLCyf8
tNEuj+eu84JGfb4qRYg0MTQbc4kOU3eSxokd0LisKHk+AZO1yVTYzkQYxKKbi29B
yxGVaYGmKOPCD3oi3qt8/Y8DIXyr3cMGIQ3BqwHhBwh9iZaQk5j1lgpzpKix8J8Q
lXTw9QKBgQCNKN1p9UNxpX19ni7Mg+MVPRkZgC68DBXYxNDnAexFjlatV+dIo03u
SxGBsB8Y1q4AbqHwWe8lSp+erADzeWtkYD4u9BSZl4WD50Mbev/Fut9dGJwnI+BJ
0ldr96qyslFD1RitRl5Xc6gOTcF4Bt/O5GRo5+2F4fDwJm6+dYIjJA==
-----END RSA PRIVATE KEY-----
"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm going to copy and paste the ca.crt output into a file called ca.crt.&lt;/p&gt;

&lt;p&gt;OK, now I can try to connect the right way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cockroach sql &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s1"&gt;'postgres://roach:Q7gc8rEdS@34.139.126.177:26257/defaultdb?sslmode=verify-full&amp;amp;sslrootcert=ca.crt'&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Welcome to the CockroachDB SQL shell.&lt;/span&gt;
&lt;span class="c"&gt;# All statements must be terminated by a semicolon.&lt;/span&gt;
&lt;span class="c"&gt;# To exit, type: \q.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
ERROR: failed to connect to &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;34.139.126.177 &lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;roach &lt;span class="nv"&gt;database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;defaultdb&lt;span class="sb"&gt;`&lt;/span&gt;: failed to write startup message &lt;span class="o"&gt;(&lt;/span&gt;x509: certificate is valid &lt;span class="k"&gt;for &lt;/span&gt;127.0.0.1, not 34.139.126.177&lt;span class="o"&gt;)&lt;/span&gt;
Failed running &lt;span class="s2"&gt;"sql"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice this time I pass my ca.crt file and use &lt;code&gt;sslmode=verify-full&lt;/code&gt; and I get an error saying that my node certs are not authorized to respond as 34.139.126.177.&lt;/p&gt;

&lt;p&gt;To fix this, I need to re-issue my node certs to know about their load-balancer IP.  If you want to create a DNS record like &lt;code&gt;crdb.myproject.mydomain.com&lt;/code&gt;, now is a good time to do that because we'll want to include that domain name on our new certs, too.&lt;/p&gt;

&lt;p&gt;The process to generate the node certs is described in &lt;a href="https://www.cockroachlabs.com/docs/v22.2/deploy-cockroachdb-with-kubernetes?filters=manual#create-certificates" rel="noopener noreferrer"&gt;this doc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Depending on how you installed CRDB in k8s in the first place, you might have these various certs and keys in place in your local file system, or you might have to download them from the certs (as we did above for the ca.crt file).&lt;/p&gt;

&lt;p&gt;You don't need to re-create the ca key/crt pair, but you want to recreate the CRDB node cert.  When you get to that step, add the IP address and DNS name (if you have one) as additional parameters to this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cockroach cert create-node &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; localhost 127.0.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cockroachdb-public &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cockroachdb-public.default &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cockroachdb-public.default.svc.cluster.local &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.cockroachdb &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.cockroachdb.default &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.cockroachdb.default.svc.cluster.local &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 34.139.126.177 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; crdb.myproject.mydomain.com &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;--certs-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certs &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;--ca-key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-safe-directory/ca.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a node.crt/node.key pair.  I am re-naming those to tls.key/tls.crt since that's what they're called in my installation.&lt;/p&gt;

&lt;p&gt;You can examine the tls.crt file to see if it includes our IP and dns name by running this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;openssl x509 &lt;span class="nt"&gt;-in&lt;/span&gt; certs/tls.crt &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-text&lt;/span&gt; | egrep &lt;span class="nt"&gt;-A&lt;/span&gt; 2 &lt;span class="s1"&gt;'X509v3 Subject Alternative Name'&lt;/span&gt;
            X509v3 Subject Alternative Name: 
                DNS:localhost, DNS:cockroachdb-public, DNS:cockroachdb-public.default, DNS:cockroachdb-public.default.svc.cluster.local, DNS:&lt;span class="k"&gt;*&lt;/span&gt;.cockroachdb, DNS:&lt;span class="k"&gt;*&lt;/span&gt;.cockroachdb.default, DNS:&lt;span class="k"&gt;*&lt;/span&gt;.cockroachdb.default.svc.cluster.local, DNS:crdb.myproject.mydomain.com, IP Address:127.0.0.1, IP Address:34.139.126.177
    Signature Algorithm: sha256WithRSAEncryption
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that our IP and domain name are listed.&lt;/p&gt;

&lt;p&gt;Now we need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Delete the existing secret&lt;/li&gt;
&lt;li&gt;Re-create the secret with the new cert/key files&lt;/li&gt;
&lt;li&gt;Restart the pods so they will pick up the new secrets/certs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After doing so, if I try to connect using the "right way", I am able to do so successfully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cockroach sql &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s1"&gt;'postgres://roach:Q7gc8rEdS@34.139.126.177:26257/defaultdb?sslmode=verify-full&amp;amp;sslrootcert=certs/ca.crt'&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Welcome to the CockroachDB SQL shell.&lt;/span&gt;
&lt;span class="c"&gt;# All statements must be terminated by a semicolon.&lt;/span&gt;
&lt;span class="c"&gt;# To exit, type: \q.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Client version: CockroachDB CCL v22.2.5 (aarch64-apple-darwin21.2, built 2023/02/16 16:37:38, go1.19.4)&lt;/span&gt;
&lt;span class="c"&gt;# Server version: CockroachDB CCL v22.2.2 (x86_64-pc-linux-gnu, built 2023/01/04 17:23:00, go1.19.1)&lt;/span&gt;

warning: server version older than client! proceed with caution&lt;span class="p"&gt;;&lt;/span&gt; some features may not be available.

&lt;span class="c"&gt;# Cluster ID: 7539a31a-fc44-4f89-a154-cc60f8aaeddd&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Enter \? for a brief introduction.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
roach@34.139.126.177:26257/defaultdb&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;When running CRDB in k8s in Production, you'll want to expose the CRDB nodes externally by using a load balancer.  And, you'll want to re-create your node certs to references the load balancer details.&lt;/p&gt;

&lt;p&gt;Happy CRDBing!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Memory Management in CockroachDB</title>
      <dc:creator>Jim Hatcher</dc:creator>
      <pubDate>Mon, 27 Feb 2023 14:42:42 +0000</pubDate>
      <link>https://dev.to/jhatcher9999/memory-management-in-cockroachdb-29c4</link>
      <guid>https://dev.to/jhatcher9999/memory-management-in-cockroachdb-29c4</guid>
      <description>&lt;p&gt;If you've played with CockroachDB before, you may have seen some memory-related flags when starting Cockroach.  Specifically, things called "cache" and "max-sql-memory":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cockroach start &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--certs-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certs &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--advertise-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;node1 address&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;node1 address&amp;gt;,&amp;lt;node2 address&amp;gt;,&amp;lt;node3 address&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.25 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--max-sql-memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We talk a little bit about &lt;a href="https://www.cockroachlabs.com/docs/v22.2/recommended-production-settings#cache-and-sql-memory-size" rel="noopener noreferrer"&gt;these settings&lt;/a&gt; in our docs, but how are they actually used?&lt;/p&gt;

&lt;p&gt;I put together a few diagrams to illustrate what we mean by cache and sql-memory, and I'll try to illustrate how they're used.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjmxau7r3whkpq2wae3r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjmxau7r3whkpq2wae3r.png" alt="Diagram of CRDB Node and its memory caches" width="665" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above is a diagram of a node running in a CockroachDB cluster.  You can see that there are two caches managed by the &lt;code&gt;cockroach&lt;/code&gt; process called SQL Memory and Pebble Cache.  What we refer to above as "cache" is this Pebble cache.  Pebble is the name of the Key/Value storage engine that CockroachDB leverages to actually store data.&lt;/p&gt;

&lt;p&gt;There are some other areas of memory available outside of the &lt;code&gt;cockroach&lt;/code&gt; process, namely the Operating System's page cache.&lt;/p&gt;

&lt;p&gt;Let's consider a read that happens against a CRDB cluster, and let's assume for this example that this is the first read that's happened against this node so there is nothing loaded into any caches.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fya67gqt112x6fa3g053q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fya67gqt112x6fa3g053q.png" alt="Read - no cache hits" width="783" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are the steps that happen to satisfy this read query:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SQL SELECT query hits the SQL engine&lt;/li&gt;
&lt;li&gt;SQL engine asks KV for data&lt;/li&gt;
&lt;li&gt;KV asks the O/S for data&lt;/li&gt;
&lt;li&gt;O/S reads from disk &amp;amp; loads files into the page cache (compressed -- all SSTables in CRDB are compressed)&lt;/li&gt;
&lt;li&gt;O/S gives KV data&lt;/li&gt;
&lt;li&gt;KV puts data in Pebble cache (uncompressed)&lt;/li&gt;
&lt;li&gt;KV processes data and gives to SQL&lt;/li&gt;
&lt;li&gt;SQL uses memory to process data (ordering, grouping, etc.)&lt;/li&gt;
&lt;li&gt;SQL returns results to client&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can imagine that on a subsequent read, that some of these steps could be short-circuited if they got a cache hit -- namely, you might be able to avoid the read from disk and only hit the O/S Page cache; or, even better, if the Pebble cache has the data, you don't need to even talk to the O/S at all.&lt;/p&gt;

&lt;p&gt;The write path for a CockroachDB node is simpler.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwbdp6gy9amz2tl4qlzqn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwbdp6gy9amz2tl4qlzqn.png" alt="Write path" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The write involves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Mutation query (i.e., INSERT/UPDATE/DELETE) hits the SQL engine&lt;/li&gt;
&lt;li&gt;SQL engine passes data to KV engine&lt;/li&gt;
&lt;li&gt;KV writes data to SSTables in memory&lt;/li&gt;
&lt;li&gt;KV calls fsync to flush memory to disk (compressed)&lt;/li&gt;
&lt;li&gt;KV updates the Pebble cache (uncompressed)&lt;/li&gt;
&lt;li&gt;KV acknowledges write to SQL engine&lt;/li&gt;
&lt;li&gt;SQL returns acknowledgement to client&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Hopefully, this blog clears up what "SQL Memory" and "Cache" mean in the context of CockroachDB.  Happy SQL-ing!&lt;/p&gt;

</description>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Spark Update Optimizations</title>
      <dc:creator>Jim Hatcher</dc:creator>
      <pubDate>Tue, 21 Feb 2023 05:17:10 +0000</pubDate>
      <link>https://dev.to/jhatcher9999/spark-update-optimizations-5hlg</link>
      <guid>https://dev.to/jhatcher9999/spark-update-optimizations-5hlg</guid>
      <description>&lt;p&gt;About a year ago, I posted a blog on using &lt;a href="https://dev.to/jhatcher9999/apache-spark-job-to-update-cockroachdb-data-bmi"&gt;Apache Spark jobs to do updates against CockroachDB&lt;/a&gt;.  At the end of that post, I pledged to come back and do some optimizations to speed things up.  And, true to my word, here I am.&lt;/p&gt;

&lt;p&gt;Between the time I published my last blog and now, I am running on a new laptop, so I needed to get Spark running on my laptop.  I did the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installed Scala 2.12 from homebrew&lt;/li&gt;
&lt;li&gt;installed Spark from homebrew (latest as of the time of me writing this is 3.3.2)&lt;/li&gt;
&lt;li&gt;tried to install Java 11 but gave up and installed Java 17 (Spark 3.3.2's &lt;a href="https://spark.apache.org/docs/latest/#downloading" rel="noopener noreferrer"&gt;overview page&lt;/a&gt; tells me I can run on Java 8/11/17)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I then tried to simply re-run the examples from my last blog post and ran into a gnarly error:&lt;br&gt;
&lt;code&gt;java.lang.IllegalAccessException: final field has no write access&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After a little googling, I found a similarly-described problem &lt;a href="https://issues.apache.org/jira/browse/SPARK-40729" rel="noopener noreferrer"&gt;here&lt;/a&gt;.  This issue is fixed in the not-yet-released Spark 3.4.0, so I was a little stuck until I looked at the &lt;a href="https://github.com/apache/spark/commit/290e8a7cdf924309034dd5c8744be13813997510" rel="noopener noreferrer"&gt;changes included in the fix&lt;/a&gt; and decided I could reproduce them myself.&lt;/p&gt;

&lt;p&gt;Namely, the fix seems to be starting your Java process with the following option: &lt;code&gt;-Djdk.reflect.useDirectMethodHandle=false&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I added this option to my java driver options like so, and was able to run my previous examples.  (I also bumped up my Postgres driver version a bit):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spark-shell
  &lt;span class="nt"&gt;--driver-class-path&lt;/span&gt; /Users/jimhatcher/.m2/repository/org/postgresql/postgresql/42.5.1/postgresql-42.5.1.jar
  &lt;span class="nt"&gt;--jars&lt;/span&gt; /Users/jimhatcher/.m2/repository/org/postgresql/postgresql/42.5.1/postgresql-42.5.1.jar
  &lt;span class="nt"&gt;--conf&lt;/span&gt; spark.jars&lt;span class="o"&gt;=&lt;/span&gt;/Users/jimhatcher/.m2/repository/org/postgresql/postgresql/42.5.1/postgresql-42.5.1.jar
  &lt;span class="nt"&gt;--conf&lt;/span&gt; spark.executor.memory&lt;span class="o"&gt;=&lt;/span&gt;4g
  &lt;span class="nt"&gt;--conf&lt;/span&gt; spark.driver.memory&lt;span class="o"&gt;=&lt;/span&gt;4g
  &lt;span class="nt"&gt;--driver-java-options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-Djdk&lt;/span&gt;.reflect.useDirectMethodHandle&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a nasty little workaround (I admit), but I can omit it once Spark 3.4.0+ comes out.  And, at this point, I was able to re-run my previous tests.  My previous test involved creating a table with an id column and one additional field (which started null) and then updating all the records in the table where that field was null to have a value of 1.&lt;/p&gt;

&lt;p&gt;I then proceeded to try to tune various settings (batch size, number of partitions in the Spark dataframe, fetch size in the dataframe, etc.). I could make some small improvements but nothing significant or interesting.&lt;/p&gt;

&lt;p&gt;I looked as the SQL Statements page in the CockroachDB DB Console and saw the queries that were being generated and run against the cluster.  All my updates were coming through as individual statements.  Each statement itself was fairly quick (~1ms), but running a bunch of individual statements is never as quick as batching data together.&lt;/p&gt;

&lt;p&gt;In the Postgres JDBC driver, there is a parameter called &lt;code&gt;reWriteBatchInserts&lt;/code&gt; which can have a huge performance lift when writing data to CockroachDB.  I verified that I was passing the parameter on my JDBC URL, and I was.  This parameter only comes into play when running INSERTs, not UPDATEs, so I wrote a different version of my Spark job that would read from one table and INSERT values into another table.  When I ran this job, it took around 6sec to INSERT 1 million records instead of the 40sec I had been seeing when trying to UPDATE 1 million records.&lt;/p&gt;

&lt;p&gt;Here's the important bits of that Spark code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="nv"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;coalesce&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;foreachPartition&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Iterator&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Row&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;dbc&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;DriverManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;batchSize&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;st&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PreparedStatement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;dbc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"INSERT INTO test.test_table_target ( id, f1 ) VALUES ( ?, 1 );"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="nv"&gt;partition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;grouped&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batchSize&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;foreach&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;batch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;foreach&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;st&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; 
            &lt;span class="nv"&gt;st&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;addBatch&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;st&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;executeBatch&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="nv"&gt;dbc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;close&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then turned my thoughts to how I could force CockroachDB to do UPDATE statements in a batch, similarly to how the Postgres JDBC driver leverages query re-writing to batch INSERT queries in a very efficient manner.  One such technique that we can leverage is to pass a bunch of values into a SQL statement as arrays and then use the &lt;code&gt;UNNEST&lt;/code&gt; statement to convert these arrays into tabular input.&lt;/p&gt;

&lt;p&gt;Instead of running a single update like this:&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;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test_table&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;f1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&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 turned my attention to an approach like this:&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;test_table&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;f1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_f1&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;UNNEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;UNNEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;new_f1&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;data_table&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;test_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To help visualize what is happening in with the UNNEST function, here is a simple example of un-nesting a simple array in CockroachDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@localhost:26257/test&amp;gt; SELECT UNNEST(ARRAY[1, 2, 3]);
  unnest
----------
       1
       2
       3
(3 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this goal in mind, I re-wrote my Scala job to submit one UPDATE query for each batch (defined by my batch size variable).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.sql._&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.apache.spark.sql._&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;scala.collection.mutable.ListBuffer&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"jdbc:postgresql://&amp;lt;CRDB host name here&amp;gt;:26257/test?sslmode=require&amp;amp;sslcert=/Users/jimhatcher/spark-cluster-certs/client.root.crt&amp;amp;sslkey=/Users/jimhatcher/spark-cluster-certs/client.root.key.der&amp;amp;sslrootcert=/Users/jimhatcher/spark-cluster-certs/ca.crt&amp;amp;reWriteBatchedInserts=true"&lt;/span&gt;


&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;sql&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"( select id from test.test_table where f1 IS NULL) t1"&lt;/span&gt;
&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;df&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;spark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;read&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jdbc"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"url"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dbtable"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"partitionColumn"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"lowerBound"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"upperBound"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1000000"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"numPartitions"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"10"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;load&lt;/span&gt;

&lt;span class="nv"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;coalesce&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;foreachPartition&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Iterator&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Row&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;dbc&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;DriverManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;batchSize&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;st&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PreparedStatement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;dbc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UPDATE test_table SET f1 = data_table.new_f1 FROM ( SELECT UNNEST(?) AS id, UNNEST(?) AS new_f1 ) AS data_table WHERE test_table.id = data_table.id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="nv"&gt;partition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;grouped&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batchSize&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;foreach&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ListBuffer&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;BigInt&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fl1s&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ListBuffer&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;BigInt&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
        &lt;span class="nv"&gt;batch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;foreach&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;fl1s&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;st&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setArray&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;st&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;createArrayOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BIGINT"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ids&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;toArray&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="nv"&gt;st&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setArray&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;st&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;createArrayOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BIGINT"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;fl1s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;toArray&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="nv"&gt;st&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="nv"&gt;dbc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;close&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using this job, I was able to UPDATE 1 million records in 3.6sec instead of ~40sec.&lt;/p&gt;

&lt;p&gt;Note: It's generally considered bad form in Scala to use mutable lists in the way I'm doing above.  I could probably re-write my batch loop above using for comprehensions and make this a little more Scala-acceptable.  But, hey, that will give me something to write about in my next Spark optimization blog!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
      <category>discuss</category>
    </item>
    <item>
      <title>A Tale of Two Connection Pools</title>
      <dc:creator>Jim Hatcher</dc:creator>
      <pubDate>Mon, 13 Feb 2023 15:20:50 +0000</pubDate>
      <link>https://dev.to/jhatcher9999/a-tale-of-two-connection-pools-bld</link>
      <guid>https://dev.to/jhatcher9999/a-tale-of-two-connection-pools-bld</guid>
      <description>&lt;p&gt;It was the best of pools; it was the worst of pools...&lt;br&gt;
Ok, enough with the Dickensian references.&lt;/p&gt;
&lt;h2&gt;
  
  
  Connection Pools
&lt;/h2&gt;

&lt;p&gt;I have recently had two contrasting experiences with application-side, connection pooling frameworks when connecting to CockroachDB, and I thought I would share some of my findings.&lt;/p&gt;

&lt;p&gt;I am a Solutions Engineer at Cockroach Labs, so I often work with customers and prospects on testing out their codebase running against a CockroachDB cluster.  CockroachDB is a distributed SQL database (meaning a CockroachDB cluster is made up of many nodes).  This distributed DNA comes with obvious advantages like being about to do a rolling restart of the cluster nodes and not having to take the database down.  However, it's a good idea to make sure that your application is configured to handle a node restarts.&lt;/p&gt;

&lt;p&gt;For you application to gracefully handle node restarts, we typically recommend:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load Balancer - Point to a load balancer that is round-robin-ing traffic among the CRDB nodes and monitoring for DB node health (and taking dead nodes out of the spray).  This can be a simple L4, TCP load balancer.  If you're running CockroachDB in the our managed service offering, this load balancer is provided for you.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Connection Pool - Use a connection pool in your code (HikariCP is great for Java).  We would recommend a few settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;max pool size&lt;/em&gt; (recommendations on size here: &lt;a href="https://www.cockroachlabs.com/docs/stable/connection-pooling.html#sizing-connection-pools" rel="noopener noreferrer"&gt;https://www.cockroachlabs.com/docs/stable/connection-pooling.html#sizing-connection-pools&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;max connection lifetime&lt;/em&gt; - set to maybe 8 hours and tune it down as necessary; this will make sure that your pool has a balanced number of connections within 8 hours of a rolling restart event.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Include Retry Logic in your code - We recommend catching and retrying serialization retry (i.e., 40001) errors in code.  You can add a similar class of errors for 57* errors and 08* errors (see: &lt;a href="https://www.postgresql.org/docs/current/errcodes-appendix.html" rel="noopener noreferrer"&gt;https://www.postgresql.org/docs/current/errcodes-appendix.html&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's an example of a project I wrote in C# that shows this &lt;a href="https://github.com/jhatcher9999/csharp-samples/blob/main/SimpleDataService.cs#L149" rel="noopener noreferrer"&gt;retry logic in action&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These three elements (LB + CP + Retry Logic) interact with each other, and with all three in place, you will have a fool-proof approach to handling restarts in your code with zero impact.&lt;/p&gt;

&lt;p&gt;Here's a diagram to shows how this interaction works.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3hrytcuutsdhq93t83je.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3hrytcuutsdhq93t83je.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How to handle leaking state across session
&lt;/h2&gt;

&lt;p&gt;Connection pools are great in that they save us the latencies associated with opening new connections to our database, and they allow us to multiplex lots of queries across a fewer number of connections to the database.&lt;/p&gt;

&lt;p&gt;Now, let's talk about one risk associated with leveraging them.&lt;/p&gt;

&lt;p&gt;Each database connection instance has various metadata associated with it.  In the CockroachDB world (which is Postgres compatible), one such example of state is "session variables."  For instance, there is a session variable called &lt;code&gt;statement_timeout&lt;/code&gt; which, by default, has a value of 0 (which means unlimited).  Suppose you set this variable to 60s, do something with the connection and then return it to the pool.  Then, the next process to retrieve that particular connection from the pool will have the &lt;code&gt;statement_timeout&lt;/code&gt; value set to 60s and will have no notice that they're not getting a "clean" session/connection.&lt;/p&gt;

&lt;p&gt;To battle this idea of state leakage, some connection pool instances automatically run commands on connections when they're returned to the pool whose purpose is to restore all the state back to defaults.&lt;/p&gt;

&lt;p&gt;I was working on a proof-of-concept recently with a company who writes their code in C#/.NET and they were using NPGSQL (which is a Postgres driver library for .NET) to connect to CockroachDB.  When you use the connection pool builtin to this project and close a connection, &lt;a href="https://github.com/npgsql/npgsql/blob/001c5ef35cf29ec9109e9a8d2ae54108a5d7ac9e/src/Npgsql/Internal/NpgsqlConnector.cs#L2061" rel="noopener noreferrer"&gt;NPGSQL runs several commands&lt;/a&gt; against the connection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CLOSE ALL;&lt;/li&gt;
&lt;li&gt;UNLISTEN *;&lt;/li&gt;
&lt;li&gt;SELECT pg_advisory_unlock_all();&lt;/li&gt;
&lt;li&gt;DISCARD SEQUENCES;&lt;/li&gt;
&lt;li&gt;DISCARD TEMP;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It makes sense that a Postgres-specific driver would run Postgres-specific commands against a data source that is known to be Postgres (or Postgres-compatible).&lt;/p&gt;

&lt;p&gt;However, in the Java world, developers have many connection pooling toolsets from which to choose.  A popular choice is HikariCP.  HikariCP is not specific to a particular DB (like Postgres), but can be used against any JDBC Data Source (Oracle, MySQL, etc.). I have always assumed that Hikari does similar "resetting" logic but I tested that assumption this week and learned that it does not.  I suppose this makes sense since it would have to have knowledge about the "right" commands for each DB platform in order to reset state properly.&lt;/p&gt;

&lt;p&gt;So, I went about trying to make Hikari do this resetting for me.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code Testing
&lt;/h2&gt;

&lt;p&gt;First, I setup some simple code to show the problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;showBroken&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;SQLException&lt;/span&gt;  &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;HikariDataSource&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HikariDataSource&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setJdbcUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jdbc:postgresql://localhost:26257/default?sslmode=require"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setUsername&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"roach"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPassword&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"roach"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMaximumPoolSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;conn1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;Statement&lt;/span&gt; &lt;span class="n"&gt;stmt1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createStatement&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stmt1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"show statement_timeout;"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"value of statement_timeout before messing with it: "&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="nc"&gt;Statement&lt;/span&gt; &lt;span class="n"&gt;stmt2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createStatement&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;stmt2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"set statement_timeout = '60s';"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stmt1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"show statement_timeout;"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"value of statement_timeout after setting it: "&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;conn1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

            &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;conn2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;Statement&lt;/span&gt; &lt;span class="n"&gt;stmt3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createStatement&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stmt3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"show statement_timeout;"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"value of statement_timeout in new connection without changing anything: "&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;conn2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

            &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;


    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I run this code, I get this output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;value of statement_timeout before messing with it: 
0
value of statement_timeout after setting it: 
60000
value of statement_timeout in new connection without changing anything: 
60000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we're "leaking" the 60s value between sessions.  Not good!&lt;/p&gt;

&lt;p&gt;I found &lt;a href="https://github.com/brettwooldridge/HikariCP/issues/503" rel="noopener noreferrer"&gt;one suggestion&lt;/a&gt; from the author of HikariCP on how to address this, which I implemented and it worked.  However, there are additional classes involved, and it feels a little clunky and hard to follow.&lt;/p&gt;

&lt;p&gt;So, I reached out to my colleague, Kai Niemi, who is a seriously smart dude, and he gave me two other suggestions which I found to be much cleaner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1 - Use a proxy
&lt;/h3&gt;

&lt;p&gt;In this example, we use some Java Proxy + Reflection magic to create a HikariCP pool instance but we give ourselves a "hook" into the pooling events where we can insert a call to "DISCARD ALL;"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;runTest2&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;SQLException&lt;/span&gt;  &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;HikariDataSource&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HikariDataSource&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nd"&gt;@Override&lt;/span&gt;
                &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="nf"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;Proxy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newProxyInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                            &lt;span class="nc"&gt;DataSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClassLoader&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;},&lt;/span&gt;
                            &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"close"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                                    &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
                                    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Statement&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createStatement&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                                        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DISCARD ALL;"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                                    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                                        &lt;span class="c1"&gt;// not much to do, proceed with close&lt;/span&gt;
                                    &lt;span class="o"&gt;}&lt;/span&gt;
                                &lt;span class="o"&gt;}&lt;/span&gt;
                                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delegate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                            &lt;span class="o"&gt;});&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;};&lt;/span&gt;
            &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setJdbcUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jdbc:postgresql://localhost:26257/default?sslmode=require"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setUsername&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"roach"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPassword&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"roach"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMaximumPoolSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;conn1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;Statement&lt;/span&gt; &lt;span class="n"&gt;stmt1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createStatement&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stmt1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"show statement_timeout;"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"value of statement_timeout before messing with it: "&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="nc"&gt;Statement&lt;/span&gt; &lt;span class="n"&gt;stmt2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createStatement&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;stmt2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"set statement_timeout = '60s';"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stmt1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"show statement_timeout;"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"value of statement_timeout after setting it: "&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;conn1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

            &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;conn2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;Statement&lt;/span&gt; &lt;span class="n"&gt;stmt3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createStatement&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stmt3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"show statement_timeout;"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"value of statement_timeout in new connection without changing anything: "&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;conn2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

            &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;


    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output we get from this code shows that we're not leaking anymore - yay!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;value of statement_timeout before messing with it: 
0
value of statement_timeout after setting it: 
60000
value of statement_timeout in new connection without changing anything: 
0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option 2 - Use a library
&lt;/h3&gt;

&lt;p&gt;In this option, we wrap our HikariCP instance in another object.  The nice thing about this option is that it can be used with any connection pooling library (not just HikariCP); but, it does require that you add an additional dependency to your project: &lt;a href="https://github.com/jdbc-observations/datasource-proxy" rel="noopener noreferrer"&gt;https://github.com/jdbc-observations/datasource-proxy&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;runTest3&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

            &lt;span class="nc"&gt;HikariConfig&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HikariConfig&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setJdbcUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jdbc:postgresql://localhost:26257/default?sslmode=require"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setUsername&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"roach"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPassword&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"roach"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMaximumPoolSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="nc"&gt;DataSource&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProxyDataSourceBuilder&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HikariDataSource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JdbcLifecycleEventListenerAdapter&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                        &lt;span class="nd"&gt;@Override&lt;/span&gt;
                        &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;afterGetConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MethodExecutionContext&lt;/span&gt; &lt;span class="n"&gt;executionContext&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                            &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;executionContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getResult&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Statement&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createStatement&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DISCARD ALL;"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                                &lt;span class="c1"&gt;// whatever&lt;/span&gt;
                            &lt;span class="o"&gt;}&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;
                    &lt;span class="o"&gt;})&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

            &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;conn1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;Statement&lt;/span&gt; &lt;span class="n"&gt;stmt1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createStatement&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stmt1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"show statement_timeout;"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"value of statement_timeout before messing with it: "&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="nc"&gt;Statement&lt;/span&gt; &lt;span class="n"&gt;stmt2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createStatement&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;stmt2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"set statement_timeout = '60s';"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stmt1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"show statement_timeout;"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"value of statement_timeout after setting it: "&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;conn1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

            &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;conn2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;Statement&lt;/span&gt; &lt;span class="n"&gt;stmt3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createStatement&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;rs3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stmt3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"show statement_timeout;"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"value of statement_timeout in new connection without changing anything: "&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;conn2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, we see that the output proves that our leak has been plugged.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;value of statement_timeout before messing with it: 
0
value of statement_timeout after setting it: 
60000
value of statement_timeout in new connection without changing anything: 
0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>vibecoding</category>
    </item>
    <item>
      <title>Recursive CTEs in CockroachDB</title>
      <dc:creator>Jim Hatcher</dc:creator>
      <pubDate>Thu, 28 Jul 2022 16:58:56 +0000</pubDate>
      <link>https://dev.to/jhatcher9999/recursive-ctes-in-cockroachdb-46f</link>
      <guid>https://dev.to/jhatcher9999/recursive-ctes-in-cockroachdb-46f</guid>
      <description>&lt;p&gt;I have been thinking about how CockroachDB could be used to implement a very flexible authorization system.&lt;/p&gt;

&lt;p&gt;I have previous experience using Graph databases which differ from relational databases in several key ways -- but one big differentiator is that graph databases are good for "highly relational data" (and I don't mean relational in the RDBMS sense).  Graph databases are good at answering questions like, "Given a family tree, are persons X and Y related, and if so, what is their relationship?", or "In my LinkedIn professional network, am I connected to person X, and if so, how many 'hops' away are we?"&lt;/p&gt;

&lt;p&gt;Questions like this are tricky to answer in general because the "distance" between things is not known, but they can be particularly difficult to answer in relational databases using standard data models, highly-structured schemas, and join-based queries.&lt;/p&gt;

&lt;p&gt;For my authorization system needs, I want to be able these types of unstructured/unknown questions.  For instance: "If I'm a user in a system, and I'm a member of a groups A, B, C, do I have permission to do action X"  This is further complicated by the fact that I would want to allow a group hierarchy where a group can belong to another group which can belong to a tree of groups, and the necessary permission(s) can be attached to groups anywhere in that tree.&lt;/p&gt;

&lt;p&gt;Most relational databases -- including CockroachDB -- have a structure that can help with these type of "walk the tree" problems; this structure is called a "Recursive CTE."&lt;/p&gt;

&lt;p&gt;A normal, non-recursive &lt;a href="https://www.cockroachlabs.com/docs/stable/common-table-expressions.html"&gt;CTE&lt;/a&gt; (Common Table Expression) is cool in and of itself since it allows you to create a query that can be re-used more than once in a series of queries.  Beyond facilitating re-use, they can also simply your queries by eliminating complex derived tables which helps to create more easily maintainable SQL code.&lt;/p&gt;

&lt;p&gt;A recursive CTE adds another layer of goodness because it allows recursive execution of the expression.&lt;/p&gt;

&lt;p&gt;Let's look at an example of a non-recursive CTE borrowed from the CockroachDB docs:&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;WITH&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;rides&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;revenue&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;98&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="c1"&gt;-- here we're joining to the 'r' CTE&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rider_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use a recursive CTE, we need a slightly different syntax.  Let's have a look at an example:&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;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;cte&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;-- initial subquery&lt;/span&gt;
    &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;factorial&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cte&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="c1"&gt;-- recursive subquery&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cte&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recursive CTEs have to have the keyword "RECURSIVE", as well as an initial statement (a starting point) UNIONed together with a recursive query.  There can also optionally be a field list which is used to explicitly name the columns returned in the CTE table-valued structure.&lt;/p&gt;

&lt;p&gt;Now, back to the example of an authorization system.  I created a GitHub repo which has an example with some example data on how such an authorization system could look in CockroachDB and how that data could be effectively queried using a recursive CTE.&lt;br&gt;
&lt;a href="https://github.com/cockroachlabs-field/perms-example"&gt;https://github.com/cockroachlabs-field/perms-example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I won't repeat the contents of the GitHub repo here, but here's an example of the CTE I'm using there.  We're asking the question: Does the identity 'Andy, GM' have rights to read data from the Customer table?&lt;br&gt;
If the query returns results, the permission exists; if no results are returned, the permission doesn't exist.&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;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;roles_hierarchy_cte&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;

    &lt;span class="c1"&gt;--query all the roles for the user we care about&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;perms_example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role_instance&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
    &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;perms_example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity_role_assignment&lt;/span&gt; &lt;span class="n"&gt;ira&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ira&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ira&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'aaaaaaaa-1111-1111-1111-111111111111'&lt;/span&gt; &lt;span class="c1"&gt;-- Andy, GM&lt;/span&gt;

    &lt;span class="k"&gt;UNION&lt;/span&gt;

    &lt;span class="c1"&gt;--walk up the tree and find any other parent roles&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;rh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;child_rid&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;perms_example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role_hierarchy&lt;/span&gt; &lt;span class="n"&gt;rh&lt;/span&gt;
    &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;roles_hierarchy_cte&lt;/span&gt; &lt;span class="n"&gt;rhc&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;rhc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent_rid&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="cm"&gt;/* get all the permissions that this identity is assigned to directly */&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;role_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;perms_example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity_instance&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;perms_example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity_permission_assignment&lt;/span&gt; &lt;span class="n"&gt;ipa&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ipa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iid&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;perms_example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission_instance&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;ipa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'aaaaaaaa-1111-1111-1111-111111111111'&lt;/span&gt; &lt;span class="c1"&gt;-- Andy, GM&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'cccccccc-1111-1111-1111-111111111111'&lt;/span&gt; &lt;span class="c1"&gt;-- 'Read Customer Data&lt;/span&gt;

&lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;

&lt;span class="cm"&gt;/* also, get any permissions that this identity picks up due to role permissions
     and also role permissions from the role hierarchy */&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;perms_example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role_instance&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;perms_example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role_permission_assignment&lt;/span&gt; &lt;span class="n"&gt;rpa&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rpa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;perms_example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission_instance&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;rpa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;
&lt;span class="c1"&gt;--get me 1 identity row so I can include that on the output&lt;/span&gt;
&lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity_name&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;perms_example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity_instance&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'aaaaaaaa-1111-1111-1111-111111111111'&lt;/span&gt; &lt;span class="c1"&gt;--Andy, GM&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;rhc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;roles_hierarchy_cte&lt;/span&gt; &lt;span class="n"&gt;rhc&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'cccccccc-1111-1111-1111-111111111111'&lt;/span&gt; &lt;span class="c1"&gt;-- 'view customer data&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do you have other examples where recursive CTEs may be useful? &lt;br&gt;
 Post them in the comments!&lt;/p&gt;

&lt;p&gt;Happy recursing!&lt;/p&gt;

</description>
      <category>sql</category>
      <category>cockroachdb</category>
      <category>datamodeling</category>
      <category>cte</category>
    </item>
    <item>
      <title>Apache Spark job to update CockroachDB data</title>
      <dc:creator>Jim Hatcher</dc:creator>
      <pubDate>Fri, 04 Feb 2022 14:26:56 +0000</pubDate>
      <link>https://dev.to/jhatcher9999/apache-spark-job-to-update-cockroachdb-data-bmi</link>
      <guid>https://dev.to/jhatcher9999/apache-spark-job-to-update-cockroachdb-data-bmi</guid>
      <description>&lt;p&gt;Apache Spark is a distributed execution framework which is a wonderfully complementary tool for working with distributed systems.&lt;/p&gt;

&lt;p&gt;When working in Spark, my go-to interface is Spark SQL since I can leverage my SQL skills to get at the data I want.  However, you can't do any kind of data mutations via SparkSQL (only reads).  For writes, you have to use the DataFrames/DataSets or the RDD interfaces.&lt;/p&gt;

&lt;p&gt;If you're doing an initial import of a table from CSV (or something similar), you can do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="nv"&gt;dataFrameCSV&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;write&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jdbc"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"url"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"jdbc:postgresql:dbserver"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dbtable"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"schema.tablename"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;save&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a great option for cases when you're either loading the whole table for the first time.&lt;/p&gt;

&lt;p&gt;But, for cases when you want to update some of the records in the table, this &lt;code&gt;write()&lt;/code&gt; interface is not great because Spark will either complain that the table already has records, or if you tell Spark to overwrite, it will wipe out the records in your table.&lt;/p&gt;

&lt;p&gt;So, for more nuanced mutations, you can drop down into the java.sql library and have Spark execute batches of updates.&lt;/p&gt;

&lt;p&gt;Here's how I went about doing this.&lt;/p&gt;

&lt;p&gt;First, I needed to install Spark on my environment (my MacBook), so I used brew to install:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scala 2.12 (Spark 3 doesn't like Scala 2.11)&lt;/li&gt;
&lt;li&gt;Oracle Java 11 (Spark doesn't seem to like more recent OpenJDK versions)&lt;/li&gt;
&lt;li&gt;Spark (Spark 3.2.1 is the latest as of the time of me writing this blog)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I already had a Postgres driver installed via Maven, but you can install this too, if necessary.&lt;/p&gt;

&lt;p&gt;I ran the spark shell using this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spark-shell &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--driver-class-path&lt;/span&gt; /Users/jimhatcher/.m2/repository/org/postgresql/postgresql/42.2.19/postgresql-42.2.19.jar &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--jars&lt;/span&gt; /Users/jimhatcher/.m2/repository/org/postgresql/postgresql/42.2.19/postgresql-42.2.19.jar &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--conf&lt;/span&gt; spark.jars&lt;span class="o"&gt;=&lt;/span&gt;/Users/jimhatcher/.m2/repository/org/postgresql/postgresql/42.2.19/postgresql-42.2.19.jar &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--conf&lt;/span&gt; spark.executor.memory&lt;span class="o"&gt;=&lt;/span&gt;4g &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--conf&lt;/span&gt; spark.driver.memory&lt;span class="o"&gt;=&lt;/span&gt;4g
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spark runs as three separate components: the Spark master, the driver, and the executor(s).  I passed references to the Postgres jar in several parameters to make sure that the various pieces knew how to reference the Postgres driver.&lt;/p&gt;

&lt;p&gt;I also bumped up the memory available to the executor and drivers.&lt;/p&gt;

&lt;p&gt;The next thing I needed to do was to make sure that I had good certs that I could use to access my CockroachDB instance.  When CRDB issues keys, it uses a PEM format.  Using JDBC (as we will in this Spark job), Java doesn't mind the certs, but it wants the keys in a DER format.  So, I ran the following command to convert my client key to DER:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl pkcs8 &lt;span class="nt"&gt;-topk8&lt;/span&gt; &lt;span class="nt"&gt;-inform&lt;/span&gt; PEM &lt;span class="nt"&gt;-outform&lt;/span&gt; DER &lt;span class="nt"&gt;-in&lt;/span&gt; client.root.key &lt;span class="nt"&gt;-out&lt;/span&gt; client.root.key.der &lt;span class="nt"&gt;-nocrypt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wrote the following program to read a table from Cockroach, find the records where a certain field was null and then update that field.&lt;/p&gt;

&lt;p&gt;After some trial and error, this is what I ended up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.sql._&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.apache.spark.sql._&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"jdbc:postgresql://&amp;lt;host_name_here&amp;gt;:26257/test?sslmode=require&amp;amp;sslcert=/Users/jimhatcher/spark-cluster-certs/client.root.crt&amp;amp;sslkey=/Users/jimhatcher/spark-cluster-certs/client.root.key.der&amp;amp;sslrootcert=/Users/jimhatcher/spark-cluster-certs/ca.crt&amp;amp;reWriteBatchedInserts=true"&lt;/span&gt;
&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;sql&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"( select id from test.test_table where f1 IS NULL) t1"&lt;/span&gt;
&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;df&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;spark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;read&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jdbc"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"url"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dbtable"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"partitionColumn"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"lowerBound"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"upperBound"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"30000000"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;option&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"numPartitions"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"10"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;load&lt;/span&gt;

&lt;span class="nv"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;coalesce&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;foreachPartition&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Iterator&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Row&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;dbc&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;DriverManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;batchSize&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;st&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PreparedStatement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;dbc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;prepareStatement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UPDATE test.test_table SET f1 = 1 WHERE id = ?"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="nv"&gt;partition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;grouped&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batchSize&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;foreach&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;batch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;foreach&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;st&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; 
            &lt;span class="nv"&gt;st&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;addBatch&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="nv"&gt;st&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;executeBatch&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="nv"&gt;dbc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;close&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran this in the Spark shell by copying/pasting.  (The ":paste" command is nice for this.)&lt;/p&gt;

&lt;p&gt;This program wasn't particularly fast.  I think there's some optimization I could do against it.  I also didn't run it on a real Spark cluster.  One super cool thing about Spark is that it's so easy to parallelize jobs like this by throwing more hardware resources at them.&lt;/p&gt;

&lt;p&gt;I'd like to go back and try to optimize this further.  But in the meantime, hopefully this is a decent example that folks can build on for doing Spark jobs against CockroachDB.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
