<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>database on Digi Hunch</title><link>https://www.digihunch.com/tag/database/</link><description>Recent content in database on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Thu, 17 Apr 2025 14:03:52 -0400</lastBuildDate><atom:link href="https://www.digihunch.com/tag/database/index.xml" rel="self" type="application/rss+xml"/><item><title>A taste of IoT device tracking</title><link>https://www.digihunch.com/2023/03/a-taste-of-iot-device-tracking/</link><pubDate>Fri, 03 Mar 2023 10:53:00 -0400</pubDate><guid>https://www.digihunch.com/2023/03/a-taste-of-iot-device-tracking/</guid><description>&lt;img src="https://www.digihunch.com/wp-content/uploads/2025/04/feature-iot-device-tracking.webp" alt="Featured image of post A taste of IoT device tracking" /&gt;&lt;p class="wp-block-paragraph"&gt;Last fall I worked on a demo project for IoT device tracking on AWS so I had some reading. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Overview&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;From a strategic level, as servers move away to the cloud, AWS envisions that the footprint left on premise will mainly be IoT devices. The role of the cloud therefore becomes a central point of management for IoT devices. As a result, most of the AWS services for IoT are managed services. The best resources are two white papers: &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/iot-lens/welcome.html?did=wp_card&amp;amp;trk=wp_card"&gt;IoT Lens &amp;#8211; AWS Well-Architected Framework&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/securing-iot-with-aws/securing-iot-with-aws.html?did=wp_card&amp;amp;trk=wp_card"&gt;Securing Internet of Things (IoT) with AWS&lt;/a&gt;. The most important services are:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;IoT Analytics&lt;/strong&gt;: Makes it easy to run sophisticated analytics on volumes on IoT data. It connects to the underlying IoT data store and allow you to build your own analytical queries and Jupiter notebooks.&lt;/li&gt;&#10;&lt;li&gt;IoT Events&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/startups/an-introduction-to-aws-iot-core/"&gt;IoT Core&lt;/a&gt;: Core features for IoT. &lt;/li&gt;&#10;&lt;li&gt;IoT SiteWise&lt;/li&gt;&#10;&lt;li&gt;IoT Device Management&lt;/li&gt;&#10;&lt;li&gt;IoT Greengrass&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;FreeRTOS&lt;/strong&gt;: IoT devices usually need to be small and power efficient. The software and OS running on the device is important. FreeRTOS is a real-time operating system for micro-controllers supported by AWS. FreeRTOS provides kernel, OS and libraries to securely connect your edge device to the cloud in no time.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this post I will explore IoT architecture at high level. In real life, you program your device with AWS IoT Device SDK and AWS IoT API in different languages. In this post, I use a script to simulate GPS data, and push it to AWS IoT using SDK. Then I render the location using sample AWS code for Amazon Location.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-iot-architecture"&gt;IoT Architecture&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When creating IoT services we consider registration and telemetry capturing flow. The Well architected white paper proposes registration flow as such:&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full is-resized"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="668" src="https://www.digihunch.com/wp-content/uploads/2023/03/registration-flow.webp" alt="" class="wp-image-12900" style="width:569px;height:auto" srcset="https://www.digihunch.com/wp-content/uploads/2023/03/registration-flow.webp 1024w, https://www.digihunch.com/wp-content/uploads/2023/03/registration-flow-300x196.webp 300w, https://www.digihunch.com/wp-content/uploads/2023/03/registration-flow-768x501.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;figcaption class="wp-element-caption"&gt;Registration Flow&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Device Registry to keep track of devices (aka Things). You can find where your devices are, and filter by a common feature (e.g. ModelX device only). Registration flow usually involves a testing of communication between device and server. The authentication must be 2-way where server needs to validate device identity, and device needs to validate server identity. You can use a unique X.509 certificate per device to adhere to security best practices on AWS. This way, if one device gets hacked, the entire fleet of devices is not affected by one certificate being compromised. An alternative authentication method is Cognito. With Cognito you can sign your users into a mobile application, so you use IAM policies to authenticate them into viewing different dashboards or viewing the data that pertains to them specifically. IoT Core policies can help manage authorization.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The white paper also proposes a few options for capturing telemetry:&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="593" src="https://www.digihunch.com/wp-content/uploads/2023/03/opt-cap-telemetry.webp" alt="" class="wp-image-12902" srcset="https://www.digihunch.com/wp-content/uploads/2023/03/opt-cap-telemetry.webp 1024w, https://www.digihunch.com/wp-content/uploads/2023/03/opt-cap-telemetry-300x174.webp 300w, https://www.digihunch.com/wp-content/uploads/2023/03/opt-cap-telemetry-768x445.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;figcaption class="wp-element-caption"&gt;Options for capturing telemetry&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;These options presents a common pub-sub pattern, where the device streams message by topic to the Message Broker in IoT core. The IoT core also involves policy and rules. A rule may involve a subscriber to consume the messages. A more comprehensive architecture from AWS &lt;a href="https://aws-quickstart.github.io/quickstart-iot-device-connectivity/"&gt;IoT device connectivity workshop&lt;/a&gt; looks like this:&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="548" src="https://www.digihunch.com/wp-content/uploads/2023/03/iot-device-connectivity.webp" alt="" class="wp-image-12903" srcset="https://www.digihunch.com/wp-content/uploads/2023/03/iot-device-connectivity.webp 1024w, https://www.digihunch.com/wp-content/uploads/2023/03/iot-device-connectivity-300x161.webp 300w, https://www.digihunch.com/wp-content/uploads/2023/03/iot-device-connectivity-768x411.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The message path from device to IoT core remains the same. When building an IoT solution, we first address the messaging path.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;IoT Protocols&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In IoT core, Device Gateway is the entry point for IoT devices connecting to AWS.It supports MQTT, WebSockets and HTTP 1.1 protocols, on top of TLS. Registration flow uses HTTP/REST protocol for provisioning, and with MQTT protocol for a message test. For pushing telemetry, we can use both but prefer &lt;a href="https://mqtt.org/"&gt;MQTT&lt;/a&gt; (topic based) because of its advantages in IoT messaging. &lt;a href="https://www.hivemq.com/blog/mqtt-vs-http-protocols-in-iot-iiot/"&gt;Here&lt;/a&gt; is a good article on the differences. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AWS has a white paper on &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/designing-mqtt-topics-aws-iot-core/designing-mqtt-topics-aws-iot-core.html"&gt;designing MQTT topics&lt;/a&gt;, with a few communication patterns and best practices. The SDK documentation also explained the &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/protocols.html"&gt;communication protocols&lt;/a&gt;, including:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;HTTPS: publish only&lt;/li&gt;&#10;&lt;li&gt;MQTT: publish and subscribe&lt;/li&gt;&#10;&lt;li&gt;MQTT over WebSocket: publish and subscribe. Device Gateway will maintain long lived, bi-directional connections, enabling devices to send and receive messages at any time with low latency.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Pay attention to the authentication mechanism. As to what protocol is used in a communication, they can be dynamically negotiated using the ALPN protocol. ALPN (Application-Layer Protocol Negotiation) is a TLS protocol extension that allows the application layer to negotiate which&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/Communications_protocol"&gt;protocol&lt;/a&gt;&amp;nbsp;should be performed over a secure connection in a manner that avoids additional round trips and which is independent of the application-layer protocols.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;GPS data simulator&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I don&amp;#8217;t have a GPS chip. To get sample GPS data, I used geojson.io website, use a pen to paint the points and collect the result in JSON format. The data looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;FeatureCollection&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;features&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Feature&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;properties&amp;#34;&lt;/span&gt;: {},&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;geometry&amp;#34;&lt;/span&gt;: {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;coordinates&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;119.&lt;span style="color:#a6e22e"&gt;4966304331144&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 49.&lt;span style="color:#a6e22e"&gt;88901098598916&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;119.&lt;span style="color:#a6e22e"&gt;4966304331144&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 49.&lt;span style="color:#a6e22e"&gt;889903059931726&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;119.&lt;span style="color:#a6e22e"&gt;49658503509582&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 49.&lt;span style="color:#a6e22e"&gt;89066350338888&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;119.&lt;span style="color:#a6e22e"&gt;49658503509582&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 49.&lt;span style="color:#a6e22e"&gt;891365440561145&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;119.&lt;span style="color:#a6e22e"&gt;4966304331144&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 49.&lt;span style="color:#a6e22e"&gt;89219897769374&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;119.&lt;span style="color:#a6e22e"&gt;4966304331144&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 49.&lt;span style="color:#a6e22e"&gt;88735841200943&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;119.&lt;span style="color:#a6e22e"&gt;49660773410511&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 49.&lt;span style="color:#a6e22e"&gt;8880603972496&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;119.&lt;span style="color:#a6e22e"&gt;4966304331144&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 49.&lt;span style="color:#a6e22e"&gt;88885011844141&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;LineString&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Save this file as map.geojson to later feed it to device simulation script. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Rendering location data&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In AWS &lt;a href="https://github.com/aws-samples/amazon-location-samples"&gt;location samples&lt;/a&gt; project, the sample project &lt;strong&gt;&lt;a href="https://github.com/aws-samples/amazon-location-samples/tree/main/maplibre-js-react-iot-asset-tracking"&gt;maplibre-js-react-iot-asset-tracking&lt;/a&gt;&lt;/strong&gt; is a good demo of IT. The &lt;a href="https://github.com/aws-samples/amazon-location-samples/blob/main/maplibre-js-react-iot-asset-tracking/README.md"&gt;readme&lt;/a&gt; document contains a walk through, using AWS amplify services. The steps includes creating certificates, configuring lambda function to add location data to tracker. The project directory also includes the device simulation script, as &lt;a href="https://github.com/aws-samples/amazon-location-samples/blob/main/maplibre-js-react-iot-asset-tracking/generate_thing_events/index.js"&gt;index.js&lt;/a&gt;. I slightly modified the content to this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;awsIot&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;aws-iot-device-sdk&amp;#34;&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Replace with your AWS IoT endpoint&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;THING_ENDPOINT&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;safdsa-ats.iot.us-east-1.amazonaws.com&amp;#34;&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// get from console&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CLIENT_ID&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;trackThing01&amp;#34;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOT_TOPIC&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;iot/trackedAssets&amp;#34;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DEVICE_ID&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;thing123&amp;#34;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GEOJSON_FILEPATH&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;geojson/map.geojson&amp;#34;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;fs&amp;#39;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;file_raw&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;readFileSync&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;GEOJSON_FILEPATH&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;toString&lt;/span&gt;();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;positions_raw&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;parse&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;file_raw&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;features&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;geometry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;coordinates&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;POINTS_ON_MAP&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;[]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;positions_raw&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt;;&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;POINTS_ON_MAP&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;push&lt;/span&gt;({&lt;span style="color:#a6e22e"&gt;lat&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;positions_raw&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;][&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;],&lt;span style="color:#66d9ef"&gt;long&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;positions_raw&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;][&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;]})&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;device&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;awsIot&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;device&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;host&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;THING_ENDPOINT&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;keyPath&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;__dirname&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/certs/private.pem.key`&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;certPath&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;__dirname&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/certs/certificate.pem.crt`&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;caPath&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;__dirname&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/certs/root-CA.pem`&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;clientId&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CLIENT_ID&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;keepalive&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;60000&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Connecting to %s with client ID %s&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;THING_ENDPOINT&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;CLIENT_ID&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;device&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;on&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;connect&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; () {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Connected to device %s&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;CLIENT_ID&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;point&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;of&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;POINTS_ON_MAP&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;message&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;payload&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;deviceId&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DEVICE_ID&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;timestamp&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date().&lt;span style="color:#a6e22e"&gt;getTime&lt;/span&gt;(),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;location&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;point&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Publishing message to topic %s: %s&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;IOT_TOPIC&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stringify&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;message&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;device&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;publish&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;IOT_TOPIC&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stringify&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;message&lt;/span&gt;), { &lt;span style="color:#a6e22e"&gt;qos&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; });&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Set timeout to sleep&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Promise((&lt;span style="color:#a6e22e"&gt;resolve&lt;/span&gt;) =&amp;gt; &lt;span style="color:#a6e22e"&gt;setTimeout&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;resolve&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt;));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;device&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;end&lt;/span&gt;();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can obtain certificate ID from AWS console or by CLI command: &lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;aws iot list-certificates --output text --query &lt;span style="color:#e6db74"&gt;&amp;#39;reverse(sort_by(certificates,&amp;amp;creationDate))[:1].[certificateId]&amp;#39;&lt;/span&gt; | cat&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;When running the script, it pushes data to IoT core service. The AWS Amplify project creates Lambda function that is subscribed to the topic and trigger actions. The data are used to render points on a map, which is available on the front end.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This is an overly simplified use case but it covers the basics. IoT solution will use a lot managed service and familiar technologies (e.g. TLS, &lt;a href="https://www.digihunch.com/2021/08/creating-tls-certificate-kubernetes/"&gt;certificate&lt;/a&gt;). Creating an IoT solution is mostly about address the onboarding services and make use of the MQTT based workflow. AWS managed services makes these easier.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="320" src="https://www.digihunch.com/wp-content/uploads/2023/02/tracking-1024x320.png" alt="" class="wp-image-8342" srcset="https://www.digihunch.com/wp-content/uploads/2023/02/tracking-1024x320.png 1024w, https://www.digihunch.com/wp-content/uploads/2023/02/tracking-300x94.png 300w, https://www.digihunch.com/wp-content/uploads/2023/02/tracking-768x240.png 768w, https://www.digihunch.com/wp-content/uploads/2023/02/tracking-1536x481.png 1536w, https://www.digihunch.com/wp-content/uploads/2023/02/tracking-2048x641.png 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;More IoT &lt;a href="https://workshops.aws/categories/AWS%20IoT"&gt;workshops&lt;/a&gt; are available on &lt;a href="https://workshops.aws/"&gt;AWS workshops&lt;/a&gt;. &lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2023/02/dicom-testing-with-tls/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;DICOM testing over TLS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2023/03/landing-zone-in-azure/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Landing Zone in Azure – Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Hosting database on Kubernetes</title><link>https://www.digihunch.com/2022/05/hosting-database-on-kubernetes/</link><pubDate>Sun, 29 May 2022 11:01:00 -0400</pubDate><guid>https://www.digihunch.com/2022/05/hosting-database-on-kubernetes/</guid><description>&lt;img src="https://www.digihunch.com/wp-content/uploads/2025/04/feature-k8s-database.webp" alt="Featured image of post Hosting database on Kubernetes" /&gt;&lt;h2 class="wp-block-heading"&gt;Background&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&amp;#8220;We want to host Postgres database on Kubernetes. Can you help us?&amp;#8221;. The client appears assertive and reluctant to resort to managed services. So I did some homework and went through &lt;a href="https://www.youtube.com/watch?v=3TFXztwat_s"&gt;this&lt;/a&gt; tutorial. My thought: it&amp;#8217;s doable, but don&amp;#8217;t do it unless operating database as a service is your main business.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I believed that was the client&amp;#8217;s best interest, until I came across this the blog post &lt;a href="https://thenewstack.io/a-case-for-databases-on-kubernetes-from-a-former-skeptic/"&gt;A Case for Databases on Kubernetes from a Former Skeptic&lt;/a&gt;. The author explained his journey from being a skeptic, to grudging acceptance, and eventually to an evangelist on running database on Kubernetes. The same voice came from the author of the upcoming book &lt;a href="https://www.oreilly.com/library/view/managing-cloud-native/9781098111380/"&gt;Managing Cloud Native Data on Kubernetes&lt;/a&gt;, who also advocates hosting database on Kubernetes. While the points in the chapters are valid, the book also includes a good amount of technical details which might lead reader to believe the opposite view.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Just a few years back, Kubernetes was not mature to host database. This is changing in 2022. Nowadays, for clients with their own Kubernetes platform, technological maturity is no longer the main reason that keeps them from hosting database on Kubernetes, it is the operational cost. The operational cost has to do with whether the client has in-house expertise in database and Kubernetes. If they do, the hard path makes economical sense.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this post, we discuss what we need to be aware of in order to host database on Kubernetes.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-benefit-with-kubernetes"&gt;Benefit with Kubernetes&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The first few versions of Kubernetes only supported stateless workload (reference &lt;a href="https://www.youtube.com/watch?v=BE77h7dmoQU"&gt;documentary&lt;/a&gt;). That is what Kubernetes was born to solve. Built-in objects such as replicaSet, deployment, horizontalPodAutoscaler are abstractions of operations particular to stateless workload. Pods for stateless workload are ephemeral: they crash and get replaced at any time. Because they don&amp;#8217;t carry persistent data themselves, they are expendable. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Kubernetes&amp;#8217; orchestration capability are driven by controllers. As &lt;a href="https://www.digihunch.com/2022/04/kubernetes-operator/"&gt;discussed&lt;/a&gt;, the &lt;a href="https://kubernetes.io/docs/concepts/architecture/controller/#controller-pattern"&gt;controller pattern&lt;/a&gt; is adopted in all controller implementations. They are the engines of the platform that works tirelessly in a control loop to ensure desired states matches their declared states. This is a key feature of Kubernetes as container platform. Let&amp;#8217;s examine a web service that requires 5 instances behind load balancer. With traditional hosting model on Linux servers, you&amp;#8217;d have it installed on all five VMs. If the process on one of the VMs dies, the VM has to be removed from the load balancer&amp;#8217;s target pool. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;One may wrap the process with process monitor and control utility such as &lt;a href="http://supervisord.org/"&gt;supervisord&lt;/a&gt;, and re-install the application using automation utility (e.g. &lt;a href="https://www.ansible.com/products/controller"&gt;Ansible&lt;/a&gt;). However, each server is unaware of the status of its peer. Without a central &amp;#8220;Control Plane&amp;#8221;, there is no coordination between the activities of each VMs. Kubernetes controller solved all these operational problems. &lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://k21academy.com/wp-content/uploads/2021/05/Actual_DesiredState.png" alt="Kubernetes Control Loop"/&gt;&lt;figcaption class="wp-element-caption"&gt;Control Loop&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Kubernetes comes with a set of build-in &lt;a href="https://kubernetes.io/docs/concepts/architecture/controller/"&gt;controllers&lt;/a&gt; that run inside the kube-controller-manager. Here is a good page about how &lt;a href="https://www.containiq.com/post/kubernetes-controllers"&gt;controllers&lt;/a&gt; work. Controller is what is missing in many automation tools other that Kubernetes. Even though Red Hat now brands Ansible as Automation &lt;em&gt;Controller&lt;/em&gt;, it does not involve a control loop or controller pattern. If there&amp;#8217;s one thing that sets Kubernetes apart from other hosting platforms and automation platforms, it is the implementation of controller pattern. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Stateful workload&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Does the controller pattern also benefit stateful workload? Yes. How to orchestrate Pods for stateful workload is usually more tricky. CRD can define a custom object type for controller to consume. In this case, an operator is an implementation of the controller pattern. This pattern is also known as the &lt;a href="https://www.digihunch.com/2022/04/kubernetes-operator/"&gt;operator pattern&lt;/a&gt;. In a replicaSet, Pod names have extensions of randomly generated numbers. A statefulSet names its the Pods by sequential numbers. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For Postgres database, Bitnami built a good &lt;a href="https://artifacthub.io/packages/helm/bitnami/postgresql-ha"&gt;Helm Chart&lt;/a&gt; to install the database automatically. However, it does not have a control loop. If someone changes the workload after initial installation, the change is not monitored or controlled by any controller. This is a disadvantage of Helm chart as compared with operators. For PostgreSQL, there are a &lt;a href="https://blog.flant.com/comparing-kubernetes-operators-for-postgresql/"&gt;number of operators&lt;/a&gt;, the most notable being PGO (&lt;a href="https://access.crunchydata.com/documentation/postgres-operator/v5/"&gt;Postgres Operator&lt;/a&gt;) from &lt;a href="https://www.crunchydata.com/"&gt;Crunchy Data&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To install an instance of PostgreSQL database, we need to install the operator, and then declare a Custom Resource using the &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/v5/references/crd/"&gt;PostgresCluster&lt;/a&gt; CRD. The operator will set up the cluster according to the declaration made in the &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/v5/tutorial/create-cluster/"&gt;PostgresCluster&lt;/a&gt; CR. I used the &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/v5/quickstart/"&gt;quick start guide&lt;/a&gt; to bring Postgres up real quick on an Azure Kubernetes cluster. The &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/v5/"&gt;operator&lt;/a&gt; (v5) supports common cloud Kubernetes platforms (GKE, EKS, AKS), VMware Tanzu, Openshift, Rancher, Kubernetes. It does not explicitly indicate whether PGO supports Minikube or kind.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;So far, I&amp;#8217;ve discussed the pros of running PostgreSQL on Kubernetes using Postgres Operator. We can describe the database deployment in a CR and the controller (operator) will monitor the resource incessantly to ensure the actual state matches the state defined in the CR. Not only is it doable to host database in Kubernetes, it makes our lives even easier. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Persistent storage&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Database is not only a stateful workload, it also has special requirement on storage. It needs to persist data, support ACID transaction, and make optimal use of disks. When we operate everything on premise, we use fibre cable with a &lt;a href="https://www.digihunch.com/2019/05/storage-nitty-gritty-2-5/"&gt;SAN&lt;/a&gt; as the storage media for database file. The operating system allows the database process to interact with blocks on the storage volume via device mapper.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Kubernetes, we need to give Pods persistent volumes. There are a few APIs: Storage Class, Volume Storage Class, Persistent Volume and Persistent Volume Claims. Storage Class represents how Pod can connect to a storage. Pods will need PVCs in order to read and write on PVs. However, since Pods are ephemeral &amp;#8211; a Pod may crash any time, even if it is in the middle of writing to a PV, during an ACID transaction. The scheduler may reschedule the crashed Pod to a different node. Then it will need to pick up the PV from where it left off, on the new Node. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Take Azure Kubernetes Service for example, a few storage classes are available by default, backed by Azure managed disk (managed-csi) or Azure file storage (azurefile-csi):&lt;/p&gt;&#10;&lt;table id="tablepress-19" class="tablepress tablepress-id-19 tbody-has-connected-cells"&gt;&#10;&lt;thead&gt;&#10;&lt;tr class="row-1"&gt;&#10;&#9;&lt;td class="column-1"&gt;&lt;/td&gt;&lt;th class="column-2"&gt;StorageClass&lt;/th&gt;&lt;th class="column-3"&gt;Azure storage service&lt;/th&gt;&#10;&lt;/tr&gt;&#10;&lt;/thead&gt;&#10;&lt;tbody class="row-striping row-hover"&gt;&#10;&lt;tr class="row-2"&gt;&#10;&#9;&lt;td rowspan="4" class="column-1"&gt;in-tree&lt;/td&gt;&lt;td class="column-2"&gt;default&lt;/td&gt;&lt;td class="column-3"&gt;Managed Disk using Azure StandardSSD&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-3"&gt;&#10;&#9;&lt;td class="column-2"&gt;managed-premium&lt;/td&gt;&lt;td class="column-3"&gt;Managed Disk using Azure Premium Storage&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-4"&gt;&#10;&#9;&lt;td class="column-2"&gt;azurefile&lt;/td&gt;&lt;td class="column-3"&gt;Azure File Share using Azure Standard Storage&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-5"&gt;&#10;&#9;&lt;td class="column-2"&gt;azurefile-premium&lt;/td&gt;&lt;td class="column-3"&gt;Azure File Share using Azure Premium Storage&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-6"&gt;&#10;&#9;&lt;td rowspan="4" class="column-1"&gt;csi&lt;/td&gt;&lt;td class="column-2"&gt;managed-csi&lt;/td&gt;&lt;td class="column-3"&gt;Managed Disk using Azure StandardSSD&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-7"&gt;&#10;&#9;&lt;td class="column-2"&gt;managed-csi-premium&lt;/td&gt;&lt;td class="column-3"&gt;Managed Disk using Azure Premium Storage&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-8"&gt;&#10;&#9;&lt;td class="column-2"&gt;azurefile-csi&lt;/td&gt;&lt;td class="column-3"&gt;Azure File Share using Azure Standard Storage&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-9"&gt;&#10;&#9;&lt;td class="column-2"&gt;azurefile-csi-premium&lt;/td&gt;&lt;td class="column-3"&gt;Azure File Share using Azure Premium Storage&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;!-- #tablepress-19 from cache --&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If we use storage class based on Azure disks to create a PV, only one Pod can use the PV. If we use storage class based on Azure files to create a PV, then the storage is mounted as NFS (Linux) or SMB (Windows) share. File storage is not a valid &lt;a href="https://www.digihunch.com/2020/08/cloud-storage-overview/"&gt;use case&lt;/a&gt; for database workload and it can significantly degrade database performance. When I tried to use a file-storage based CSI with PGO, the Pod reports an &lt;a href="https://github.com/CrunchyData/postgres-operator/issues/2870"&gt;error&lt;/a&gt; and will not start properly. We should use Azure disk based CSI storage classes. That leaves us with two options: managed-csi and managed-csi-premium. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;High Availability&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Even with these to options left, we still have to investigate how database Pods interact with persistent volume for high availability, in order to determine whether any of the options are suitable. The two storage classes differ by disk performance but both have its own &lt;a href="https://docs.microsoft.com/en-us/azure/aks/availability-zones#azure-disk-availability-zone-support"&gt;limitation&lt;/a&gt; with multi-AZ support on Azure managed disks. When the cluster operates across zones, the Kubernetes scheduler may reschedule a Pod crashed in one zone to a Node in a different availability zone (a different data centre). Even though the managed disks, when attached to VMs, can be configured as zone-redundant, when they are used as Kubernetes volume, they are NOT zone-redundant. So the node in a different zone will not be able to attach PV to the new Pod. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are SDS (software-defined storage) solution such as &lt;a href="https://portworx.com/wp-content/uploads/2020/06/portworx-microsoft-aks-reference-architecture.pdf"&gt;Portworx&lt;/a&gt; that solves the limitation of Azure disk for cross-region storage volume. The SDS layer brings managed disks from multiple availability zones into a pool. This storage pool acts as a highly available, cross-zone storage tier presented to AKS as persistent volumes. We can install Portworx as the SDS layer using Portworx operator. To do so, we first have to configure &lt;a href="https://docs.portworx.com/portworx-enterprise/platform/kubernetes/azure-aks/install/azure-managed-identity-on-aks"&gt;grant the cluster the permission &lt;/a&gt;to provision resources in Azure, because the Portworx operator will use node&amp;#8217;s identity (kubelet identity) to provision Azure resources on behalf of the nodes. Portworx will provision Azure disks and acts as the intermediary layer.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="726" src="https://www.digihunch.com/wp-content/uploads/2025/04/aks-data-1024x726.webp" alt="" class="wp-image-13110" srcset="https://www.digihunch.com/wp-content/uploads/2025/04/aks-data-1024x726.webp 1024w, https://www.digihunch.com/wp-content/uploads/2025/04/aks-data-300x213.webp 300w, https://www.digihunch.com/wp-content/uploads/2025/04/aks-data-768x545.webp 768w, https://www.digihunch.com/wp-content/uploads/2025/04/aks-data.webp 1430w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apart from cross-zone high availability enabled by PX-Store, Portworx can also help with cross-region replication of persistent volumes. The PX-DR component can perform asynchronous replication across Azure regions. The destination region needs to have its own cluster because a single AKS cluster cannot span across regions.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Storage Class&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Once we have portworx installed, the following storage classes are available by default:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;px-db&lt;/li&gt;&#10;&lt;li&gt;px-db-cloud-snapshot&lt;/li&gt;&#10;&lt;li&gt;px-db-cloud-snapshot-encrypted&lt;/li&gt;&#10;&lt;li&gt;px-db-encrypted&lt;/li&gt;&#10;&lt;li&gt;px-db-local-snapshot&lt;/li&gt;&#10;&lt;li&gt;px-db-local-snapshot-encrypted&lt;/li&gt;&#10;&lt;li&gt;px-replicated&lt;/li&gt;&#10;&lt;li&gt;px-replicated-encrypted&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The steps for installing porworx on AKS are documented &lt;a href="https://web.archive.org/web/20230204230139/https://docs.portworx.com/install-portworx/cloud/azure/"&gt;here&lt;/a&gt;. This blog post has more details in the &lt;a href="https://portworx.com/blog/portworx-enterprise-2-8-installation-on-oracle-kubernetes-engine-oke/"&gt;installation&lt;/a&gt; process on a different platform. We can also built CSI based storage classes with different IO priority and replication factors.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In summary, Kubernetes operator pattern makes it easier to manage stateful workload. However, database performance depends largely on storage. To host database on Kubernetes, one will have to also manage the storage volumes on their own. There has not been a study on the impact to performance by moving database to Kubernetes platform. However, I only expect a degraded performance due to the layers introduced.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Example&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this section we configure a (minimally viable) PostgreSQL cluster using Crunchy Data pgo to demonstrate the idea. The steps are based on its &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/5.1.1/tutorial/"&gt;tutorial&lt;/a&gt; but it works on a local KinD cluster. As discussed in a &lt;a href="https://www.digihunch.com/2021/09/single-node-kubernetes-cluster-minikube/"&gt;previous post&lt;/a&gt;, I use KinD for testing workload requiring persistent storage because Minikube has this open &lt;a href="https://github.com/kubernetes/minikube/issues/12360"&gt;issue&lt;/a&gt; with permissions on PVs with multiple nodes.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To prepare the cluster, we can use &lt;a href="https://github.com/digihunch/real-quicK-cluster/blob/main/kind/kind-config.yaml"&gt;kind-config.yaml&lt;/a&gt; file from my &lt;a href="https://github.com/digihunch/real-quicK-cluster"&gt;real-quicK-cluster&lt;/a&gt; repo:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kind create cluster --config&lt;span style="color:#f92672"&gt;=&lt;/span&gt;kind-config.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# to delete cluster after testing: kind delete cluster --name kind&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We use Helm to install the operator. Since the Helm chart is not hosted in a public repo, we&amp;#8217;d have to download the directory of the Helm Chart.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git clone https://github.com/CrunchyData/postgres-operator-examples&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd postgres-operator-examples&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install -n postgres-operator --create-namespace crunchy-pgo helm/install&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n postgres-operator get po --watch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl explain postgresclusters&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Now we can create a YAML file for the Custom Resource and let&amp;#8217;s call it test-cluster.yaml with the following content:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;postgres&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;crunchydata&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;com&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1beta1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PostgresCluster&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hippo&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;postgres&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;backups&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;pgbackrest&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;image&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;registry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;developers&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;crunchydata&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;com&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;crunchydata&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;crunchy&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pgbackrest&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ubi8&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2.38&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;repos&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;repo1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;volume&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;volumeClaimSpec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;accessModes&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ReadWriteOnce&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resources&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;requests&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Gi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storageClassName&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;standard&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;image&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;registry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;developers&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;crunchydata&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;com&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;crunchydata&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;crunchy&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;postgres&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ubi8&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;14.3&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;instances&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;dataVolumeClaimSpec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;accessModes&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ReadWriteOnce&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resources&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;requests&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Gi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storageClassName&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;standard&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;instance1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;replicas&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;minAvailable&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;postgresVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In the manifest, we specified a cluster, using storageclass named &amp;#8220;standard&amp;#8221;, with 3 replicas and requiring 2 available. We assume a storage class named &amp;#8220;standard&amp;#8221; already exists and optimized for database workload. In the manifest, we also configured a backup job. We can apply the manifest and watch for the Pods to come up in a few minutes.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply -f test-cluster.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n postgres-operator get po --watch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n postgres-operator describe postgresclusters hippo&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The Pods in the postgres-operator namespace should report something like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME READY STATUS RESTARTS AGE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hippo-backup-mwpm-ps8wk 0/1 Completed &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 21s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hippo-instance1-6mls-0 4/4 Running &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m35s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hippo-instance1-hjp6-0 4/4 Running &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m35s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hippo-instance1-k4qf-0 4/4 Running &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m35s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hippo-repo-host-0 2/2 Running &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m35s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pgo-548d5f48bc-9w4z4 1/1 Running &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 8m41s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pgo-upgrade-566b9cc98f-d7gkr 1/1 Running &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 8m41s&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Three Pods for PostgreSQL are all up. The first backup run has completed already. We can connect to the cluster using psql following the &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/5.1.1/quickstart/"&gt;quick start guide&lt;/a&gt;. We can also configure an application. A good example application that uses PostgreSQL database is &lt;a href="https://www.keycloak.org/"&gt;KeyCloak&lt;/a&gt;. We briefly mentioned it in OIDC &lt;a href="https://www.digihunch.com/2022/02/istio-external-authorization/"&gt;discussion&lt;/a&gt;. Currently the keycloak example on Crunchy pgo&amp;#8217;s &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/5.1.1/quickstart/"&gt;quick start guide&lt;/a&gt; is outdated. Instead, use the following content as keycloak.yaml:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;apps&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Deployment&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;postgres&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;kubernetes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;selector&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;matchLabels&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;kubernetes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;template&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;kubernetes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;containers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;image&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;quay&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;args&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;start-dev&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;env&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DB_VENDOR&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;postgres&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DB_ADDR&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;valueFrom&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;secretKeyRef&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hippo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pguser&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hippo&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;host&lt;/span&gt; } }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DB_PORT&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;valueFrom&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;secretKeyRef&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hippo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pguser&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hippo&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt; } }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DB_DATABASE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;valueFrom&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;secretKeyRef&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hippo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pguser&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hippo&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;dbname&lt;/span&gt; } }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DB_USER&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;valueFrom&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;secretKeyRef&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hippo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pguser&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hippo&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt; } }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DB_PASSWORD&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;valueFrom&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;secretKeyRef&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hippo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pguser&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hippo&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;password&lt;/span&gt; } }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;KEYCLOAK_USER&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;admin&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;KEYCLOAK_PASSWORD&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;admin&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PROXY_ADDRESS_FORWARDING&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ports&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;containerPort&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;containerPort&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;8443&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;readinessProbe&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;httpGet&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;path&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;/realms/master # https://stackoverflow.com/questions/70577004/keycloak-could-not-find-resource-for-full-path&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;initialDelaySeconds&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;restartPolicy&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Always&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Once we apply keycloak.yaml, in a minute we should see and be able to port-forward web traffic:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f keycloak.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n postgres-operator get po -l app.kubernetes.io/name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;keycloak&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME READY STATUS RESTARTS AGE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;keycloak-7995d78d7c-zjp4d 1/1 Running &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 4m29s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl port-forward deploy/keycloak -n postgres-operator 8080:8080&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;After using the port-forward command, we can browse to web portal on my MacBook by http://localhost:8080 and configure an initial password, as shown here:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1086" height="937" src="https://www.digihunch.com/wp-content/uploads/2022/07/image.png" alt="" class="wp-image-6112"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In real life system we would need a proper Ingress. After testing, delete the cluster with kind command and specify the cluster name (kind).&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Operation Cost&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Operation cost is an important consideration. Troubleshooting on Kubernetes platform is in general more complicated than just on a Unix system. Hosting database on Kubernetes requires skills not only on the Kubernetes platform, but also on database. There used to be database administrator positions where someone has to maintain the upgrade, the storage, the replication, the multi-tenancy and the performance optimization of database. With a database hosted on Kubernetes, the database administrator will have to perform all these activities on a containerized platform. This is not an easy undertaking, and in many occasions warrants a full-time position on its own. Therefore, don&amp;#8217;t host your database on Kubernetes, unless that is your main business. It is not the technology that shots down this option. It is the operation cost, such as complexity of configuration, and staff skillset, that makes this option not worth it.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2022/05/fsx-ontap-enterprise-storage-on-aws/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;FSx ONTAP – Enterprise storage on AWS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2022/06/etcd-the-key-value-store-for-kubernetes/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Etcd – the key-value store for Kubernetes&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Census Data from Statistics Canada</title><link>https://www.digihunch.com/2021/02/interpret-census-data-from-statistics-canada/</link><pubDate>Thu, 25 Feb 2021 21:18:09 -0400</pubDate><guid>https://www.digihunch.com/2021/02/interpret-census-data-from-statistics-canada/</guid><description>&lt;p class="wp-block-paragraph"&gt;&lt;a class="rank-math-link" href="http://www.statcan.gc.ca"&gt;Statistics Canada&lt;/a&gt; carries census every 5 years, with 2016 being the last run. The census data by Statistics Canada provides a wealth of insights but are published in raw format. Post-processing work is needed to extrapolate information, such as median income of a neighbourhood, age distribution of a city, etc. For someone like myself without any background in geographical informatics, it took a bit of learning to see how these work together. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The following information are typically included in the Census data:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Population&lt;/li&gt;&#10;&lt;li&gt;Population density&lt;/li&gt;&#10;&lt;li&gt;Age&lt;/li&gt;&#10;&lt;li&gt;Structural type of dewellings&lt;/li&gt;&#10;&lt;li&gt;Family size&lt;/li&gt;&#10;&lt;li&gt;Marital status&lt;/li&gt;&#10;&lt;li&gt;Language&lt;/li&gt;&#10;&lt;li&gt;Income&lt;/li&gt;&#10;&lt;li&gt;Place of birth&lt;/li&gt;&#10;&lt;li&gt;Level of education&lt;/li&gt;&#10;&lt;li&gt;Occupation&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We will start with level of Geographics. The level of geographics may change slightly between census programs in different years. The most recent 2016 census uses the following &lt;a href="https://www12.statcan.gc.ca/census-recensement/2016/ref/dict/figures/f1_1-eng.cfm" class="rank-math-link"&gt;diagram &lt;/a&gt;to depict levels of geographics:&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://www12.statcan.gc.ca/census-recensement/2016/ref/dict/figures/f1_1-eng.jpg" alt="Figure 1.1 Hierarchy of standard geographic areas for dissemination, 2016 Census"/&gt;&lt;figcaption class="wp-element-caption"&gt;Geographic Levels&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This diagram reflects a number of different hierarchies of geographic units. The best resource to understand each block, is the &lt;a href="https://www150.statcan.gc.ca/n1/pub/92-195-x/92-195-x2016001-eng.htm" class="rank-math-link"&gt;illustrated glossary&lt;/a&gt; and the chapter &lt;a href="https://www12.statcan.gc.ca/census-recensement/2016/ref/98-304/chap12-eng.cfm" class="rank-math-link"&gt;Census Geography&lt;/a&gt; in comprehensive &lt;a href="https://www12.statcan.gc.ca/census-recensement/2016/ref/98-304/index-eng.cfm" class="rank-math-link"&gt;Guide to the Census Population&lt;/a&gt;. For example, the chain on the far left of the diagram runs across these levels:&lt;/p&gt;&#10;&lt;p class="has-white-background-color has-background wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="121px" viewBox="-0.5 -0.5 121 321" style="max-width:100%;max-height:321px;"&gt;&lt;defs&gt;&lt;/defs&gt;&lt;g&gt;&lt;path d="M 60 40 L 60 60 L 60 50 L 60 63.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 60 68.88 L 56.5 61.88 L 60 63.63 L 63.5 61.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="0" y="0" width="120" height="40" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 20px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Canada&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="60" y="24" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Canada&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 60 110 L 60 130 L 60 120 L 60 133.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 60 138.88 L 56.5 131.88 L 60 133.63 L 63.5 131.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="0" y="70" width="120" height="40" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 90px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Geographical Region of Canada&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="60" y="94" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Geographical Region&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 60 180 L 60 200 L 60 190 L 60 203.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 60 208.88 L 56.5 201.88 L 60 203.63 L 63.5 201.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="0" y="140" width="120" height="40" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 160px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Province or Territory&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="60" y="164" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Province or Territory&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 60 250 L 60 270 L 60 260 L 60 273.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 60 278.88 L 56.5 271.88 L 60 273.63 L 63.5 271.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="0" y="210" width="120" height="40" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 230px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Forward Sortation Area&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="60" y="234" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Forward Sortation Ar&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="0" y="280" width="120" height="40" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 300px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Postal Code&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="60" y="304" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Postal Code&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;/g&gt;&lt;a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank" rel="noopener"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Viewer does not support full SVG 1.1&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this hierarchy, the level of &lt;a href="https://www150.statcan.gc.ca/n1/pub/92-195-x/2011001/geo/region/region-eng.htm" class="rank-math-link"&gt;Geographical Region&lt;/a&gt; of Canada is standardized in &lt;a href="https://www150.statcan.gc.ca/n1/pub/92-195-x/2011001/other-autre/sgc-cgt/sgc-cgt-eng.htm" class="rank-math-link"&gt;Standard Geographic Classification&lt;/a&gt; (SGC), in which the provinces and territories are also &lt;a href="https://www12.statcan.gc.ca/census-recensement/2016/ref/dict/geo038-eng.cfm" class="rank-math-link"&gt;encoded&lt;/a&gt;. Note that each Census include a &lt;a href="https://www12.statcan.gc.ca/census-recensement/2016/ref/dict/az1-eng.cfm" class="rank-math-link"&gt;dictionary &lt;/a&gt;where all sorts of codes are kept. The dictionary also includes definition of the rest two levels: FSA (forward sortation area as the first three digits of postal code) and &lt;a href="https://www12.statcan.gc.ca/census-recensement/2016/ref/dict/geo035-eng.cfm" class="rank-math-link"&gt;postal code&lt;/a&gt; (all six digits). Note that postal code is a mark of Canada Post Corporation, and you may translate postal code into other levels in standard geographic areas, such as CD. This is not straightforward though. You will need a product called &lt;a href="https://www150.statcan.gc.ca/n1/en/catalogue/92-154-X" class="rank-math-link"&gt;Postal Code Conversion File&lt;/a&gt; (PCCF) for the conversion. Statistics Canada does not directly distribute this product. It works with its &lt;a href="https://www.statcan.gc.ca/eng/dli/dli" class="rank-math-link"&gt;Data Liberation Initiative&lt;/a&gt; (DLI) partners to deliver this product.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On the diagram there are also other path to run down the hierarchy. For example, from Canada down to federal electoral district (aka ridings). However, the census is not carried out by either election ridings or postal code. Instead, it is carried out by its own collection of levels dedicated for census purpose. When using census data, we need to be familiar with these units.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Census metropolitan area (CMA) and census agglomeration (CA)&lt;/strong&gt;: formed by one or more adjacent municipalities centred on a population centre (known as the core), such as Chatham-Kent CA, Kitchener-Cambridge-Waterloo CMA. Note that CMA and CA can expand across provincial borders, such as Ottawa &amp;#8211; Gatineau CMA. So CMA or CA is not a unit under province or territory.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Census Division (CD, essentially a region or county)&lt;/strong&gt;: general term for provincially legislated areas (such as county, municipalité régionale de comté and regional district) or their equivalents.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Census Subdivision (CSD, essentially a city)&lt;/strong&gt;: the general term for municipalities or areas treated as municipal equivalents for statistical purposes.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;&lt;a href="https://www12.statcan.gc.ca/census-recensement/2016/ref/dict/geo013-eng.cfm" class="rank-math-link"&gt;Census Tract&lt;/a&gt; (CT)&lt;/strong&gt;: small, relatively stable geographic areas that usually have a population of less than 10,000 persons, based on data from the previous Census of Population Program.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Dissemination Area (DA)&lt;/strong&gt;: &amp;nbsp;is a small, relatively stable geographic unit composed of one or more adjacent dissemination blocks with an average population of&amp;nbsp;400 to 700&amp;nbsp;persons based on data from the previous Census of Population Program. It is the smallest standard geographic area for which &lt;span style="text-decoration: underline;"&gt;all census data&lt;/span&gt; are disseminated.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Dissemination Block (DB)&lt;/strong&gt;: an area bounded on all sides by roads and/or boundaries of standard geographic areas. The dissemination block is the smallest geographic area for which &lt;span style="text-decoration: underline;"&gt;population &lt;/span&gt;and dwelling counts are disseminated.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With these in mind, we can build two hierarchies closely related to census data:&lt;/p&gt;&#10;&lt;p class="has-white-background-color has-background wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="291px" viewBox="-0.5 -0.5 291 331" style="max-width:100%;max-height:331px;"&gt;&lt;defs&gt;&lt;/defs&gt;&lt;g&gt;&lt;path d="M 60 30 L 60 50 L 60 40 L 60 53.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 60 58.88 L 56.5 51.88 L 60 53.63 L 63.5 51.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="0" y="0" width="120" height="30" rx="4.5" ry="4.5" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 15px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Canada&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="60" y="19" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Canada&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 60 90 L 60 110 L 60 100 L 60 113.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 60 118.88 L 56.5 111.88 L 60 113.63 L 63.5 111.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="0" y="60" width="120" height="30" rx="4.5" ry="4.5" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 75px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;CMA/CA&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="60" y="79" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;CMA/CA&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 60 150 L 60 170 L 60 160 L 60 173.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 60 178.88 L 56.5 171.88 L 60 173.63 L 63.5 171.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="0" y="120" width="120" height="30" rx="4.5" ry="4.5" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 135px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Census Tract&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="60" y="139" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Census Tract&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 60 210 L 60 230 L 60 220 L 60 233.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 60 238.88 L 56.5 231.88 L 60 233.63 L 63.5 231.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="0" y="180" width="120" height="30" rx="4.5" ry="4.5" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 195px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Dissemination Area&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="60" y="199" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Dissemination Area&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="0" y="240" width="120" height="30" rx="4.5" ry="4.5" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 255px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Dissemination Block&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="60" y="259" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Dissemination Block&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 230 30 L 230 50 L 230 40 L 230 53.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 230 58.88 L 226.5 51.88 L 230 53.63 L 233.5 51.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="170" y="0" width="120" height="30" rx="4.5" ry="4.5" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 15px; margin-left: 171px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Canada&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="230" y="19" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Canada&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 230 90 L 230 110 L 230 100 L 230 113.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 230 118.88 L 226.5 111.88 L 230 113.63 L 233.5 111.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="170" y="60" width="120" height="30" rx="4.5" ry="4.5" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 75px; margin-left: 171px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Geo. Region&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="230" y="79" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Geo. Region&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 230 150 L 230 170 L 230 160 L 230 173.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 230 178.88 L 226.5 171.88 L 230 173.63 L 233.5 171.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="170" y="120" width="120" height="30" rx="4.5" ry="4.5" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 135px; margin-left: 171px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Census Division&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="230" y="139" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Census Division&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 230 210 L 230 230 L 230 220 L 230 233.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 230 238.88 L 226.5 231.88 L 230 233.63 L 233.5 231.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="170" y="180" width="120" height="30" rx="4.5" ry="4.5" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 195px; margin-left: 171px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Census Subdivision&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="230" y="199" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Census Subdivision&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 230 270 L 230 290 L 230 280 L 230 293.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 230 298.88 L 226.5 291.88 L 230 293.63 L 233.5 291.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="170" y="240" width="120" height="30" rx="4.5" ry="4.5" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 255px; margin-left: 171px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Dissemination Area&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="230" y="259" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Dissemination Area&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="170" y="300" width="120" height="30" rx="4.5" ry="4.5" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 315px; margin-left: 171px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Dissemination Block&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="230" y="319" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Dissemination Block&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;/g&gt;&lt;a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank" rel="noopener"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Viewer does not support full SVG 1.1&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now we download &lt;a href="https://www12.statcan.gc.ca/census-recensement/2016/dp-pd/prof/details/download-telecharger/comp/page_dl-tc.cfm?Lang=E" class="rank-math-link"&gt;census profile&lt;/a&gt; &lt;a href="https://www12.statcan.gc.ca/census-recensement/2016/dp-pd/prof/index.cfm?Lang=E" class="rank-math-link"&gt;data &lt;/a&gt;from &lt;a href="https://www12.statcan.gc.ca/census-recensement/2016/dp-pd/index-eng.cfm" class="rank-math-link"&gt;Statistics Canada&lt;/a&gt;. In the dropdown you can pick from the many of the aforementioned geographic levels.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1158" height="245" src="https://www.digihunch.com/wp-content/uploads/2021/02/image.png" alt="" class="wp-image-2176"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If you pick Census tracts (CT), there is one data file. The CSV file is about 160M. Also note that under geographic level column, it indicates two levels: CA/CMA and CT, which is important to keep in mind as we go through the data. In the content of the CSV, under the GEO_LEVEL column, value 1 stands for CA/CMA and value 2 stands for CT. Therefore, when GEO_LEVEL=1, the GEO_CODE value is a &lt;a href="https://www23.statcan.gc.ca/imdb/p3VD.pl?Function=getVD&amp;amp;TVD=314312&amp;amp;CVD=314313&amp;amp;CPV=B&amp;amp;CST=01012016&amp;amp;CLV=1&amp;amp;MLV=3" class="rank-math-link"&gt;CA&lt;/a&gt;/&lt;a href="https://www23.statcan.gc.ca/imdb/p3VD.pl?Function=getVD&amp;amp;TVD=314312&amp;amp;CVD=314313&amp;amp;CPV=A&amp;amp;CST=01012016&amp;amp;CLV=1&amp;amp;MLV=3" class="rank-math-link"&gt;CMA&lt;/a&gt; &lt;a href="https://www23.statcan.gc.ca/imdb/p3VD.pl?Function=getVD&amp;amp;TVD=314312" class="rank-math-link"&gt;code &lt;/a&gt;based on &lt;a href="https://www.statcan.gc.ca/eng/subjects/standard/sgc/2016/introduction" class="rank-math-link"&gt;Statistical Area Classification&lt;/a&gt;; when GEO_LEVEL=2, the GEO_CODE value is a CT numerical name (preceded by CMA/CA code). What CT numerical name represents what geographic area, is all defined in &lt;a href="https://www12.statcan.gc.ca/census-recensement/2011/geo/map-carte/ref/cma_ca_ct-rmr_ar_sr/index-eng.cfm" class="rank-math-link"&gt;Census Tract Reference Map&lt;/a&gt;. There is no textual name for each census tract.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To take another example, select Dissemination areas (DAs) from the dropdown. Now the size of the CSV becomes 1.6G, but smaller data files are provided by province and territories. Select the data file for Ontario only.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="916" height="965" src="https://www.digihunch.com/wp-content/uploads/2021/02/image-1.png" alt="" class="wp-image-2177"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that there are five geographic levels as indicated: Canada, provinces/territories, CDs, CSDs and DAs. This suggests we will see 5 different values under the GEO_LEVEL column in the data file:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;0 &amp;#8211; Canada&lt;/li&gt;&#10;&lt;li&gt;1 &amp;#8211; Provinces and Territories&lt;/li&gt;&#10;&lt;li&gt;2 &amp;#8211; CDs&lt;/li&gt;&#10;&lt;li&gt;3 &amp;#8211; CSDs&lt;/li&gt;&#10;&lt;li&gt;4 &amp;#8211; DAs&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Read the &lt;a href="https://www.statcan.gc.ca/eng/subjects/standard/sgc/2016/introduction" class="rank-math-link"&gt;SGC documentation&lt;/a&gt; to understand the code from level Canada to level CSD. DA is similar to CT because the code is defined in &lt;a href="https://www12.statcan.gc.ca/census-recensement/2016/geo/ADA/ADA-eng.cfm" class="rank-math-link"&gt;reference map&lt;/a&gt; here. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apart from DA and CT, there are other levels (such as ridings) with reference maps, as outlined in the &lt;a href="https://www12.statcan.gc.ca/census-recensement/2016/geo/index-eng.cfm" class="rank-math-link"&gt;Census geography&lt;/a&gt; page.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With all the above information, we can parse the data programmatically. Of course, the schema and coding information applies to Canada. Outside of Canada, pretty much all states have a counterpart government agency that manages census and statistics, just with different formats to understand from ground up. A lot of census geography concepts applies to other countries as well. For example:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://www.census.gov/" class="rank-math-link"&gt;Census Bureau&lt;/a&gt; of United States&lt;/li&gt;&#10;&lt;li&gt;Australian &lt;a href="https://www.abs.gov.au/" class="rank-math-link"&gt;Bureau of Statistics&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.ons.gov.uk/census" class="rank-math-link"&gt;Office for National Statistics&lt;/a&gt; (UK)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://ec.europa.eu/eurostat/web/main" class="rank-math-link"&gt;Eurostat &lt;/a&gt;(European Union)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Welcome to the world of data.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2021/02/basic-resource-object-in-kubernetes-2-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Basic Resource Object in Kubernetes 2 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2021/03/git-branching-strategy/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Git Branching Strategy&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>How imaging devices talk to each other (in DICOM)</title><link>https://www.digihunch.com/2020/11/how-imaging-devices-talk-to-each-other-tip-in-dicom/</link><pubDate>Sun, 15 Nov 2020 18:40:00 -0400</pubDate><guid>https://www.digihunch.com/2020/11/how-imaging-devices-talk-to-each-other-tip-in-dicom/</guid><description>&lt;h3 class="wp-block-heading" id="h-overview"&gt;Overview&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the &lt;a href="https://www.digihunch.com/2020/11/medical-imaging-web-server-deployment-pipeline/" class="rank-math-link"&gt;previous post &lt;/a&gt;I briefly touched on DICOM as the crucial standard in medical imaging for both data exchanging and data storage. It is important to understand that DICOM is such a massive standard that, beyond data exchanging and storage, has expanded into many different areas around imaging, that no device (or information system) can ever implement every single aspect of the standard. A device or information system complies to (and implements) a subset of the DICOM standard. The manufacturer must provide a document (DICOM conformance statement) to spec out which parts of the standard are implemented. Care providers (e.g. hospitals) are supposed to review these specs as part of the procurement process to ensure interoperability with existing information system.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-medical-imaging-informatics"&gt;Medical Imaging Informatics&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When it comes to diagnosis, historical examinations provide baseline reference for radiologist. They are sometimes even more revealing than current imaging data acquired from a patient. Compared to the current exams, historical ones are referred to as priors. Priors are useful only if they are relevant to the current exams in terms of body part, modality, and exam procedure (the particular problem being studied). The effort to find out and pre-load relevant priors so they are ready to display along with current exams, is called &amp;#8220;prefetch&amp;#8221;. With huge demand in exchanging imaging data, &amp;#8220;prefetch&amp;#8221; has developed into its own sub-market in imaging informatics industry. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Trust me, this is a difficult undertaking (and why I was in the industry). First, patient identities in each healthcare organization are usually different for lack of universal medical record number; patient&amp;#8217;s name can change (marriage, divorce, or just for fun); or it can be unavailable, if patient is simply not in a condition to provide identify (e.g. trauma). Second, even if you get &amp;#8220;who&amp;#8217;s who&amp;#8221; right, you&amp;#8217;d have to dig into all his history for useful information from several different systems. The definition of relevant prior can be different depending on the specific medical specialty. Then, the old data are typically stored in a slow part of the storage from their source system, yet the patient might be bleeding and dying on the table, waiting for prior retrieval like pulling teeth. Last, but not least, the priors being retrieve might be from a modality of previous generation from 1990s; good luck with current display application. Sorry that sounds a lot but in real life, there are even more challenges. In General, there are four categories of applications that need to support DICOM:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Acquisition devices: Modalities need to store newly acquired studies persistently;&lt;/li&gt;&#10;&lt;li&gt;Routing applications: usually by the name of some routers, gateways or bridges that receive imaging studies, decorate the metadata (because a lot of legacy devices can&amp;#8217;t do it), and send to one or multiple defined destination;&lt;/li&gt;&#10;&lt;li&gt;Archives (e.g. PACS, VNA): They usually use dedicated database (metadata) and storage systems (pixel data). They are the repository of imaging data and must provide full support of transfer capability;&lt;/li&gt;&#10;&lt;li&gt;Peripheral applications that uses DICOM data, such as 3D post-processing or DICOM testing (grassroot dicom, dcmtk) &lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;No matter what the devices are, they must follow certain protocols in order to communicate with each other. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-data-structure-and-encoding"&gt;Data structure and encoding&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;DICOM PS3.5 defines data types in VR (value representation). Each VR has its own purpose, allowed characters, and length limit. For example:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-stripes"&gt;&lt;table class="has-background" style="background-color:#e9fbe5"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;VR&lt;/th&gt;&lt;th&gt;Definition&lt;/th&gt;&lt;th&gt;Allowed Characters&lt;/th&gt;&lt;th&gt;Length Limit&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;SH&lt;br&gt;Short String&lt;/td&gt;&lt;td&gt;A string of characters&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;16 maximum&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;LO&lt;br&gt;Long String&lt;/td&gt;&lt;td&gt;A string of characters&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;64 maximum&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AE&lt;br&gt;Application Entity&lt;/td&gt;&lt;td&gt;A string of characters that identifies a DICOM application running on a compliant device&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;16 maximum&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CS&lt;br&gt;Code String&lt;/td&gt;&lt;td&gt;A string to represent code&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;16 maximum&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;PN&lt;br&gt;Person Name&lt;/td&gt;&lt;td&gt;Person&amp;#8217;s name, with caret (^) as delimiter&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;64 maximum&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;UI&lt;br&gt;Unique Identifier&lt;/td&gt;&lt;td&gt;An ID that uniquely identify an item, such as 1.2.840.100008.1.1&lt;/td&gt;&lt;td&gt;0-9 and period (.)&lt;/td&gt;&lt;td&gt;64 maximum&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DA&lt;br&gt;Date&lt;/td&gt;&lt;td&gt;A string to represent date YYYYMMDD&lt;/td&gt;&lt;td&gt;0-9&lt;/td&gt;&lt;td&gt;8&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;US&lt;br&gt;Unsigned Short&lt;/td&gt;&lt;td&gt;Unsigned binary integer, 16 bits long&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SQ&lt;br&gt;Sequence of other items&lt;/td&gt;&lt;td&gt;Sequence of other items&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;UN&lt;br&gt;Unknown&lt;/td&gt;&lt;td&gt;A string of bytes where the encoding of contents is unknown&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;figcaption class="wp-element-caption"&gt;Value Representatives&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;DICOM metadata is a dataset comprised of a set of data elements. Each element includes tag, (optional) VR, length of value, and the actual value, as shown below: &lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://3.bp.blogspot.com/-hAI3mF_ZL-I/TtQOMjy6LmI/AAAAAAAAKvE/SDqvwxaXnog/s1600/DICOM+Element.png" alt=""/&gt;&lt;figcaption class="wp-element-caption"&gt;DICOM data elements&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;VR is optional because it can be implicitly determined based on DICOM data dictionary defined in PS3.6. The most common tags are:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-stripes"&gt;&lt;table class="has-background" style="background-color:#e9fbe5"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Level&lt;/th&gt;&lt;th&gt;Tag&lt;/th&gt;&lt;th&gt;Meaning&lt;/th&gt;&lt;th&gt;VR&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Patient&lt;/td&gt;&lt;td&gt;0010,0010&lt;/td&gt;&lt;td&gt;Patient&amp;#8217;s Name&lt;/td&gt;&lt;td&gt;PN&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Patient&lt;/td&gt;&lt;td&gt;0010,0020&lt;/td&gt;&lt;td&gt;Patient ID&lt;/td&gt;&lt;td&gt;LO&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Patient&lt;/td&gt;&lt;td&gt;0010,0021&lt;/td&gt;&lt;td&gt;Issuer of Patient ID&lt;/td&gt;&lt;td&gt;LO&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Patient&lt;/td&gt;&lt;td&gt;0010,0024&lt;/td&gt;&lt;td&gt;Issuer of Patient ID Qualifier Sequence&lt;/td&gt;&lt;td&gt;SQ&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Patient&lt;/td&gt;&lt;td&gt;0010,0030&lt;/td&gt;&lt;td&gt;Patient&amp;#8217;s Birth Date&lt;/td&gt;&lt;td&gt;DA&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Study&lt;/td&gt;&lt;td&gt;0008,0020&lt;/td&gt;&lt;td&gt;Study Date&lt;/td&gt;&lt;td&gt;DA&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Study&lt;/td&gt;&lt;td&gt;0008,0050&lt;/td&gt;&lt;td&gt;Accession Number&lt;/td&gt;&lt;td&gt;SH&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Study&lt;/td&gt;&lt;td&gt;0008,0061&lt;/td&gt;&lt;td&gt;Modalities In Study&lt;/td&gt;&lt;td&gt;CS&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Study&lt;/td&gt;&lt;td&gt;0008,1030&lt;/td&gt;&lt;td&gt;Study Description&lt;/td&gt;&lt;td&gt;LO&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Study&lt;/td&gt;&lt;td&gt;0020,000D&lt;/td&gt;&lt;td&gt;Study Instance UID&lt;/td&gt;&lt;td&gt;UI&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Study&lt;/td&gt;&lt;td&gt;0020,0010&lt;/td&gt;&lt;td&gt;Study ID&lt;/td&gt;&lt;td&gt;SH&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Series&lt;/td&gt;&lt;td&gt;0008,103E&lt;/td&gt;&lt;td&gt;Series Description&lt;/td&gt;&lt;td&gt;LO&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Series&lt;/td&gt;&lt;td&gt;0008,0015&lt;/td&gt;&lt;td&gt;Body Part Examined&lt;/td&gt;&lt;td&gt;CS&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Series&lt;/td&gt;&lt;td&gt;0008,0060&lt;/td&gt;&lt;td&gt;Modality&lt;/td&gt;&lt;td&gt;CS&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Series&lt;/td&gt;&lt;td&gt;0020,0011&lt;/td&gt;&lt;td&gt;Series Number&lt;/td&gt;&lt;td&gt;IS&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Series&lt;/td&gt;&lt;td&gt;0020,000E&lt;/td&gt;&lt;td&gt;Series Instance UID&lt;/td&gt;&lt;td&gt;UI&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Series&lt;/td&gt;&lt;td&gt;0020,0060&lt;/td&gt;&lt;td&gt;Laterality&lt;/td&gt;&lt;td&gt;CS&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SOP inst&lt;/td&gt;&lt;td&gt;0008,0016&lt;/td&gt;&lt;td&gt;SOP Class UID&lt;/td&gt;&lt;td&gt;UI&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SOP inst&lt;/td&gt;&lt;td&gt;0008,0018&lt;/td&gt;&lt;td&gt;SOP Instance UID&lt;/td&gt;&lt;td&gt;UI&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SOP inst&lt;/td&gt;&lt;td&gt;0012,0010&lt;/td&gt;&lt;td&gt;Transfer Syntax UID&lt;/td&gt;&lt;td&gt;UI&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SOP inst&lt;/td&gt;&lt;td&gt;0020,0013&lt;/td&gt;&lt;td&gt;Instance Number&lt;/td&gt;&lt;td&gt;IS&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;figcaption class="wp-element-caption"&gt;Common DICOM tags&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When VR is not explicitly spelled out, the data encoding is known as implicit VR. The opposite is explicit VR, where each data element spells out the VR type. When storing numeric value, such as tags or numeric values for tags, the predominant format stores lower byte before higher bytes, known as &lt;a href="https://en.wikipedia.org/wiki/Endianness" class="rank-math-link"&gt;little endian&lt;/a&gt;. Rarely seen in DICOM is big endian, the opposite order of storing numeric values.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The tag include a group number (e.g. 0020) and element number (e.g. 0013). If group number is odd, it is a private tag not defined in PS3.6&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The actual length of tag value shall always be even number. Odd-sized value should add an additional character (e.g. trailing space), known as even-length padding to meet this requirement.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Above is the basic rules for DICOM data structure and encoding. For more information about encoding, including transfer syntax, refer to &lt;a href="https://www.digihunch.com/2018/06/dicom-data-encoding/" class="rank-math-link"&gt;this previous post&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-transactions-dimses-and-sop-classes"&gt;Transactions (DIMSEs and SOP classes)&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;DICOM has its own &lt;a href="http://dicom.nema.org/medical/dicom/current/output/chtml/part04/chapter_6.html" class="rank-math-link"&gt;information model &lt;/a&gt;of real world. It requires some clinical knowledge to come to full understanding. For technical people, we just need to understand the patient-study-series-image hierarchy.&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;One patient may have multiple studies&lt;/li&gt;&#10;&lt;li&gt;Each study may include one or more image series&lt;/li&gt;&#10;&lt;li&gt;Each series has one or more images&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Images are also referred to as SOP instance, a general terms that include not only images, but also reports, and other types of objects.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A business transaction in DICOM is termed DIMSE, for example:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-stripes"&gt;&lt;table class="has-background" style="background-color:#e9fbe5"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Name&lt;/th&gt;&lt;th&gt;Group&lt;/th&gt;&lt;th&gt;Type&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;C-STORE&lt;/td&gt;&lt;td&gt;DIMSE-C&lt;/td&gt;&lt;td&gt;Operation&lt;/td&gt;&lt;td&gt;A stores an image to B&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;C-MOVE&lt;/td&gt;&lt;td&gt;DIMSE-C&lt;/td&gt;&lt;td&gt;Operation&lt;/td&gt;&lt;td&gt;A tells B to store an image to C&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;C-FIND&lt;/td&gt;&lt;td&gt;DIMSE-C&lt;/td&gt;&lt;td&gt;Operation&lt;/td&gt;&lt;td&gt;Query for patient, study, series, images&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;C-ECHO&lt;/td&gt;&lt;td&gt;DIMSE-C&lt;/td&gt;&lt;td&gt;Operation&lt;/td&gt;&lt;td&gt;DICOM level &amp;#8220;ping&amp;#8221;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;N-EVENT-REPORT&lt;/td&gt;&lt;td&gt;DIMSE-N&lt;/td&gt;&lt;td&gt;Notification&lt;/td&gt;&lt;td&gt;Report an event&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;N-ACTION&lt;/td&gt;&lt;td&gt;DIMSE-N&lt;/td&gt;&lt;td&gt;Operation&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;figcaption class="wp-element-caption"&gt;DIMSE&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Some DIMSEs involves multiple SOP classes based on the type of image being processed. For example, SOP Class UID (10.2.840.10008.5.1.4.1.1.1) represents storage for CR image. A c-store transaction includes:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Requestor (C-STORE SCU) sends a request (C-STORE-RQ), to store specified SOP class in certain transfer syntax&lt;/li&gt;&#10;&lt;li&gt;The request is followed by the actual data (PDU)&lt;/li&gt;&#10;&lt;li&gt;Once completed, the Response (C-STORE SCP) respond with C-STORE-RP&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 class="wp-block-heading" id="h-handshake-association"&gt;Handshake (Association)&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the previous C-STORE example, proper syntax that are mutually supported must be used in order for the requestor and receiver to process the transaction. Both parties learn each other&amp;#8217;s supported transfer syntax through an upfront handshake process known as DICOM association.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In DICOM association, the initiating party presents a list of supported pairs of SOP class and transfer syntax. Each pair is called a presentation context, and the responding party must respond to each presentation context in the proposed list, with either yes or no.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This negotiation is similar to the cipher negotiation in TLS handshake.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For more detailed information, please refer to Pianykh&amp;#8217;s book &amp;#8220;&lt;a href="https://www.springer.com/gp/book/9783642108495" class="rank-math-link"&gt;DICOM, a practical introduction and survival guide&lt;/a&gt;&amp;#8220;.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/11/medical-imaging-web-server-deployment-pipeline/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Automatic deployment of Orthanc on AWS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/11/ipvs-iptables-and-kube-proxy/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;IPVS, iptables and kube-proxy&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Spark, Cassandra and Python</title><link>https://www.digihunch.com/2020/09/spark-cassandra-and-python/</link><pubDate>Tue, 15 Sep 2020 16:24:09 -0400</pubDate><guid>https://www.digihunch.com/2020/09/spark-cassandra-and-python/</guid><description>&lt;p class="wp-block-paragraph"&gt;In this &lt;a href="https://www.digihunch.com/2020/09/intro-to-big-data-projects/"&gt;post&lt;/a&gt; we touch briefly on &lt;a href="https://en.wikipedia.org/wiki/Apache_Spark"&gt;Apache Spark&lt;/a&gt; as a cluster computing framework that supports a number of drivers to pipe data in, and that its stunning performance thanks much to resilient distributed dataset (RDD) as its architectural foundation. In this hands-on guide, we expand on how to configure Spark, and use Python to connect to Cassandra data source. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Spark supports Sala, Java and Python shells. I&amp;#8217;m not familiar with Scala but I have had Python background and know it&amp;#8217;s importance in big data processing. One key data structure with big data processing in Python is Pandas &lt;a href="https://www.digitalvidya.com/blog/dataframes-in-python/"&gt;data frame&lt;/a&gt;. Spark has the ability to map its &lt;a href="https://www.analyticsvidhya.com/blog/2016/10/spark-dataframe-and-operations/"&gt;own data frame&lt;/a&gt; to Pandas data frame.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Spark also needs a third party connector to connect to Cassandra. This connector is provided by Datastax in this open-source project called &lt;a href="https://github.com/datastax/spark-cassandra-connector"&gt;spark-cassandra-connector&lt;/a&gt;. The Github page includes a README with compatibility matrix, which is very important to understand before any configuration works. However, the Github is only the source code repository for anyone to build the project themselves. An alternative source of the dependency is this &lt;a href="https://mvnrepository.com/artifact/com.datastax.spark/spark-cassandra-connector_2.11/2.5.1"&gt;page&lt;/a&gt; from Maven repository. When running Spark we can simply reference that page URL as dependency.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Suppose we install spark onto CentOS, we download and unzip &lt;a href="https://spark.apache.org/downloads.html"&gt;this&lt;/a&gt; package to somewhere such as user directory (~). Assuming we already have Open JDK 1.8 installed, when we run spark binary, it places cache and jar files in ~/.ivy2, potentially we need to manually move the following dependencies to ~/.ivy2/jars:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;org.codehaus.groovy_groovy-json-2.5.7.jar&lt;/li&gt;&lt;li&gt;com.github.jnr_jffi-1.2.19.jar&lt;/li&gt;&lt;li&gt;org.codehaus.groovy_groovy-2.5.7.jar&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;These jar files are available for download from Maven&amp;#8217;s repository as well if you wish provide them as package dependencies. We have two flavours of interactive shells to connect to Spark: the Scala shell (spark-shell) and python shell (PySpark)&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Scala Shell&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can enter the default scala shell by &lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ ./bin/spark-shell --packages com.datastax.spark:spark-cassandra-connector_2.11:2.5.1 --conf spark.cassandra.connection.host&lt;span style="color:#f92672"&gt;=&lt;/span&gt;10.10.10.151 --verbose&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;During the start, note a stdout line that says:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Spark context Web UI available at http://spark-host:4040&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then we can open that tcp port on iptables and view that job in browser. From within scala shell we can test connectivity to Cassandra with the following commands:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; val new_exam = spark.read.format(&amp;#34;org.apache.spark.sql.cassandra&amp;#34;).options(Map(&amp;#34;table&amp;#34; -&amp;gt; &amp;#34;new_exam&amp;#34;,&amp;#34;keyspace&amp;#34; -&amp;gt; &amp;#34;examarchive&amp;#34;)).load()&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Python Shell&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Python Shell (aka &lt;a href="https://realpython.com/pyspark-intro/"&gt;PySpark&lt;/a&gt;) brings Python shell which is known to many engineers from system admin or development background. By default, python 2 will be used. To specify python version, set some environment variables before we start pyspark with cassandra connector package specified:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ export PYSPARK_PYTHON&lt;span style="color:#f92672"&gt;=&lt;/span&gt;python3&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ export PYSPARK_DRIVER_PYTHON&lt;span style="color:#f92672"&gt;=&lt;/span&gt;python3&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ export SPARK_HOME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/home/dhunch/spark-2.4.6-bin-hadoop2.7&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ export PATH&lt;span style="color:#f92672"&gt;=&lt;/span&gt;$SPARK_HOME/bin:$PATH&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ ./bin/pyspark --packages com.datastax.spark:spark-cassandra-connector_2.11:2.5.1 --conf spark.cassandra.connection.host&lt;span style="color:#f92672"&gt;=&lt;/span&gt;10.10.10.151&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Once you&amp;#8217;re in the interactive shell, you can start with loading required python libraries, and test your connectivity:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; from pyspark import SparkContext, SparkConf&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; from pyspark.sql import SQLContext&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; load_options &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;table&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;new_exam&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;keyspace&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;examarchive&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; df&lt;span style="color:#f92672"&gt;=&lt;/span&gt;spark.read.format&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;org.apache.spark.sql.cassandra&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;.options&lt;span style="color:#f92672"&gt;(&lt;/span&gt;**load_options&lt;span style="color:#f92672"&gt;)&lt;/span&gt;.load&lt;span style="color:#f92672"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; df.show&lt;span style="color:#f92672"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; df.write.csv&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;/tmp/mycsv.csv&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; &lt;span style="color:#75715e"&gt;#df.registerTempTable(&amp;#34;ne&amp;#34;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; df.createTempView&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;ne&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; tw1&lt;span style="color:#f92672"&gt;=&lt;/span&gt;sqlContext.sql&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;select count(*) from ne&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; tw1.show&lt;span style="color:#f92672"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; qrdf2&lt;span style="color:#f92672"&gt;=&lt;/span&gt;sqlContext.sql&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;select study_key, image_count from ne where current_exam_version=exam_version&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; qrdf2.write.csv&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;/tmp/tw2&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Note that the load method returns type pyspark.sql.dataframe.DataFrame, which is already a distributed data structure. So there is no need to parallelize it with parallelize() method. As of Spark 2.0, we are supposed to use createTempView() method instead of the old registerTempTables() method. Read &lt;a href="https://dwgeek.com/spark-sql-create-temporary-tables-syntax-and-examples.html/"&gt;this&lt;/a&gt; for further information.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Python Application&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With interactive shell you run one or several commands at a time. We can build a python script and submit the whole script as an application. This is an example command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;./bin/spark-submit --packages com.datastax.spark:spark-cassandra-connector_2.11:2.5.1 sample.py&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Note that the sample.py script name must be provided after &amp;#8211;packages switch. Otherwise, you will get an error saying missing dependency (Failed to find data source: org.apache.spark.sql.cassandra). In the script, we can manipulate the data from Cassandra with greater flexibility. For example, we can map one field to several fields. For example, if one of the fields stores an XML document, the script can drill down the XML tree structure parse out values at different levels of child nodes, into separate data base columns. Here is an example of python script where we register a custom UDF declared in python and apply it to some existing columns to build new columns:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#! /usr/bin/python3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# To submit this script as an application to spark:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ./bin/spark-submit --packages com.datastax.spark:spark-cassandra-connector_2.11:2.5.1 examstat.py&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Note that the script name must be placed after --packages &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; sys&lt;span style="color:#f92672"&gt;,&lt;/span&gt;datetime&lt;span style="color:#f92672"&gt;,&lt;/span&gt;re&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; xml.etree.ElementTree &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; ET&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; pyspark &lt;span style="color:#f92672"&gt;import&lt;/span&gt; SparkContext, SparkConf&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; pyspark.sql &lt;span style="color:#f92672"&gt;import&lt;/span&gt; SQLContext, SparkSession&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; pyspark.sql.functions &lt;span style="color:#f92672"&gt;import&lt;/span&gt; udf &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; pyspark.sql.types &lt;span style="color:#f92672"&gt;import&lt;/span&gt; StringType,StructType,StructField&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cluster_seeds&lt;span style="color:#f92672"&gt;=&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#39;dest_cass_host&amp;#39;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pTrimExamCode&lt;/span&gt;(raw_code):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;NULL&amp;#39;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; raw_code &lt;span style="color:#f92672"&gt;is&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt; &lt;span style="color:#f92672"&gt;or&lt;/span&gt; raw_code&lt;span style="color:#f92672"&gt;==&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;None&amp;#39;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; str(raw_code)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;replace(&lt;span style="color:#e6db74"&gt;&amp;#39;,&amp;#39;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rstrip(&lt;span style="color:#e6db74"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\r\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;is_valid_date&lt;/span&gt;(date_str):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; isValidDate&lt;span style="color:#f92672"&gt;=&lt;/span&gt;bool(re&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;match&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;^(19|20)\d\d(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])$&amp;#34;&lt;/span&gt;,date_str))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; isValidDate:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; datetime&lt;span style="color:#f92672"&gt;.&lt;/span&gt;datetime(int(date_str[:&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;]),int(date_str[&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;]),int(date_str[&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;8&lt;/span&gt;]))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ValueError&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; isValidDate&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; isValidDate&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pPullTags&lt;/span&gt;(study_key,raw_xml_field):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ns&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;vc&amp;#34;&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;&amp;#34;http://medical.nema.org/mint&amp;#34;&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; StudyDateTag&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;None&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; StudyDescriptionTag&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;None&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; raw_xml_field &lt;span style="color:#f92672"&gt;is&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; summary_tree&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ET&lt;span style="color:#f92672"&gt;.&lt;/span&gt;fromstring(str(raw_xml_field)) &lt;span style="color:#75715e"&gt;# str function outputs &amp;#39;None&amp;#39; or null object&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; xml_find_res&lt;span style="color:#f92672"&gt;=&lt;/span&gt;summary_tree&lt;span style="color:#f92672"&gt;.&lt;/span&gt;find(&lt;span style="color:#e6db74"&gt;&amp;#34;vc:attributes/vc:attr[@tag=&amp;#39;00080020&amp;#39;]&amp;#34;&lt;/span&gt;,ns)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; xml_find_res &lt;span style="color:#f92672"&gt;is&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;: StudyDateTag&lt;span style="color:#f92672"&gt;=&lt;/span&gt;str(xml_find_res&lt;span style="color:#f92672"&gt;.&lt;/span&gt;attrib&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#39;val&amp;#39;&lt;/span&gt;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;xml_find_res&lt;span style="color:#f92672"&gt;=&lt;/span&gt;summary_tree&lt;span style="color:#f92672"&gt;.&lt;/span&gt;find(&lt;span style="color:#e6db74"&gt;&amp;#34;vc:attributes/vc:attr[@tag=&amp;#39;00081030&amp;#39;]&amp;#34;&lt;/span&gt;,ns)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; xml_find_res &lt;span style="color:#f92672"&gt;is&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;: StudyDescriptionTag&lt;span style="color:#f92672"&gt;=&lt;/span&gt;str(xml_find_res&lt;span style="color:#f92672"&gt;.&lt;/span&gt;attrib&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#39;val&amp;#39;&lt;/span&gt;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#34;-----------------------&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; examstat: error parsing metadta for study_key &amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;+&lt;/span&gt;study_key)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; (StudyDateTag,StudyDescriptionTag)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# custom StructType for the output tuple&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;XMLExtractType&lt;span style="color:#f92672"&gt;=&lt;/span&gt;StructType([&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; StructField(&lt;span style="color:#e6db74"&gt;&amp;#34;StudyDate&amp;#34;&lt;/span&gt;,StringType(),&lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; StructField(&lt;span style="color:#e6db74"&gt;&amp;#34;StudyDescription&amp;#34;&lt;/span&gt;,StringType(),&lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;)])&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; __name__ &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sparkSession&lt;span style="color:#f92672"&gt;=&lt;/span&gt;SparkSession&lt;span style="color:#f92672"&gt;.&lt;/span&gt;builder \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;.&lt;/span&gt;appName(&lt;span style="color:#e6db74"&gt;&amp;#39;examstat&amp;#39;&lt;/span&gt;) \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;.&lt;/span&gt;config(&lt;span style="color:#e6db74"&gt;&amp;#39;spark.cassandra.connection.host&amp;#39;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;join(cluster_seeds)) \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;.&lt;/span&gt;master(&lt;span style="color:#e6db74"&gt;&amp;#39;local[*]&amp;#39;&lt;/span&gt;) \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;.&lt;/span&gt;getOrCreate()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; load_options &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {&lt;span style="color:#e6db74"&gt;&amp;#34;table&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;new_exam&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;keyspace&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;examarchive&amp;#34;&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sqlContext&lt;span style="color:#f92672"&gt;=&lt;/span&gt;SQLContext(sparkSession)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# pyspark.sql.dataframe.DataFrame is already a distributed data structure. No need to parallelize it.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; df0&lt;span style="color:#f92672"&gt;=&lt;/span&gt;sqlContext&lt;span style="color:#f92672"&gt;.&lt;/span&gt;read&lt;span style="color:#f92672"&gt;.&lt;/span&gt;format(&lt;span style="color:#e6db74"&gt;&amp;#39;org.apache.spark.sql.cassandra&amp;#39;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;options(&lt;span style="color:#f92672"&gt;**&lt;/span&gt;load_options)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;load()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; df0&lt;span style="color:#f92672"&gt;.&lt;/span&gt;createTempView(&lt;span style="color:#e6db74"&gt;&amp;#34;new_exam&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# pyspark.sql.functions.udf(python function,output type)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sparkSession&lt;span style="color:#f92672"&gt;.&lt;/span&gt;udf&lt;span style="color:#f92672"&gt;.&lt;/span&gt;register(&lt;span style="color:#e6db74"&gt;&amp;#34;uTrimExamCode&amp;#34;&lt;/span&gt;,udf(pTrimExamCode,StringType()))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sparkSession&lt;span style="color:#f92672"&gt;.&lt;/span&gt;udf&lt;span style="color:#f92672"&gt;.&lt;/span&gt;register(&lt;span style="color:#e6db74"&gt;&amp;#34;uPullTags&amp;#34;&lt;/span&gt;,udf(pPullTags,XMLExtractType))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# use custom UDFs uTrimExamCode and uPullTags to calculate new columns and remove dups and deleted studies&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; df1&lt;span style="color:#f92672"&gt;=&lt;/span&gt;sqlContext&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sql(&lt;span style="color:#e6db74"&gt;&amp;#34;select study_key as StudyKey,uTrimExamCode(exam_id) as ExamCode,image_count as ImgCnt,Total_pixel_data_size as PixelSize, uPullTags(study_key,metadata_summary) as XMLExtract, metadata_summary from new_exam where exam_version=current_exam_version and is_deleted=False&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; df1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;createTempView(&lt;span style="color:#e6db74"&gt;&amp;#34;uniq_study&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# map the four fields in XMLExtract to separate columns. we take this as separate step as we don&amp;#39;t want uPullTags to execute multiple times in previous step &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; df2&lt;span style="color:#f92672"&gt;=&lt;/span&gt;sqlContext&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sql(&lt;span style="color:#e6db74"&gt;&amp;#34;select StudyKey,ExamCode,ImgCnt,PixelSize,XMLExtract.StudyDate as StudyDate,XMLExtract.StudyDescription as StudyDescription from uniq_study&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; df2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;createTempView(&lt;span style="color:#e6db74"&gt;&amp;#34;uniq_study_stat&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Run analytical query&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; df3&lt;span style="color:#f92672"&gt;=&lt;/span&gt;sqlContext&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sql(&lt;span style="color:#e6db74"&gt;&amp;#34;SELECT ExamCode, round(avg(PixelSize)/1024/1024) as avg_size_mb, round(sum(PixelSize)/1024/1024/1024,2) as total_size_gb,count(StudyKey) as study_count FROM uniq_study_stat GROUP BY ExamCode order by study_count desc&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;#data frames are lazily loaded and processing not started until the following call&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; df3&lt;span style="color:#f92672"&gt;.&lt;/span&gt;write&lt;span style="color:#f92672"&gt;.&lt;/span&gt;csv(&lt;span style="color:#e6db74"&gt;&amp;#39;/tmp/examstat_&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;+&lt;/span&gt;datetime&lt;span style="color:#f92672"&gt;.&lt;/span&gt;datetime&lt;span style="color:#f92672"&gt;.&lt;/span&gt;now()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;strftime(&lt;span style="color:#e6db74"&gt;&amp;#34;%m&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%d&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%H%M%S&amp;#34;&lt;/span&gt;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;It is important to understand the concept of lazy evaluation in Spark RDD here. The execution of function to RDD does not start until an action is triggered (eg. show method, or write method). Spark maintains the record of which operation is being called through DAG (&lt;a href="https://data-flair.training/blogs/dag-in-apache-spark/"&gt;directed acyclic graph&lt;/a&gt;). Such record is referred to as a transformation. We need to understand whether each RDD method is a transformation, or an action so we know whether it will be lazily evaluated (&lt;a href="https://data-flair.training/blogs/spark-rdd-operations-transformations-actions/"&gt;here&amp;#8217;s&lt;/a&gt; more information).&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This is a &lt;a href="https://data-flair.training/blogs/spark-vs-hadoop-mapreduce/"&gt;major difference&lt;/a&gt; between Apache Spark and Hadoop MapReduce. With MapReduce, developer spend a lot of time in minimizing the number of MapReduce passes. It happens by clubbing the operations together. &lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/09/intro-to-big-data-projects/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Intro to Big Data Projects&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/09/log-file-navigator-lnav/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Log file navigator (lnav)&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>DataStax Python Driver</title><link>https://www.digihunch.com/2020/06/iterate-through-cassandra-table-with-datastax-python-driver/</link><pubDate>Sat, 27 Jun 2020 14:20:34 -0400</pubDate><guid>https://www.digihunch.com/2020/06/iterate-through-cassandra-table-with-datastax-python-driver/</guid><description>&lt;p class="wp-block-paragraph"&gt;For someone with relational database background, analyzing data in Cassandra isn&amp;#8217;t intuitive. There are two reasons. First, Cassandra data table is hardly updated or deleted in avoidance of tombstones. Insertion is the only action on the table resulting in multiple versions of each record all stored in the same table, thus a much longer table than its relational counterpart. Second, Cassandra schema is designed around how end-user will query the database, rather than a modelling of entity-relations. There are less fields, but some field may contain large data chunk, such as an entire XML document being stored in a column.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Data engineers with Cassandra may need to run full table scan, and extract values from wide columns of XML document by drilling down the XML tree structure, in order to produce a data frame (two-dimensional mutable, possibly heterogeneous tabular data structure with labeled rows and columns). &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I&amp;#8217;ve came across this task in the past and the duration of a full table scan on Cassandra table is in the order of hours, which is beyond what the built-in cqlsh tool can handle. I had to use Python to iterate through 200 million rows. Datastax Provides Cassandra client driver as a Python3 package, known as &lt;a href="https://docs.datastax.com/en/developer/python-driver/index.html"&gt;DataStax Python Driver&lt;/a&gt;. It allows us to build a simple Python3 script to complete a full table scan. The driver can be installed with pip3:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pip3 install cassandra-driver&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;With the driver installed, we can start to pull data from Cassandra table into Python client class. here is a basic example of how to print the rows into a file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#! /usr/bin/python3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; cassandra.query &lt;span style="color:#f92672"&gt;import&lt;/span&gt; SimpleStatement&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; cassandra.cluster &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Cluster&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; cassandra &lt;span style="color:#f92672"&gt;import&lt;/span&gt; ConsistencyLevel&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; datetime&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; __name__ &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cluster &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Cluster([&lt;span style="color:#e6db74"&gt;&amp;#39;cass_host&amp;#39;&lt;/span&gt;],port&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;9042&lt;/span&gt;,protocol_version&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print (datetime&lt;span style="color:#f92672"&gt;.&lt;/span&gt;datetime&lt;span style="color:#f92672"&gt;.&lt;/span&gt;now()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;strftime(&lt;span style="color:#e6db74"&gt;&amp;#34;%Y-%m-&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%d&lt;/span&gt;&lt;span style="color:#e6db74"&gt; %H:%M:%S&amp;#34;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;+&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34; start&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; session &lt;span style="color:#f92672"&gt;=&lt;/span&gt; cluster&lt;span style="color:#f92672"&gt;.&lt;/span&gt;connect(&lt;span style="color:#e6db74"&gt;&amp;#39;myownkeyspace&amp;#39;&lt;/span&gt;, wait_for_all_pools&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; query &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;SELECT * FROM mytable&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; statement &lt;span style="color:#f92672"&gt;=&lt;/span&gt; SimpleStatement(query, fetch_size&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;, consistency_level&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ConsistencyLevel&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ONE)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; csv_file &lt;span style="color:#f92672"&gt;=&lt;/span&gt; open(&lt;span style="color:#e6db74"&gt;&amp;#39;result.csv&amp;#39;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;8192&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; csv_file&lt;span style="color:#f92672"&gt;.&lt;/span&gt;write(&lt;span style="color:#e6db74"&gt;&amp;#34;header&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; tbrow &lt;span style="color:#f92672"&gt;in&lt;/span&gt; session&lt;span style="color:#f92672"&gt;.&lt;/span&gt;execute(statement,timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2.0&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; csv_file&lt;span style="color:#f92672"&gt;.&lt;/span&gt;write(tbrow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;user_id&lt;span style="color:#f92672"&gt;+&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exception&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; ex:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(ex)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;KeyboardInterrupt&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#34;Task Interrupted by SIGINT.&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;finally&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cluster&lt;span style="color:#f92672"&gt;.&lt;/span&gt;shutdown()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; csv_file&lt;span style="color:#f92672"&gt;.&lt;/span&gt;close()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print (datetime&lt;span style="color:#f92672"&gt;.&lt;/span&gt;datetime&lt;span style="color:#f92672"&gt;.&lt;/span&gt;now()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;strftime(&lt;span style="color:#e6db74"&gt;&amp;#34;%Y-%m-&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%d&lt;/span&gt;&lt;span style="color:#e6db74"&gt; %H:%M:%S&amp;#34;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;+&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34; finish&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Note that the fetch_size can be set to larger number, but it may increase the chance of server read timeout (code=1200) in the middle of execution.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The processing logic can be implemented in the loop while each record in the table is being pulled out. The logic is repeated for every row so it will have a significant impact on the overall execution time.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Some the data needs to be ported into pandas data frame for further engineering, instead of being printed out to file. The following snippet will do the trick:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#! /usr/bin/python3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; cassandra.query &lt;span style="color:#f92672"&gt;import&lt;/span&gt; SimpleStatement&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; cassandra.cluster &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Cluster&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; cassandra &lt;span style="color:#f92672"&gt;import&lt;/span&gt; ConsistencyLevel&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; datetime&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; pandas &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; pd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pandas_factory&lt;/span&gt;(colnames,rows):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; res &lt;span style="color:#f92672"&gt;=&lt;/span&gt; []&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; res&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame(rows, columns&lt;span style="color:#f92672"&gt;=&lt;/span&gt;colnames))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; res&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; __name__ &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cluster &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Cluster([&lt;span style="color:#e6db74"&gt;&amp;#39;cass_host&amp;#39;&lt;/span&gt;],port&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;9042&lt;/span&gt;,protocol_version&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print (datetime&lt;span style="color:#f92672"&gt;.&lt;/span&gt;datetime&lt;span style="color:#f92672"&gt;.&lt;/span&gt;now()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;strftime(&lt;span style="color:#e6db74"&gt;&amp;#34;%Y-%m-&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%d&lt;/span&gt;&lt;span style="color:#e6db74"&gt; %H:%M:%S&amp;#34;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;+&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34; start&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; session &lt;span style="color:#f92672"&gt;=&lt;/span&gt; cluster&lt;span style="color:#f92672"&gt;.&lt;/span&gt;connect(&lt;span style="color:#e6db74"&gt;&amp;#39;myownkeyspace&amp;#39;&lt;/span&gt;, wait_for_all_pools&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; session&lt;span style="color:#f92672"&gt;.&lt;/span&gt;row_factory &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pandas_factory&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; query &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;SELECT * FROM mytable&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; statement &lt;span style="color:#f92672"&gt;=&lt;/span&gt; SimpleStatement(query, consistency_level&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ConsistencyLevel&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ONE,fetch_size&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; df&lt;span style="color:#f92672"&gt;=&lt;/span&gt;pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; tbrow &lt;span style="color:#f92672"&gt;in&lt;/span&gt; session&lt;span style="color:#f92672"&gt;.&lt;/span&gt;execute(statement):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; df&lt;span style="color:#f92672"&gt;=&lt;/span&gt;df&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(tbrow&lt;span style="color:#f92672"&gt;.&lt;/span&gt;user_id,ignore_index&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exception&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; ex:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(ex)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;KeyboardInterrupt&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#34;Task Interrupted by SIGINT.&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;finally&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cluster&lt;span style="color:#f92672"&gt;.&lt;/span&gt;shutdown()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print (datetime&lt;span style="color:#f92672"&gt;.&lt;/span&gt;datetime&lt;span style="color:#f92672"&gt;.&lt;/span&gt;now()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;strftime(&lt;span style="color:#e6db74"&gt;&amp;#34;%Y-%m-&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%d&lt;/span&gt;&lt;span style="color:#e6db74"&gt; %H:%M:%S&amp;#34;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;+&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34; finish&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In my test environment with 180 million rows in the table, the execution of the first script takes 37 minutes (of course there&amp;#8217;s a lot of factors at play). I experimented several approaches to improve the speed, such as tuning the &lt;a href="https://medium.com/@bramblexu/understand-the-buffer-policy-in-python-78e91e7759ca"&gt;buffering options&lt;/a&gt; for file write. However, It turns out that the speed bottleneck of the script is not even file IO, but rather pulling data out of Cassandra.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The output can be stored as CSV file, which can be lately loaded to relational database for analysis. PostgreSQL would be a good open-source choice because it is both transactional and analytical.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/06/performance-analysis-tools/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Performance Analysis&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/07/dockersnetwork/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Docker network in different modes&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Cassandra data model (as opposed to relational model)</title><link>https://www.digihunch.com/2020/04/cassandra-data-model-as-opposed-to-relational-database/</link><pubDate>Wed, 29 Apr 2020 18:45:00 -0400</pubDate><guid>https://www.digihunch.com/2020/04/cassandra-data-model-as-opposed-to-relational-database/</guid><description>&lt;p class="wp-block-paragraph"&gt;Bad data model design with &lt;a href="https://www.digihunch.com/2018/03/cassandra-architecture-summary/"&gt;Cassandra&lt;/a&gt; causes chronic pains as application scales. I had to re-read about data model design in &amp;#8220;&lt;a href="http://shop.oreilly.com/product/0636920299837.do"&gt;Cassandra &amp;#8211; the Definitive Guide&lt;/a&gt;&amp;#8221; and keep my notes and thoughts in this post.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The data modelling in the relational world is indoctrinated to every students out of university. It embraces several things:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Entity-Relation: we typically start with tables that represents entities, and then tables that expresses relations;&lt;/li&gt;&#10;&lt;li&gt;Query design after table design: we can join multiple tables, index certain fields for better query performance;&lt;/li&gt;&#10;&lt;li&gt;Data normalization: several normal forms (NFs) are brought up to better organize data; de-normalization only occurs when 1) performance bottleneck reached; and 2) specific requirement on retaining snapshots of previous (un-updated) value in a field;&lt;/li&gt;&#10;&lt;li&gt;referential integrity: we can specify foreign keys on a table to reference the primary key of a record in another table; we can configure cascading deletes, etc;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Anybody with years of experience with relational database may have all these built in their instinct. Unfortunately, Cassandra does not follow any of these patterns. For someone with relational database background, the trip entering the Cassandra design is very counter-intuitive.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-no-joins"&gt;No joins&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Cassandra you have very limited options to achieve what you can do with joins in relational realm. One option is to duplicate the data column on different tables (a pattern against the &amp;#8220;normalization&amp;#8221; best practice). The second table is denormalized and it represents the join results. The other option rarely applied is to do the work on the client side.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-query-driven-data-modelling"&gt;Query-driven data modelling&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In relational database, you start writing queries after tables are laid out to pull together disparate data, using the relationship defined by the keys. The queries is secondary concern. It is assumed that you can always get the data you want as long as you have your tables modelled properly, even if you have to use several complex subqueries or join statements.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Cassandra, You do not start with tables to represent entity. Instead, you would start with queries, and then organize data around the queries. This means an upfront effort must be made to investigate what queries the client application may perform, and work backwards with tables that answers those queries in the most efficient manner. Table names in Cassandra often takes names such as: hotels_by_poi, avaialbe_rooms_by_hotel_date, reservations_by_guest, reservations_by_hotel_date. On the tables the selection of partition key and clustering keys should also consider best query performance and avoids wide partitions. It should also ensure with best effort that a query should not have to travel across multiple partitions in order to return results.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-denormalization"&gt;Denormalization&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Due to the query-driven modelling approach, Cassandra usually need to be designed with denormalization. The entire concept of normalization applies only to relational world and in Cassandra it&amp;#8217;s perfectly normal to organize data that are against NFs.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-designing-for-optimal-storage"&gt;Designing for optimal storage&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Cassandra tables are each stored in separate files on disk. Its best practice to keep related columns defined together in the same table. We need to minimize the number of partitions that must be searched in order to satisfy a given query. Because the partition is a unit of storage that does not get divided across nodes, a query that searches a single partition will typically yield the best performance.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The book &amp;#8220;Cassandra: the definitive guide&amp;#8221; contains a great example of modelling hotel reservation system. &lt;a href="https://www.datastax.com/blog/2015/02/basic-rules-cassandra-data-modeling"&gt;This article&lt;/a&gt; is also a good guideline.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="780" height="1024" src="https://www.digihunch.com/wp-content/uploads/2025/04/cassandra-book-780x1024.webp" alt="" class="wp-image-13112" style="width:193px;height:254px" srcset="https://www.digihunch.com/wp-content/uploads/2025/04/cassandra-book-780x1024.webp 780w, https://www.digihunch.com/wp-content/uploads/2025/04/cassandra-book-229x300.webp 229w, https://www.digihunch.com/wp-content/uploads/2025/04/cassandra-book-768x1008.webp 768w, https://www.digihunch.com/wp-content/uploads/2025/04/cassandra-book-1171x1536.webp 1171w, https://www.digihunch.com/wp-content/uploads/2025/04/cassandra-book-1561x2048.webp 1561w, https://www.digihunch.com/wp-content/uploads/2025/04/cassandra-book.webp 1951w" sizes="auto, (max-width: 780px) 100vw, 780px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/04/how-memory-usage-adds-up-in-linux/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;How memory usage adds up in Linux&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/05/understanding-where-the-memory-goes-on-linux-vm/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Balloon steals memory from virtual machines&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Storage Nitty-Gritty 5 of 5 – Replication</title><link>https://www.digihunch.com/2019/11/storage-nitty-gritty-5-of-5-replication/</link><pubDate>Tue, 19 Nov 2019 00:10:23 -0400</pubDate><guid>https://www.digihunch.com/2019/11/storage-nitty-gritty-5-of-5-replication/</guid><description>&lt;h4 class="wp-block-heading"&gt;Replication Terms&lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;PIT (point in time) replica&lt;/strong&gt; &amp;#8211; snapshot of the source at some specific timestamp;&lt;br&gt;&lt;strong&gt;Continuous Replica&lt;/strong&gt; &amp;#8211; always in-sync with the production data;&lt;br&gt;&lt;strong&gt;Recoverability &lt;/strong&gt;&amp;#8211; enables restoration of data from the replica to the source if data loss or corruption occurs;&lt;br&gt;&lt;strong&gt;Restartability&lt;/strong&gt; &amp;#8211; enables restarting business operations using the replicas;&lt;/p&gt;&#10;&lt;h4 class="wp-block-heading"&gt;Local Replication &lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Use Case&lt;/strong&gt;:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Alternative source for backup&lt;/li&gt;&lt;li&gt;Fast recovery&lt;/li&gt;&lt;li&gt;Decision-support activities such as data warehousing&lt;/li&gt;&lt;li&gt;Testing platform&lt;/li&gt;&lt;li&gt;Data migration&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Consistency in file system replication &lt;/strong&gt;&lt;br&gt;File systems buffer the data in the host memory to improve the application response time. The buffered data is periodically written to the disk. In UNIX operating systems, &lt;span style="text-decoration: underline;"&gt;sync daemon&lt;/span&gt; is the process that flushes the buffers to the disk at set intervals. In some cases, the replica is created between the set intervals, which might result in the creation of an inconsistent replica. Therefore, host memory buffers must be flushed to ensure data consistency on the replica, prior to its creation.&lt;br&gt;&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="545" height="346" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-64.png" alt="" class="wp-image-413"/&gt;&lt;figcaption&gt;Flushing the file system buffer&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;br&gt;In the illustration above, If the host memory buffers are not flushed, the data on the replica will not contain the information that was buffered in the host. If the file system is unmounted before creating the replica, the buffers will be automatically flushed and the data will be consistent on the replica.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Consistency in database replication&lt;/strong&gt;&lt;br&gt;When a database is replicated while it is online, changes made to the database at this time must be applied to the replica to make it consistent. A consistent replica of an online database is created by using the dependent write I/O principle or by holding I/Os momentarily to the source before creating the replica.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A dependent write I/O principle is inherent in many applications and database management systems (DBMS) to ensure consistency. According to this principle, a write I/O is not issued by an application until a prior related write I/O has completed. For example, a data write is dependent on the successful completion of the prior log write.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For a transaction to be deemed complete, databases require a series of writes to have occurred in a particular order. These writes will be recorded on the various devices or file systems.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another way to ensure consistency is to make sure that the write I/O to all&lt;br&gt;source devices is held for the duration of creating the replica. This creates a&lt;br&gt;consistent image on the replica. However, databases and applications might time out if the I/O is held for too long.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Local Replication Technologies&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Host-based Local Replication&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;LVM-based replication: logical volume manager (LVM) is responsible for creating and controlling the host-level logical volumes. Each logical block in a logical volume is mapped to two physical blocks on two different physical volumes. LVM-based replication is part of operating system and comes without additional license cost. However, every write generated by application translates into two writes on the disk, and thus, an additional burden is placed on the host CPU. This can degrade application performance. Presenting an LVM-based logical replica to another host is usually not possible because the replica will still be part of the volume group, which is accessed by one host at any given time. You can&amp;#8217;t track changes on LVMs either so it does not support incremental resynchronization.&lt;br&gt;&lt;/li&gt;&lt;/ul&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;File system snapshot: a pointer-based replica that requires a fraction of the space used by the production FS. This snapshot can be implemented by either FS or by LVM. It uses the Copy on First Write (CoFW) principle to create snapshot. When a snapshot is created, a bitmap and blockmap are created in the metadata of the Snap FS. The bitmap is used to keep track of blocks that are changed on the production FS after the snap creation. The blockmap is used to indicate the exact address from which the data is to be read when the data is accessed from the Snap FS. Immediately after the creation of the FS snapshot, all reads from the snapshot are actually served by reading the production FS. In a CoFW mechanism, if a write I/O is issued to the production FS for the fi rst time after the creation of a snapshot, the I/O is held and the original data of production FS corresponding to that location is moved to the Snap FS. Then, the write is allowed to the production FS. The bitmap and blockmap are updated accordingly. Subsequent writes to the same location do not initiate the CoFW activity. To read from the Snap FS, the bitmap is consulted. If the bit is 0, then the read is directed to the production FS. If the bit is 1, then the block address is obtained from the blockmap, and the data is read from that address on the Snap FS. Read requests from the production FS work as normal.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="560" height="384" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-68.png" alt="" class="wp-image-417"/&gt;&lt;figcaption&gt;File system snapshot&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Storage Array-based local replication&lt;/strong&gt;&lt;br&gt;the array-operating environment performs the local replication process. The host resources, such as the CPU and memory, are not used in the replication process. Consequently, the host is not burdened by the replication operations. The replica can be accessed by an alternative host for other business operations.&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;&lt;strong&gt;Full-Volume Mirroring&lt;/strong&gt; &amp;#8211; the target is attached to the source and established as a mirror of the source. After all the data is copied and both the source and the target contain identical data, the target can be considered as a mirror of the source. After the synchronization is complete, the target can be detached from the source and made available for other business operations. The target becomes a point-in-time (PIT) copy of the source. After detachment, changes made to both the source and replica can be tracked at some predefined granularity. This enables incremental resynchronization (source to target) or incremental restore (target to source). The granularity of the data change can range from 512 byte blocks to 64 KB blocks or higher.&lt;br&gt;&lt;/li&gt;&lt;/ul&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="493" height="482" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-69.png" alt="" class="wp-image-418"/&gt;&lt;figcaption&gt;Full volume mirroring&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;&lt;strong&gt;Pointer-based, Full-Volume Replication&lt;/strong&gt; &amp;#8211; the target is immediately accessible by the BC host after the replication session is activated. Therefore, data synchronization and detachment of the target is not required to access it.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;&lt;strong&gt;Pointer-based, Virtual Replication&lt;/strong&gt; &amp;#8211; at the time of the replication session activation, the target contains pointers to the location of the data on the source. The target does not contain data at any time. Therefore, the target is known as a virtual replica. the target is immediately accessible after the replication session activation. A protection bitmap is created for all data blocks on the source device. Granularity of data blocks can range from 512 byte blocks to 64 KB blocks or greater.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Network-based local replication&lt;/strong&gt;: the replication occurs at the network layer between host and storage arrays. By offloading replication from servers and arrays, network-based replication can work across a large number of server platforms and storage arrays, making it ideal for highly heterogeneous environments.&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;&lt;strong&gt;Continuous Data Protection&lt;/strong&gt;: CDP provides the ability to restore data to any previous PIT. In CDP, data changes are continuously captured and stored in a separate location from the primary storage. With CDP, recovery from data corruption poses no problem because it allows going back to a PIT image prior to the data corruption incident. CDP uses a journal volume to store all data changes on the primary storage. The journal volume contains all the data that has changed from the time the replication session started. The amount of space that is configured for the journal determines how far back the recovery points can go. CDP appliance is an intelligent hardware platform that runs the CDP software and manages local and remote data replications. Write splitters intercept writes to the production volume from the host and split each write into two copies. Write splitting can be performed at the host, fabric, or storage array.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;&lt;strong&gt;CDP Local Replication Operation&lt;/strong&gt;: before the start of replication, the replica is synchronized with the source and then the replication process starts. After the replication starts, all the writes to the source are split into two copies. One of the copies is sent to the CDP appliance and the other to the production volume. When the CDP appliance receives a copy of a write, it is written to the journal volume along with its timestamp. As a next step, data from the journal volume is sent to the replica at predefi ned intervals.&lt;br&gt;&lt;/li&gt;&lt;/ul&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-70.png" alt="" class="wp-image-419" width="385" height="361"/&gt;&lt;figcaption&gt;Continuous Data Protection&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Tracking Changes to Source and Replica&lt;/strong&gt;&lt;br&gt;Changes can occur on the replica device if it is used for other business operations. To enable incremental resynchronization or restore operations, changes to both the source and replica devices after the PIT should be tracked.&lt;br&gt;This is typically done using bitmaps, where each bit represents a block of data. For example, if the block size is 32 KB, then a 1-GB device would require 32,768 bits (1 GB divided by 32 KB). The size of the bitmap would be 4 KB. If the data in any 32 KB block is changed, the corresponding bit in the bitmap is flagged. If the block size is reduced for tracking purposes, then the bitmap size increases correspondingly.&lt;br&gt;The bits in the source and target bitmaps are all set to 0 (zero) when the replica is created. Any changes to the source or replica are then fl agged by setting the appropriate bits to 1 in the bitmap. When resynchronization or restore is required, a logical OR operation between the source bitmap and the target bitmap is performed. The bitmap resulting from this operation references all blocks that have been modifi ed in either the source or replica.&lt;br&gt;This enables an optimized resynchronization or a restore operation because it eliminates the need to copy all the blocks between the source and the replica. The direction of data movement depends on whether a resynchronization or a restore operation is performed.&lt;br&gt;If resynchronization is required, changes to the replica are overwritten with the corresponding blocks from the source. If a restore is required, changes to the source are overwritten with the corresponding blocks from the replica.&lt;br&gt;If a restore is required, changes to the source are overwritten with the corresponding blocks from the replica.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="486" height="522" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-71.png" alt="" class="wp-image-420"/&gt;&lt;figcaption&gt;Tracking Changes&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="561" height="354" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-72.png" alt="" class="wp-image-421"/&gt;&lt;figcaption&gt;Comparison of local replication technologies&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Local Replication in a Virtualized Environment&lt;/strong&gt;&lt;br&gt;Typically, local replication of VMs is performed by the hypervisor at the compute level. However, it can also be performed at the storage level using array-based local replication, similar to the physical environment. In the array-based method, the LUN on which the VMs reside is replicated to another LUN in the same array. VM Snapshot captures the state and data of a running virtual machine at a specifi c point in time. The VM state includes VM files, such as BIOS, network confi guration, and its power state (powered-on, powered-off, or suspended). The VM data includes all the files that make up the VM, including virtual disks and memory. A VM Snapshot uses a separate delta file to record all the changes to the virtual disk since the snapshot session is activated. Snapshots are useful when a VM needs to be reverted to the previous state in the event of logical corruptions. Reverting a VM to a previous state causes all settings confi gured in the guest OS to be reverted to that PIT when that snapshot was created. There are some challenges associated with the VM Snapshot technology. It does not support data replication if a virtual machine accesses the data by using raw disks. Also, using the hypervisor to perform snapshots increases the load on the compute and impacts the compute performance.&lt;/p&gt;&#10;&lt;h4 class="wp-block-heading"&gt;Remote Replication&lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Synchronous remote replication &amp;#8211; writes must be committed to the source and remote replica (or target), prior to acknowledging &amp;#8220;write complete&amp;#8221; to the host. Additional writes on the source cannot occur until each preceding write has been completed and acknowledged. This ensures that data is identical on the source and replica at all times. Further, writes are transmitted to the remote site exactly in the order in which they are received at the source. Therefore, write ordering is maintained. If a source-site failure occurs, synchronous remote replication provides zero or near-zero RPO. However, application response time is increased with synchronous remote replication because writes must be committed on both the source and target before sending the “write complete” acknowledgment to the host. The degree of impact on response time depends primarily on the distance between sites, bandwidth, and quality of service (QOS) of the network connectivity infrastructure.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="507" height="334" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-73.png" alt="" class="wp-image-422"/&gt;&lt;figcaption&gt;Synchronous replication&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In asynchronous remote replication, a write is committed to the source and immediately acknowledged to the host. In this mode, data is buffered at the source and transmitted to the remote site later. Asynchronous replication eliminates the impact to the application’s response time because the writes are acknowledged immediately to the source host. This enables deployment of asynchronous replication over distances ranging from several hundred to several thousand kilometers between the primary and remote sites.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="462" height="324" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-74.png" alt="" class="wp-image-423"/&gt;&lt;figcaption&gt;Asynchronous replication&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Below are the bandwith requirement for both:&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="503" height="297" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-75.png" alt="" class="wp-image-424"/&gt;&lt;figcaption&gt;Bandwidth requirement for synchronous replication&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="538" height="269" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-76.png" alt="" class="wp-image-425"/&gt;&lt;figcaption&gt;Bandwidth requirement for asynchonous replication&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Asynchronous replication implementation can also take advantage of locality of reference (repeated writes to the same location). If the same location is written multiple times in the buffer prior to transmission to the remote site, only the final version of the data is transmitted. This feature conserves link bandwidth.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Remote Replication Technologies&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Host-Based Remote Replication&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;LVM-based remote replication&lt;/strong&gt;: performed and managed at the volume group level. Writes to the source volumes are transmitted to the remote host by the LVM. The LVM on the remote host receives the writes and commits them to the remote volume group.&lt;br&gt;LVM-based remote replication supports both synchronous and asynchronous modes of replication. LVM-based remote replication is independent of the storage arrays and therefore supports replication between heterogeneous storage arrays.&lt;br&gt;The replication process adds overhead on the host CPUs. CPU resources on the source host are shared between replication tasks and applications. Because the remote host is also involved in the replication process, it must be continuously up and available.&lt;br&gt;&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="563" height="351" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-77.png" alt="" class="wp-image-426"/&gt;&lt;figcaption&gt;LVM based remote replication&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Host-Based Log Shipping&lt;/strong&gt;&lt;br&gt;Database replication via log shipping is a host-based replication technology supported by most databases. Transactions to the source database are captured in logs, which are periodically transmitted by the source host to the remote host. The remote host receives the logs and applies them to the remote database.&lt;br&gt;RPO at the remote site is fi nite and depends on the size of the log and the frequency of log switching. Available network bandwidth, latency, rate of updates to the source database, and the frequency of log switching should be considered when determining the optimal size of the log file. Host-based log shipping requires low network bandwidth because it transmits only the log fi les at regular intervals.&lt;br&gt;&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="562" height="358" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-78.png" alt="" class="wp-image-427"/&gt;&lt;figcaption&gt;Host based log shipping&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Storage Array-Based Remote Replication&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Synchronous replication mode&lt;br&gt;To optimize the replication process and to minimize the impact on application response time, the write is placed on cache of the two arrays. The intelligent storage arrays destage these writes to the appropriate disks later.&lt;br&gt;If the network links fail, replication is suspended; however, production work can continue uninterrupted on the source storage array. The array operating environment keeps track of the writes that are not transmitted to the remote storage array. When the network links are restored, the accumulated data is transmitted to the remote storage array. During the time of network link outage, if there is a failure at the source site, some data will be lost, and the RPO at the target will not be zero.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="561" height="309" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-79.png" alt="" class="wp-image-428"/&gt;&lt;figcaption&gt;Array-based remote synchronous replication&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Asynchronous replication mode Fig 12-8&lt;br&gt; Data is buffered at the source and transmitted to the remote site later. The source and the target devices do not contain identical data at all times. The data on the target device is behind that of the source, so the RPO in this case is not zero. Asynchronous replication writes are placed in cache on the two arrays and are later destaged to the appropriate disks. Some implementations of asynchronous remote replication maintain write ordering. A timestamp and sequence number are attached to each write when it is received by the source. Writes are then transmitted to the remote array, where they are committed to the remote replica in the exact order in which they were buffered at the source. This implicitly guarantees consistency of data on the remote replicas.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="580" height="297" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-80.png" alt="" class="wp-image-429"/&gt;&lt;figcaption&gt;Array-based asynchronous replication&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Disk-buffered replication mode: a combination of local and remote technologies. A consistent PIT local replica of the source device is fi rst created. This is then replicated to a remote replica on the target array.&lt;br&gt; At the beginning of the cycle, the network links between the two arrays are suspended, and there is no transmission of data. While production application runs on the source device, a consistent PIT local replica of the source device is created. The network links are enabled, and data on the local replica in the source array transmits to its remote replica in the target array.&lt;br&gt;&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="566" height="337" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-81.png" alt="" class="wp-image-430"/&gt;&lt;figcaption&gt;Disk buffered remote replication&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Network-based Remote Replication&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;CDP remote replication&lt;br&gt; Fig 12-10&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Three site replication&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Cascade/Multihop: data fl ows from the source to the intermediate storage array, known as a bunker, in the fi rst hop, and then from a bunker to a storage array at a remote site in the second hop. Replication between the source and the remote sites can be performed in two ways: synchronous + asynchronous or synchronous + disk buffered. Replication between the source and bunker occurs synchronously, but replication between the bunker and the remote site can be achieved either as disk-buffered mode or asynchronous mode.&lt;br&gt;&lt;/li&gt;&lt;/ul&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="561" height="479" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-82.png" alt="" class="wp-image-431"/&gt;&lt;figcaption&gt;Three-site remote replication cascade/multihop&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Triangle/Multitarget: data at the source storage array is concurrently replicated to two different arrays at two different sites. The source-to-bunker site (target 1) replication is synchronous with a near-zero RPO. The source-to-remote site (target 2) replication is asynchronous with an RPO in the order of minutes. The distance between the source and the remote sites could be thousands of miles. The key benefit of three-site triangle/multitarget replication is the ability to failover to either of the two remote sites in the case of source-site failure, with disaster recovery (asynchronous) protection between the bunker and remote sites.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="561" height="474" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-83.png" alt="" class="wp-image-432"/&gt;&lt;figcaption&gt;Three-site replication triangle/multitarget&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Data migration solutions&lt;br&gt;Data mobility refers to moving data between heterogeneous storage arrays for cost, performance, or any other reason. It helps implement a tiered storage strategy. &lt;br&gt;Data migration refers to moving data from one storage array to other heterogeneous storage arrays for technology refresh, consolidation, or any other reason. The array performing the replication operations is called the control array.&lt;br&gt;Data migration solutions perform push and pull operations for data movement.&lt;br&gt;These terms are defined from the perspective of the control array. In the push operation, data is moved from the control array to the remote array.&lt;br&gt;The control device, therefore, acts like the source, while the remote device is the target.&lt;br&gt;In the pull operation, data is moved from the remote array to the control array.&lt;br&gt;The remote device is the source, and the control device is the target.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The push and pull operations can be either hot or cold. These terms apply to the control devices only. In a cold operation the control device is inaccessible to the host during replication. Cold operations guarantee data consistency because both the control and the remote devices are offl ine. In a hot operation the control device is online for host operations. During hot push and pull operations, changes can be made to the control device because the control array can keep track of all changes and thus ensure data integrity.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Remote replication and migration in a virtualized environment&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In hypervisor-to-hypervisor VM migration, the entire active state of a VM is moved from one hypervisor to another. This method involves copying the contents of virtual machine memory from the source hypervisor to the target and then transferring the control of the VM’s disk fi les to the target hypervisor. Because the virtual disks of the VMs are not migrated, this technique requires both source and target hypervisor access to the same storage.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-84.png" alt="" class="wp-image-433" width="353" height="267"/&gt;&lt;figcaption&gt;Hypervisor-to-hypervisor VM migration&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In array-to-array VM migration, virtual disks are moved from the source&lt;br&gt; array to the remote array. This approach enables the administrator to move VMs across dissimilar storage arrays. Array-to-array migration starts by copying the metadata about the VM from the source array to the target. The metadata essentially consists of configuration, swap, and log files. After the metadata is copied, the VM disk file is replicated to the new location.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" src="https://www.digihunch.com/wp-content/uploads/2019/11/image-85.png" alt="" class="wp-image-434" width="402" height="371"/&gt;&lt;figcaption&gt;Array-to-array VM migration&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;h4 class="wp-block-heading"&gt;Related Postings&lt;/h4&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;&lt;a href="https://www.digihunch.com/2019/03/storage-nitty-gritty-1-5/"&gt;Disk and RAID&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://www.digihunch.com/2019/05/storage-nitty-gritty-2-5/"&gt;SAN&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://www.digihunch.com/2019/07/storage-nitty-gritty-3-of-5-nas-and-object-storage/"&gt;NAS and Object Storage&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://www.digihunch.com/2019/10/storage-nitty-gritty-4-of-5-backup-and-archive-solutions/"&gt;Backup and Archive Solution&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2019/11/networking-basics-layer-1-and-layer-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Networking Basics 1 of 3 – Layer 1 through Layer 3&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2019/12/tcp-ip-basics-2-of-3-layer-4-and-common-technologies/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Networking basics 2 of 3 – Layer 4 and common network configurations&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Cassandra Architecture</title><link>https://www.digihunch.com/2018/03/cassandra-architecture-summary/</link><pubDate>Tue, 20 Mar 2018 18:18:00 -0400</pubDate><guid>https://www.digihunch.com/2018/03/cassandra-architecture-summary/</guid><description>&lt;p class="wp-block-paragraph"&gt;Excerpts from Cassandra The Definitive Guide&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Gossip and Failure Detection&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Cassandra uses a gossip protocol that allows each node to keep track of state information about the other nodes in the cluster. The gossiper runs every second on a timer.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Gossip protocols assumes a faulty network, are commonly commonly employed in very large, decentralized network systems, and are often used as an automatic mechanism for replication in distributed databases. When a server node is started, it registers itself with the gossiper to receive endpoint state information. Because Cassandra gossip is used for failure detection, the Gossiper class maintains a list of nodes that are alive and dead.&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Once per second, the gossiper will choose a random node in the cluster and initialize a gossip session with it. Each round of gossip requires three messages.&lt;/li&gt;&#10;&lt;li&gt;The gossip initiator sends its chosen friend a GossipDigestSynMessage.&lt;/li&gt;&#10;&lt;li&gt;When the friend receives this message, it returns a GossipDigestAckMessage.&lt;/li&gt;&#10;&lt;li&gt;When the initiator receives the ack message from the friend, it sends the friend a GossipDigestAck2Message to complete the round of gossip.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When the gossiper determines that another endpoint is dead, it “convicts” that endpoint by marking it as dead in its local list and logging that fact.&amp;nbsp;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Cassandra has robust support for failure detection, as specified by a popular algorithm for distributed computing called Phi Accrual Failure Detection. The traditional failure detection (based on whether heartbeat is received or not) is deemed naive.&amp;nbsp; Accrual failure detection determines suspicion level. Suspicion offers a more fluid and proactive indication of the weaker or stronger possibility of failure based on interpretation (sampling of heartbeats), as opposed to a simple binary assessment.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Accrual Failure Detectors output a value associated with each process (or node). This value is called Phi. The value is output in a manner that is designed from the ground up to be adaptive in the face of volatile network conditions, so it’s not a binary condition that simply checks whether a server is up or down.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Phi convict threshold in the configuration adjusts the sensitivity of the failure detector. Lower values increase the sensitivity and higher values decrease it, but not in a linear fashion.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Phi value refers to a level of suspicion that a server might be down. Applications such as Cassandra that employ an AFD can specify variable conditions for the Phi value they emit. Cassandra can generally detect a failed node in about 10 seconds using this mechanism.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Snitches&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A snitch determines relative host proximity for each node in a cluster, which is used to determine which nodes to read and write from. Snitches gather information about your network topology so that Cassandra can efficiently route requests. The snitch will figure out where nodes are in relation to other nodes. Snitch property can be adjusted (endpoint_snitch in cassandra.yaml)&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Rings and Token&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A cassandra cluster presents itself as a ring. Each node in the ring is assigned one or more ranges of data described by a token, which determines its position in the ring. A token is a 64-bit integer ID used to identify each partition.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A node claims ownership of the range of values less than or equal to each token and greater than the token of previous node. The node with lowest token owns the range less than or equal to its token and the range greater than the highest token, which is also known as the &amp;#8220;wrapping range&amp;#8221; In this way the token specifies a complete ring.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Data is assigned to nodes by using a hash function to calculate a token for the partition key. This partition key token is compared to the token values for the various nodes to identify the range, and therefore the node that owns the data.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="932" height="624" src="https://www.digihunch.com/wp-content/uploads/2020/03/image-1.png" alt="" class="wp-image-679"/&gt;&lt;/figure&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Virtual Nodes&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Instead of assigning a single token to a cassandra node, the token range is broken up into multiple smaller ranges, each represented by a vNode. By default a cassandra node will be assigned 256 vnodes (small range of tokens). Vnodes make it easier to maintain a cluster containing heterogeneous machines. Nodes in a cluster with more computing resources available can manage an increased number of vnode (num_tokens property in cassandra.yaml)&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Replication Strategies&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A node serves as a replica for different ranges of data. If one node goes down, other replicas can respond to queries for that range of data. Cassandra replicates data across nodes in a manner transparent to the user, and the replication factor is the number of nodes in your cluster that will receive copies (replicas) of the same data.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The first replica will always be the node that claims the range in which the token falls, but the remainder of the replicas are placed according to the replication strategy (sometimes also referred to as the replica placement strategy). Out of the box, Cassandra provides two primary implementations of this interface (extensions of the abstract class): SimpleStrategy and NetworkTopologyStrategy. They are specified at the time of keyspace creation.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The SimpleStrategy places replicas at consecutive nodes around the ring, starting with the node indicated by the partitioner. The NetworkTopologyStrategy allows you to specify a different replication factor for each data center. Within a data center, it allocates replicas to different racks in order to maximize availability.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Consistency Levels&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Cassandra provides tuneable consistency levels that allow you to make trade-offs with CAP at a fine-grained level. You specify a consistency level on each read or write query that indicates how much consistency you require.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For read queries, the consistency level specifies how many replica nodes must respond to a read request before returning the data. For write operations, the consistency level specifies how many replica nodes must respond for the write to be reported as successful to the client. Because Cassandra is eventually consistent, updates to other replica nodes may continue in the background.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Consistency levels include:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;response from an absolute number of nodes: ONE, TWO or THREE&lt;/li&gt;&#10;&lt;li&gt;response from the majority of the replica nodes (e.g. replication factor/2+1): QUORUM&lt;/li&gt;&#10;&lt;li&gt;response from all nodes: ALL&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ALL and QUORUM are considered strong consistency level. But in general we can consider a cluster of strong consistency if it meets this condition:&amp;nbsp;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;R + W &amp;gt; N&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;where&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;R is read consistency level&lt;/li&gt;&#10;&lt;li&gt;W is write consistency level&lt;/li&gt;&#10;&lt;li&gt;N is replication factor&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Queries and Coordinator Nodes&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A client may connect to any node in the cluster to initiate a read or write query. This node is known as the coordinator node. The coordinator identifies which nodes are replicas for the data that is being written or read and forwards the queries to them.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For a write, the coordinator node contacts all replicas, as determined by the consistency level and replication factor, and considers the write successful when a number of replicas commensurate with the consistency level acknowledge the write.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For a read, the coordinator contacts enough replicas to ensure the required consistency level is met, and returns the data to the client.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="944" height="584" src="https://www.digihunch.com/wp-content/uploads/2020/03/image-2.png" alt="" class="wp-image-680"/&gt;&lt;/figure&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Memtables, SSTables and Commit Logs&lt;/h3&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="932" height="464" src="https://www.digihunch.com/wp-content/uploads/2020/03/image.png" alt="" class="wp-image-678"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When you perform a write operation, it’s immediately written to a commit log so the write operation is considered successful. If you shut down the database or it crashes unexpectedly, the commit log can ensure that data is not lost. That’s because the next time you start the node, the commit log gets replayed. In fact, that’s the only time the commit log is read; clients never read from it.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After it’s written to the commit log, the value is written to a memory-resident data structure called the memtable. Each memtable contains data for a specific table. In early implementations of Cassandra, memtables were stored on the JVM heap, but&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;improvements starting with the 2.1 release have moved the majority of memtable data to native memory. (check out the memtable_allocation_type property: heap_buffers/offheap_buffers/offheap_objects). This makes Cassandra less susceptible to fluctuations in performance due to Java garbage collection.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When the number of objects stored in the memtable reaches a threshold, the contents of the memtable are flushed to disk in a file called an SSTable. A new memtable is then created. This flushing is a non-blocking operation; multiple memtables may exist for a single table, one current and the rest waiting to be flushed. They typically should not have to wait very long, as the node should flush them very quickly unless it is overloaded.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Each commit log maintains an internal bit flag to indicate whether it needs flushing. When a write operation is first received, it is written to the commit log and its bit flag is set to 1. There is only one bit flag per table, because only one commit log is ever being written to across the entire server. All writes to all tables will go into the same commit log, so the bit flag indicates whether a particular commit log contains anything that hasn’t been flushed for a particular table. Once the memtable has been properly flushed to disk, the corresponding commit log’s bit flag is set to 0, indicating that the commit log no longer has to maintain that data for durability purposes. Like regular logfiles, commit logs have a configurable rollover threshold, and once this file size threshold is reached, the log will roll over, carrying with it any extant dirty bit flags.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The SSTable is a concept borrowed from Google’s Bigtable. Once a memtable is flushed to disk as an SSTable, it is immutable and cannot be changed by the application. Despite the fact that SSTables are compacted, this compaction changes only their on-disk representation; it essentially performs the “merge” step of a mergesort into new files and removes the old files on success.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Cassandra supports the compression of SSTables in order to maximize use of the available storage. This compression is configurable per table. Each SSTable also has an associated Bloom filter, which is used as an additional performance enhancer.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;All writes are sequential, which is the primary reason that writes perform so well in Cassandra. No reads or seeks of any kind are required for writing a value to Cassandra because all writes are append operations. This makes one key limitation on performance&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;the speed of your disk. Compaction is intended to amortize the reorganization of data, but it uses sequential I/O to do so. So the performance benefit is gained by splitting; the write operation is just an immediate append, and then compaction helps to organize for better future read performance. If Cassandra naively inserted values where they ultimately belonged, writing clients would pay for seeks up front.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On reads, Cassandra will read both SSTables and memtables to find data values, as the memtable may contain values that have not yet been flushed to disk.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Caching&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Cassandra provides three forms of caching:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Key cache: stores a map of partition keys to row index entries, facilicating faster read access into SSTables stored on disk. The key cache is stored on the JVM heap, configurable through key_cache_size_in_mb and key_cache_save_period in cassandra.yaml;&lt;/li&gt;&#10;&lt;li&gt;Row cache: caches entire rows and can greatly speed up read access for frequently accessed rows, at the cost of more memory usage. The row cache is stored in off-heap memory, configurable through row_cache_size_in_mb and row_cache_save_period in cassandra.yaml;&lt;/li&gt;&#10;&lt;li&gt;counter cache: improve counter performance by reducing lock contention for the most frequently accessed counters.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;By default, key and counter caching are enabled, while row caching is disabled, as it requires more memory. Cassandra saves its caches to disk periodically in order to warm them up more quickly on a node restart.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Hinted Handoff&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Hinted handoff mechanism is introduced to cope with the situation where a write request is sent to Cassandra but the replica node where the write belongs is not available. In this situation, the coordinator will create a hint to hang onto this write. Once the coordinator detects via gossip that the intended node is back online, the coordinator node will &amp;#8220;hand off&amp;#8221; to the intended node the &amp;#8220;hint&amp;#8221; regarding the write. Cassandra holds a separate hint for each partition that is to be written.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This allows Cassandra to be always available for writes, and generally enables a cluster to sustain the same write load even when some of the nodes are down. It also reduces the time that a failed node will be inconsistent after it does come back online.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Hints do not count as writes for the purposes of consistency level, except for consistency level ANY. Hinted handoff can be configured through properties hinted_handoff_enabled, max_hint_window_in_ms and hinted_handoff_throttle_in_kb, max_hints_delivery_threads and batchlog_replay_throttle_in_kb in cassandra.yaml.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There is a practical problem with hinted handoffs (and guaranteed delivery approaches, for that matter): if a node is offline for some time, the hints can build up considerably on other nodes. Then, when the other nodes notice that the failed node has come back online, they tend to flood that node with requests, just at the moment it is most vulnerable (when it is struggling to come back into play after a failure). To address this problem, Cassandra limits the storage of hints to a configurable time window. It is also possible to disable hinted handoff entirely.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Although hinted handoff helps increase Cassandra’s availability, it does not fully replace the need for manual repair to ensure consistency.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Lightweight Transactions&amp;nbsp;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If a client is going to read (check existence) and then write a record (only if not existed already). We&amp;#8217;d like to guarantee linearizable consistency. In other words, no other client can come in between our read and write queries with their own modification. Lightweight transaction is a mechanism to support linearizable consistency based on Paxos. Paxos is a consensus algorithm that allows distributed peer nodes to agree on a proposal, without requiring a master to coordinate a transaction. It emerged as alternative to traditional two-phase commit.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Cassandra&amp;#8217;s lightweight transaction are limited to a single partition.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Tombstones&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When you execute a delete operation, the data is not immediately deleted. Instead, it&amp;#8217;s treated as an update operation that places a tombstone on the record. A tombstone is a deletion marker that is required to suppress older data in SSTables until compaction can run. The per-table setting gc_grace_period is the amount of time that the server will wait to garbage-collect tombstones. Once a tombstones ages over the grace period, they will be garbage-collected.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Bloom Filters&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Introduced to boost the performance of reads, Bloom filters are very fast, non-deterministic algorithms for testing whether an element is a member of a set. Being deterministic means false-positive is possible but not false-negative. In other words, if the filter indicates the given element exists in the set, cassandra needs to make sure by checking the set (disk); if the filter indicates the given element does not exist in the set, it certainly doesn&amp;#8217;t. Bloom filter is a special kind of cache, stored in memory to improve performance by reducing the need for disk access on key lookups. The accuracy can be increased (to reduce the chance of false positives) by increasing the filter size, at the cost of more memory. This is tunable per table using bloom_filter_fp_chance. Bloom filters are used in other distributed database and caching technologies as well such as Hadoop.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Compaction&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A compaction operation in Cassandra is performed in order to merge SSTables. During compaction, the data in SSTables is merged: the keys are merged, columns are combined, tombstones are discarded, and a new index is created. Compaction is the process of freeing up space by merging large accumulated data files.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This is roughly analogous to rebuilding a table in the relational world. But the primary difference in Cassandra is that it is intended as a transparent operation that is amortized across the life of the server.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another important function of compaction is to improve performance by reducing the number of required seeks. There are a bounded number of SSTables to inspect to find the column data for a given key. If a key is frequently mutated, it’s very likely that the mutations will all end up in flushed SSTables. Compacting them prevents the database from having to perform a seek to pull the data from each SSTable in order to locate the current value of each column requested in a read request.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When compaction is performed, there is a temporary spike in disk I/O and the size of data on disk while old SSTables are read and new SSTables are being written. Cassandra supports multiple algorithms for compaction via the strategy pattern. The compaction strategy is an option that is set for each table. Strategies include:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;SizeTieredCompactionStrategy (STCS) is the default compaction strategy and is recommended for write-intensive tables;&lt;/li&gt;&#10;&lt;li&gt;LeveledCompactionStrategy (LCS) is recommended for read-intensive tables;&lt;/li&gt;&#10;&lt;li&gt;DateTieredCompactionStrategy (DTCS), which is intended for time series or otherwise date-based data.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When compaction is performed, there is a temporary spike in disk I/O and the size of data on disk while old SSTables are read and new SSTables are being written.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Repairs&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Replica synchronization is supported via two different modes known as read repair and antri-entropy repair.&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Read repair: the synchronization of replicas as data is read. Cassandra reads data from multiple replicas in order to achieve the requested consistency leve, and detects if any replicas have out of date values. If an insufficient number of nodes have the latest value, a read repair is performed to update the out of date replicas, either immediately or in the background.&lt;/li&gt;&#10;&lt;li&gt;Anti-entropy repair (aka manual repair) is manually initiated operation performed on nodes as part of a regular maintenance process. This is initiated with nodetool repair command, which executes a major compaction. During a major compaction, the server initiates a TreeRequest/TreeResponse conversation to exchange Merkle trees with neighbouring nodes.  The Merkel tree is a hash representing the data in that table. If the trees from different nodes don&amp;#8217;t match, they have to be reconciled (repaired) to determine the latest data values they should all be set to. DynamoDB also use Merkle tress for anti-entropy, with a slightly different implementation.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Reference:&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2018/03/bash-tricks-continued/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Linux Admin Basics 2 of 3 – shell scripting&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2018/04/centos-remove-swap-safely/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;CentOS – Remove Swap safely&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>