<?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: Fabio Ghirardello</title>
    <description>The latest articles on DEV Community by Fabio Ghirardello (@fabiog1901).</description>
    <link>https://dev.to/fabiog1901</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%2F469108%2Fa6ea006d-39ce-42b8-bada-fd550237fbfe.png</url>
      <title>DEV Community: Fabio Ghirardello</title>
      <link>https://dev.to/fabiog1901</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fabiog1901"/>
    <language>en</language>
    <item>
      <title>CockroachDB Graceful Shutdowns</title>
      <dc:creator>Fabio Ghirardello</dc:creator>
      <pubDate>Thu, 09 Oct 2025 13:30:58 +0000</pubDate>
      <link>https://dev.to/cockroachlabs/cockroachdb-graceful-shutdowns-4dn</link>
      <guid>https://dev.to/cockroachlabs/cockroachdb-graceful-shutdowns-4dn</guid>
      <description>&lt;p&gt;This is a brief extension to the &lt;strong&gt;Node Shutdown&lt;/strong&gt; section discussed in blog &lt;a href="https://dev.to/cockroachlabs/repaving-cockroachdb-cluster-node-vms-the-easy-way-2cfg"&gt;Repaving CockroachDB cluster node VMs the easy way&lt;/a&gt;. In this blog, we go through some hands-on experiments which will help you observe the client, load-balancer and CockroachDB nodes behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a local 9 node cluster
&lt;/h2&gt;

&lt;p&gt;Start a 3x3 node cluster, locally.&lt;br&gt;
Run each of these commands in its own terminal window.&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;# NY region&lt;/span&gt;
cockroach start &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;node1 &lt;span class="nt"&gt;--listen-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26291 &lt;span class="nt"&gt;--http-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:8091 &lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26291,localhost:26281,localhost:26271 &lt;span class="nt"&gt;--locality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;NY,dc&lt;span class="o"&gt;=&lt;/span&gt;a
cockroach start &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;node2 &lt;span class="nt"&gt;--listen-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26292 &lt;span class="nt"&gt;--http-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:8092 &lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26291,localhost:26281,localhost:26271 &lt;span class="nt"&gt;--locality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;NY,dc&lt;span class="o"&gt;=&lt;/span&gt;b
cockroach start &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;node3 &lt;span class="nt"&gt;--listen-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26293 &lt;span class="nt"&gt;--http-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:8093 &lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26291,localhost:26281,localhost:26271 &lt;span class="nt"&gt;--locality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;NY,dc&lt;span class="o"&gt;=&lt;/span&gt;c

&lt;span class="c"&gt;# TX region&lt;/span&gt;
cockroach start &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;node4 &lt;span class="nt"&gt;--listen-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26281 &lt;span class="nt"&gt;--http-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:8081 &lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26291,localhost:26281,localhost:26271 &lt;span class="nt"&gt;--locality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TX,dc&lt;span class="o"&gt;=&lt;/span&gt;a
cockroach start &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;node5 &lt;span class="nt"&gt;--listen-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26282 &lt;span class="nt"&gt;--http-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:8082 &lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26291,localhost:26281,localhost:26271 &lt;span class="nt"&gt;--locality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TX,dc&lt;span class="o"&gt;=&lt;/span&gt;b
cockroach start &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;node6 &lt;span class="nt"&gt;--listen-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26283 &lt;span class="nt"&gt;--http-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:8083 &lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26291,localhost:26281,localhost:26271 &lt;span class="nt"&gt;--locality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TX,dc&lt;span class="o"&gt;=&lt;/span&gt;c

&lt;span class="c"&gt;# CA region&lt;/span&gt;
cockroach start &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;node7 &lt;span class="nt"&gt;--listen-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26271 &lt;span class="nt"&gt;--http-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:8071 &lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26291,localhost:26281,localhost:26271 &lt;span class="nt"&gt;--locality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;CA,dc&lt;span class="o"&gt;=&lt;/span&gt;a
cockroach start &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;node8 &lt;span class="nt"&gt;--listen-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26272 &lt;span class="nt"&gt;--http-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:8072 &lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26291,localhost:26281,localhost:26271 &lt;span class="nt"&gt;--locality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;CA,dc&lt;span class="o"&gt;=&lt;/span&gt;b
cockroach start &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;node9 &lt;span class="nt"&gt;--listen-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26273 &lt;span class="nt"&gt;--http-addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:8073 &lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:26291,localhost:26281,localhost:26271 &lt;span class="nt"&gt;--locality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;CA,dc&lt;span class="o"&gt;=&lt;/span&gt;c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Init the cluster, then start a SQL prompt&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cockroach init &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--port&lt;/span&gt; 26291

cockroach sql &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--port&lt;/span&gt; 26291
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the DB Console at &lt;a href="http://localhost:8091/#/reports/localities" rel="noopener noreferrer"&gt;http://localhost:8091/#/reports/localities&lt;/a&gt; to review the topology.&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%2Fkrfcxfiims6srddy4lwu.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%2Fkrfcxfiims6srddy4lwu.png" alt="localities" width="800" height="970"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We configure cluster settings so that the server pauses for 45s before initialing the drain, and 70s for the connection pool to close off all existing connections.&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;shutdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initial_wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'45s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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;shutdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'70s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  HAProxy configuration
&lt;/h2&gt;

&lt;p&gt;Check the HAProxy &lt;code&gt;.cfg&lt;/code&gt; files.&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;# haproxy-ny.cfg&lt;/span&gt;

global
  maxconn 4096

defaults
    mode                tcp
    retries             3
    &lt;span class="nb"&gt;timeout &lt;/span&gt;connect     10s
    &lt;span class="nb"&gt;timeout &lt;/span&gt;client      10m
    &lt;span class="nb"&gt;timeout &lt;/span&gt;server      10m
    option              clitcpka

listen psql
    &lt;span class="nb"&gt;bind&lt;/span&gt; :26290
    mode tcp
    balance roundrobin
    option httpchk GET /health?ready&lt;span class="o"&gt;=&lt;/span&gt;1
    default-server inter 10s fall 3 rise 2
    server cockroach1 127.0.0.1:26291 check port 8091
    server cockroach2 127.0.0.1:26292 check port 8092
    server cockroach3 127.0.0.1:26293 check port 8093

    &lt;span class="c"&gt;# Secondary (TX LB)&lt;/span&gt;
    server tx_lb 127.0.0.1:26280 check port 8080 backup

    &lt;span class="c"&gt;# Tertiary (CA LB)&lt;/span&gt;
    server ca_lb 127.0.0.1:26270 check port 8070 backup

listen http
    &lt;span class="nb"&gt;bind&lt;/span&gt; :8090
    mode tcp
    balance roundrobin
    server cockroach1 127.0.0.1:8091
    server cockroach2 127.0.0.1:8092
    server cockroach3 127.0.0.1:8093
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# haproxy-tx.cfg&lt;/span&gt;

global
  maxconn 4096

defaults
    mode                tcp
    retries             3
    &lt;span class="nb"&gt;timeout &lt;/span&gt;connect     10s
    &lt;span class="nb"&gt;timeout &lt;/span&gt;client      10m
    &lt;span class="nb"&gt;timeout &lt;/span&gt;server      10m
    option              clitcpka

listen psql
    &lt;span class="nb"&gt;bind&lt;/span&gt; :26280
    mode tcp
    balance roundrobin
    option httpchk GET /health?ready&lt;span class="o"&gt;=&lt;/span&gt;1
    default-server inter 10s fall 3 rise 2
    server cockroach1 127.0.0.1:26281 check port 8081
    server cockroach2 127.0.0.1:26282 check port 8082
    server cockroach3 127.0.0.1:26283 check port 8083

    &lt;span class="c"&gt;# Secondary (NY LB)&lt;/span&gt;
    server ny_lb 127.0.0.1:26290 check port 8090 backup

    &lt;span class="c"&gt;# Tertiary (CA LB)&lt;/span&gt;
    server ca_lb 127.0.0.1:26270 check port 8070 backup

listen http
    &lt;span class="nb"&gt;bind&lt;/span&gt; :8080
    mode tcp
    balance roundrobin
    server cockroach1 127.0.0.1:8081
    server cockroach2 127.0.0.1:8082
    server cockroach3 127.0.0.1:8083
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# haproxy-ca.cfg&lt;/span&gt;
global
  maxconn 4096

defaults
    mode                tcp
    retries             3
    &lt;span class="nb"&gt;timeout &lt;/span&gt;connect     10s
    &lt;span class="nb"&gt;timeout &lt;/span&gt;client      10m
    &lt;span class="nb"&gt;timeout &lt;/span&gt;server      10m
    option              clitcpka

listen psql
    &lt;span class="nb"&gt;bind&lt;/span&gt; :26270
    mode tcp
    balance roundrobin
    option httpchk GET /health?ready&lt;span class="o"&gt;=&lt;/span&gt;1
    default-server inter 10s fall 3 rise 2
    server cockroach1 127.0.0.1:26271 check port 8071
    server cockroach2 127.0.0.1:26272 check port 8072
    server cockroach3 127.0.0.1:26273 check port 8073

    &lt;span class="c"&gt;# Secondary (TX LB)&lt;/span&gt;
    server tx_lb 127.0.0.1:26280 check port 8080 backup

    &lt;span class="c"&gt;# Tertiary (NY LB)&lt;/span&gt;
    server ny_lb 127.0.0.1:26290 check port 8090 backup

listen http
    &lt;span class="nb"&gt;bind&lt;/span&gt; :8070
    mode tcp
    balance roundrobin
    server cockroach1 127.0.0.1:8071
    server cockroach2 127.0.0.1:8072
    server cockroach3 127.0.0.1:8073
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that we have lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;option httpchk GET /health?ready=1
default-server inter 10s fall 3 rise 2

server tx_lb 127.0.0.1:26280 check port 8080 backup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HAProxy will&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;probe every 10s and declare a server down after 3 failed attempts.&lt;/li&gt;
&lt;li&gt;probe server endpoint &lt;code&gt;/health?ready=1&lt;/code&gt; for server health.&lt;/li&gt;
&lt;li&gt;if all servers in the pool are down, we have backup servers available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start the Load Balancers, running each command in its own terminal window.&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;# NY LB&lt;/span&gt;
haproxy &lt;span class="nt"&gt;-f&lt;/span&gt; haproxy-ny.cfg

&lt;span class="c"&gt;# TX LB&lt;/span&gt;
haproxy &lt;span class="nt"&gt;-f&lt;/span&gt; haproxy-tx.cfg

&lt;span class="c"&gt;# CA LB&lt;/span&gt;
haproxy &lt;span class="nt"&gt;-f&lt;/span&gt; haproxy-ca.cfg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running the Client App
&lt;/h2&gt;

&lt;p&gt;The simple python app &lt;code&gt;test.py&lt;/code&gt; creates a connection pool, then queries and prints the elapsed time, the node location and the connection object details.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;psycopg_pool&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConnectionPool&lt;/span&gt;

&lt;span class="c1"&gt;# note we specify the NY, TX, CA LBs in the connection string.
# by default, the first host is chosen unless the host is down
&lt;/span&gt;
&lt;span class="n"&gt;DB_DSN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres://root@localhost:26290,localhost:26280,localhost:26270/defaultdb?sslmode=disable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="c1"&gt;# Create a pool with exactly 5 connections
# the `check` params checks the validity of the connection before handing it out of the pool.
# if the conneciton is unhealthy, a new one will be recreated.
&lt;/span&gt;
&lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConnectionPool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;conninfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DB_DSN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;min_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_lifetime&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;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ConnectionPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;autocommit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&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;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&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;cur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;rs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;show locality&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&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 sufficient to test connections health, and it allows to observe what connection we are using and to what server node is connected.&lt;/p&gt;

&lt;p&gt;Notice in the &lt;code&gt;test.py&lt;/code&gt; file, in the &lt;code&gt;pool&lt;/code&gt; init command, we set a &lt;code&gt;max_lifetime=60&lt;/code&gt; seconds.&lt;br&gt;
Also, we have set that connections should be validated before handed out using &lt;code&gt;check=ConnectionPool.check_connection&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also, this app will only connect to the NY LB, however, in the connection string I have also configured the TX and CA loadbalancers as failback options.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;DB_DSN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres://root@localhost:26290,localhost:26280,localhost:26270/defaultdb?sslmode=disable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the app.&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;# you first need to install the dependencies&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;psycopg[pool] psycopg-binary

&lt;span class="c"&gt;# now you can run the app&lt;/span&gt;
python test.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let the app run continuosly.&lt;/p&gt;

&lt;p&gt;Notice that we are connected to the NY LB, identifiable by port 26290, and are correctly reaching a cockroachdb NY based node, &lt;code&gt;region=NY,dc=a&lt;/code&gt;.&lt;br&gt;
Finally, keep an eye on the &lt;code&gt;0x1041...&lt;/code&gt; memory address, which identifies the connection object.&lt;br&gt;
You will see there are 5 distinct objects, but after 60s, these will be replaced by a new set of connections.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5 ('region=NY,dc=a',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x104161090&amp;gt;
6 ('region=NY,dc=b',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x104147310&amp;gt;
6 ('region=NY,dc=c',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x104146e10&amp;gt;

11 ('region=NY,dc=a',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x104161090&amp;gt;
11 ('region=NY,dc=a',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x101d55210&amp;gt;
11 ('region=NY,dc=b',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x104147310&amp;gt;

16 ('region=NY,dc=c',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x104146810&amp;gt;
16 ('region=NY,dc=c',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x104146e10&amp;gt;
16 ('region=NY,dc=a',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x104161090&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If below tests succeed, you will NOT see any error messages, and instead all events have been handled gracefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 1 - node shutdown
&lt;/h2&gt;

&lt;p&gt;We test a node going down for maintenance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick any of the terminals where you started a &lt;strong&gt;NY&lt;/strong&gt; node, and &lt;code&gt;Ctrl+C&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The node will start a graceful shutdown.&lt;/li&gt;
&lt;li&gt;The node sets its healthcheck endpoint to HTTP503, signaling to the LB that the server is unhealthy.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;initial_wait&lt;/code&gt; timeout will start.&lt;/li&gt;
&lt;li&gt;The server is however still working. Both existing and new connections succeed.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After about 30s, the LB has declared this server as down and will be removed from the LB pool.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[WARNING]  (42103) : Server psql/cockroach1 is DOWN, 
reason: Layer7 wrong status, code: 503, 
info: "Service Unavailable", check duration: 0ms. 
2 active and 2 backup servers left. 
0 sessions active, 0 requeued, 0 remaining in queue.
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Therefore, new connection requests will be routed elsewhere.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After 45s, the &lt;code&gt;initial_wait&lt;/code&gt; is done and the node starts to drain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;connections_timeout&lt;/code&gt; of 75s kicks in. Existing connections are allowed to continue working, waiting for the connection pool to retire them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After about 60s, the connection pool will have retired all existing connections and recreated them elsewhere.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;connections_timeout&lt;/code&gt; expires, and any remaining connection will be severed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Eventually, node shutdown completes, gracefully.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The app won't have noticed any disruption.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bring the node back up.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Test 2 - datacenter shutdown
&lt;/h2&gt;

&lt;p&gt;In this test, all nodes in a given datacenter go down. The NY load balancer should route all traffic to TX LB.&lt;/p&gt;

&lt;p&gt;Note from the HAProxy config file, I have added &lt;code&gt;backup&lt;/code&gt; servers in case the 3 primary servers fail, and note that the 2 backup servers are the LB of the other regions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start a graceful shutdown for all 3 NY servers, using &lt;code&gt;Ctrl+C&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;initial_wait&lt;/code&gt; timeout kicks in, and the healthcheck endpoint returns HTTP503.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After about 30s, the NY HAProxy will mark all NY nodes as unavailable, and route new connections to the TX LB.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[WARNING]  (90981) : Server psql/cockroach1 is DOWN, 
reason: Layer7 wrong status, code: 503, 
info: "Service Unavailable", check duration: 0ms. 
2 active and 2 backup servers left. 1 sessions active, 0 requeued, 0 remaining in queue.

[WARNING]  (90981) : Server psql/cockroach2 is DOWN, 
reason: Layer7 wrong status, code: 503, 
info: "Service Unavailable", check duration: 0ms. 
1 active and 2 backup servers left. 2 sessions active, 0 requeued, 0 remaining in queue.

[WARNING]  (90981) : Server psql/cockroach3 is DOWN, 
reason: Layer7 wrong status, code: 503, 
info: "Service Unavailable", check duration: 0ms. 
0 active and 2 backup servers left. 
Running on backup. 2 sessions active, 0 requeued, 0 remaining in queue.
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;initial_wait&lt;/code&gt; is completed, and the &lt;code&gt;connections_timeout&lt;/code&gt; kicks in. New connections will not be accepted.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After about 60s, all connections have been closed by the conn pool.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Everything proceeds like before. Despite all nodes going down, the app still found a way to the CockroachDB cluster, in this case, to TX, see below the transition from NY to TX, still via the NY LB (port 26290)&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;125 ('region=NY,dc=b',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x10b940490&amp;gt;
125 ('region=NY,dc=c',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x10b925590&amp;gt;
125 ('region=TX,dc=b',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x10b91f110&amp;gt;

130 ('region=NY,dc=b',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x10b91e390&amp;gt;
130 ('region=TX,dc=c',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x10b91ce90&amp;gt;
130 ('region=TX,dc=b',) &amp;lt;psycopg.Connection [IDLE] (host=localhost port=26290 user=root database=defaultdb) at 0x10b9405d0&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bring all 3 nodes back up. Soon, the LB will start rerouting new connections to the NY nodes again.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Test 3 - LB goes down
&lt;/h2&gt;

&lt;p&gt;In this test, we simulate the NY LB crashing.&lt;/p&gt;

&lt;p&gt;When the NY LB goes down, all its connections will be broken, so they need to be recreated.&lt;br&gt;
I have instantiated the connection pool with the &lt;code&gt;check&lt;/code&gt; parameter so that every connection is health-checked before it is given to the caller.&lt;/p&gt;

&lt;p&gt;The pool will verify the connection, identify that it is broken, delete it and create a new one.&lt;br&gt;
As however the NY LB is down, the pool will use the 2nd host listed in the connection string, which is the TX LB.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stop the NY LB by using &lt;code&gt;Ctrl+C&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check the app output. Notice that without throwing any error, connections now are established against port 26280, which is TX.&lt;/li&gt;
&lt;li&gt;Bring the NY LB back up.&lt;/li&gt;
&lt;li&gt;After all connections have been recycled, after 60s, you will see that new connections are routed back to 26270, NY.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By careful configuration, you can handle all services disruption gracefully; even if you don't use HAProxy as your LB solution, you will find in your LB very similar configuration options.&lt;/p&gt;

&lt;p&gt;It is important to understand that &lt;strong&gt;all 3 services&lt;/strong&gt; need to be configured, not just CockroachDB.&lt;/p&gt;

&lt;p&gt;The example tests in this blog should help you validate your own setup, even if you don't use &lt;code&gt;psycopg_pool&lt;/code&gt; as your connection pool. For Java apps, HikariCP can do exactly the same things.&lt;/p&gt;

&lt;p&gt;You can now stop all processes and delete the &lt;code&gt;node*&lt;/code&gt; directory in your filesystem.&lt;/p&gt;

</description>
      <category>cockroachdb</category>
    </item>
    <item>
      <title>CockroachDB SSO integration using Keycloak</title>
      <dc:creator>Fabio Ghirardello</dc:creator>
      <pubDate>Fri, 06 Jun 2025 15:54:39 +0000</pubDate>
      <link>https://dev.to/cockroachlabs/cockroachdb-sso-integration-using-keycloak-4b17</link>
      <guid>https://dev.to/cockroachlabs/cockroachdb-sso-integration-using-keycloak-4b17</guid>
      <description>&lt;p&gt;In this blog post, I will cover the steps required to enable SSO in a CockroachDB cluster, specifically, when running locally on MacOS, but the steps are similar for any environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;We need to install the following software:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CockroachDB&lt;/li&gt;
&lt;li&gt;PostgreSQL Server&lt;/li&gt;
&lt;li&gt;OpenJDK&lt;/li&gt;
&lt;li&gt;Keycloak&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h3&gt;
  
  
  PostgreSQL Server
&lt;/h3&gt;

&lt;p&gt;Install PostgreSQL Server for MacOS: &lt;a href="https://postgresapp.com/" rel="noopener noreferrer"&gt;https://postgresapp.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Upon installation you will see the elephant in your toolbar close to the clock&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%2Fabq0ibwjszhysoy4k7f3.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%2Fabq0ibwjszhysoy4k7f3.png" alt="posgresapp" width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure you create a username and password with admin privileges, and start the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  CockroachDB
&lt;/h3&gt;

&lt;p&gt;Make sure CRDB is running locally and listens on port 8080.&lt;br&gt;
Instructions on where to download the binary is on the &lt;a href="https://www.cockroachlabs.com/docs/releases" rel="noopener noreferrer"&gt;Release page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is the command I use&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/local/bin/cockroach start-single-node &lt;span class="nt"&gt;--certs-dir&lt;/span&gt; /Users/fabio/certs &lt;span class="nt"&gt;--store&lt;/span&gt; /Users/fabio/cockroach-data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use my fancy &lt;a href="https://github.com/fabiog1901/macos_crdb_launcher" rel="noopener noreferrer"&gt;CRDB Launcher for MacOS&lt;/a&gt;. It's not strictly required, but I loved how the PostgresApp menu bar looks and wanted the same for CockroachDB.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenJDK 21+
&lt;/h3&gt;

&lt;p&gt;Keycloak requires Java 21+, so I installed openJDK from &lt;code&gt;brew&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;brew &lt;span class="nb"&gt;install &lt;/span&gt;openjdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, I was still seeing Java 14 when issuing a &lt;code&gt;java -version&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Check for solutions in the keg 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="nv"&gt;$ &lt;/span&gt;brew info openjdk
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; openjdk: stable 23.0.2 &lt;span class="o"&gt;(&lt;/span&gt;bottled&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;keg-only]
Development kit &lt;span class="k"&gt;for &lt;/span&gt;the Java programming language
https://openjdk.java.net/
Installed
/usr/local/Cellar/openjdk/23.0.2 &lt;span class="o"&gt;(&lt;/span&gt;602 files, 337.0MB&lt;span class="o"&gt;)&lt;/span&gt;
  Poured from bottle using the formulae.brew.sh API on 2025-06-05 at 13:28:19
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/o/openjdk.rb
License: GPL-2.0-only WITH Classpath-exception-2.0
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Dependencies
Build: autoconf ✘, pkgconf ✘
Required: freetype ✔, giflib ✔, harfbuzz ✔, jpeg-turbo ✔, libpng ✔, little-cms2 ✔
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Requirements
Build: Xcode &lt;span class="o"&gt;(&lt;/span&gt;on macOS&lt;span class="o"&gt;)&lt;/span&gt; ✔
Required: macOS &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; 10.15 &lt;span class="o"&gt;(&lt;/span&gt;or Linux&lt;span class="o"&gt;)&lt;/span&gt; ✔
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Caveats
For the system Java wrappers to find this JDK, symlink it with
  &lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-sfn&lt;/span&gt; /usr/local/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk

&lt;span class="o"&gt;[&lt;/span&gt;...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I thus run as suggested&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 ln&lt;/span&gt; &lt;span class="nt"&gt;-sfn&lt;/span&gt; /usr/local/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and I now get&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;java &lt;span class="nt"&gt;-version&lt;/span&gt;
openjdk version &lt;span class="s2"&gt;"23.0.2"&lt;/span&gt; 2025-01-21
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Keycloak
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Installation and basic config
&lt;/h4&gt;

&lt;p&gt;Download the Keycloak binaries for the OpenJDK platform, see link in the &lt;a href="https://www.Keycloak.org/getting-started/getting-started-zip#_download_keycloak" rel="noopener noreferrer"&gt;OpenJDK Getting Started&lt;/a&gt; page.&lt;/p&gt;

&lt;p&gt;Unzip it, cd into the dir and issue the start command.&lt;br&gt;
We just want to confirm there are no problem getting it up and running.&lt;/p&gt;

&lt;p&gt;Note, I use port 8081 as the default, 8080, is reserved for my locally running CockroachDB cluster.&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;bin/kc.sh start-dev &lt;span class="nt"&gt;--http-port&lt;/span&gt; 8081
Running the server &lt;span class="k"&gt;in &lt;/span&gt;development mode. DO NOT use this configuration &lt;span class="k"&gt;in &lt;/span&gt;production.
2025-06-05 13:45:53,073 INFO  &lt;span class="o"&gt;[&lt;/span&gt;org.Keycloak.quarkus.runtime.storage.infinispan.CacheManagerFactory] &lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt; Starting Infinispan embedded cache manager
2025-06-05 13:45:53,144 INFO  &lt;span class="o"&gt;[&lt;/span&gt;org.Keycloak.quarkus.runtime.storage.infinispan.CacheManagerFactory] &lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt; JGroups JDBC_PING discovery enabled.
2025-06-05 13:45:53,270 INFO  &lt;span class="o"&gt;[&lt;/span&gt;org.infinispan.CONTAINER] &lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt; Virtual threads support enabled
2025-06-05 13:45:53,463 INFO  &lt;span class="o"&gt;[&lt;/span&gt;org.infinispan.CONTAINER] &lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt; ISPN000556: Starting user marshaller &lt;span class="s1"&gt;'org.infinispan.commons.marshall.ImmutableProtoStreamMarshaller'&lt;/span&gt;
2025-06-05 13:45:53,832 INFO  &lt;span class="o"&gt;[&lt;/span&gt;org.Keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory] &lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt; Node name: node_904367, Site name: null
2025-06-05 13:45:54,791 INFO  &lt;span class="o"&gt;[&lt;/span&gt;io.quarkus] &lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt; Keycloak 26.2.5 on JVM &lt;span class="o"&gt;(&lt;/span&gt;powered by Quarkus 3.20.1&lt;span class="o"&gt;)&lt;/span&gt; started &lt;span class="k"&gt;in &lt;/span&gt;5.618s. Listening on: http://0.0.0.0:8081
2025-06-05 13:45:54,791 INFO  &lt;span class="o"&gt;[&lt;/span&gt;io.quarkus] &lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt; Profile dev activated. 
2025-06-05 13:45:54,791 INFO  &lt;span class="o"&gt;[&lt;/span&gt;io.quarkus] &lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt; Installed features: &lt;span class="o"&gt;[&lt;/span&gt;agroal, cdi, hibernate-orm, jdbc-h2, Keycloak, narayana-jta, opentelemetry, reactive-routes, rest, rest-jackson, smallrye-context-propagation, vertx]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to &lt;a href="http://localhost:8081" rel="noopener noreferrer"&gt;http://localhost:8081&lt;/a&gt; to make sure you see the website.&lt;/p&gt;

&lt;p&gt;You'll be prompted to create a temporary admin user, then to login.&lt;br&gt;
Upon login, you should see something like the below&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%2Fglgo1lcd3plpndw2wy7i.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%2Fglgo1lcd3plpndw2wy7i.png" alt="Keycloak" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can &lt;code&gt;Ctrl+C&lt;/code&gt; to stop the server.&lt;/p&gt;

&lt;p&gt;While you can keep using the builtin database, I prefer using Postgres in case data gets lost.&lt;br&gt;
Related docs are &lt;a href="https://www.Keycloak.org/server/db" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Start Keycloak pointing at your local PostgreSQL Server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/kc.sh start-dev &lt;span class="nt"&gt;--http-port&lt;/span&gt; 8081 &lt;span class="nt"&gt;--db-url-host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost &lt;span class="nt"&gt;--db-password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="nt"&gt;--db-username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fabio &lt;span class="nt"&gt;--db&lt;/span&gt; postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server should be up and running, and if you inspect PostgreSQL server, you'll see a new database &lt;code&gt;keycloak&lt;/code&gt; was created with a bunch of tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fabio: ~ &lt;span class="nv"&gt;$ &lt;/span&gt;psql &lt;span class="nt"&gt;-d&lt;/span&gt; keycloak
Timing is on.
psql &lt;span class="o"&gt;(&lt;/span&gt;17.4, server 16.2 &lt;span class="o"&gt;(&lt;/span&gt;Postgres.app&lt;span class="o"&gt;))&lt;/span&gt;
Type &lt;span class="s2"&gt;"help"&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;help.

&lt;span class="nv"&gt;keycloak&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# \d&lt;/span&gt;
                   List of relations
 Schema |             Name              | Type  | Owner 
&lt;span class="nt"&gt;--------&lt;/span&gt;+-------------------------------+-------+-------
 public | admin_event_entity            | table | fabio
 public | associated_policy             | table | fabio
 public | authentication_execution      | table | fabio
 public | authentication_flow           | table | fabio
 public | authenticator_config          | table | fabio
 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;Everything is ready, we can start configuring Keycloak and CRDB for SSO integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create admin user in Keycloak
&lt;/h3&gt;

&lt;p&gt;Now you can login into Keycloak with the temp user. Since you will be prompted to delete it after the first login, call it &lt;code&gt;tempadmin&lt;/code&gt; and password &lt;code&gt;tempadmin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Upon login, go to &lt;code&gt;Users&lt;/code&gt;, click &lt;code&gt;Add User&lt;/code&gt; button and create a username called, simply, &lt;code&gt;admin&lt;/code&gt; and optionally set the "Email Verified" toggle to &lt;code&gt;On&lt;/code&gt;.&lt;br&gt;
Click &lt;code&gt;Create&lt;/code&gt;, then click on the newly created user.&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%2Fj0nibfrpff3ljgrpvizp.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%2Fj0nibfrpff3ljgrpvizp.png" alt="admin" width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to the &lt;code&gt;Credential&lt;/code&gt; tab and create a password, simply &lt;code&gt;admin&lt;/code&gt; to make it easy to remember.&lt;br&gt;
Then go to tab &lt;code&gt;Role mapping&lt;/code&gt; to add the admin role to the user &lt;code&gt;admin&lt;/code&gt;. So click on &lt;code&gt;Assign Role&lt;/code&gt;, make sure you choose the &lt;code&gt;Filter by realm roles&lt;/code&gt; and there you'll see the &lt;code&gt;role_admin&lt;/code&gt;.&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%2Firp7l9ctswu0l9pxla7c.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%2Firp7l9ctswu0l9pxla7c.png" alt="roleadmin" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can logout, log back in as the &lt;code&gt;admin&lt;/code&gt; user, go to the &lt;code&gt;Users&lt;/code&gt; section and delete the &lt;code&gt;tempadmin&lt;/code&gt; user.&lt;/p&gt;
&lt;h4&gt;
  
  
  Create regular users and group
&lt;/h4&gt;

&lt;p&gt;You can now go ahead and create a regular user, set a password, then create a group, and assign the user to the group.&lt;/p&gt;

&lt;p&gt;Make sure the user you create has a corresponding username in CRDB.&lt;/p&gt;

&lt;p&gt;For example, I created my Keycloak user &lt;code&gt;fabio&lt;/code&gt; with password &lt;code&gt;fabio&lt;/code&gt;, and there's also a &lt;code&gt;fabio&lt;/code&gt; username in my CRDB cluster.&lt;br&gt;
The keycloack and crdb password for the user don't have to match.&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%2Fywh5ugcb25nni0buwtie.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%2Fywh5ugcb25nni0buwtie.png" alt="users" width="800" height="367"&gt;&lt;/a&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;show&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                                                                                                                                                                                                  
    &lt;span class="n"&gt;username&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;options&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;member_of&lt;/span&gt;
&lt;span class="c1"&gt;---------------+---------+------------&lt;/span&gt;
  &lt;span class="k"&gt;admin&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;span class="n"&gt;cockroach&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;span class="k"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;fabio&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;span class="k"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;root&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;span class="k"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;execution&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a Realm &lt;code&gt;main&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Setup a new realm and client as instructed in the &lt;a href="https://www.Keycloak.org/getting-started/getting-started-zip" rel="noopener noreferrer"&gt;Getting Started doc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I called my realm &lt;code&gt;main&lt;/code&gt;.&lt;br&gt;
When you set your current realm to &lt;code&gt;main&lt;/code&gt;, go to Realm Settings page, and scroll to the bottom to see the &lt;strong&gt;Endpoints&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;well-known&lt;/code&gt; page for a realm called &lt;code&gt;main&lt;/code&gt; is &lt;a href="http://localhost:8081/realms/main/.well-known/openid-configuration" rel="noopener noreferrer"&gt;http://localhost:8081/realms/main/.well-known/openid-configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you open the endpoint, you'll see something like this: we need these details to configure the SSO integration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"issuer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8081/realms/main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"authorization_endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8081/realms/main/protocol/openid-connect/auth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"token_endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8081/realms/main/protocol/openid-connect/token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"introspection_endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8081/realms/main/protocol/openid-connect/token/introspect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"userinfo_endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8081/realms/main/protocol/openid-connect/userinfo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end_session_endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8081/realms/main/protocol/openid-connect/logout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"frontchannel_logout_session_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"frontchannel_logout_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jwks_uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8081/realms/main/protocol/openid-connect/certs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"check_session_iframe"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8081/realms/main/protocol/openid-connect/login-status-iframe.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"grant_types_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"authorization_code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"client_credentials"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"implicit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try clicking on the link in &lt;code&gt;jwks_uri&lt;/code&gt;: you'll see the keys that will help CRDB validate the authenticity of the JWT.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Client &lt;code&gt;BankApp&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Go to the Clients page and create a new client called &lt;code&gt;BankApp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;client_authentication&lt;/code&gt; to &lt;code&gt;on&lt;/code&gt;, make sure &lt;code&gt;Standard flow&lt;/code&gt; and &lt;code&gt;Direct Access Grant&lt;/code&gt; is checked.&lt;/p&gt;

&lt;p&gt;Set the &lt;code&gt;valid redirect URI&lt;/code&gt; to CockroachDB's callback url: &lt;code&gt;https://localhost:8080/oidc/v1/callback&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Set the &lt;code&gt;web origin&lt;/code&gt; to CockroachDB's webserver: &lt;code&gt;https://localhost:8080&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create the client, then go to the Credentials tab and jot down the client secret.&lt;/p&gt;

&lt;p&gt;In my case, the client secret is &lt;code&gt;401O9E8awNtCL934rPx3pbWgIoJxv1Bb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Save the configuration, then verify we can get a JWT from Keycloak manually from the terminal client, simulating any webservice such as CockroachDB.&lt;/p&gt;

&lt;p&gt;Open a Terminal, and run the below. I hope you have &lt;code&gt;jq&lt;/code&gt; installed else try installing it with &lt;code&gt;brew&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Notice that we're hitting the &lt;code&gt;token_endpoint&lt;/code&gt; taken from the &lt;code&gt;well-known&lt;/code&gt; configuration for this realm.&lt;br&gt;
We are also passing the &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt;, plus the username and password for the user that wishes to authenticate, in this case, user &lt;code&gt;fabio&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;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"http://localhost:8081/realms/main/protocol/openid-connect/token"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/x-www-form-urlencoded"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"grant_type=password"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"client_id=BankApp"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"client_secret=401O9E8awNtCL934rPx3pbWgIoJxv1Bb"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"username=fabio"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"password=fabio"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"scope=openid"&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is truncated here as the tokens are very large&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJhb..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expires_in"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"refresh_expires_in"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"refresh_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ey..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ey..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"not-before-policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ba6328d5-39d6-42df-9ceb-225319245541"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openid email profile"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;Grab the &lt;code&gt;id_token&lt;/code&gt; and paste it into a JWT debugger such as &lt;a href="https://token.dev/" rel="noopener noreferrer"&gt;https://token.dev/&lt;/a&gt;, it will decode it.&lt;br&gt;
Please note the token is not &lt;em&gt;encrypted&lt;/em&gt;, it's just base64 &lt;em&gt;encoded&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The website will show these interesting details about who logged in.&lt;br&gt;
Notice that &lt;code&gt;preferred_username&lt;/code&gt; is what Keycloak uses to share the username field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1749153006&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1749152706&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jti"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dda84f1c-3f07-4e59-8553-5d1672f8a66d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8081/realms/main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BankApp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"18596f04-b197-4752-8e6c-790222e235c7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"typ"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"azp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BankApp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5f20475c-4930-407e-b5c1-37d736eba6e8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"at_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"epLu6r16ndQosFol9K6sOw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"acr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email_verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fabio Ghirardello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"preferred_username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fabio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"given_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fabio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"family_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ghirardello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fabio@cockroachlabs.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the bottom of the page you'll also see the private and public keys.&lt;br&gt;
These are used by CockroachDB to validate the authenticity of the JWT, that is, it will provide proof that the JWT comes indeed from the Keycloak IdP,&lt;br&gt;
and it can do so because we will share the keys that were used to sign the JWT (remember, those are the keys in &lt;code&gt;jwks_uri&lt;/code&gt; in the &lt;code&gt;well-known&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Well, we get the token just fine, so Keycloak is ready!&lt;/p&gt;
&lt;h3&gt;
  
  
  CockroachDB configuration
&lt;/h3&gt;

&lt;p&gt;Relevant docs are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cockroachlabs.com/docs/v25.2/sso-sql#identity-map-configuration" rel="noopener noreferrer"&gt;Cluster Single Sign-on (SSO) using JSON Web Tokens (JWTs)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cockroachlabs.com/docs/v25.2/sso-db-console" rel="noopener noreferrer"&gt;Single Sign-on (SSO) for DB Console&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus I setup these cluster settings.&lt;br&gt;
Notice that the values are taken mostly from the &lt;code&gt;well-known&lt;/code&gt; page.&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="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- SQL SSO&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;

&lt;span class="c1"&gt;-- trivial&lt;/span&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;jwt_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- taken from field "issuer" in the .well-known page&lt;/span&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;jwt_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;issuers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://localhost:8081/realms/main'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- taken by navigating to the URL in field "jwks_uri" in the .well-known page&lt;/span&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;jwt_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jwks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{"keys":[{"kid":"h....."}]}'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- this is the client_id&lt;/span&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;jwt_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;audience&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'["BankApp"]'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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;oidc_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'401O9E8awNtCL934rPx3pbWgIoJxv1Bb'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- this is the field we grab from the token to use as the db's username&lt;/span&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;jwt_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;claim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'preferred_username'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- reference only: this strips the @bank.com in, say, fabio@bank.com &lt;/span&gt;
&lt;span class="c1"&gt;-- SET CLUSTER SETTING server.identity_map.configuration = 'http://localhost:8081/realms/main /^(\S+)(?:@) \1';&lt;/span&gt;

&lt;span class="c1"&gt;-- takes everything passed in the `claim`, that is, `preferred_username`.&lt;/span&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;identity_map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://localhost:8081/realms/main /^(&lt;/span&gt;&lt;span class="se"&gt;\S&lt;/span&gt;&lt;span class="s1"&gt;+) &lt;/span&gt;&lt;span class="se"&gt;\1&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- DBConsole SSO&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&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;oidc_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;   &lt;span class="p"&gt;;&lt;/span&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;oidc_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'BankApp'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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;oidc_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'401O9E8awNtCL934rPx3pbWgIoJxv1Bb'&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&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;oidc_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;claim_json_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'preferred_username'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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;oidc_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provider_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://localhost:8081/realms/main'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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;oidc_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redirect_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://localhost:8080/oidc/v1/callback'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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;oidc_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scopes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'openid'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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;oidc_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Keycloak SSO'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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;oidc_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_cluster_sso_token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&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;Once these are set, you should see these 2 buttons appear in the Login page&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%2Fh62whhjf2m6oqc71i2kt.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%2Fh62whhjf2m6oqc71i2kt.png" alt="login" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  DB Console SSO login
&lt;/h4&gt;

&lt;p&gt;Clicking on &lt;code&gt;Keycloak SSO&lt;/code&gt; will direct you to the Keycloack SSO website.&lt;br&gt;
Login as &lt;code&gt;fabio/fabio&lt;/code&gt; as that's the Keycloak username.&lt;/p&gt;

&lt;p&gt;Once you hit enter, Keycloak will authenticate you and, if successful, redirect you to the DBConsole.&lt;br&gt;
Behind the scene, a JWT is sent by Keycloak to CRDB that confirms your authentication.&lt;br&gt;
CockroachDB verifies and validates the JWT, and grants you access.&lt;/p&gt;
&lt;h4&gt;
  
  
  SQL SSO login
&lt;/h4&gt;

&lt;p&gt;Either using the &lt;code&gt;curl&lt;/code&gt; command above or by clicking on the &lt;code&gt;Generate JWT auth token&lt;/code&gt; button in the DBConsole, get hold of your &lt;code&gt;id_token&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then use it as a password in your connection DBURL.&lt;br&gt;
Make sure you add &lt;code&gt;options=--crdb:jwt_auth_enabled=true&lt;/code&gt; to the DBURL string to inform CRDB that what you're passing is not a password, but a JWT.&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;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ey...
&lt;span class="nv"&gt;$ &lt;/span&gt;cockroach sql &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s2"&gt;"postgres://fabio:&lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;@localhost:26257/defaultdb?sslmode=require&amp;amp;options=--crdb:jwt_auth_enabled=true"&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 v25.1.0 (x86_64-apple-darwin19, built 2025/02/14 18:09:08, go1.22.8 X:nocoverageredesign)&lt;/span&gt;
&lt;span class="c"&gt;# Server version: CockroachDB CCL v25.1.7 (x86_64-apple-darwin19, built 2025/05/26 13:41:39, go1.22.8 X:nocoverageredesign)&lt;/span&gt;
&lt;span class="c"&gt;# Cluster ID: e360faa9-2ba3-4e92-bd51-fc7e88cf24a8&lt;/span&gt;
&lt;span class="c"&gt;# Organization: Workshop&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;
fabio@localhost:26257/defaultdb&amp;gt;    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This concludes the tutorial, I hope you had fun setting up your local IdP! &lt;/p&gt;

&lt;p&gt;For any question, reach out to the &lt;a href="https://www.cockroachlabs.com/join-community/" rel="noopener noreferrer"&gt;CockroachDB Community Slack channel&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cockroachdb</category>
      <category>keycloak</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Kafka 2 crdb</title>
      <dc:creator>Fabio Ghirardello</dc:creator>
      <pubDate>Wed, 09 Apr 2025 21:36:54 +0000</pubDate>
      <link>https://dev.to/fabiog1901/kafka-2-crdb-3pdf</link>
      <guid>https://dev.to/fabiog1901/kafka-2-crdb-3pdf</guid>
      <description></description>
      <category>kafka</category>
      <category>cloud</category>
      <category>database</category>
      <category>devops</category>
    </item>
    <item>
      <title>From CockroachDB to AWS SNS via AWS API Gateway</title>
      <dc:creator>Fabio Ghirardello</dc:creator>
      <pubDate>Wed, 09 Oct 2024 16:11:55 +0000</pubDate>
      <link>https://dev.to/cockroachlabs/from-cockroachdb-to-aws-sns-via-cdc-and-aws-api-gateway-bna</link>
      <guid>https://dev.to/cockroachlabs/from-cockroachdb-to-aws-sns-via-cdc-and-aws-api-gateway-bna</guid>
      <description>&lt;p&gt;This is a brief write up on how to send rows inserted into a CockroachDB table as messages to a SNS topic.&lt;/p&gt;

&lt;p&gt;CockroachDB Change Data Capture (CDC) features several sinks, see docs for a full list &lt;a href="https://www.cockroachlabs.com/docs/stable/changefeed-sinks.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;, however AWS SNS is not yet one of these.&lt;/p&gt;

&lt;p&gt;We can workaround this limitation by leveraging the &lt;a href="https://www.cockroachlabs.com/docs/stable/changefeed-sinks.html#webhook-sink" rel="noopener noreferrer"&gt;webhook sink&lt;/a&gt;, which publishes to a HTTP endpoint, and API Gateway, which provides such HTTP endpoint and is tightly integrated internally with other AWS services, such as SNS.&lt;/p&gt;

&lt;p&gt;At high level, the pipeline works as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Changefeed is created on the CockroachDB table.&lt;/li&gt;
&lt;li&gt;The Changefeed watches for INSERTs (any write operation, to be precise) on the table and emits messages to the API Gateway endpoint.&lt;/li&gt;
&lt;li&gt;API Gateway receives the message and publishes the payload to the SNS topic via an internal AWS integration.&lt;/li&gt;
&lt;li&gt;Subscribers of the SNS topic get a notification.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We will create this pipeline backwards, starting from creating the SNS topic back to configuring the Changefeed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create SNS topic
&lt;/h2&gt;

&lt;p&gt;This is the easiest step of them all, thanks to the AWS Console for making it so easy.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the AWS Console and head to &lt;strong&gt;Simple Notification Service&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You'll be welcomed to a page where you are prompted to create a new &lt;em&gt;topic&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Create a topic named "events". Keep all the default settings;
for this test we won't bother with certs and keys, we want to make sure the pipeline works first.&lt;/li&gt;
&lt;li&gt;Create a subscriber. In my example, I choose "Protocol" to be "Email", and entered my email address as the "Endpoint".&lt;/li&gt;
&lt;li&gt;Finally, very important, test it out directly from the Console by using the &lt;strong&gt;Publish message&lt;/strong&gt; button. &lt;/li&gt;
&lt;li&gt;Check your inbox, you should have received a request to verify your email address, and the message you published.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Jot down your topic ARN, in my case is &lt;code&gt;arn:aws:sns:ca-central-1:3xxx8:events&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;Good, you have created the topic and validated it's working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create AWS Role
&lt;/h2&gt;

&lt;p&gt;A prerequisite for the API Gateway is to have a AWS Role with privileges to Publish to the SNS topic we just created.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;AWS IAM&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Go to Roles and "Create role". Select AWS Service as 'Trusted Entity Type' and "API Gateway" as the 'Use case'.&lt;/li&gt;
&lt;li&gt;Continue to Step 3. Name it "events_role" and create it.&lt;/li&gt;
&lt;li&gt;Your role is now created, but it doesn't have the permission it needs. Find the role in the list of roles and select it.&lt;/li&gt;
&lt;li&gt;In the Permission section, Add Permission and choose "AmazonSNSFullAccess", for simplicity, then save.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Jot down the ARN of the role, in my case it's &lt;a&gt;arn:aws:iam::3xxx8:role/events_role&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create API Gateway endpoint
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;From the AWS Console, head to &lt;strong&gt;API Gateway&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Create API&lt;/strong&gt; button and select a 'REST API'.&lt;/li&gt;
&lt;li&gt;Name it "events", and set 'API Endpoint type' to "Regional".&lt;/li&gt;
&lt;li&gt;Within the "events" API selected, click the Create Resource button and choose &lt;code&gt;/&lt;/code&gt; as Resource Path and &lt;code&gt;events&lt;/code&gt; as Resource Name.
No need to enable CORS or the Proxy Resource.&lt;/li&gt;
&lt;li&gt;Once in the Resource Detail page for the &lt;code&gt;/events/&lt;/code&gt; path, choose Create Method&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose:&lt;br&gt;
Method type = &lt;code&gt;POST&lt;/code&gt;&lt;br&gt;
Integration type = &lt;code&gt;AWS service&lt;/code&gt;&lt;br&gt;
AWS Region = &lt;code&gt;ca-central-1&lt;/code&gt;, which is the region I've used to deploy the SNS topic&lt;br&gt;
AWS Service = &lt;code&gt;Simple Notification Service&lt;/code&gt;&lt;br&gt;
AWS Subdomain = keep it blank&lt;br&gt;
HTTP method = &lt;code&gt;POST&lt;/code&gt;&lt;br&gt;
Action Type = &lt;code&gt;Use path override&lt;/code&gt;&lt;br&gt;
Path override = &lt;code&gt;arn:aws:apigateway:ca-central-1:sns:path//&lt;/code&gt;  &amp;lt;-- substitute with your region&lt;br&gt;
Execution role = the ARN role you jotted down previously&lt;/p&gt;

&lt;p&gt;Leave the rest the default then "Create method"&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You should now have something that looks like this:&lt;/p&gt;

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

&lt;p&gt;Now, we need to create the tidbit that takes the incoming HTTP payload and inserts it as to the SNS topic.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click the "Integration Request" tab, then Edit&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Scroll to the "URL request headers parameters" section and add&lt;/p&gt;

&lt;p&gt;Name = &lt;code&gt;Content-Type&lt;/code&gt;&lt;br&gt;
Mapped from = &lt;code&gt;'application/x-www-form-urlencoded'&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to "Mapping templates" and set&lt;/p&gt;

&lt;p&gt;Content type = &lt;code&gt;application/json&lt;/code&gt;&lt;br&gt;
Template body =&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#set($topic="arn:aws:sns:ca-central-1:3xxx8:events")
#set($msg=$input.path('$.payload'))
Action=Publish&amp;amp;TopicArn=$util.urlEncode($topic)&amp;amp;Message=$util.urlEncode($msg)
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Substitute my value with your SNS topic ARN&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save, then you should see the below&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Finally, click the Test tab, and in the Request Body enter&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"test_event_api"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get a confirmation that the message was published, and soon you should receive the same SNS email in your inbox.&lt;/p&gt;

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

&lt;p&gt;Wow, it's working, getting there!&lt;/p&gt;

&lt;p&gt;Now we need to publish this API, and create a usage plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy API
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;On the main page for this API, click "Deploy API"&lt;/li&gt;
&lt;li&gt;At the prompt, choose &lt;code&gt;New stage&lt;/code&gt; and call it &lt;code&gt;DEV&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click 'Deploy'&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You'll be redirected to the &lt;em&gt;Stages&lt;/em&gt; section.&lt;br&gt;
Jot down the Invoke URL, in my case, it's &lt;a href="https://72eebc6w0k.execute-api.ca-central-1.amazonaws.com/DEV" rel="noopener noreferrer"&gt;https://72eebc6w0k.execute-api.ca-central-1.amazonaws.com/DEV&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Create usage plan
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;On the main page for this API, click 'Usage Plans'&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new usage plan. I added these sample values for this functional test&lt;/p&gt;

&lt;p&gt;Name = events_plan&lt;br&gt;
Rate = 10&lt;br&gt;
Burst = 10&lt;br&gt;
Requests = 100 per day&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click 'Create usage plan'&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Re-select the newly create &lt;code&gt;events_plan&lt;/code&gt; usage plan, and add a Stage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;At the prompt, choose&lt;/p&gt;

&lt;p&gt;API = &lt;code&gt;events&lt;/code&gt;&lt;br&gt;
Stage = &lt;code&gt;DEV&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save your changes.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Test locally
&lt;/h3&gt;

&lt;p&gt;Now that the API is live and that we have a billing plan attached, we are ready to test if that can be invoked by an external client.&lt;/p&gt;

&lt;p&gt;On your laptop, thus locally, invoke it using curl&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -X POST https://72eebc6w0k.execute-api.ca-central-1.amazonaws.com/DEV/events --json '{"payload":"hello_from_curl"}' | jq
{
  "PublishResponse": {
    "PublishResult": {
      "MessageId": "7a3e9555-6031-5d2b-b72a-cad5c8ddf3b4",
      "SequenceNumber": null
    },
    "ResponseMetadata": {
      "RequestId": "a63ca446-743e-520b-b689-fbc6dcd2bc94"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And sure enough I got my email with the same text.&lt;/p&gt;

&lt;p&gt;We are now ready to have CockroachDB send data to our endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create test table and configure the changefeed
&lt;/h2&gt;

&lt;p&gt;Create a simple &lt;code&gt;events&lt;/code&gt; table such as&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;events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;():::&lt;/span&gt;&lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;pk&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;id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the changefeed on the newly created &lt;code&gt;events&lt;/code&gt; table.&lt;br&gt;
You will need to update the endpoint hostname with yours.&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="c1"&gt;-- enable rangefeed&lt;/span&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;kv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rangefeed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;CHANGEFEED&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="s1"&gt;'webhook-https://72eebc6w0k.execute-api.ca-central-1.amazonaws.com/DEV/events?insecure_tls_skip_verify=true'&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolved&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'20s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schema_change_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;backfill&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initial_scan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;no&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;webhook_sink_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{ "Flush": {"Messages": 100, "Frequency": "5s"}, "Retry": { "Max": 4, "Backoff": "10s"} }'&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;--         job_id&lt;/span&gt;
&lt;span class="c1"&gt;-- -----------------------&lt;/span&gt;
&lt;span class="c1"&gt;--   1010627173653250049&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check that the changefeed is running and posting a &lt;code&gt;resolved&lt;/code&gt; timestamp&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="n"&gt;running_status&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;show&lt;/span&gt; &lt;span class="n"&gt;changefeed&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;job_id&lt;/span&gt; &lt;span class="mi"&gt;1010627173653250049&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;--                running_status&lt;/span&gt;
&lt;span class="c1"&gt;-- --------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;--   running: resolved=1728489668.321914000,0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good, it's running so we're finally ready to test by inserting a row into the table&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;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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="s1"&gt;'{"my_key" : "my_value"}'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your inbox:&lt;/p&gt;

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

</description>
      <category>cockroachdb</category>
      <category>sns</category>
      <category>apigateway</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Generate multiple, large, sorted CSV files with pseudo-random data</title>
      <dc:creator>Fabio Ghirardello</dc:creator>
      <pubDate>Fri, 16 Feb 2024 16:00:59 +0000</pubDate>
      <link>https://dev.to/cockroachlabs/generate-multiple-large-sorted-csv-files-with-pseudo-random-data-1jo4</link>
      <guid>https://dev.to/cockroachlabs/generate-multiple-large-sorted-csv-files-with-pseudo-random-data-1jo4</guid>
      <description>&lt;p&gt;For this exercise, I was tasked to create a 500 million rows dataset for an &lt;code&gt;IMPORT INTO&lt;/code&gt; speed test.&lt;br&gt;
Details about the &lt;code&gt;IMPORT INTO&lt;/code&gt; command can be found &lt;a href="https://www.cockroachlabs.com/docs/stable/import-into"&gt;here&lt;/a&gt;.&lt;br&gt;
The requirement was to create 100 sorted CSV files, each containing 5 million rows, and save them to an S3 bucket.&lt;br&gt;
The data should also be sorted &lt;em&gt;across&lt;/em&gt; all files.&lt;/p&gt;

&lt;p&gt;In this blog, I show how to use &lt;a href="https://github.com/fabiog1901/pgworkload"&gt;pgworkload&lt;/a&gt; to create such pseudo-random dataset.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup pgworkload and aws-cli
&lt;/h2&gt;

&lt;p&gt;Provision a large machine with plenty of RAM.&lt;br&gt;
The AWS instance type &lt;code&gt;r7i.8xlarge&lt;/code&gt;, which sports 32 vCPUs and 256GB RAM, is a good candidate.&lt;br&gt;
Make sure to attach an &lt;strong&gt;IAM Role&lt;/strong&gt; with permissions to write to your S3 bucket.&lt;/p&gt;

&lt;p&gt;Once ready, ssh into the box and install the tools&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;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; python3-pip unzip

pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; pip
pip &lt;span class="nb"&gt;install &lt;/span&gt;pgworkload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logout and log back in so that pgworkload is in the PATH...&lt;/p&gt;

&lt;p&gt;Now install AWS CLI, here the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"&gt;official docs&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"awscliv2.zip"&lt;/span&gt;
unzip awscliv2.zip
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./aws/install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm it's working&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ aws --version
aws-cli/2.15.17 Python/3.11.6 Linux/5.15.0-1052-aws exe/x86_64.ubuntu.20 prompt/off

# confirm you're authenticated..
$ aws s3 ls workshop-ca
                          PRE fab/

# put some files in the bucket
$ aws s3 cp s.sql 's3://workshop-ca/fab/'
upload: ./s.sql to s3://workshop-ca/fab/s.sql                  

# check the value is there
$ aws s3 ls workshop-ca/fab/
                           PRE 2024/
                           PRE metadata/
2024-02-02 19:03:10       3585 s.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good, we're all set!&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate data
&lt;/h2&gt;

&lt;p&gt;The data that needs to be created must match the schema of the table into which it will be imported.&lt;/p&gt;

&lt;p&gt;The DDL of the schema is in file &lt;code&gt;s.sql&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="c1"&gt;-- file s.sql&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;accounts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;acc_no&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pos_dt&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ent_id&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_00&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_01&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_02&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_03&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_04&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&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;col_05&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&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;col_06&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_07&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_08&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_09&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_10&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_11&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_12&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&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;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&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;col_13&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&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;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&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;col_14&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_15&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_16&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&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;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&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;col_17&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_18&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;8&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;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&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;col_19&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&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;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&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;col_20&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_21&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_22&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&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;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&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;col_23&lt;/span&gt; &lt;span class="n"&gt;STRING&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;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_24&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_25&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_26&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_27&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;8&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;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&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;col_28&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_29&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_30&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_31&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_32&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_33&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_34&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_35&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_36&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_37&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_38&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_39&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_40&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_41&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_42&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_43&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col_44&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;pk&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;acc_no&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pos_dt&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ent_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given the schema, &lt;code&gt;pgworkload&lt;/code&gt; can generate an intermediate representation of what needs to be generated - a definition file - in YAML syntax.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pgworkload util yaml &lt;span class="nt"&gt;-i&lt;/span&gt; s.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is a file called, by default, &lt;code&gt;s.yaml&lt;/code&gt;, below a snippet of the first few lines&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="c1"&gt;# file s.yaml&lt;/span&gt;
&lt;span class="na"&gt;accounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
  &lt;span class="na"&gt;sort-by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
  &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;acc_no&lt;/span&gt;&lt;span class="pi"&gt;:&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;string&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
        &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
        &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.7225861820526325&lt;/span&gt;
        &lt;span class="na"&gt;null_pct&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.0&lt;/span&gt;
        &lt;span class="na"&gt;array&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="na"&gt;pos_dt&lt;/span&gt;&lt;span class="pi"&gt;:&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;date&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2022-01-01'&lt;/span&gt;
        &lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2022-12-31'&lt;/span&gt;
        &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d'&lt;/span&gt;
        &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.24769809060740589&lt;/span&gt;
        &lt;span class="na"&gt;null_pct&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.0&lt;/span&gt;
        &lt;span class="na"&gt;array&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="na"&gt;ent_id&lt;/span&gt;&lt;span class="pi"&gt;:&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;float&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10000&lt;/span&gt;
        &lt;span class="na"&gt;round&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
        &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.028215986930010706&lt;/span&gt;
        &lt;span class="na"&gt;null_pct&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.0&lt;/span&gt;
        &lt;span class="na"&gt;array&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="na"&gt;col_00&lt;/span&gt;&lt;span class="pi"&gt;:&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;string&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
        &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
        &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.8785098436269427&lt;/span&gt;
        &lt;span class="na"&gt;null_pct&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.0&lt;/span&gt;
        &lt;span class="na"&gt;array&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="na"&gt;col_01&lt;/span&gt;&lt;span class="pi"&gt;:&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;string&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
        &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
        &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.8561702097239098&lt;/span&gt;
        &lt;span class="na"&gt;null_pct&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.0&lt;/span&gt;
        &lt;span class="na"&gt;array&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;...&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just a template, we need to configure it as per our needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generate 500,000,000 rows&lt;/li&gt;
&lt;li&gt;ensure dataset is sorted as per Primary Key&lt;/li&gt;
&lt;li&gt;the 2nd column must always be the same date.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is therefore the updated head of the file&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;accounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500000000&lt;/span&gt;
  &lt;span class="na"&gt;sort-by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;acc_no"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pos_dt"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ent_id"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;acc_no&lt;/span&gt;&lt;span class="pi"&gt;:&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;string&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
        &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
        &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.7225861820526325&lt;/span&gt;
        &lt;span class="na"&gt;null_pct&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.0&lt;/span&gt;
        &lt;span class="na"&gt;array&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="na"&gt;pos_dt&lt;/span&gt;&lt;span class="pi"&gt;:&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;costant&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-02-01"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we're ready to generate the data.&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 the options available for the `util csv` command&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;pgworkload util csv &lt;span class="nt"&gt;--help&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;
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ *  --input         -i      FILE       Filepath to the YAML data generation file. [default: None] [required]                                  │
│    --output        -o      DIRECTORY  Output directory for the CSV files. Defaults to &amp;lt;input-basename&amp;gt;.                                      │
│    --procs         -x      INTEGER    Number of processes to spawn. Defaults to &amp;lt;system-cpu-count&amp;gt;.                                          │
│    --csv-max-rows          INTEGER    Max count of rows per resulting CSV file. [default: 100000]                                            │
│    --hostname      -n      INTEGER    The hostname of the http server that serves the CSV files.                                             │
│    --port          -p      INTEGER    The port of the http server that servers the CSV files. [default: 3000]                                │
│    --table-name    -t      TEXT       The table name used in the import statement. [default: table_name]                                     │
│    --compression   -c      TEXT       The compression format. [default: None]                                                                │
│    --delimiter     -d      TEXT       The delimeter char to use for the CSV files. Defaults to "tab".                                        │
│    --help                             Show this message and exit.                                                                            │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, you are interested in setting the &lt;code&gt;--csv-max-rows&lt;/code&gt; and &lt;code&gt;-x&lt;/code&gt; parameters.&lt;/p&gt;

&lt;p&gt;To make sure we end up with 100 files, we will use 10 processes (&lt;code&gt;-x&lt;/code&gt;) with each process generating files of size 5,000,000 until the total is 500,000,000.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pgworkload util csv &lt;span class="nt"&gt;-i&lt;/span&gt; s.yaml &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;","&lt;/span&gt; &lt;span class="nt"&gt;--csv-max-rows&lt;/span&gt; 5000000 &lt;span class="nt"&gt;-x&lt;/span&gt; 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will take time, and memory: monitor memory utilization using &lt;code&gt;top&lt;/code&gt; or &lt;code&gt;free&lt;/code&gt;.&lt;br&gt;
Once completed, you have 100 CSV files in a &lt;code&gt;s&lt;/code&gt; directory.&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;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-A1&lt;/span&gt; s/
accounts.0_0_0.csv
accounts.0_0_1.csv
accounts.0_0_2.csv
accounts.0_0_3.csv
accounts.0_0_4.csv
accounts.0_0_5.csv
accounts.0_0_6.csv
accounts.0_0_7.csv
accounts.0_0_8.csv
accounts.0_0_9.csv
accounts.0_1_0.csv
accounts.0_1_1.csv
&lt;span class="o"&gt;[&lt;/span&gt;...]
accounts.0_9_8.csv
accounts.0_9_9.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inspecting one file, we see it's sorted&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;&lt;span class="nb"&gt;head &lt;/span&gt;s/accounts.0_0_0.csv
abcdgzrjyphtemsbcjvt,2024-02-01,4314.45,jkiuotkttqwjnjnbxzbxhgsyke,uxejzkiirmitunpzybjnakoic,ovvqhgmsbwoajhwmiyhnugj,...
aefkzjdckjylb,2024-02-01,9375.53,bswvhyjkodukhwpcxf,uevjmwqhdfaobtlf,oahiaiztayyzftmfkyuez,qtxhjuwpfalfzaeoiiahuoxamns,...
aerkycbddriqtygvilb,2024-02-01,6150.55,mprfweeqoe,nvddlibqqzncrwdnffm,phcnnzvxrauxllj,vjnabkrzgiimmt,...
agjiqkisyshjeorqna,2024-02-01,9901.21,fipnlqezgzzdfreg,yokerzbkxcrzfdeckjkk,guaeeecdqgbbwtnzleopfznzcuv,lqglvuyetypvnovdbflbnodozfebz,...
atpkoobmhvhhxuqxceurv,2024-02-01,2455.17,cwmkzijlrqhtdcx,jtbelvfoajfdagwigpevnmameq,uedouumekwxagdgwbtivewaq,uytgiqpewfexlqkmbelpik,...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have the data, sorted by file, but not &lt;em&gt;across&lt;/em&gt; all files.&lt;br&gt;
That is, each file is sorted as per PK, but across all the files the data is not yet sorted.&lt;br&gt;
Currently, pgworkload doesn't have the capability to do so, so we have to develop our own &lt;strong&gt;sorted-merge&lt;/strong&gt; script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; You can't possibly be thinking to load everything into memory, sort, and save.&lt;br&gt;
That's too big to fit into a single machine, no matter how big it is.&lt;br&gt;
Instead, you must read in chunks and write in chunks, so that it can scale.&lt;br&gt;
Below script will work no matter how many files you have or how large they are.&lt;/p&gt;

&lt;p&gt;Here's my quick and dirty Python script.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;UPDATE&lt;/strong&gt;: as of version 0.1.8, the sort merge functionality has been added to pgworkload, check with &lt;code&gt;pgworkload util merge --help&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# file: sort_merge.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TextIOWrapper&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="c1"&gt;# input CSV files - it assumes files are already sorted
&lt;/span&gt;&lt;span class="n"&gt;CSVs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&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="n"&gt;CSV_MAX_ROWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000000&lt;/span&gt;
&lt;span class="n"&gt;COUNTER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&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="n"&gt;file_handlers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TextIOWrapper&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;def&lt;/span&gt; &lt;span class="nf"&gt;initial_fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    opens the CSV file, saves the file handler,
    read few lines into source list for the index.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;file_handlers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&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;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# reached end of file
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;initial_fill: CSV file &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; at source index &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; reached EOF.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;replenish_source_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Refills the source list with a new value from the source file
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file_handlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# reached end of file
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;index &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; reached EOF.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;file_handlers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Excepton in replenish_queue: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write_to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;COUNTER&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;CSV_MAX_ROWS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;COUNTER&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zfill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;COUNTER&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;    

&lt;span class="c1"&gt;# init the source dict by opening each CSV file
# and only reading few lines.
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CSVs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&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="nf"&gt;initial_fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# the source dict now has a key for every file and a list of the first values read
&lt;/span&gt;
&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="c1"&gt;# pop the first value in each source to a list `l`
# `l` will have the first values of all source CSV files
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&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="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;IndexError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="n"&gt;first_k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="n"&gt;first_v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zfill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;COUNTER&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# sort list `l`
# pop the first value (the smallest) in `first_v`
# make a note of the source of that value in `first_k`
# replenish the corrisponding source
&lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;first_k&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;replenish_source_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;first_k&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;pop&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="n"&gt;first_k&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;IndexError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# the source list is empty
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source list &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;first_k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is now empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;first_k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;if&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;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&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="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;first_v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;first_k&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="nf"&gt;pop&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="nf"&gt;write_to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;IndexError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Exception in main: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;


&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;done!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it&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;python3 sort_merge.py s/&lt;span class="k"&gt;*&lt;/span&gt;
index 58 reached EOF.
index 82 reached EOF.
&lt;span class="o"&gt;[&lt;/span&gt;...]
&lt;span class="nb"&gt;source &lt;/span&gt;list 26 is now empty
&lt;span class="nb"&gt;source &lt;/span&gt;list 69 is now empty

&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inspect the new files, also 100 in total&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;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-A1&lt;/span&gt; out_&lt;span class="k"&gt;*&lt;/span&gt;
out_000.csv
out_001.csv
out_002.csv
&lt;span class="o"&gt;[&lt;/span&gt;...]
out_098.csv
out_099.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see that the data is now sorted also &lt;em&gt;across&lt;/em&gt; files, 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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;head &lt;/span&gt;out_000.csv 
aabrwawoedcqnosvgzcvf,2024-02-01,5285.54,...
aasobyznvehzvrppwijpbbxfrjzdj,2024-02-01,7942.57,..
abcppzyblqnksovdnf,2024-02-01,7577.34,...
abjfxjatqangpalindkcdzsmzbasfx,2024-02-01,9831.37,...
aepkvifbrl,2024-02-01,1239.02,...

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;head &lt;/span&gt;out_099.csv 
zxwcmhfnwuqarb,2024-02-01,8477.3,...
zyjucqqytplxf,2024-02-01,5049.06,...
zyvwzspgaxzcymlvo,2024-02-01,5590.13,...
zzmrrnytooz,2024-02-01,7936.68,...
zzqpnbksbdheo,2024-02-01,7950.73,...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now upload those files to your S3 bucket using the &lt;code&gt;aws cli&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;aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;s/ &lt;span class="s1"&gt;'s3://workshop-ca/sorted_across_files/'&lt;/span&gt; &lt;span class="nt"&gt;--recursive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, you can safely terminate the AWS instance.&lt;/p&gt;

</description>
      <category>python</category>
      <category>cockroachdb</category>
    </item>
    <item>
      <title>CockroachDB SSO login to the SQL prompt via JWT</title>
      <dc:creator>Fabio Ghirardello</dc:creator>
      <pubDate>Wed, 30 Aug 2023 17:38:43 +0000</pubDate>
      <link>https://dev.to/cockroachlabs/cockroachdb-sso-login-to-the-sql-prompt-via-jwt-3acd</link>
      <guid>https://dev.to/cockroachlabs/cockroachdb-sso-login-to-the-sql-prompt-via-jwt-3acd</guid>
      <description>&lt;p&gt;In this post, I walk through how to configure CockroachDB to allow users to login to the &lt;strong&gt;SQL prompt&lt;/strong&gt; using their SSO login.&lt;/p&gt;

&lt;p&gt;Instructions on how to configure SSO access for the &lt;strong&gt;DBConsole&lt;/strong&gt; are available &lt;a href="https://www.cockroachlabs.com/docs/stable/sso-db-console" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The users will request a JSON Web Token (JWT) from their identity provider (IdP) and use that token as the temporary password.&lt;/p&gt;

&lt;p&gt;There are many IdPs, and in this example I am using &lt;a href="https://www.okta.com/" rel="noopener noreferrer"&gt;Okta&lt;/a&gt;. The workflow is similar for all IdPs, so while the details for how to configure the IdP varies from IdP to IdP, from the point of view of CockroachDB this is entirely transparent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisite: IdP (Okta) setup
&lt;/h2&gt;

&lt;p&gt;To generate a JWT, we first need to setup an IdP. Okta offers a &lt;a href="https://developer.okta.com/signup/" rel="noopener noreferrer"&gt;developer account&lt;/a&gt;, so I signed up and quickly created an &lt;a href="https://developer.okta.com/signup/" rel="noopener noreferrer"&gt;App integration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is a quick walk-through:&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a few users
&lt;/h3&gt;

&lt;p&gt;Navigate to &lt;em&gt;Directory &amp;gt; People&lt;/em&gt; and click &lt;strong&gt;Add Person&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Here's my list: I set password &lt;code&gt;Mazinga123&lt;/code&gt; for all these users.&lt;br&gt;
Later, we'll use the password to request a JWT.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjni9cb9wfcqoa2p0mp4c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjni9cb9wfcqoa2p0mp4c.png" alt="users"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a Group
&lt;/h3&gt;

&lt;p&gt;Go to &lt;em&gt;Directory &amp;gt; Groups&lt;/em&gt;, click &lt;strong&gt;Add Group&lt;/strong&gt; and call it "Bankers".&lt;/p&gt;

&lt;p&gt;Then, select that group and assign users to it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2ga1tmcu0tyvsh70q7z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2ga1tmcu0tyvsh70q7z.png" alt="group"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Create an App Integration
&lt;/h3&gt;

&lt;p&gt;Navigate to &lt;em&gt;Applications &amp;gt; Applications&lt;/em&gt; and click &lt;strong&gt;Create App Integration&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Select the "OIDC" as the protocol, and "Native application" as the type.&lt;/p&gt;

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

&lt;p&gt;I call the integration "BankApp". Ensure "Resource Owner Password" is selected.&lt;/p&gt;

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

&lt;p&gt;Under "Assignments", add the "Bankers" group we previously created.&lt;/p&gt;

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

&lt;p&gt;Click &lt;strong&gt;Save&lt;/strong&gt;.&lt;br&gt;
Now, create a Client Secret.&lt;/p&gt;

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

&lt;p&gt;Make sure you jot down your credentials. In my case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client_id=0oab1arbbieTSsFVJ5d7
client_secret=WbAWHW9UeURzXez99PMGWvuVp4KiGDXTNQrDajvszN2iQ3YyMjGyx0sWts1sM36J
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure Token fields
&lt;/h3&gt;

&lt;p&gt;Next, we need to &lt;a href="https://developer.okta.com/docs/guides/customize-tokens-returned-from-okta/main/" rel="noopener noreferrer"&gt;edit the Okta JWT&lt;/a&gt; by adding the &lt;code&gt;user.login&lt;/code&gt; field.&lt;br&gt;
This is the "Username" field in the &lt;strong&gt;Person&lt;/strong&gt; profile.&lt;/p&gt;

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

&lt;p&gt;Go to &lt;em&gt;Security &amp;gt; API&lt;/em&gt;, select the "default" Authorization Server, and select the "Claims" tab.&lt;br&gt;
From here, add a claim:&lt;/p&gt;

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

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

&lt;p&gt;You can actually preview what the token will look like.&lt;br&gt;
Check the bottom right hand corner, in the Payload section, the "login" field is now present.&lt;/p&gt;

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

&lt;p&gt;Finally, jot down the Issuer URI, we will need this URL later to find the &lt;code&gt;.well-known&lt;/code&gt; details. Ours is &lt;a href="https://dev-85651931.okta.com/oauth2/default" rel="noopener noreferrer"&gt;https://dev-85651931.okta.com/oauth2/default&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;We are now ready to configure CockroachDB against our Okta BankApp integration.&lt;/p&gt;
&lt;h2&gt;
  
  
  CockroachDB setup
&lt;/h2&gt;

&lt;p&gt;Start a CockroachDB demo cluster, as it automatically loads a temporary license.&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 demo &lt;span class="nt"&gt;--no-example-database&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Welcome to the CockroachDB demo database!&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# You are connected to a temporary, in-memory CockroachDB cluster of 1 node.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# This demo session will send telemetry to Cockroach Labs in the background.&lt;/span&gt;
&lt;span class="c"&gt;# To disable this behavior, set the environment variable&lt;/span&gt;
&lt;span class="c"&gt;# COCKROACH_SKIP_ENABLING_DIAGNOSTIC_REPORTING=true.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Reminder: your changes to data stored in the demo session will not be saved!&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# If you wish to access this demo cluster using another tool, you will need&lt;/span&gt;
&lt;span class="c"&gt;# the following details:&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;#   - Connection parameters:&lt;/span&gt;
&lt;span class="c"&gt;#      (webui)    http://127.0.0.1:8080/demologin?password=demo9642&amp;amp;username=demo&lt;/span&gt;
&lt;span class="c"&gt;#      (cli)      cockroach sql --certs-dir=/Users/fabio/.cockroach-demo -u demo -d defaultdb&lt;/span&gt;
&lt;span class="c"&gt;#      (sql)      postgresql://demo:demo9642@127.0.0.1:26257/defaultdb?sslmode=require&amp;amp;sslrootcert=%2FUsers%2Ffabio%2F.cockroach-demo%2Fca.crt&lt;/span&gt;
&lt;span class="c"&gt;#   &lt;/span&gt;
&lt;span class="c"&gt;#   - Username: "demo", password: "demo9642"&lt;/span&gt;
&lt;span class="c"&gt;#   - Directory with certificate files (for certain SQL drivers/tools): /Users/fabio/.cockroach-demo&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# You can enter \info to print these details again.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Server version: CockroachDB CCL v23.1.4 (x86_64-apple-darwin19, built 2023/06/16 20:54:39, go1.19.4) (same version as client)&lt;/span&gt;
&lt;span class="c"&gt;# Cluster ID: f72ea38c-c5b3-437e-bd03-2391e6b5d150&lt;/span&gt;
&lt;span class="c"&gt;# Organization: Cockroach Demo&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;
demo@127.0.0.1:26257/defaultdb&amp;gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relevant documentation to configure CockroachDB with the IdP for SSO login is &lt;a href="https://www.cockroachlabs.com/docs/stable/sso-sql" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, open the &lt;code&gt;.well-known&lt;/code&gt; page for our IdP.&lt;br&gt;
In our case, this is URL &lt;a href="https://dev-85651931.okta.com/oauth2/default/.well-known/openid-configuration" rel="noopener noreferrer"&gt;https://dev-85651931.okta.com/oauth2/default/.well-known/openid-configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here, we can take the &lt;code&gt;server.jwt_authentication.issuers&lt;/code&gt; setting, and by navigating to the URL indicated by the &lt;code&gt;jwkt_uri&lt;/code&gt; field, also the &lt;code&gt;server.jwt_authentication.jwks&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="c1"&gt;-- trivial&lt;/span&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;jwt_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- taken from field "issuer" in the .well-known page&lt;/span&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;jwt_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;issuers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://dev-85651931.okta.com/oauth2/default'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- taken by navigating to the URL in field "jwks_uri" in the .well-known page&lt;/span&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;jwt_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jwks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{"keys":[{"kty":"RSA","alg":"RS256","kid":"6jnGo_xZFr13SVCKgH5Tl8RN9cXqybYBQEo2Vf7Wagw","use":"sig","e":"AQAB","n":"mF3tIoT8h_2lpvYSCE_YopPW9Yp9fx4ddDNYFhFhmcqeMKMLl_JIXjnfs2EMB9zlwBm0hHkphGGWPfsy8wLZob2bVwVj8-3yPK3qRIt7ouW8OhNGn8tmQHB_fSDugPp-A_MuedNAkgeFB4zEcEJbVHjykC6cEYbyyPmhD0VJf0q3ifkg2Fxm8075QcV0iIcl46RHxTToTDeW44gPyMqzLrVq2UxOw-yn_I-o_M065hiONMthdiIR6KvjhO-fqtBbD37BH6XehSe2rtAxUpuLvGnoa25Gl7GkhD7_f6qIa3tmoYMPVvbxQSU8Ly2_AGJ7BCqgOMMyE0knsGLUi-dlBw"}]}'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- this is the client_id&lt;/span&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;jwt_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;audience&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'["0oab1arbbieTSsFVJ5d7"]'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- this is the name of the custom field we added to our token&lt;/span&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;jwt_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;claim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'login'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- this strips the @bank.com&lt;/span&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;identity_map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://dev-85651931.okta.com/oauth2/default /^(&lt;/span&gt;&lt;span class="se"&gt;\S&lt;/span&gt;&lt;span class="s1"&gt;+)(?:@) &lt;/span&gt;&lt;span class="se"&gt;\1&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let's create some users, with the name matching the name part of the email address we saved in Okta.&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="c1"&gt;-- 'mrossi@bank.com' is mapped to user 'mrossi', that is, &lt;/span&gt;
&lt;span class="c1"&gt;-- we strip the '@bank.com' part&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="n"&gt;mrossi&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;USER&lt;/span&gt; &lt;span class="n"&gt;akumar&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;USER&lt;/span&gt; &lt;span class="n"&gt;yfofana&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;We are now ready to request Okta for a JWT.&lt;/p&gt;

&lt;p&gt;We use curl, following the example in the Okta &lt;a href="https://developer.okta.com/docs/guides/customize-tokens-returned-from-okta/main/#request-a-token-with-the-custom-claim-2" rel="noopener noreferrer"&gt;doc&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-type:application/x-www-form-urlencoded"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"client_id=0oab1arbbieTSsFVJ5d7&amp;amp;client_secret=WbAWHW9UeURzXez99PMGWvuVp4KiGDXTNQrDajvszN2iQ3YyMjGyx0sWts1sM36J&amp;amp;grant_type=password&amp;amp;username=mrossi@bank.com&amp;amp;password=Mazinga123&amp;amp;scope=openid"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://dev-85651931.okta.com/oauth2/default/v1/token"&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the formatted response. We are only interested in the &lt;code&gt;id_token&lt;/code&gt;, so copy that string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expires_in"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJraWQiOiI2am5Hb194WkZyMTNTVkNLZ0g1VGw4Uk45Y1hxeWJZQlFFbzJWZjdXYWd3IiwiYWxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULlMtT0wzdmo2cl9Kem82RFMyOGcyNDRhUnVOSWFKUDBqRXVHVUlLZkFvajQiLCJpc3MiOiJodHRwczovL2Rldi04NTY1MTkzMS5va3RhLmNvbS9vYXV0aDIvZGVmYXVsdCIsImF1ZCI6ImFwaTovL2RlZmF1bHQiLCJpYXQiOjE2OTM0MTI2MTAsImV4cCI6MTY5MzQxNjIxMCwiY2lkIjoiMG9hYjFhcmJiaWVUU3NGVko1ZDciLCJ1aWQiOiIwMHViMWRuNXFjN0pBaFpHOTVkNyIsInNjcCI6WyJvcGVuaWQiXSwiYXV0aF90aW1lIjoxNjkzNDEyNjEwLCJzdWIiOiJtcm9zc2lAYmFuay5jb20ifQ.LqiPT1lwzTVWJ1yCFUfL7toFANAIcx4v9c6fYl5HTApbq3wmrpRCJQ9Jy-UgH-SHp4DxYV-tt-i1I-l3409_nVPTK751CxqNCySBsUqXiUHvsV7ZfNxkzgw_0BxCatirk32T08oJc0wuARyP9a1Pif_BwzawHP46vdhrikGwRXrrdtjV9yDLvzGwiHlS8IyTz0lRcCwVR0tq02EfEpBxFg992HJl-VZ4NMzFPj-D6LgAdo4vpJnY-fgHP-IZo26ijfSD9mvyB2V5lu4GAmcD9bV7LfSH_CilPrGFh481y2-YrBknMxUT_4tmN3LY6TZeBo3nDt4gqRjxPxhdDgcTHg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJraWQiOiI2am5Hb194WkZyMTNTVkNLZ0g1VGw4Uk45Y1hxeWJZQlFFbzJWZjdXYWd3IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwMHViMWRuNXFjN0pBaFpHOTVkNyIsInZlciI6MSwiaXNzIjoiaHR0cHM6Ly9kZXYtODU2NTE5MzEub2t0YS5jb20vb2F1dGgyL2RlZmF1bHQiLCJhdWQiOiIwb2FiMWFyYmJpZVRTc0ZWSjVkNyIsImlhdCI6MTY5MzQxMjYxMCwiZXhwIjoxNjkzNDE2MjEwLCJqdGkiOiJJRC41M3FaQ3pRVzZNa3dLZXh0d2FFcG1HWDFyX1k1bGFpNEp3TUJlWGl1c2J3IiwiYW1yIjpbInB3ZCJdLCJpZHAiOiIwMG9iMHUzOTI0QXJ1Q3RLZjVkNyIsImF1dGhfdGltZSI6MTY5MzQxMjYxMCwiYXRfaGFzaCI6IllqTV8xUGFxRHpGU3V6bkJPQ3Y4QVEiLCJsb2dpbiI6Im1yb3NzaUBiYW5rLmNvbSJ9.abhdpxyTW7WnQApWBJmiuZo_ziz7UEoKkHnf4nEaHsnDPzen32KwLWV4fNM4lvGl7YSND6lFErcZ_xLhBuqHutiH888UEyLzbuOSSFch0ie63VS0ElKlF6M1sXq_U8vRKikWpxC0dF3z1VTnKbRcfxN7QB6sxYn7iAUWt2xbEkaRTFO5lL4t5lFx8xUUBqtFFgy6im4w4CUyEvOww0DGwTC9gVifPiTa-fxGfN-OLFDyUZPM8F2JMmaYhpVWy-dplQjrmOv96zJ07GbsutPP5BrSb2JRCrEBZY0nTK8LklcVV4uZUbZJtTRDpHgNlmgzqzwlRWrt4dDvybnRZRq3Xg"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the sake of learning, we can use a tool such as &lt;a href="https://token.dev/" rel="noopener noreferrer"&gt;token.dev&lt;/a&gt; to decode the JWT.&lt;br&gt;
Note our "login" field in the Payload.&lt;/p&gt;

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

&lt;p&gt;Armed with our JWT, we can now proceed to login.&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="s2"&gt;"postgresql://mrossi:eyJraWQiOiI2am5Hb194WkZyMTNTVkNLZ0g1VGw4Uk45Y1hxeWJZQlFFbzJWZjdXYWd3IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwMHViMWRuNXFjN0pBaFpHOTVkNyIsInZlciI6MSwiaXNzIjoiaHR0cHM6Ly9kZXYtODU2NTE5MzEub2t0YS5jb20vb2F1dGgyL2RlZmF1bHQiLCJhdWQiOiIwb2FiMWFyYmJpZVRTc0ZWSjVkNyIsImlhdCI6MTY5MzQxMjYxMCwiZXhwIjoxNjkzNDE2MjEwLCJqdGkiOiJJRC41M3FaQ3pRVzZNa3dLZXh0d2FFcG1HWDFyX1k1bGFpNEp3TUJlWGl1c2J3IiwiYW1yIjpbInB3ZCJdLCJpZHAiOiIwMG9iMHUzOTI0QXJ1Q3RLZjVkNyIsImF1dGhfdGltZSI6MTY5MzQxMjYxMCwiYXRfaGFzaCI6IllqTV8xUGFxRHpGU3V6bkJPQ3Y4QVEiLCJsb2dpbiI6Im1yb3NzaUBiYW5rLmNvbSJ9.abhdpxyTW7WnQApWBJmiuZo_ziz7UEoKkHnf4nEaHsnDPzen32KwLWV4fNM4lvGl7YSND6lFErcZ_xLhBuqHutiH888UEyLzbuOSSFch0ie63VS0ElKlF6M1sXq_U8vRKikWpxC0dF3z1VTnKbRcfxN7QB6sxYn7iAUWt2xbEkaRTFO5lL4t5lFx8xUUBqtFFgy6im4w4CUyEvOww0DGwTC9gVifPiTa-fxGfN-OLFDyUZPM8F2JMmaYhpVWy-dplQjrmOv96zJ07GbsutPP5BrSb2JRCrEBZY0nTK8LklcVV4uZUbZJtTRDpHgNlmgzqzwlRWrt4dDvybnRZRq3Xg@localhost:26257/defaultdb?options=--crdb:jwt_auth_enabled=true&amp;amp;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;# Server version: CockroachDB CCL v23.1.4 (x86_64-apple-darwin19, built 2023/06/16 20:54:39, go1.19.4) (same version as client)&lt;/span&gt;
&lt;span class="c"&gt;# Cluster ID: f72ea38c-c5b3-437e-bd03-2391e6b5d150&lt;/span&gt;
&lt;span class="c"&gt;# Organization: Cockroach Demo&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;
mrossi@localhost:26257/defaultdb&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success! Let's try to use that token with another user, &lt;code&gt;akumar&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;cockroach sql &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s2"&gt;"postgresql://akumar:eyJraWQiOiI2am5Hb194WkZyMTNTVkNLZ0g1VGw4Uk45Y1hxeWJZQlFFbzJWZjdXYWd3IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwMHViMWRuNXFjN0pBaFpHOTVkNyIsInZlciI6MSwiaXNzIjoiaHR0cHM6Ly9kZXYtODU2NTE5MzEub2t0YS5jb20v
b2F1dGgyL2RlZmF1bHQiLCJhdWQiOiIwb2FiMWFyYmJpZVRTc0ZWSjVkNyIsImlhdCI6MTY5MzQxNjM4NCwiZXhwIjoxNjkzNDE5OTg0LCJqdGkiOiJJRC5feXhFOEd4MG9oZVZvQ1RYRXkwZFNwc0JrWlFZeHFoam9sY0JZZVBLTXFJIiwiYW1yIjpbInB3ZCJdLCJpZHAiOiIwMG9iMHUzOTI0QXJ1Q3RLZjVkNyIsImF1dGhfdGltZSI6M
TY5MzQxNjM4NCwiYXRfaGFzaCI6IlljNF9SUF9UWUxNM3B1RjdjSjlCYmciLCJsb2dpbiI6Im1yb3NzaUBiYW5rLmNvbSJ9.GrviZKNF4HezfhnmpvpZFZgKrYmivMknsZGFjD1eXSx2IoiBaG-t-q8CB9oVL48e3ZQXeXqXIl9PvL84a4ed8VCR1wJWH53xKTsAMGmx3dAJpxdO7PhNK1P0Eg2eY4p76mJ2qHbUGb201As_oC-OQAGkuGEZq
lBHV634Mv3zcdKjFFuUYaFkadMXqvIZomSoWUL5jDM8fqKtOUNoONHilthnOgZ3RxgfzGyeijdFS1_ODscbjbIzamKw6C7u698KG7eohVJMP2dIJr4jo33jpcwztRRQfU5ElcuINqLBw8Gv-_A1MlM4WrPHzsYN_OWgaRM_wd8UzOiyuMLFKirHFA@localhost:26257/defaultdb?options=--crdb:jwt_auth_enabled=true&amp;amp;sslm
ode=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;
ERROR: JWT authentication: invalid principal
SQLSTATE: 28000
DETAIL: token issued &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;mrossi@bank.com] and login was &lt;span class="k"&gt;for &lt;/span&gt;akumar
Failed running &lt;span class="s2"&gt;"sql"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Failed as expected!&lt;/p&gt;

&lt;p&gt;Your IdP administrator will be responsible for instructing on how to request a JWT, and what &lt;em&gt;claim&lt;/em&gt; to use, so do not worry if all this is overwhelming.&lt;/p&gt;

&lt;p&gt;From the CockroachDB side, what matters is that our SQL username matches with what the IdP has stored for a particular user: in this example, we used the email address, but it can be any unique ID that your company has adopted.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cockroachlabs.com/docs/stable/sso-sql" rel="noopener noreferrer"&gt;Cluster Single Sign-on (SSO) using a JSON web token (JWT)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cockroachlabs.com/blog/sso-to-clusters-with-jwt/" rel="noopener noreferrer"&gt;SSO to CockroachDB clusters using JWT - blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cockroachlabs.com/docs/stable/sso-db-console" rel="noopener noreferrer"&gt;Single Sign-on (SSO) for DB Console&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.okta.com/" rel="noopener noreferrer"&gt;Okta&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/signup/" rel="noopener noreferrer"&gt;Okta Developer Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/docs/guides/customize-tokens-returned-from-okta/main/" rel="noopener noreferrer"&gt;Okta docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cockroachdb</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Kafka 2 CockroachDB via JDBC Sink Connector Blueprint</title>
      <dc:creator>Fabio Ghirardello</dc:creator>
      <pubDate>Tue, 14 Mar 2023 18:08:13 +0000</pubDate>
      <link>https://dev.to/cockroachlabs/kafka-2-cockroachdb-via-jdbc-sink-connector-blueprint-3nb5</link>
      <guid>https://dev.to/cockroachlabs/kafka-2-cockroachdb-via-jdbc-sink-connector-blueprint-3nb5</guid>
      <description>&lt;p&gt;This is a short write up on the exercise of inserting batches of Kafka Records into &lt;a href="https://www.cockroachlabs.com/product/" rel="noopener noreferrer"&gt;CockroachDB&lt;/a&gt; using Confluent's &lt;a href="https://docs.confluent.io/kafka-connectors/jdbc/current/sink-connector/overview.html" rel="noopener noreferrer"&gt;JDBC Sink Connector&lt;/a&gt;, a 'no-code' solution for data ingestion.&lt;br&gt;
An example on how to setup the pipeline locally using Docker is documented in &lt;a href="https://dev.to/cockroachlabs/ingesting-data-from-kafka-to-cockroachdb-via-kafka-connect-1f9e"&gt;this blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The GitHub repository referenced in this write up is &lt;a href="https://github.com/fabiog1901/kafka2cockroachdb" rel="noopener noreferrer"&gt;&lt;code&gt;fabiog1901/kafka2cockroachdb&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The pipeline is very simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A python script generates data that gets ingested into a Kafka Topic within the Kafka Broker.&lt;/li&gt;
&lt;li&gt;The Topic is partitioned.&lt;/li&gt;
&lt;li&gt;Exactly 1 Kafka Connect &lt;em&gt;task&lt;/em&gt; is started for each partition.&lt;/li&gt;
&lt;li&gt;The Task reads from the topic partition and inserts into CockroachDB by making a conneciton through the Load Balancer.&lt;/li&gt;
&lt;/ol&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%2Fqbyius6x033zp6cetvlx.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%2Fqbyius6x033zp6cetvlx.png" alt="pipeline" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Infrastructure and Components Setup
&lt;/h2&gt;

&lt;p&gt;Infrastructure was deployed using Ansible on Google Cloud VMs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single node Confluent Platform (Kafka broker and Kafka Connect) on &lt;code&gt;n2-standard-16&lt;/code&gt; instance type.&lt;/li&gt;
&lt;li&gt;3 nodes CockroachDB cluster using the &lt;a href="https://cloud.google.com/compute/docs/general-purpose-machines#n2d_machines" rel="noopener noreferrer"&gt;&lt;code&gt;n2d-standard-8|16|32&lt;/code&gt;&lt;/a&gt; instance types.
Each VM was provisioned with 1 x 2.5TB Persistent SSD (&lt;code&gt;pd-ssd&lt;/code&gt;) volume.&lt;/li&gt;
&lt;li&gt;Single node Load Balancer instance running HAProxy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main Kafka backend was installed using the &lt;a href="https://docs.confluent.io/ansible/current/overview.html" rel="noopener noreferrer"&gt;Ansible Playbooks for Confluent Platform&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The CockroachDB cluster and the HAProxy load balancer instance were installed using the &lt;a href="https://github.com/fabiog1901/cockroachdb-collection" rel="noopener noreferrer"&gt;&lt;code&gt;fabiog1901.cockroachdb&lt;/code&gt; Ansible Collection&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The test was run executing convenience Python script &lt;code&gt;play.py&lt;/code&gt;.&lt;br&gt;
The script coordinates the execution of 4 Ansible Playbooks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;kafka.yaml&lt;/code&gt; - Provision and prepare the Kafka cluster.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cockroachdb.yaml&lt;/code&gt; - Provision and prepare the CockroachDB cluster.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kafka-producer.yaml&lt;/code&gt; - Prepare Kafka broker and start the Kafka producer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kafka-consumer.yaml&lt;/code&gt; - Run the Kafka consumer i.e. Kafka Connect.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Kafka Producer
&lt;/h3&gt;

&lt;p&gt;To load data into the Kafka Topic we used a simple generator written in Python, &lt;code&gt;libs/gen.py&lt;/code&gt;.&lt;br&gt;
The generator leverages the &lt;code&gt;confluent-kafka&lt;/code&gt; &lt;a href="https://github.com/confluentinc/confluent-kafka-python" rel="noopener noreferrer"&gt;package&lt;/a&gt; for publishing Avro records of about 60 fields.&lt;br&gt;
The generator is started and let run for 20 minutes before any consumer process is started, so that the Topic is always well filled with records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kafka Consumer
&lt;/h3&gt;

&lt;p&gt;Kafka Connect was configured with the &lt;strong&gt;JDBC Sink Connector&lt;/strong&gt;, however, a custom &lt;code&gt;kafka-connect-jdbc-10.6.1.jar&lt;/code&gt; file was used: the only change made to the original version was to set &lt;code&gt;autocommit=true&lt;/code&gt; for the SQL transactions, &lt;a href="https://github.com/confluentinc/kafka-connect-jdbc/blob/v10.6.1/src/main/java/io/confluent/connect/jdbc/sink/JdbcDbWriter.java#L57" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
This change is important as it allows statements to be executed implicitly, saving therefore a roundtrip for the commit message.&lt;br&gt;
The Jar file can be found in the &lt;code&gt;libs&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Similarly, a custom &lt;a href="https://jdbc.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL JDBC Driver&lt;/a&gt; was used, allowing for batch statements to be larger than 128 records, see &lt;a href="https://github.com/pgjdbc/pgjdbc/blob/REL42.5.0/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java#L1726" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
The result is we can now test with multi-value INSERT statements that have more than 128 values.&lt;br&gt;
The custom driver Jar file is also in the &lt;code&gt;libs&lt;/code&gt; directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  CockroachDB Cluster
&lt;/h2&gt;

&lt;p&gt;The 3 nodes CockroachDB cluster runs version 22.2.5|6.&lt;br&gt;
The database was seeded with approximately 0.5TB of data.&lt;br&gt;
The data was generated externally and imported from Google Cloud Storage directly into the database.&lt;br&gt;
CockroachDB stored the data with a Replication Factor of 3, the default.&lt;br&gt;
This implies that every single node has a full copy of the entire dataset.&lt;br&gt;
See custom settings and DDL statements executed in file &lt;code&gt;libs/s.sql&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Description
&lt;/h2&gt;

&lt;p&gt;We tested with 3 &lt;strong&gt;instance types&lt;/strong&gt;, multiple Kafka topic &lt;strong&gt;partitions&lt;/strong&gt; and &lt;strong&gt;batch sizes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Script &lt;code&gt;play.py&lt;/code&gt; was used to run the tests.&lt;br&gt;
In short, for each instance type, we cycled through all partitions, and for each partition, we cycled through all batch sizes.&lt;/p&gt;

&lt;p&gt;On each &lt;strong&gt;partition&lt;/strong&gt; cycle, the JDBC Sink Connector was created with &lt;code&gt;tasks.max&lt;/code&gt; set to the same number as the partition count.&lt;br&gt;
Here, a &lt;em&gt;task&lt;/em&gt; is a process that creates a database connection, consumes records from the assigned topic partition, prepares the INSERT statement and finally sends it to CockroachDB for execution.&lt;/p&gt;

&lt;p&gt;On each &lt;strong&gt;batch size&lt;/strong&gt; cycle, the JDBC Sink Connector was created with &lt;code&gt;batch.size&lt;/code&gt; and &lt;code&gt;consumer.override.max.poll.records&lt;/code&gt; set to the current &lt;code&gt;batch_size&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;Results of transaction latency, throughput (TPS) and CPU util are shown below for each of the test cases.&lt;br&gt;
&lt;code&gt;per_stmt_latency_ms&lt;/code&gt; is a computed value, derived by dividing &lt;code&gt;txn_latency_ms&lt;/code&gt; by &lt;code&gt;batch_size&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using n2d-standard-8
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;total_vcpus&lt;/th&gt;
&lt;th&gt;k_partitions&lt;/th&gt;
&lt;th&gt;batch_size&lt;/th&gt;
&lt;th&gt;tps&lt;/th&gt;
&lt;th&gt;cpu_util_pct&lt;/th&gt;
&lt;th&gt;txn_latency_ms&lt;/th&gt;
&lt;th&gt;per_stmt_latency_ms&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3160&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;9984&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;10.7&lt;/td&gt;
&lt;td&gt;1.34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;12064&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;19.3&lt;/td&gt;
&lt;td&gt;1.21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;14457&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;32.7&lt;/td&gt;
&lt;td&gt;1.02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;15920&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;59.9&lt;/td&gt;
&lt;td&gt;0.94&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;17820&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;105.3&lt;/td&gt;
&lt;td&gt;0.82&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5839&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;3.3&lt;/td&gt;
&lt;td&gt;3.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;10653&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;22.4&lt;/td&gt;
&lt;td&gt;2.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;11854&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;41.7&lt;/td&gt;
&lt;td&gt;2.61&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;13923&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;71.4&lt;/td&gt;
&lt;td&gt;2.23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;15765&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;126.9&lt;/td&gt;
&lt;td&gt;1.98&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;17684&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;219.0&lt;/td&gt;
&lt;td&gt;1.71&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Overview Dashboard -&amp;gt; SQL Statements&lt;/em&gt;&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%2F3hml9fr6gkbja29xibfb.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%2F3hml9fr6gkbja29xibfb.png" alt="8-overview" width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hardware Dashboard -&amp;gt; CPU Utilization&lt;/em&gt;&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%2F2wc8bjv9fiq2se6nxtip.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%2F2wc8bjv9fiq2se6nxtip.png" alt="8-cpu-util" width="800" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;SQL Activity --&amp;gt; Transaction page - data for the 18 partitions test&lt;/em&gt;&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%2Ffr9xx2ftntzz03kjmxej.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%2Ffr9xx2ftntzz03kjmxej.png" alt="8-sql-activity-18-partitions" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Using n2d-standard-16
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;total_vcpus&lt;/th&gt;
&lt;th&gt;k_partitions&lt;/th&gt;
&lt;th&gt;batch_size&lt;/th&gt;
&lt;th&gt;tps&lt;/th&gt;
&lt;th&gt;cpu_util_pct&lt;/th&gt;
&lt;th&gt;txn_latency_ms&lt;/th&gt;
&lt;th&gt;per_stmt_latency_ms&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2955&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;3.3&lt;/td&gt;
&lt;td&gt;3.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;12104&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;19.0&lt;/td&gt;
&lt;td&gt;1.19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;13824&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;35.0&lt;/td&gt;
&lt;td&gt;1.09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;16187&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;61.0&lt;/td&gt;
&lt;td&gt;0.95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;18558&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;105.0&lt;/td&gt;
&lt;td&gt;0.82&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5846&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;3.3&lt;/td&gt;
&lt;td&gt;3.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;14061&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;35.0&lt;/td&gt;
&lt;td&gt;2.19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;16187&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;63.0&lt;/td&gt;
&lt;td&gt;1.97&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;18700&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;109.0&lt;/td&gt;
&lt;td&gt;1.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;21231&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;188.0&lt;/td&gt;
&lt;td&gt;1.47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;8070&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;3.8&lt;/td&gt;
&lt;td&gt;3.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;14788&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;52.0&lt;/td&gt;
&lt;td&gt;3.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;16641&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;94.0&lt;/td&gt;
&lt;td&gt;2.94&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;20007&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;154.0&lt;/td&gt;
&lt;td&gt;2.41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;20485&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;298.0&lt;/td&gt;
&lt;td&gt;2.33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;10237&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;4.1&lt;/td&gt;
&lt;td&gt;4.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;15456&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;67.0&lt;/td&gt;
&lt;td&gt;4.19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;18817&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;111.0&lt;/td&gt;
&lt;td&gt;3.47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;19569&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;212.0&lt;/td&gt;
&lt;td&gt;3.31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;18393&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;441.0&lt;/td&gt;
&lt;td&gt;3.45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;11153&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;5.0&lt;/td&gt;
&lt;td&gt;5.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;15526&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;85.0&lt;/td&gt;
&lt;td&gt;5.31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;18632&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;141.0&lt;/td&gt;
&lt;td&gt;4.41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;18488&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;277.0&lt;/td&gt;
&lt;td&gt;4.33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;18043&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;569.0&lt;/td&gt;
&lt;td&gt;4.45&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Overview Dashboard -&amp;gt; SQL Statements&lt;/em&gt;&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%2Ffzwb8eeko8wsn5v2oh8b.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%2Ffzwb8eeko8wsn5v2oh8b.png" alt="16-overview" width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hardware Dashboard -&amp;gt; CPU Utilization&lt;/em&gt;&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%2F2xkxp56ucz1uc68vqm4b.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%2F2xkxp56ucz1uc68vqm4b.png" alt="16-cpu-util" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hardware Dashboard -&amp;gt; Disk Write MiB/s&lt;/em&gt;&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%2Fjrsti3he6yer7wr7kokj.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%2Fjrsti3he6yer7wr7kokj.png" alt="16-disk-write" width="800" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Replication Dashboard -&amp;gt; Leaseholders per Node&lt;/em&gt;&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%2F9hb8n7saznzuw5zctx6t.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%2F9hb8n7saznzuw5zctx6t.png" alt="16-lh-per-node" width="800" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Using n2d-standard-32
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;total_vcpus&lt;/th&gt;
&lt;th&gt;k_partitions&lt;/th&gt;
&lt;th&gt;batch_size&lt;/th&gt;
&lt;th&gt;tps&lt;/th&gt;
&lt;th&gt;cpu_util_pct&lt;/th&gt;
&lt;th&gt;txn_latency_ms&lt;/th&gt;
&lt;th&gt;per_stmt_latency_ms&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;8237&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;35012&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;27.5&lt;/td&gt;
&lt;td&gt;0.86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;39455&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;48.7&lt;/td&gt;
&lt;td&gt;0.76&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;42938&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;88.7&lt;/td&gt;
&lt;td&gt;0.69&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;46214&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;153.3&lt;/td&gt;
&lt;td&gt;0.60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;11559&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;3.2&lt;/td&gt;
&lt;td&gt;3.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;34039&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;44.4&lt;/td&gt;
&lt;td&gt;1.39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;37177&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;93.5&lt;/td&gt;
&lt;td&gt;1.46&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;36003&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;160.8&lt;/td&gt;
&lt;td&gt;1.26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;37501&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;292.6&lt;/td&gt;
&lt;td&gt;1.14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;14253&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;3.4&lt;/td&gt;
&lt;td&gt;3.40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;32578&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;63.3&lt;/td&gt;
&lt;td&gt;1.98&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;32340&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;129.2&lt;/td&gt;
&lt;td&gt;2.02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;31045&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;260.0&lt;/td&gt;
&lt;td&gt;2.03&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;30034&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;489.4&lt;/td&gt;
&lt;td&gt;1.91&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;16325&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;3.8&lt;/td&gt;
&lt;td&gt;3.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;30576&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;86.9&lt;/td&gt;
&lt;td&gt;2.72&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;30277&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;169.2&lt;/td&gt;
&lt;td&gt;2.64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;29890&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;330.0&lt;/td&gt;
&lt;td&gt;2.58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;29235&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;668.2&lt;/td&gt;
&lt;td&gt;2.61&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Hardware Dashboard -&amp;gt; CPU Utilization - Sometimes load is slightly uneven, even if the workload is perfectly distributed&lt;/em&gt;&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%2Fdmq47bixp9w5aslpgtg1.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%2Fdmq47bixp9w5aslpgtg1.png" alt="32-cpu-util" width="800" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It is generally recommended to keep the cluster CPU Utilization at around 50% as to have headroom for sudden spikes, node failures, and background database operations such as backups, CDC feeds, import/export jobs, etc.&lt;/li&gt;
&lt;li&gt;Write throughput varies greatly depending on the hardware utilized. See public clouds hardware recommendation for CockroachDB in the &lt;a href="https://www.cockroachlabs.com/guides/2022-cloud-report/" rel="noopener noreferrer"&gt;Cloud Report&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Transaction latency varies in multi-region clusters, as you can expect transactions have to ensure at least 1 out of region replica has to be kept in sync.&lt;/li&gt;
&lt;li&gt;Other factors impacting latency include, but are not limited to: read/write ratio, count of secondary indexes, database topology, client location, record size.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;In this project, I have tweaked both the driver and the &lt;code&gt;kafka-connect-jdbc&lt;/code&gt; connector. For my next tests, I like to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explore best ways to optimize the Kafka Connector, possibly working along with the Confluent engineering team.&lt;/li&gt;
&lt;li&gt;Replace the standard JDBC PostgreSQL Driver with the &lt;a href="https://github.com/cockroachlabs-field/cockroachdb-jdbc" rel="noopener noreferrer"&gt;&lt;code&gt;cockroachdb-jdbc&lt;/code&gt;&lt;/a&gt; driver, kindly developed and maintained by &lt;a href="https://blog.cloudneutral.se/" rel="noopener noreferrer"&gt;Kai Niemi&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cockroachlabs.com/product/" rel="noopener noreferrer"&gt;CockroachDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.confluent.io/ansible/current/overview.html" rel="noopener noreferrer"&gt;Ansible Playbooks for Confluent Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fabiog1901/cockroachdb-collection" rel="noopener noreferrer"&gt;fabiog1901.cockroachdb Ansible Collection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.confluent.io/kafka-connectors/jdbc/current/sink-connector/overview.html" rel="noopener noreferrer"&gt;JDBC Sink Connector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/confluentinc/confluent-kafka-python" rel="noopener noreferrer"&gt;Confluent's Python Client confluent-kafka&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jdbc.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL JDBC Driver&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cockroachdb</category>
      <category>kafka</category>
      <category>kafkaconnect</category>
    </item>
    <item>
      <title>Repaving CockroachDB cluster node VMs the easy way</title>
      <dc:creator>Fabio Ghirardello</dc:creator>
      <pubDate>Mon, 30 Jan 2023 21:27:26 +0000</pubDate>
      <link>https://dev.to/cockroachlabs/repaving-cockroachdb-cluster-node-vms-the-easy-way-2cfg</link>
      <guid>https://dev.to/cockroachlabs/repaving-cockroachdb-cluster-node-vms-the-easy-way-2cfg</guid>
      <description>&lt;p&gt;In this brief post, I will demonstrate how you can repave/re-image your CockroachDB cluster VMs while still being online and without much data movement and replication. The process is simple and can/should be fully automated. &lt;/p&gt;

&lt;p&gt;For each node in the cluster:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Halt the VM - systemd will gracefully stop the CockroachDB node.&lt;/li&gt;
&lt;li&gt;Detach all data disks.&lt;/li&gt;
&lt;li&gt;Upgrade/replace/patch the VM image with the new image (or use a new VM pre-configured with CockroachDB).&lt;/li&gt;
&lt;li&gt;Attach disks to re-imaged/repaved VM (or the new VM).&lt;/li&gt;
&lt;li&gt;Start VM and CockroachDB process.&lt;/li&gt;
&lt;li&gt;Wait few minutes for cluster to adjust.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prepare for Node Shutdown
&lt;/h2&gt;

&lt;p&gt;Before attempting any repave or restart procedure, make sure you have configured your cluster, load balancer and application connection pool software for a graceful shutdown of the CockroachDB node.&lt;/p&gt;

&lt;p&gt;This involves following closely the &lt;strong&gt;draining&lt;/strong&gt; procedure described in the official docs for the &lt;a href="https://www.cockroachlabs.com/docs/stable/node-shutdown" rel="noopener noreferrer"&gt;Node Shutdown&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The documentation page describes in great details the sequence of events that occur whens a node is sent the &lt;code&gt;SIGTERM&lt;/code&gt; signal aka the "graceful shutdown" signal, which is what happens when &lt;code&gt;systemd&lt;/code&gt; tries to stop all the running services as a result of a system halt request, initiated by a sysadmin during a patch, repave or any maintenance task. I wrote another blog with a hands-on example, see &lt;a href="https://dev.to/cockroachlabs/cockroachdb-graceful-shutdowns-4dn"&gt;CockroachDB Graceful Shutdwons&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Draining&lt;/strong&gt; is the CockroachDB term for bringing a node to a state that the process can be terminated without impact to clients or other nodes. There are 5 phases – the sequence is quite complex and it’s important that we understand how to mitigate risks to the app at every phase.&lt;/p&gt;

&lt;p&gt;The 1st phase starts with the node setting its health-check status to HTTP503 (&lt;code&gt;Service Unavailable&lt;/code&gt;). This is to inform the load balancer that the node is no longer healthy and thus any new connection request should be routed elsewhere. The problem is, the LB will take some time to declare the server as unhealthy: usually, the LB will check the health-check endpoint every few seconds - the interval - and declare the server as unhealthy only after a few failed probes - the threshold - . For example, if interval=5s and threshold=3, the load balancer can take up to 5*4=20s to set the server as unavailable. &lt;/p&gt;

&lt;p&gt;By setting cluster setting &lt;code&gt;server.shutdown.initial_wait&lt;/code&gt; to a value &lt;strong&gt;larger&lt;/strong&gt; than the LB timeout, we are ensured that phase 2 will start only &lt;strong&gt;after&lt;/strong&gt; the LB has declared the server dead. &lt;br&gt;
For example, we could set the &lt;code&gt;initial_wait&lt;/code&gt; to 21s. With this value, we know for sure that the LB will declare the node unavailable way before the node starts the 2nd phase of the draining activity. This avoids the unpleasant situation that the LB routes a new connection request to the node only to be denied, bubbling up the error to the application.&lt;/p&gt;

&lt;p&gt;But what happens to already established connections? We have to wait for those to close by themselves, usually this happens because the connection pool, such as HikariCP, has a setting &lt;code&gt;maxLifetime&lt;/code&gt; that dictates how long a connection can live. In CockroachDB, you configure cluster setting &lt;code&gt;server.shutdown.connections.timeout&lt;/code&gt; to a value few seconds larger than your CP’s "max-lifetime" property. This means that the CockroachDB node will wait those many seconds before forcefully close any open connection session. But by that time, your Connection Pool will have retired/closed all existing connections anyway. For example, HikariCP's maxLifetime's default is 30minutes. That's a lot of time to wait for, so a more sensible value is, say, 5 minutes. Accordingly, set the &lt;code&gt;server.shutdown.connections.timeout&lt;/code&gt; to 360s. Only after 360s have passed will CockroachDB start the 3rd draining phase. &lt;/p&gt;

&lt;p&gt;And what if there’s a connection on which there’s a long running transaction going on, or the node itself is involved in a transaction started on another node and that transaction is still executing? Again, CockroachDB will pause its draining activity and will wait for all transactions to complete, or when the timeout set by cluster setting &lt;code&gt;server.shutdown.transactions.timeout&lt;/code&gt; has been reached - naturally, you want to set a timeout to avoid runaway transactions, such as full scans of very large tables. Only after all transactions have completed, or the transactions have been interrupted for reaching the transaction timeout, will the 5th and final phase begin.&lt;/p&gt;

&lt;p&gt;By coordinating the node shutdown procedure with the LB and the CP, you are ensured that your app will not run into any service disruption. It is also very important that you configure your &lt;code&gt;systemd&lt;/code&gt; service property &lt;code&gt;TimeoutStopSec&lt;/code&gt; accordingly - you don't want systemd to send a &lt;code&gt;SIGKILL&lt;/code&gt; before the draining process is complete and, as we saw, it can take several minutes. &lt;/p&gt;

&lt;p&gt;For example, if the total of the 3 above mentioned cluster settings is 7 minutes, and you have noticed the node usually takes 3 minutes to shed all its LeaseHolders to other nodes, set TimeoutStopSec=720 to give good 12 minutes for a graceful shutdown. Monitor the shutdown procedure to ensure the node shutdown always completes gracefully.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;I created a basic 3 nodes cluster on AWS with 1 attached EBS volume each. I started some sample load, and let it run for about 10 minutes.&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%2Fc1gughe9fhpltky5q5px.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%2Fc1gughe9fhpltky5q5px.png" alt="overview" width="800" height="425"&gt;&lt;/a&gt;&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%2Fa3dutafkot2rswd8w2zt.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%2Fa3dutafkot2rswd8w2zt.png" alt="sql-stmt-chart" width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the view of one of these nodes, see the mounted disk at &lt;code&gt;/dev/sdf&lt;/code&gt;? That's an EBS volume.&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%2F9ezl0h1rh9kddqwtjz1e.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%2F9ezl0h1rh9kddqwtjz1e.png" alt="ec2-instance-description" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I then reached out to the AWS Console and stopped a node, &lt;code&gt;n3&lt;/code&gt;. CockroachDB complains about a suspect/dead node, and shows that some ranges are under-replicated.&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%2F1hh7wdbo27kjbpwf52xo.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%2F1hh7wdbo27kjbpwf52xo.png" alt="overview-underreplication" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the VM was stopped, I detached the EBS volume (see green banner)... &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%2Fpq4ak74rbwy0o8u407y6.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%2Fpq4ak74rbwy0o8u407y6.png" alt="vol-detached" width="800" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...and attached it to a new and up and running VM.&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%2Fich056deg0tany5nl0xr.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%2Fich056deg0tany5nl0xr.png" alt="attach-vol" width="800" height="710"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I then ssh'ed into the new VM, and started the CockroachDB process. If you are repaving into a &lt;strong&gt;new&lt;/strong&gt; VM, make sure the &lt;a href="https://www.cockroachlabs.com/docs/stable/security-reference/transport-layer-security.html#tls-between-cockroachdb-nodes" rel="noopener noreferrer"&gt;TLS certificates for inter-node communication&lt;/a&gt; are correctly installed.&lt;br&gt;
Immediately, the node connects to the cluster and complains about under-replicated ranges are gone.&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%2F2ofrs04tan1cbwhoogxz.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%2F2ofrs04tan1cbwhoogxz.png" alt=" " width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! Confirm under-replicated ranges are gone, then wash, rinse and repeat for every other node!&lt;/p&gt;

&lt;p&gt;While, as you can see from the charts, the whole process took about 5 minutes, this can be greatly simplified and improved upon by using a dev-ops automation tool such as Ansible. &lt;/p&gt;
&lt;h3&gt;
  
  
  Cleaning up the old nodes
&lt;/h3&gt;

&lt;p&gt;Once repaved, we can remove the notion of the old, repaved node &lt;code&gt;n3&lt;/code&gt; from the cluster using the &lt;a href="https://www.cockroachlabs.com/docs/stable/cockroach-node.html#node-decommission" rel="noopener noreferrer"&gt;decommission&lt;/a&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@ip-10-10-11-95:/home/ubuntu# cockroach node status &lt;span class="nt"&gt;--certs-dir&lt;/span&gt; /var/lib/cockroach/certs/ 
  &lt;span class="nb"&gt;id&lt;/span&gt; |       address       |     sql_address     |  build  |              started_at              |              updated_at              |        locality         | is_available | is_live
&lt;span class="nt"&gt;-----&lt;/span&gt;+---------------------+---------------------+---------+--------------------------------------+--------------------------------------+-------------------------+--------------+----------
   1 | 18.224.59.170:26257 | 18.224.59.170:26257 | v22.2.3 | 2023-01-30 20:03:59.66018 +0000 UTC  | 2023-01-30 20:26:07.183524 +0000 UTC | &lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-east-2,zone&lt;span class="o"&gt;=&lt;/span&gt;a | &lt;span class="nb"&gt;true&lt;/span&gt;         | &lt;span class="nb"&gt;true
   &lt;/span&gt;2 | 18.119.133.34:26257 | 18.119.133.34:26257 | v22.2.3 | 2023-01-30 20:04:00.13035 +0000 UTC  | 2023-01-30 20:26:07.642953 +0000 UTC | &lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-east-2,zone&lt;span class="o"&gt;=&lt;/span&gt;a | &lt;span class="nb"&gt;true&lt;/span&gt;         | &lt;span class="nb"&gt;true
   &lt;/span&gt;3 | 18.117.158.82:26257 | 18.117.158.82:26257 | v22.2.3 | 2023-01-30 20:04:00.944523 +0000 UTC | 2023-01-30 20:13:52.332716 +0000 UTC | &lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-east-2,zone&lt;span class="o"&gt;=&lt;/span&gt;a | &lt;span class="nb"&gt;false&lt;/span&gt;        | &lt;span class="nb"&gt;false
   &lt;/span&gt;4 | 3.141.0.11:26257    | 3.141.0.11:26257    | v22.2.3 | 2023-01-30 20:21:37.594691 +0000 UTC | 2023-01-30 20:26:07.609098 +0000 UTC | &lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-east-2,zone&lt;span class="o"&gt;=&lt;/span&gt;a | &lt;span class="nb"&gt;true&lt;/span&gt;         | &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;4 rows&lt;span class="o"&gt;)&lt;/span&gt;
root@ip-10-10-11-95:/home/ubuntu# 
root@ip-10-10-11-95:/home/ubuntu# 
root@ip-10-10-11-95:/home/ubuntu# cockroach node decommission 3 &lt;span class="nt"&gt;--certs-dir&lt;/span&gt; /var/lib/cockroach/certs/ 

  &lt;span class="nb"&gt;id&lt;/span&gt; | is_live | replicas | is_decommissioning |   membership    | is_draining
&lt;span class="nt"&gt;-----&lt;/span&gt;+---------+----------+--------------------+-----------------+--------------
   3 |  &lt;span class="nb"&gt;false&lt;/span&gt;  |        0 |        &lt;span class="nb"&gt;true&lt;/span&gt;        | decommissioning |    &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;1 row&lt;span class="o"&gt;)&lt;/span&gt;

No more data reported on target nodes. Please verify cluster health before removing the nodes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fy9o6gb9po0wstgjrd1us.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%2Fy9o6gb9po0wstgjrd1us.png" alt="decommissioned-nodes" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The cluster is now updated and as good as new.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cockroachlabs.com/product/" rel="noopener noreferrer"&gt;CockroachDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cockroachlabs.com/docs/stable/node-shutdown" rel="noopener noreferrer"&gt;Node Shutdown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cockroachdb</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Ingesting data from Kafka to CockroachDB via Kafka Connect</title>
      <dc:creator>Fabio Ghirardello</dc:creator>
      <pubDate>Wed, 21 Dec 2022 19:22:27 +0000</pubDate>
      <link>https://dev.to/cockroachlabs/ingesting-data-from-kafka-to-cockroachdb-via-kafka-connect-1f9e</link>
      <guid>https://dev.to/cockroachlabs/ingesting-data-from-kafka-to-cockroachdb-via-kafka-connect-1f9e</guid>
      <description>&lt;p&gt;In this brief write-up, I demonstrate how to build a working pipeline to ingest Kafka records into CockroachDB via Kafka Connect's &lt;a href="https://docs.confluent.io/kafka-connectors/jdbc/current/sink-connector/overview.html"&gt;JDBC Sink Connector&lt;/a&gt;, locally, using Docker. &lt;/p&gt;

&lt;p&gt;This serves as &lt;strong&gt;functional testing&lt;/strong&gt;, that is, for understanding the concepts and all moving parts before moving on to performance testing, on real Production grade clusters.&lt;br&gt;
Stay tuned for a follow-up blog where we will use this content to build and run a performance test.&lt;br&gt;
UPDATE: performance test is &lt;a href="https://dev.to/cockroachlabs/kafka-2-cockroachdb-via-jdbc-sink-connector-blueprint-3nb5"&gt;here&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Save below as file &lt;code&gt;kafka2crdb.yaml&lt;/code&gt;.&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="c1"&gt;# kafka2crdb.yaml&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;zookeeper&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;confluentinc/cp-zookeeper:7.3.0&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zookeeper&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zookeeper&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="s"&gt;2181:2181&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ZOOKEEPER_CLIENT_PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2181&lt;/span&gt;
      &lt;span class="na"&gt;ZOOKEEPER_TICK_TIME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2000&lt;/span&gt;

  &lt;span class="na"&gt;broker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;confluentinc/cp-server:7.3.0&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;broker&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;broker&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;zookeeper&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="s"&gt;9092:9092&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;9101:9101&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_BROKER_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_ZOOKEEPER_CONNECT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;zookeeper:2181'&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_LISTENER_SECURITY_PROTOCOL_MAP&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_ADVERTISED_LISTENERS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PLAINTEXT://broker:29092,PLAINTEXT_HOST://localhost:9092&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_METRIC_REPORTERS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;io.confluent.metrics.reporter.ConfluentMetricsReporter&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_CONFLUENT_LICENSE_TOPIC_REPLICATION_FACTOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_CONFLUENT_BALANCER_TOPIC_REPLICATION_FACTOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_TRANSACTION_STATE_LOG_MIN_ISR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_JMX_PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9101&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_JMX_HOSTNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_CONFLUENT_SCHEMA_REGISTRY_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://schema-registry:8081&lt;/span&gt;
      &lt;span class="na"&gt;CONFLUENT_METRICS_REPORTER_BOOTSTRAP_SERVERS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;broker:29092&lt;/span&gt;
      &lt;span class="na"&gt;CONFLUENT_METRICS_REPORTER_TOPIC_REPLICAS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;CONFLUENT_METRICS_ENABLE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;
      &lt;span class="na"&gt;CONFLUENT_SUPPORT_CUSTOMER_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;anonymous'&lt;/span&gt;

  &lt;span class="na"&gt;schema-registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;confluentinc/cp-schema-registry:7.3.0&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;schema-registry&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;schema-registry&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;broker&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="s"&gt;8081:8081&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SCHEMA_REGISTRY_HOST_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;schema-registry&lt;/span&gt;
      &lt;span class="na"&gt;SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;broker:29092'&lt;/span&gt;
      &lt;span class="na"&gt;SCHEMA_REGISTRY_LISTENERS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://0.0.0.0:8081&lt;/span&gt;

  &lt;span class="na"&gt;kafka-connect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cnfldemos/cp-server-connect-datagen:0.6.0-7.3.0&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;connect&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;connect&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;broker&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;schema-registry&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="s"&gt;8083:8083&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_BOOTSTRAP_SERVERS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;broker:29092'&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_REST_ADVERTISED_HOST_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;connect&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_GROUP_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;compose-connect-group&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_CONFIG_STORAGE_TOPIC&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker-connect-configs&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_OFFSET_FLUSH_INTERVAL_MS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10000&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_OFFSET_STORAGE_TOPIC&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker-connect-offsets&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_STATUS_STORAGE_TOPIC&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker-connect-status&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_STATUS_STORAGE_REPLICATION_FACTOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_KEY_CONVERTER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;org.apache.kafka.connect.storage.StringConverter&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_VALUE_CONVERTER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;io.confluent.connect.avro.AvroConverter&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_VALUE_CONVERTER_SCHEMA_REGISTRY_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://schema-registry:8081&lt;/span&gt;
      &lt;span class="na"&gt;CLASSPATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/share/java/monitoring-interceptors/monitoring-interceptors-7.3.0.jar&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_PRODUCER_INTERCEPTOR_CLASSES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;io.confluent.monitoring.clients.interceptor.MonitoringProducerInterceptor"&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_CONSUMER_INTERCEPTOR_CLASSES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;io.confluent.monitoring.clients.interceptor.MonitoringConsumerInterceptor"&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_PLUGIN_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/share/java,/usr/share/confluent-hub-components"&lt;/span&gt;
      &lt;span class="na"&gt;CONNECT_LOG4J_LOGGERS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;org.apache.zookeeper=ERROR,org.I0Itec.zkclient=ERROR,org.reflections=ERROR&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;# Installing connector plugin&lt;/span&gt;
        &lt;span class="s"&gt;confluent-hub install --no-prompt confluentinc/kafka-connect-jdbc:latest&lt;/span&gt;
        &lt;span class="s"&gt;# Downloading newer Postgres JDBC driver&lt;/span&gt;
        &lt;span class="s"&gt;cd /usr/share/confluent-hub-components/confluentinc-kafka-connect-jdbc/lib&lt;/span&gt;
        &lt;span class="s"&gt;rm -rf postgresql*&lt;/span&gt;
        &lt;span class="s"&gt;wget https://jdbc.postgresql.org/download/postgresql-42.5.0.jar &lt;/span&gt;
        &lt;span class="s"&gt;# Launching Kafka Connect worker&lt;/span&gt;
        &lt;span class="s"&gt;/etc/confluent/docker/run &amp;amp;&lt;/span&gt;
        &lt;span class="s"&gt;sleep infinity&lt;/span&gt;

  &lt;span class="na"&gt;control-center&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;confluentinc/cp-enterprise-control-center:7.3.0&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;control-center&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;control-center&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;broker&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;schema-registry&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kafka-connect&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="s"&gt;9021:9021&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CONTROL_CENTER_BOOTSTRAP_SERVERS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;broker:29092'&lt;/span&gt;
      &lt;span class="na"&gt;CONTROL_CENTER_CONNECT_CONNECT-DEFAULT_CLUSTER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;connect:8083'&lt;/span&gt;
      &lt;span class="na"&gt;CONTROL_CENTER_KSQL_KSQLDB1_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://ksqldb-server:8088"&lt;/span&gt;
      &lt;span class="na"&gt;CONTROL_CENTER_KSQL_KSQLDB1_ADVERTISED_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8088"&lt;/span&gt;
      &lt;span class="na"&gt;CONTROL_CENTER_SCHEMA_REGISTRY_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://schema-registry:8081"&lt;/span&gt;
      &lt;span class="na"&gt;CONTROL_CENTER_REPLICATION_FACTOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;CONTROL_CENTER_INTERNAL_TOPICS_PARTITIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;CONTROL_CENTER_MONITORING_INTERCEPTOR_TOPIC_PARTITIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;CONFLUENT_METRICS_TOPIC_REPLICATION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&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;9021&lt;/span&gt;

  &lt;span class="na"&gt;cockroach&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cockroachdb/cockroach:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cockroach-1&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;start-single-node --insecure&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="s"&gt;26257:26257&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8080:8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice in the &lt;code&gt;kafka-connect&lt;/code&gt; definition how we're installing the latest version of the JDBC Sink Connector and update the &lt;a href="https://jdbc.postgresql.org/"&gt;JDBC Postgresql driver&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Bring the Docker Compose up. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; &lt;br&gt;
&lt;em&gt;You might have to tweak your Docker env to allow for more resources.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;I use Docker Desktop for macOS configured with 12 CPUs and 22GB Memory.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; kafka2crdb.yaml up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure everything is up and 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;docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; kafka2crdb.yaml ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
broker              &lt;span class="s2"&gt;"/etc/confluent/dock…"&lt;/span&gt;   broker              running             0.0.0.0:9092-&amp;gt;9092/tcp, 0.0.0.0:9101-&amp;gt;9101/tcp
cockroach-1         &lt;span class="s2"&gt;"/cockroach/cockroac…"&lt;/span&gt;   cockroach           running             0.0.0.0:8080-&amp;gt;8080/tcp, 0.0.0.0:26257-&amp;gt;26257/tcp
connect             &lt;span class="s2"&gt;"bash -c '# Installi…"&lt;/span&gt;   kafka-connect       running             0.0.0.0:8083-&amp;gt;8083/tcp, 9092/tcp
control-center      &lt;span class="s2"&gt;"/etc/confluent/dock…"&lt;/span&gt;   control-center      running             0.0.0.0:9021-&amp;gt;9021/tcp
schema-registry     &lt;span class="s2"&gt;"/etc/confluent/dock…"&lt;/span&gt;   schema-registry     running             0.0.0.0:8081-&amp;gt;8081/tcp
zookeeper           &lt;span class="s2"&gt;"/etc/confluent/dock…"&lt;/span&gt;   zookeeper           running             2888/tcp, 0.0.0.0:2181-&amp;gt;2181/tcp, 3888/tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the Control Center at &lt;a href="http://localhost:9021"&gt;http://localhost:9021&lt;/a&gt; and wait until the broker shows up as healthy.&lt;/p&gt;

&lt;p&gt;Open another tab for the CockroachDB Console at &lt;a href="http://localhost:8080"&gt;http://localhost:8080&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;We create the below pipeline:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;datagen(source) --&amp;gt; Kafka Topic --&amp;gt; Kafka JDBC Sink Connector --&amp;gt; CockroachDB(target)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can do most of below tasks from within the Control Center.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Kafka topic
&lt;/h3&gt;

&lt;p&gt;Connect to the &lt;code&gt;broker&lt;/code&gt; container, and create a topic &lt;code&gt;transactions&lt;/code&gt; with 4 partitions&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; broker /bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once in the &lt;code&gt;broker&lt;/code&gt; shell&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;# $ create topic with 4 partitions&lt;/span&gt;
kafka-topics &lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; broker:9092 &lt;span class="nt"&gt;--create&lt;/span&gt; &lt;span class="nt"&gt;--topic&lt;/span&gt; transactions &lt;span class="nt"&gt;--partitions&lt;/span&gt; 4 

&lt;span class="c"&gt;# describe it&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kafka-topics &lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; broker:9092 &lt;span class="nt"&gt;--topic&lt;/span&gt; transactions &lt;span class="nt"&gt;--describe&lt;/span&gt;
Topic: transactions     TopicId: F6NF00Z8Ry2xIWnbYVF9vg PartitionCount: 4       ReplicationFactor: 1    Configs: 
        Topic: transactions     Partition: 0    Leader: 1       Replicas: 1     Isr: 1  Offline: 
        Topic: transactions     Partition: 1    Leader: 1       Replicas: 1     Isr: 1  Offline: 
        Topic: transactions     Partition: 2    Leader: 1       Replicas: 1     Isr: 1  Offline: 
        Topic: transactions     Partition: 3    Leader: 1       Replicas: 1     Isr: 1  Offline: 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the topic partitioned into 4, we can have an equal amount of consumer processes, or tasks, consuming from the topic and ingesting into CockroachDB, in parallel.&lt;/p&gt;

&lt;p&gt;All set, now we are ready to ingest data into this topic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure the Source Connector
&lt;/h3&gt;

&lt;p&gt;Our source will be a built-in data generator.&lt;/p&gt;

&lt;p&gt;Open a new terminal, and create the &lt;a href="https://docs.confluent.io/kafka-connectors/datagen/current/index.html"&gt;Datagen Connector&lt;/a&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="c"&gt;# the datagen-connect container listens on port 8083&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8083/connectors/ &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
        "name": "datagen-transactions",
        "config": {
            "connector.class": "io.confluent.kafka.connect.datagen.DatagenConnector",
            "key.converter": "org.apache.kafka.connect.storage.StringConverter",
            "kafka.topic": "transactions",
            "tasks.max": "64",
            "max.interval": "100",
            "quickstart": "transactions"
        }
    }'&lt;/span&gt; | jq &lt;span class="s1"&gt;'.'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm in the Control Center that messages are getting generated by visiting the &lt;code&gt;Topics&lt;/code&gt; section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AwHHDd_E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u38hi81n7l1uzl28ni3w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AwHHDd_E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u38hi81n7l1uzl28ni3w.png" alt="topics" width="800" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check also on the Messages and Schema tabs to see what the records look like&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iIYgs9ro--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/akzrdf3tl426za7lrgt8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iIYgs9ro--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/akzrdf3tl426za7lrgt8.png" alt="messages" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure the Sink Connector
&lt;/h3&gt;

&lt;p&gt;We're ready to setup the &lt;a href="https://docs.confluent.io/kafka-connectors/jdbc/current/sink-connector/overview.html"&gt;JDBC Sink Connector&lt;/a&gt; to ingest data into CockroachDB.&lt;/p&gt;

&lt;p&gt;Note how we set &lt;code&gt;batch.size&lt;/code&gt; the same as &lt;code&gt;max.poll.records&lt;/code&gt; to make sure 1 transaction includes only 128 records.&lt;/p&gt;

&lt;p&gt;Because we've set &lt;code&gt;reWriteBatchedInserts=true&lt;/code&gt;, the JDBC Postgres driver will conflate the 128 individual INSERT statements into a single, multi-record INSERT statement.&lt;/p&gt;

&lt;p&gt;Note however that this single statement transaction is still an &lt;strong&gt;explicit&lt;/strong&gt; transaction.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;br&gt;
&lt;em&gt;I've re-built the connector set with &lt;a href="https://github.com/confluentinc/kafka-connect-jdbc/blob/master/src/main/java/io/confluent/connect/jdbc/sink/JdbcDbWriter.java#L57"&gt;autocommit=true&lt;/a&gt; to leverage implicit transactions.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;I have therefore replaced the original file &lt;code&gt;kafka-connect-jdbc-10.6.1.jar&lt;/code&gt; in &lt;code&gt;/usr/share/confluent-hub-components/confluentinc-kafka-connect-jdbc/lib/&lt;/code&gt; with my custom build JAR file.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Below screenshots show the results from using this custom build&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We also set &lt;code&gt;tasks.max=4&lt;/code&gt; to have 4 consumer processes reading from the 4 topic partitions and ingesting data into CockroachDB in parallel. Each task creates its own database connection and reads from a specific topic partition.&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;# register the connector &lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8083/connectors/ &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
        "name": "sink-crdb",
            "config": {
            "connector.class": "io.confluent.connect.jdbc.JdbcSinkConnector",
            "connection.url": "jdbc:postgresql://cockroach-1:26257/defaultdb?reWriteBatchedInserts=true&amp;amp;ApplicationName=txns",
            "topics": "transactions",
            "tasks.max": "4",
            "key.converter": "org.apache.kafka.connect.storage.StringConverter",
            "value.converter": "io.confluent.connect.avro.AvroConverter",
            "value.converter.schema.registry.url": "http://schema-registry:8081",
            "connection.user": "root",
            "connection.password": "",
            "auto.create": true,
            "auto.evolve": true,
            "insert.mode": "insert",
            "pk.mode": "none",
            "pk.fields": "none",
            "batch.size": 128,
            "consumer.override.max.poll.records": 128
            }
    }'&lt;/span&gt; | jq &lt;span class="s1"&gt;'.'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see now 4 active SQL connections established to CockroachDB, and activity on the SQL Statement chart&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dv637xtQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qd60z0nvw4lypw0z9hus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dv637xtQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qd60z0nvw4lypw0z9hus.png" alt="statements-chart" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, in CockroachDB Console in the &lt;em&gt;SQL Activity &amp;gt; Transaction&lt;/em&gt; page, we confirm each transaction writes 128 rows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9H0va8vM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vol2hccsjxmojgsmfpej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9H0va8vM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vol2hccsjxmojgsmfpej.png" alt="txn" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Drilling down into the transaction, we can see of how many statements it is composed. In this case, as expected, it is just 1 INSERT statement&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z2rmgpv---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xtjtk72693961ztwvb23.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z2rmgpv---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xtjtk72693961ztwvb23.png" alt="txn-description" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Further drilling down into the Statement itself, we can see this is a multi-record INSERT statements with 128 records, executed as an implicit transaction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YRIlrBHL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/60jzw18goko5j7bllvo6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YRIlrBHL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/60jzw18goko5j7bllvo6.png" alt="stmt-description" width="800" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Query data in CockroachDB
&lt;/h3&gt;

&lt;p&gt;Open a SQL prompt&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; cockroach-1 cockroach sql &lt;span class="nt"&gt;--insecure&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="c1"&gt;-- the table was automatically created by the Kafka JDBC Sink Connector &lt;/span&gt;
&lt;span class="c1"&gt;-- this behavior is of course configurable&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;schema_name&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="k"&gt;table_name&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;estimated_row_count&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;locality&lt;/span&gt;
&lt;span class="c1"&gt;--------------+--------------+-------+-------+---------------------+-----------&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;root&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="k"&gt;NULL&lt;/span&gt;

&lt;span class="c1"&gt;-- check the schema of the created table &lt;/span&gt;
&lt;span class="c1"&gt;-- note how CockroachDB created its own Primary Key&lt;/span&gt;
&lt;span class="c1"&gt;-- this is also configurable&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;table_name&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt;                      &lt;span class="n"&gt;create_statement&lt;/span&gt;
&lt;span class="c1"&gt;---------------+--------------------------------------------------------------&lt;/span&gt;
  &lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="o"&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="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
               &lt;span class="o"&gt;|&lt;/span&gt;     &lt;span class="n"&gt;transaction_id&lt;/span&gt; &lt;span class="n"&gt;INT8&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="o"&gt;|&lt;/span&gt;     &lt;span class="n"&gt;card_id&lt;/span&gt; &lt;span class="n"&gt;INT8&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="o"&gt;|&lt;/span&gt;     &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="o"&gt;|&lt;/span&gt;     &lt;span class="n"&gt;purchase_id&lt;/span&gt; &lt;span class="n"&gt;INT8&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="o"&gt;|&lt;/span&gt;     &lt;span class="n"&gt;store_id&lt;/span&gt; &lt;span class="n"&gt;INT8&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="o"&gt;|&lt;/span&gt;     &lt;span class="n"&gt;rowid&lt;/span&gt; &lt;span class="n"&gt;INT8&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;VISIBLE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;unique_rowid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
               &lt;span class="o"&gt;|&lt;/span&gt;     &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;transactions_pkey&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;rowid&lt;/span&gt; &lt;span class="k"&gt;ASC&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="c1"&gt;-- inspect few rows&lt;/span&gt;
&lt;span class="o"&gt;&amp;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;transactions&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;transaction_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;card_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;purchase_id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;store_id&lt;/span&gt;
&lt;span class="c1"&gt;-----------------+---------+---------+-------------+-----------&lt;/span&gt;
               &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;User_6&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;1&lt;/span&gt;
               &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;      &lt;span class="mi"&gt;19&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;User_7&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;6&lt;/span&gt;
               &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;      &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;User_5&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;6&lt;/span&gt;
               &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;User_&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;3&lt;/span&gt;
               &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;      &lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;User_&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;3&lt;/span&gt;

&lt;span class="c1"&gt;-- avoid contention by using AS OF SYSTEM TIME when running large aggregate queries&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&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;FROM&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt; &lt;span class="k"&gt;SYSTEM&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="s1"&gt;'-5s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;count&lt;/span&gt;
&lt;span class="c1"&gt;----------&lt;/span&gt;
  &lt;span class="mi"&gt;594633&lt;/span&gt;

&lt;span class="c1"&gt;-- wait few seconds, then query again to see count increasing...&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&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;FROM&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt; &lt;span class="k"&gt;SYSTEM&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="s1"&gt;'-5s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;count&lt;/span&gt;
&lt;span class="c1"&gt;----------&lt;/span&gt;
  &lt;span class="mi"&gt;594992&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can view more metrics and statement statistics on the DB Console, at &lt;a href="http://localhost:8080"&gt;http://localhost:8080&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean up
&lt;/h2&gt;

&lt;p&gt;Bring down and remove all containers with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; kafka2crdb.yaml down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.confluent.io/platform/current/platform-quickstart.html#quick-start-for-cp"&gt;Confluent Kafka quick start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.confluent.io/kafka-connectors/datagen/current/"&gt;Datagen Connector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.confluent.io/kafka-connectors/jdbc/current/sink-connector/overview.html"&gt;JDBC Sink Connector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jdbc.postgresql.org/documentation/use/"&gt;JDBC Postresql Driver&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kafka</category>
      <category>cockroachdb</category>
    </item>
    <item>
      <title>Display CockroachDB metrics in Splunk Dashboards</title>
      <dc:creator>Fabio Ghirardello</dc:creator>
      <pubDate>Fri, 02 Dec 2022 17:58:03 +0000</pubDate>
      <link>https://dev.to/cockroachlabs/display-cockroachdb-metrics-in-splunk-dashboards-12ib</link>
      <guid>https://dev.to/cockroachlabs/display-cockroachdb-metrics-in-splunk-dashboards-12ib</guid>
      <description>&lt;p&gt;CockroachDB ships with a very convenient built-in web monitoring interface, the &lt;a href="https://www.cockroachlabs.com/docs/stable/ui-overview" rel="noopener noreferrer"&gt;DB Console&lt;/a&gt;. In the DB Console you can visualize all important health and ops metrics by using the pre-configured dashboards. Currently, the DB Console sports 12 dashboards, covering anything from Hardware and Storage metrics to SQL and Distribution. For many customers, this is a great monitoring solution.&lt;/p&gt;

&lt;p&gt;Larger enterprises however usually have a separate team that is responsible to monitor pretty much every component of an application, including databases, so they have a centralized solution from which they can more holistically assess the health of an application. For these cases, the same metrics that power the CockroachDB Console dashboards can be forwarded to the enterprise monitoring solution. &lt;/p&gt;

&lt;p&gt;Recently, I worked on such an integration with &lt;a href="https://www.splunk.com/" rel="noopener noreferrer"&gt;Splunk&lt;/a&gt;. The Splunk dashboard files that emulate the DB Console are now available in our &lt;a href="https://github.com/cockroachdb/cockroach/tree/master/monitoring/splunk-dashboard" rel="noopener noreferrer"&gt;repo&lt;/a&gt; for everyone's benefit.&lt;/p&gt;

&lt;p&gt;In this blog, I demonstrate how I used the &lt;a href="https://opentelemetry.io/docs/collector/" rel="noopener noreferrer"&gt;OpenTelemetry Collector&lt;/a&gt; to send the metrics from CockroachDB to a Splunk instance in a way that can be done on one's laptop using docker.&lt;/p&gt;

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

&lt;p&gt;The architecture is simple: CockroachDB --&amp;gt; OTEL Collector --&amp;gt; Splunk. &lt;br&gt;
CockroachDB generates detailed time series metrics for each node in the cluster. The collector will pull these metrics from each node endpoint and push them to the Splunk HEC endpoint.&lt;br&gt;
Once in Splunk, it's just a matter to make sense of the metrics by building the right charts and group them into dashboards.&lt;/p&gt;
&lt;h3&gt;
  
  
  CockroachDB Cluster
&lt;/h3&gt;

&lt;p&gt;As customary, we use a Load Balancer to interact with the CockroachDB cluster.&lt;br&gt;
Create the &lt;code&gt;haproxy.cfg&lt;/code&gt; file and save it on the current directory.&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="c1"&gt;# file: haproxy.cfg&lt;/span&gt;
&lt;span class="s"&gt;global&lt;/span&gt;
  &lt;span class="s"&gt;maxconn &lt;/span&gt;&lt;span class="m"&gt;4096&lt;/span&gt;

&lt;span class="s"&gt;defaults&lt;/span&gt;
    &lt;span class="s"&gt;mode                tcp&lt;/span&gt;
    &lt;span class="s"&gt;timeout connect     10s&lt;/span&gt;
    &lt;span class="s"&gt;timeout client      10m&lt;/span&gt;
    &lt;span class="s"&gt;timeout server      10m&lt;/span&gt;
    &lt;span class="s"&gt;option              clitcpka&lt;/span&gt;

&lt;span class="s"&gt;listen psql&lt;/span&gt;
    &lt;span class="s"&gt;bind :26257&lt;/span&gt;
    &lt;span class="s"&gt;mode tcp&lt;/span&gt;
    &lt;span class="s"&gt;balance roundrobin&lt;/span&gt;
    &lt;span class="s"&gt;option httpchk GET /health?ready=1&lt;/span&gt;
    &lt;span class="s"&gt;server cockroach1 cockroach1:26257 check port &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
    &lt;span class="s"&gt;server cockroach2 cockroach2:26257 check port &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
    &lt;span class="s"&gt;server cockroach3 cockroach3:26257 check port &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
    &lt;span class="s"&gt;server cockroach4 cockroach4:26257 check port &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;

&lt;span class="s"&gt;listen http&lt;/span&gt;
    &lt;span class="s"&gt;bind :8080&lt;/span&gt;
    &lt;span class="s"&gt;mode tcp&lt;/span&gt;
    &lt;span class="s"&gt;balance roundrobin&lt;/span&gt;
    &lt;span class="s"&gt;option httpchk GET /health?ready=1&lt;/span&gt;
    &lt;span class="s"&gt;server cockroach1 cockroach1:8080 check port &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
    &lt;span class="s"&gt;server cockroach2 cockroach2:8080 check port &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
    &lt;span class="s"&gt;server cockroach3 cockroach3:8080 check port &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
    &lt;span class="s"&gt;server cockroach4 cockroach4:8080 check port &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Create the docker network and containers&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;# create the network bridge&lt;/span&gt;
docker network create &lt;span class="nt"&gt;--driver&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bridge &lt;span class="nt"&gt;--subnet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.28.0.0/16 &lt;span class="nt"&gt;--ip-range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.28.0.0/24 &lt;span class="nt"&gt;--gateway&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;172.28.0.1 demo-net

&lt;span class="c"&gt;# CockroachDB cluster&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroach1 &lt;span class="nt"&gt;--hostname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroach1 &lt;span class="nt"&gt;--net&lt;/span&gt; demo-net cockroachdb/cockroach:latest start &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroach1,cockroach2,cockroach3
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroach2 &lt;span class="nt"&gt;--hostname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroach2 &lt;span class="nt"&gt;--net&lt;/span&gt; demo-net cockroachdb/cockroach:latest start &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroach1,cockroach2,cockroach3
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroach3 &lt;span class="nt"&gt;--hostname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroach3 &lt;span class="nt"&gt;--net&lt;/span&gt; demo-net cockroachdb/cockroach:latest start &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroach1,cockroach2,cockroach3
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroach4 &lt;span class="nt"&gt;--hostname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroach4 &lt;span class="nt"&gt;--net&lt;/span&gt; demo-net cockroachdb/cockroach:latest start &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;--join&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockroach1,cockroach2,cockroach3

&lt;span class="c"&gt;# initialize the cluster&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; cockroach1 ./cockroach init &lt;span class="nt"&gt;--insecure&lt;/span&gt;

&lt;span class="c"&gt;# HAProxy load balancer&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; haproxy &lt;span class="nt"&gt;--net&lt;/span&gt; demo-net &lt;span class="nt"&gt;-p&lt;/span&gt; 26257:26257 &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/haproxy.cfg:/etc/haproxy.cfg:ro haproxy:latest &lt;span class="nt"&gt;-f&lt;/span&gt; /etc/haproxy.cfg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point you should be able to open the CockroachDB Console at &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Start a workload against the cluster to generate some metrics&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;# init&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; workload &lt;span class="nt"&gt;--net&lt;/span&gt; demo-net cockroachdb/cockroach:latest workload init tpcc &lt;span class="s1"&gt;'postgres://root@haproxy:26257?sslmode=disable'&lt;/span&gt; &lt;span class="nt"&gt;--warehouses&lt;/span&gt; 10
&lt;span class="c"&gt;# run the workload - you might want to use a separate terminal&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; workload &lt;span class="nt"&gt;--net&lt;/span&gt; demo-net cockroachdb/cockroach:latest workload run tpcc &lt;span class="s1"&gt;'postgres://root@haproxy:26257?sslmode=disable'&lt;/span&gt; &lt;span class="nt"&gt;--warehouses&lt;/span&gt; 10 &lt;span class="nt"&gt;--tolerate-errors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With 4 nodes, you can simulate a node failure (just stop the container) and view the range activity (replication, lease-transfers, etc).&lt;br&gt;
You can optionally setup CDC to a Kafka container, configure Row Level TTL, add more nodes, etc.&lt;/p&gt;
&lt;h3&gt;
  
  
  Splunk
&lt;/h3&gt;

&lt;p&gt;Start a Splunk container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; splunk &lt;span class="nt"&gt;--net&lt;/span&gt; demo-net &lt;span class="nt"&gt;-p&lt;/span&gt; 8088:8088 &lt;span class="nt"&gt;-p&lt;/span&gt; 8000:8000 &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SPLUNK_START_ARGS=--accept-license"&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SPLUNK_PASSWORD=cockroach"&lt;/span&gt; splunk/splunk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Login into Splunk at &lt;a href="http://localhost:8000" rel="noopener noreferrer"&gt;http://localhost:8000&lt;/a&gt; as user &lt;code&gt;admin&lt;/code&gt; with password &lt;code&gt;cockroach&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a data input and token for HEC.&lt;/li&gt;
&lt;li&gt;In Splunk, click &lt;strong&gt;Settings &amp;gt; Data Inputs&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Local Inputs&lt;/strong&gt;, click &lt;strong&gt;HTTP Event Collector&lt;/strong&gt;:

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Global Settings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;All Tokens&lt;/strong&gt;, click &lt;strong&gt;Enabled&lt;/strong&gt; if this button is not already selected.&lt;/li&gt;
&lt;li&gt;Uncheck the &lt;strong&gt;SSL&lt;/strong&gt; checkbox.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Configure an HEC token for sending data by clicking &lt;strong&gt;New Token&lt;/strong&gt;.&lt;/li&gt;

&lt;li&gt;On the &lt;strong&gt;Select Source&lt;/strong&gt; page, for &lt;strong&gt;Name&lt;/strong&gt;, enter a token name, for example "Metrics token".&lt;/li&gt;

&lt;li&gt;Leave the other options blank or unselected.&lt;/li&gt;

&lt;li&gt;Click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;

&lt;li&gt;On the &lt;strong&gt;Input Settings&lt;/strong&gt; page, for &lt;strong&gt;Source type&lt;/strong&gt;, click &lt;strong&gt;New&lt;/strong&gt;.&lt;/li&gt;

&lt;li&gt;In &lt;strong&gt;Source Type&lt;/strong&gt;, set value to "otel".&lt;/li&gt;

&lt;li&gt;For &lt;strong&gt;Source Type Category&lt;/strong&gt;, select &lt;strong&gt;Metrics&lt;/strong&gt;.&lt;/li&gt;

&lt;li&gt;Next to &lt;strong&gt;Default Index&lt;/strong&gt;, click &lt;strong&gt;Create a new index&lt;/strong&gt;.
In the &lt;strong&gt;New Index&lt;/strong&gt; dialog box:

&lt;ol&gt;
&lt;li&gt;Set &lt;strong&gt;Index Name&lt;/strong&gt; to "metrics_idx".&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Index Data Type&lt;/strong&gt;, click &lt;strong&gt;Metrics&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Select the newly created "metrics_idx".&lt;/li&gt;

&lt;li&gt;Click &lt;strong&gt;Review&lt;/strong&gt;, and then click &lt;strong&gt;Submit&lt;/strong&gt;.&lt;/li&gt;

&lt;li&gt;Copy the &lt;strong&gt;Token Value&lt;/strong&gt; that is displayed. This HEC token is required for sending data.&lt;/li&gt;

&lt;/ol&gt;

&lt;h3&gt;
  
  
  OpenTelemetry Collector
&lt;/h3&gt;

&lt;p&gt;There are 2 collector types: the &lt;a href="https://github.com/open-telemetry/opentelemetry-collector" rel="noopener noreferrer"&gt;core&lt;/a&gt; and the &lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib" rel="noopener noreferrer"&gt;contrib&lt;/a&gt;. I have used the contrib as it features the &lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/splunkhecexporter" rel="noopener noreferrer"&gt;splunk_hec&lt;/a&gt; exporter. &lt;/p&gt;

&lt;p&gt;The collectors come already pre-compiled and are available for download in the &lt;a href="https://github.com/open-telemetry/opentelemetry-collector-releases/releases" rel="noopener noreferrer"&gt;releases repo&lt;/a&gt;. Docker containers are also available.&lt;/p&gt;

&lt;p&gt;Create file &lt;code&gt;config.yaml&lt;/code&gt; and save it in the current directory.&lt;br&gt;
Ensure to replace the &lt;strong&gt;Splunk Token&lt;/strong&gt; with the one you created in the previous step.&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="c1"&gt;# file: config.yaml&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cockroachdb'&lt;/span&gt;
          &lt;span class="na"&gt;metrics_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/_status/vars'&lt;/span&gt;
          &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
          &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http'&lt;/span&gt;
          &lt;span class="na"&gt;tls_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;insecure_skip_verify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cockroach1:8080&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cockroach2:8080&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cockroach3:8080&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cockroach4:8080&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;cluster_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cockroachdb'&lt;/span&gt;

&lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;splunk_hec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel&lt;/span&gt;
    &lt;span class="na"&gt;sourcetype&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel&lt;/span&gt;
    &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;metrics_idx&lt;/span&gt;
    &lt;span class="na"&gt;max_connections&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
    &lt;span class="na"&gt;disable_compression&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
    &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;insecure_skip_verify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TOKEN&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://splunk:8088/services/collector"&lt;/span&gt;


&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;splunk_hec&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the Collector&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; otel &lt;span class="nt"&gt;--net&lt;/span&gt; demo-net &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;/config.yaml:/etc/config.yaml ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib &lt;span class="nt"&gt;--config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CockroachDB metrics should be now pulled by the Prometheus &lt;strong&gt;Receiver&lt;/strong&gt; and forwarded to Splunk via the Splunk HEC &lt;strong&gt;Exporter&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;After few minutes, you can do a quick test and run the below queries in Splunk to make sure data is received correctly.&lt;br&gt;
In Splunk, click on &lt;strong&gt;Apps&lt;/strong&gt;, then &lt;strong&gt;Search &amp;amp; Reporting&lt;/strong&gt;.&lt;br&gt;
Enter below commands:&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 what metrics we're receiving&lt;/span&gt;
| mcatalog values&lt;span class="o"&gt;(&lt;/span&gt;metric_name&lt;span class="o"&gt;)&lt;/span&gt; WHERE &lt;span class="nv"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"metrics_idx"&lt;/span&gt;

&lt;span class="c"&gt;# preview the data in its raw format&lt;/span&gt;
| mpreview &lt;span class="nv"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"metrics_idx"&lt;/span&gt;

&lt;span class="c"&gt;# execute the query to show the SQL Statements&lt;/span&gt;
| mstats 
rate_sum&lt;span class="o"&gt;(&lt;/span&gt;sql_select_count&lt;span class="o"&gt;)&lt;/span&gt; as &lt;span class="k"&gt;select&lt;/span&gt;, 
rate_sum&lt;span class="o"&gt;(&lt;/span&gt;sql_insert_count&lt;span class="o"&gt;)&lt;/span&gt; as insert, 
rate_sum&lt;span class="o"&gt;(&lt;/span&gt;sql_update_count&lt;span class="o"&gt;)&lt;/span&gt; as update, 
rate_sum&lt;span class="o"&gt;(&lt;/span&gt;sql_delete_count&lt;span class="o"&gt;)&lt;/span&gt; as delete
where &lt;span class="nv"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"metrics_idx"&lt;/span&gt; &lt;span class="nv"&gt;span&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Splunk shows data, the pipeline is working correctly and you can load the dashboards.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on &lt;strong&gt;Dashboards&lt;/strong&gt;, then on &lt;strong&gt;Create New Dashboard&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the pop-up window:

&lt;ol&gt;
&lt;li&gt;In &lt;strong&gt;Dashboard Title&lt;/strong&gt;, set "CockroachDB Overview".&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Classic Dashboard&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;The Dashboard is now in edit mode. Click on &lt;strong&gt;Source&lt;/strong&gt;.&lt;/li&gt;

&lt;li&gt;Replace the current content with the XML in the &lt;code&gt;overview.xml&lt;/code&gt; file in the &lt;a href="https://github.com/cockroachdb/cockroach/tree/master/monitoring/splunk-dashboard" rel="noopener noreferrer"&gt;repo&lt;/a&gt;.&lt;/li&gt;

&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/li&gt;

&lt;li&gt;Repeat for every Dashboard file in the repo directory.&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;Here are a few screenshots:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CockroachDB Hardware&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.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%2Fqfmtozyzz5siuhovyuzw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqfmtozyzz5siuhovyuzw.png" alt="hardware"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CockroachDB SQL&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.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%2F002il7ir3r9qsrlf7083.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F002il7ir3r9qsrlf7083.png" alt="sql"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, you can create your very own dashboards, too:&lt;/p&gt;

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

&lt;p&gt;The same Collector can be used for integration with many other sources and targets. It also features &lt;em&gt;processors&lt;/em&gt;, so you can pre-process the data (say, filtering) before pushing it to your target solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cockroachlabs.com/" rel="noopener noreferrer"&gt;CockroachDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.splunk.com/" rel="noopener noreferrer"&gt;Splunk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.splunk.com/Documentation/Splunk/9.0.2/Data/UsetheHTTPEventCollector" rel="noopener noreferrer"&gt;Splunk HTTP Event Collector (HEC) docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cockroachdb</category>
      <category>splunk</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Build a CockroachDB Control Plane using Ansible Tower</title>
      <dc:creator>Fabio Ghirardello</dc:creator>
      <pubDate>Wed, 16 Nov 2022 17:25:07 +0000</pubDate>
      <link>https://dev.to/cockroachlabs/build-a-cockroachdb-control-plane-using-ansible-tower-1lcj</link>
      <guid>https://dev.to/cockroachlabs/build-a-cockroachdb-control-plane-using-ansible-tower-1lcj</guid>
      <description>&lt;p&gt;CockroachDB can be easily deployed on the public cloud via &lt;a href="https://cockroachlabs.cloud/" rel="noopener noreferrer"&gt;Cockroach Cloud&lt;/a&gt;, Cockroach Labs DBaaS offering. With a few clicks, your Dedicated or Serverless cluster is ready in minutes if not seconds. Connect, and profit.&lt;/p&gt;

&lt;p&gt;Some customers however face restrictions with regards to public cloud usage, preferring their own private cloud instead. Installing and deploying a CockroachDB cluster is very easy, but it's very hard to beat the convenience of a Control Plane if your goal is wide company adoption and streamlined maintenance (i.e. software upgrades).&lt;/p&gt;

&lt;p&gt;In this blog, I use Ansible Tower as the cornerstone system to create a simple Control Plane. Tower's enterprise grade features allow you to create users and permissions, manage credentials and environments for executing basically any script or script workflow. Also, it has a little handy feature to create basic GUIs, the &lt;strong&gt;Survey&lt;/strong&gt;, which I leverage to take user inputs.&lt;/p&gt;

&lt;p&gt;The idea is that any user in the organization can login into Tower, create a cluster specifying its characteristics (size, number of nodes, regions, etc..) and receive the database connection string. Tower will then use an existing inventory of servers (or create new VMs), install and deploy CockroachDB, and return the connection details. You can of course add pre- and post-installation scripts at your will.&lt;/p&gt;

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

&lt;p&gt;Ansible Tower, now evolved to a new product called &lt;a href="https://www.redhat.com/en/technologies/management/ansible" rel="noopener noreferrer"&gt;Red Hat Ansible Automation Platform&lt;/a&gt;, is a licensed product. So I installed &lt;a href="https://github.com/ansible/awx" rel="noopener noreferrer"&gt;AWX&lt;/a&gt; instead, the upstream project that is free and open source.&lt;/p&gt;

&lt;p&gt;AWX requires a Kubernetes environment, and on my laptop I created a single node K8s cluster using Minikube.&lt;/p&gt;

&lt;p&gt;With AWX up and running, it is time to configure it and integrate it with other components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Execution environment
&lt;/h3&gt;

&lt;p&gt;First, we need an &lt;strong&gt;execution environment&lt;/strong&gt;, that is, the k8s Pod image that Tower will use to run our "CockroachDB cluster create" script. Ansible provides a base image called &lt;a href="https://quay.io/repository/ansible/awx-ee?tab=info" rel="noopener noreferrer"&gt;ansible/awx-ee&lt;/a&gt; which I have extended using below Dockerfile and published as &lt;code&gt;fabiog1901/awx-ee&lt;/code&gt; on &lt;a href="https://hub.docker.com/r/fabiog1901/awx-ee" rel="noopener noreferrer"&gt;Dockerhub&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; quay.io/ansible/awx-ee:latest&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; pip

&lt;span class="k"&gt;RUN &lt;/span&gt;ansible-galaxy collection &lt;span class="nb"&gt;install &lt;/span&gt;ansible.posix

&lt;span class="k"&gt;RUN &lt;/span&gt;ansible-galaxy collection &lt;span class="nb"&gt;install &lt;/span&gt;community.general

&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;boto boto3 botocore 

&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;google-api-core google-auth google-cloud-compute googleapis-common-protos 

&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;azure-common azure-core azure-identity azure-mgmt-compute azure-mgmt-core azure-mgmt-network azure-mgmt-resource

&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;cockroachdb-cloud-client

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; 1000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing crazy, just adding a few basic Ansible Collections and making sure the &lt;strong&gt;cloud provider python SDKs&lt;/strong&gt; are installed. I mentioned before that the idea was to create a CockroachDB cluster for the private cloud, but as I don't have one, I use the public cloud instead. Assume that for the private cloud we will also install the private cloud python SDKs, for example, the &lt;a href="https://docs.openstack.org/mitaka/user-guide/sdk.html" rel="noopener noreferrer"&gt;OpenStack SDK&lt;/a&gt; or the &lt;a href="https://developer.vmware.com/web/sdk/8.0/vsphere-automation-python" rel="noopener noreferrer"&gt;VMWare vSphere SDK&lt;/a&gt;; the idea is to create a docker image that has all the libraries required by the script.&lt;/p&gt;

&lt;p&gt;Here I added that docker image as the Default execution env in Tower&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Repository Project
&lt;/h3&gt;

&lt;p&gt;What has to be executed is provided to Tower via a Project. Tower integrates with GitHub so we can use a repository as our Tower Project. The &lt;a href="https://github.com/fabiog1901/awx_cockroachdb" rel="noopener noreferrer"&gt;sample repo&lt;/a&gt; I created contains a playbook to create a cluster and another playbook to destroy a cluster. The playbooks require an Ansible Collection, &lt;a href="https://github.com/fabiog1901/cockroachdb-collection" rel="noopener noreferrer"&gt;cockroachdb-collection&lt;/a&gt;, which has roles and modules to create VMs and deploy CockroachDB. As we have put the details of the collection in the &lt;code&gt;collections/requirements.yml&lt;/code&gt; file, the collection is fetched automatically at runtime by Tower.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Template
&lt;/h3&gt;

&lt;p&gt;A Tower Template is what describes what needs to be run. I created a "CockroachDB Create" template that collects the cluster information via a Survey, and runs the &lt;code&gt;create.yaml&lt;/code&gt; file in my Project. &lt;/p&gt;

&lt;p&gt;Please note that for the sake of brevity, there are many other details that I'm glossing over, like Credentials (AWS keys, SSH key, etc..), local variables, user creation, etc..&lt;/p&gt;

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

&lt;p&gt;Here the details of the Survey:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Run
&lt;/h2&gt;

&lt;p&gt;It's time to run our Template! I have created a user &lt;code&gt;fabio&lt;/code&gt; that has only permission to run the 'CockroachDB Create' job. Now I am logged in with such user and click on the rocket icon to launch the job. &lt;br&gt;
Please note that Tower powers an API server, so you are free to create your own Tower client and create better GUIs - you're not limited to Survey and Template via the standard Tower interface you've seen in these screenshot. Build your own web-client, and let the client call the Templates! &lt;/p&gt;

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

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

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

&lt;p&gt;Details on how to connect to the cluster are printed out, but of course you can have better notification mechanisms, for example, you could have Tower send an email or a Slack message.&lt;/p&gt;

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

&lt;p&gt;Creating the cluster on VMs took about 2 minutes, check the elapsed time on the top right.&lt;br&gt;
I can now access my cluster, and profit.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Extend Tower to create clusters on CockroachCloud
&lt;/h2&gt;

&lt;p&gt;You have by now realized that you can pretty much run anything via Tower: it's not a solution solely for creating CockroachDB clusters on private/public cloud infrastructure.&lt;/p&gt;

&lt;p&gt;To highlight this, I've created a new Template that uses a new Ansible playbook, &lt;code&gt;cc_create.yaml&lt;/code&gt;, that allows me to create a &lt;strong&gt;CockroachCloud cluster&lt;/strong&gt; (a CockroachDB cluster on Cockroach Labs DBaaS service). &lt;br&gt;
How does that work? CockroachCloud can be managed entirely via APIs, see the &lt;a href="https://www.cockroachlabs.com/docs/api/cloud/v1" rel="noopener noreferrer"&gt;OpenAPI spec&lt;/a&gt;. From the spec JSON file I have generated a Python SDK, &lt;a href="https://pypi.org/project/cockroachdb-cloud-client/" rel="noopener noreferrer"&gt;cockroachdb-cloud-client&lt;/a&gt;.&lt;br&gt;
The SDK has allowed me to create Ansible Modules (still a work-in-progress project) which I am keeping in the same &lt;a href="https://github.com/fabiog1901/cockroachdb-collection/tree/main/plugins/modules" rel="noopener noreferrer"&gt;cockroachdb-collection&lt;/a&gt;. You can view the module docs &lt;a href="https://fabiog1901.github.io/cockroachdb-collection/index.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The new Template has a new Survey GUI, making it very easy to create a CockroachCloud cluster directly from Tower, where you can audit and track who can do what, and setup things like billing, reporting, etc.&lt;/p&gt;

&lt;p&gt;Below is the flow for this new template, and once the Job has run, you'll see the familiar message with details of the newly created cluster&lt;/p&gt;

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

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

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

&lt;p&gt;And just to confirm, the cluster is also visible in the CockroachCloud console&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;This was a small, simple example on how we can use a system such as Tower to quickly build a Control Plane. While there are many aspects that we haven't covered, I hope I've shown what Tower can bring in terms of enterprise features and easiness of adding new workflows and scripts to automate or facilitate common tasks.&lt;/p&gt;

&lt;p&gt;The choice is not limited to Tower, however. There is an interesting project that I'm following called &lt;a href="https://www.ansible-semaphore.com/" rel="noopener noreferrer"&gt;Ansible Semaphore&lt;/a&gt;. It's by far not as mature as Tower, but worth keeping an eye on it.&lt;/p&gt;

</description>
      <category>cockroachdb</category>
      <category>ansible</category>
    </item>
    <item>
      <title>How-To: CockroachDB AuthN with Kerberos</title>
      <dc:creator>Fabio Ghirardello</dc:creator>
      <pubDate>Fri, 04 Feb 2022 13:58:29 +0000</pubDate>
      <link>https://dev.to/cockroachlabs/how-to-cockroachdb-authn-with-kerberos-159l</link>
      <guid>https://dev.to/cockroachlabs/how-to-cockroachdb-authn-with-kerberos-159l</guid>
      <description>&lt;p&gt;CockroachDB supports user AuthN using the following methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;password&lt;/li&gt;
&lt;li&gt;cert (TLS client cert)&lt;/li&gt;
&lt;li&gt;cert-password (either cert or password)&lt;/li&gt;
&lt;li&gt;trusted (always authenticate)&lt;/li&gt;
&lt;li&gt;reject (always reject)&lt;/li&gt;
&lt;li&gt;gss (Use GSSAPI e.g. Kerberos)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By default every user can authenticate using either the cert or password methods.&lt;/p&gt;

&lt;p&gt;In this brief blog, we outline the high level steps required to configure Kerberos AuthN in place of cert/password AuthN.&lt;/p&gt;

&lt;h2&gt;
  
  
  1 - Create Kerberos SPN and Keytab
&lt;/h2&gt;

&lt;p&gt;In most organizations, you might have to find and ask another department to create the SPN and its keytab on your behalf.&lt;/p&gt;

&lt;p&gt;Below the command to create the SPN, assuming we've access to the KDC server.&lt;br&gt;
The keytab default location is &lt;code&gt;/etc/krb5.keytab&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In our case, we add a Principal for the LB address only: we are permitting connection to the cluster only via the LB, that is, you can't point to a CockroachDB node directly.&lt;/p&gt;

&lt;p&gt;This is to avoid endusers hardcoding a connection to a single node.&lt;br&gt;
With this implementation, we ensure endusers go to the load-balancer for connection, which is the intended behavior.&lt;/p&gt;

&lt;p&gt;Please note: user &lt;code&gt;root&lt;/code&gt; bypasses Kerberos AuthN so it can connect from any node as long as the root key+crt are available.&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;# create principal named 'cockroach'&lt;/span&gt;
kadmin.local addprinc &lt;span class="nt"&gt;-pw&lt;/span&gt; &amp;lt;password&amp;gt; cockroach/&amp;lt;load-balancer-IP&amp;gt;@&amp;lt;YOUR.REALM&amp;gt;
kadmin.local addprinc &lt;span class="nt"&gt;-pw&lt;/span&gt; &amp;lt;password&amp;gt; cockroach/&amp;lt;load-balancer-hostname&amp;gt;@&amp;lt;YOUR.REALM&amp;gt;

&lt;span class="c"&gt;# create the principal keytab at /etc/krb5.keytab&lt;/span&gt;
kadmin.local ktadd cockroach/&amp;lt;load-balancer-IP&amp;gt;@&amp;lt;YOUR.REALM&amp;gt;
kadmin.local ktadd cockroach/&amp;lt;load-balancer-hostname&amp;gt;@&amp;lt;YOUR.REALM&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each CockroachDB &lt;em&gt;User&lt;/em&gt; must have an &lt;strong&gt;equally named&lt;/strong&gt; &lt;em&gt;Principal&lt;/em&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="c"&gt;# Principal and DB username is 'fabio'&lt;/span&gt;
kadmin.local addprinc &lt;span class="nt"&gt;-pw&lt;/span&gt; &amp;lt;password&amp;gt; fabio@&amp;lt;YOUR.REALM&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2 - Configure keytab on CockroachDB nodes
&lt;/h2&gt;

&lt;p&gt;Download keytab file &lt;code&gt;etc/krb5.keytab&lt;/code&gt; locally, then upload it to every CockroachDB cluster node.&lt;/p&gt;

&lt;p&gt;The keytab file should be located inside the CockroachDB config directory, by default is &lt;code&gt;/var/lib/cockroach&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is customary to rename the file to &lt;code&gt;cockroach.keytab&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also, we assume a Linux user &lt;code&gt;cockroach&lt;/code&gt; was created for running CockroachDB.&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;# assume krb5.keytab is available in the current directory&lt;/span&gt;
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;krb5.keytab /var/lib/cockroach/cockroach.keytab

&lt;span class="c"&gt;# ensure permissions and ownership are set correctly&lt;/span&gt;
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;644 /var/lib/cockroach/cockroach.keytab
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;cockroach:cockroach /var/lib/cockroach/cockroach.keytab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CockroachDB will look for this file by querying the environment variable &lt;code&gt;KRB5_KTNAME&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You make this env var available to CockroachDB via the &lt;a href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Environment"&gt;&lt;code&gt;Environment&lt;/code&gt; parameter&lt;/a&gt; in &lt;strong&gt;systemd&lt;/strong&gt; unit file.&lt;/p&gt;

&lt;p&gt;Here's an example systemd unit file, notice the &lt;code&gt;Environment&lt;/code&gt; parameter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=Cockroach Database cluster node
Requires=network.target
[Service]
Type=notify
WorkingDirectory=/var/lib/cockroach

Environment="KRB5_KTNAME=/var/lib/cockroach/cockroach.keytab"

ExecStart=/usr/local/bin/cockroach start \
    --certs-dir=/var/lib/cockroach/certs \
    --store=/mnt/cockroach \
    --listen-addr=0.0.0.0:26257 \
    --advertise-addr=host1:26257 \
    --cache=.25 \
    --max-sql-memory=.25 \
    --http-addr=0.0.0.0:8080 \
    --join=host1,host2,host3 \
    --locality=region=us-east4,zone=a
TimeoutStopSec=300
LimitNOFILE=65000
Restart=always
RestartSec=10
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=cockroach
User=cockroach
[Install]
WantedBy=default.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload systemd service files, and restart each CockroachDB node&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;# repeat for each CockroachDB node&lt;/span&gt;
systemctl daemon-reload
systemctl restart cockroachdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3 - Configure CockroachDB for Kerberos AuthN
&lt;/h2&gt;

&lt;p&gt;As an admin user, login into CockroachDB and enter these commands at the SQL prompt.&lt;/p&gt;

&lt;p&gt;Please note, you need an &lt;strong&gt;enterprise license&lt;/strong&gt; for this feature.&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="c1"&gt;-- enable Kerberos AuthN for all users from all hosts&lt;/span&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;host_based_authentication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'host all all all gss include_realm=0'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- if not present already, create user 'fabio'&lt;/span&gt;
&lt;span class="c1"&gt;-- the password is only used for logging into the DB Console.&lt;/span&gt;
&lt;span class="c1"&gt;-- The password doesn't have to match the &lt;/span&gt;
&lt;span class="c1"&gt;-- password you gave for the Kerberos Principal, they&lt;/span&gt;
&lt;span class="c1"&gt;-- are not related.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="n"&gt;fabio&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="s1"&gt;'cockroach'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- apply required grants to the user&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;admin&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;fabio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More info on the cluster setting value is available with great details &lt;a href="https://dr-knz.net/authentication-in-postgresql-and-cockroachdb.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4 - Test
&lt;/h2&gt;

&lt;p&gt;Login into the app server or any other server setup so that you can &lt;code&gt;kinit&lt;/code&gt; (or equivalent in your favorite programming language).&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;# get the kerberos ticket&lt;/span&gt;
kinit fabio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use &lt;code&gt;klist&lt;/code&gt; to verify the ticket is valid.&lt;br&gt;
Now you're ready to connect to the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cockroach sql &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s2"&gt;"postgresql://fabio@&amp;lt;load-balancer-hostname&amp;gt;:26257/defaultdb?sslmode=verify-full&amp;amp;sslrootcert=ca.crt&amp;amp;krbsrvname=cockroach"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;krbsrvname=cockroach&lt;/code&gt; this is where you tell CockroachDB what is the SPN user you created in step 1.&lt;/p&gt;

&lt;p&gt;To confirm, destroy the ticket and re-attempt connection.&lt;br&gt;
It should fail AuthN of the user.&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;# destroy all kerberos tickets&lt;/span&gt;
kdestroy

&lt;span class="c"&gt;# there's no ticket, AuthN will fail&lt;/span&gt;
cockroach sql &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s2"&gt;"postgresql://fabio@&amp;lt;load-balancer-hostname&amp;gt;:26257/defaultdb?sslmode=verify-full&amp;amp;sslrootcert=ca.crt&amp;amp;krbsrvname=cockroach"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cockroachlabs.com/product/"&gt;CockroachDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cockroachlabs.com/docs/stable/security-overview.html"&gt;Security Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cockroachlabs.com/docs/stable/gssapi_authentication.html"&gt;GSSAPI Kerberos AuthN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fabiog1901/cockroachdb-collection"&gt;Ansible Collection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Helpful blogs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.ervits.com/2020/05/three-headed-dog-meet-cockroach.html"&gt;Artem Ervits blog series on CockroachDB and Kerberos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/cockroachlabs/deploy-cockroachdb-on-the-public-cloud-using-ansible-1ek1"&gt;Deploy a Kerberized CockroachDB cluster using Ansible&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cockroachdb</category>
      <category>kerberos</category>
      <category>authentication</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
