<?xml version="1.0" encoding="UTF-8" ?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" version="2.0"><channel><title>Rekha Khandhadia | CrunchyData Blog</title>
<atom:link href="https://www.crunchydata.com/blog/author/rekha-khandhadia/rss.xml" rel="self" type="application/rss+xml" />
<link>https://www.crunchydata.com/blog/author/rekha-khandhadia</link>
<image><url>https://www.crunchydata.com/build/_assets/rekha-khandhadia.png-OVT7VYJI.webp</url>
<title>Rekha Khandhadia | CrunchyData Blog</title>
<link>https://www.crunchydata.com/blog/author/rekha-khandhadia</link>
<width>222</width>
<height>222</height></image>
<description>PostgreSQL experts from Crunchy Data share advice, performance tips, and guides on successfully running PostgreSQL and Kubernetes solutions</description>
<language>en-us</language>
<pubDate>Fri, 06 Jan 2023 10:00:00 EST</pubDate>
<dc:date>2023-01-06T15:00:00.000Z</dc:date>
<dc:language>en-us</dc:language>
<sy:updatePeriod>hourly</sy:updatePeriod>
<sy:updateFrequency>1</sy:updateFrequency>
<item><title><![CDATA[ Timezone Transformation Using Location Data & PostGIS ]]></title>
<link>https://www.crunchydata.com/blog/timezone-transformation-using-location-data-and-postgis</link>
<description><![CDATA[ Need to convert UTC timestamps to local time and store them? We have a fast way to do this with PostGIS by importing a shape file, doing a quick join, and creating a new local time field. ]]></description>
<content:encoded><![CDATA[ <p>Imagine your system captures event data from all over the world but the data all comes in UTC time giving no information about the local timing of an event. How can we quickly convert between the UTC timestamp and local time zone using GPS location? We can quickly solve this problem using PostgreSQL and PostGIS.<p>This example assumes you have a Postgres database running with PostGIS. If you’re new to PostGIS, see <a href=https://www.crunchydata.com/blog/postgis-for-newbies>PostGIS for Newbies</a>.<h3 id=steps-we-will-follow><a href=#steps-we-will-follow>Steps we will follow</a></h3><ol><li>Timezone Shape file Overview: For World Timezone shape file, I have been following a really nice project by <a href=https://twitter.com/evan_siroky>Evan Siroky</a>, <a href=https://github.com/evansiroky/timezone-boundary-builder/releases>Timezone Boundary Builder</a>. We’ll download the <a href=https://github.com/evansiroky/timezone-boundary-builder/releases/download/2022f/timezones-with-oceans.shapefile.zip>timezones-with-oceans.shapefile.zip</a> from this <a href=https://github.com/evansiroky/timezone-boundary-builder/releases/>location</a>.<li>Load Shape file: Using <a href=https://www.bostongis.com/pgsql2shp_shp2pgsql_quickguide.bqg>shp2pgsql</a>, convert shape file to sql to create timezones_with_ocean table.<li>PostgreSQL internal view pg_timezone_names: Understand pg_timezone_names view.<li>Events table and insert sample data: Create events table and insert sample data.<li>Transformation Query: Transform event UTC timestamp to event local timestamp.</ol><h3 id=overview-of-data-relationship><a href=#overview-of-data-relationship>Overview of data relationship</a></h3><p>Below is an overview of the data relationship and join conditions we will be using.<p><img alt=timzone_flow.png loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/d9ba7557-1e2f-4704-c18f-b8eb80233f00/public><h3 id=timezone-shape-file-overview><a href=#timezone-shape-file-overview>Timezone Shape file Overview</a></h3><p>A “shape file” commonly refers to a collection of files with .shp, .shx, .dbf, and other extensions on a common prefix name, which in our case is combined-shapefile-with-oceans.*. **combined-shapefile-with-oceans contains polygons with the boundaries of the world's timezones. With this data we can start our process.<h3 id=load-shape-file><a href=#load-shape-file>Load Shape file</a></h3><p>We will be using <a href=https://www.bostongis.com/pgsql2shp_shp2pgsql_quickguide.bqg>shp2pgsql</a> to generate sql file from shape file to create public.timezones_with_ocean and inserts data in a table. The table contains fields gid, tzid and geometry.<p>Export the Host, user, password variables<pre><code class=language-shell>export PGHOST=p.&#60pgcluster name>.db.postgresbridge.com
export PGUSER=&#60DBUser>
export PGPASSWORD=&#60dbPassword>
</code></pre><p>Create sql file from shape file<pre><code class=language-shell>shp2pgsql -s 4326  "combined-shapefile.shp" public.timezones_with_oceans   > timezone_shape.sql
</code></pre><p>Create public.timezones_with_ocean and load timestamp data<pre><code class=language-shell>psql -d timestamp -f timezone_shape.sql
</code></pre><p>Query a bit of sample data<pre><code class=language-pgsql>SELECT tzid, ST_AsText(geom), geom FROM public.timezone_with_oceans limit 10;
</code></pre><p>Visualize Sample data<p><img alt="sample data"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/6e615934-6207-4fa2-d8e2-d9cae8e7f100/public><p>Using PgAdmin highlight geom column and click on eye icon visualize the geometry on map showing below.<p><img alt="Geometry in pgAdmin"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/94c92843-0bea-4e8c-0e04-46be80ff8300/public><h3 id=postgresql-internal-view-pg_timezone_names><a href=#postgresql-internal-view-pg_timezone_names>PostgreSQL internal view pg_timezone_names</a></h3><p>PostgreSQL provides a view of <a href=https://www.postgresql.org/docs/current/view-pg-timezone-names.html>pg_timezone_names</a> with a list of time zone names recognized by SET TIMEZONE. By default, PostgreSQL also provides their associated abbreviations, UTC offsets, and daylight-savings status, which our clients need to know.<p>pg_timezone_names view columns description<table><thead><tr><th>Column<th>Type<th>Description<tbody><tr><td>name<td>text<td>Time zone name<tr><td>abbrev<td>text<td>Time zone abbreviation<tr><td>utc_offset<td>interval<td>Offset from UTC (positive means east of Greenwich)<tr><td>is_dst<td>bool<td>True if currently observing daylight savings</table><p>pg_timezone sample data<p><img alt="Sample data"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/98cd1140-dd25-481a-f5b0-6a00bf4bc200/public><h3 id=events-table-and-insert-sample-data><a href=#events-table-and-insert-sample-data>Events table and insert sample data</a></h3><p>Now that we have the timezone shape file loaded, we can create an event table, load sample transaction data, and apply a timestamp conversion transformation query.<pre><code class=language-pgsql>CREATE TABLE IF NOT EXISTS public.events
(
    event_id bigint NOT NULL,
    eventdatetime timestamp without time zone NOT NULL,
	event_type varchar(25) not null,
    latitude double precision NOT NULL,
    longitude double precision NOT NULL,
    CONSTRAINT events_pkey PRIMARY KEY (event_id)
);

INSERT INTO public.events(
	event_id, eventdatetime, event_type, latitude, longitude)
	VALUES (10086492,'2021-08-17 23:17:05','Walking',34.894089,-86.51148),
(50939,'2021-08-19 10:27:12','Hiking',34.894087,-86.511484),
(10086521,'2021-09-09 19:32:37','Swiming',34.642584,-86.761291),
(22465493,'2021-09-30 11:43:34','Swiming',33.611151,-86.799522),
(22465542,'2021-11-26 22:40:44.197','Swiming',34.64259,-86.761452),
(22465494,'2021-09-30 11:43:34','Hiking',33.611151,-86.799522),
(10087348,'2021-07-01 13:42:15','Swiming',25.956098,-97.535303),
(22466679,'2021-09-01 12:25:06','Hiking',25.956112,-97.535304),
(22466685,'2021-09-02 13:41:07','Swiming',25.956102,-97.535305),
(10088223,'2021-11-29 13:19:53','Hiking',25.956097,-97.535303),
(22246192,'2021-06-16 22:21:23','Walking',37.083726,-113.577984),
(9844188,'2021-06-23 20:18:43','Swiming',37.1067,-113.561401),
(22246294,'2021-06-25 21:50:06','Walking',37.118719,-113.598038),
(22246390,'2021-07-01 18:15:54','Hiking',37.109579,-113.562923),
(9844332,'2021-07-04 19:11:13','Walking',37.251538,-113.614708),
(9845242,'2021-11-04 13:25:40.425','Swiming',37.251542,-113.614699),
(84843,'2021-11-23 14:33:20','Swiming',37.251541,-113.614698),
(22247674,'2021-12-21 14:31:15','Swiming',37.251545,-113.614691),
(22246714,'2021-08-09 14:46:51','Swiming',37.109597,-113.562912),
(9845116,'2021-10-18 14:59:51','Swiming',37.082777,-113.554991);
</code></pre><p>Sample Event Data <img alt="event data"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/06b46d5f-67a7-4da4-1d86-e2fd38657300/public><h3 id=transformation-query><a href=#transformation-query>Transformation Query</a></h3><p>Now we can convert the UTC timestamp to the local time for an event. Using PostGIS function St_Intersects, we can find the timezone_with_oceans.geom polygon in which an event point lies. This gives the name of the timezone where the event occurred. To create our transformation query:<ul><li><p>First we create the location geometry using Longitude and Latitude from the events table.<li><p>Using PostGIS function <a href=https://postgis.net/docs/ST_Intersects.html>St_Intersects</a>, we will find common points between timezone_with_oceans.geom and an event’s location geometry giving us information on where the event occurred.<li><p>Join pg_timezone_names to timezone_with_oceans on name and tzid respectively, to retrieve abbrev, utc_offset, and is_dst fields from pg_timezone_names.<li><p>Using <a href=https://www.postgresql.org/docs/14/functions-datetime.html>PostgreSQL</a> AT TIME ZONE operator and pg_timezone_name, we convert UTC event timestamp to local event timestamp completing the process, e.g.<p><code>timestamp '2021-07-05 00:59:12' at time zone 'America/Denver' → 2021-07-04 18:59:12+00’</code></ul><p><strong>Transformation Query SQL:</strong><pre><code class=language-pgsql>SELECT event_id, latitude, longitude, abbrev,
       utc_offset,is_dst, eventdatetime,
       ((eventdatetime::timestamp WITH TIME ZONE AT TIME ZONE abbrev)::timestamp WITH TIME ZONE)
           AS eventdatetime_local
FROM public.events
JOIN timezone_with_oceans ON ST_Intersects(ST_Point(longitude, latitude, 4326) , geom)
JOIN pg_timezone_names ON tzid = name;
</code></pre><p><img alt="local timezone data"loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/b06aea16-17df-467a-2bc6-c206873b3c00/public><h2 id=closing-thoughts><a href=#closing-thoughts>Closing Thoughts</a></h2><p>PostgreSQL and PostGIS allow you to easily and dynamically solve timezone transformation. I hope this blog was helpful, and we at Crunchy Data wish you happy learning. ]]></content:encoded>
<category><![CDATA[ Spatial ]]></category>
<author><![CDATA[ Rekha.Khandhadia@crunchydata.com (Rekha Khandhadia) ]]></author>
<dc:creator><![CDATA[ Rekha Khandhadia ]]></dc:creator>
<guid isPermalink="false">ddce347e9ae9f8782958499dfa51bf40bd320dd0b1507d4e3452ac55a92e2531</guid>
<pubDate>Fri, 06 Jan 2023 10:00:00 EST</pubDate>
<dc:date>2023-01-06T15:00:00.000Z</dc:date>
<atom:updated>2023-01-06T15:00:00.000Z</atom:updated></item>
<item><title><![CDATA[ Accelerating Spatial Postgres: Varnish Cache for pg_tileserv using Kustomize ]]></title>
<link>https://www.crunchydata.com/blog/accelerating-spatial-postgres-varnish-cache-for-pg_tileserv-using-kustomize</link>
<description><![CDATA[ Rekha offers a step-by-step guide for deploying Varnish Cache for pg_tileserv using Kustomize and the Postgres Operator for Kubernetes on OpenShift. ]]></description>
<content:encoded><![CDATA[ <p>Recently I worked with one of my Crunchy Data PostgreSQL clients on implementing caching for pg_tileserv. <a href=https://github.com/CrunchyData/pg_tileserv>pg_tileserv</a> is a lightweight microservice to expose spatial data to the web and is a key service across many of our geospatial customer sites. pg_tileserv can generate a fair amount of database server load, depending on the complexity of the map data and number of end users, so putting a proxy cache such as Varnish in front of it is a best practice. Using Paul Ramsey's <a href=https://www.crunchydata.com/blog/production-postgis-vector-tiles-caching>Production PostGIS Vector Tiles Caching</a> as a starting point, I started to experiment with client standard toolset of Kustomize, OpenShift, and Podman. I found this was an easy and fast way to deploy Crunchy Data Kubernetes components and caching.<p>In this blog I would like to share with you a step-by-step guide on the process of deploying Varnish Caching for pg_tileserv using Kustomize in <a href=https://www.redhat.com/en/technologies/cloud-computing/openshift/container-platform>Redhat Openshift Container Platform</a> and <a href=https://www.crunchydata.com/products/crunchy-postgresql-for-kubernetes>PGO, the Postgres Operator from Crunchy Data</a> (PostgreSQL 13.x, PostGIS, pg_tileserv).<p><img alt=architecture loading=lazy src=https://imagedelivery.net/lPM0ntuwQfh8VQgJRu0mFg/a9c85c55-d338-40fc-b882-a6727f33de00/public><h3 id=prerequisites><a href=#prerequisites>Prerequisites</a></h3><ul><li>Access to Redhat Openshift Container Platform, with permission to deploy Kubernetes objects in the Namespace.<li>Access to Openshift CLI 4.9 and knowledge of <code>oc</code> and Kubernetes commands.<li>Access to deployed <dfn>PostgreSQL Operator for Kubernetes</dfn> (<abbr>PGO</abbr>) (PostgreSQL 13.x, PostGIS, pg_tileserv) environment able to view deployed components information.<li>pg_tileserv pod/deployment, services are deployed, and in our environment pg_tileserv service is named tileserv.</ul><h2 id=deployment-steps><a href=#deployment-steps>Deployment Steps</a></h2><h3 id=varnish-image><a href=#varnish-image>Varnish Image</a></h3><p>Find the right Varnish Cache image for your use case and upload it to your private client repository. Below are the steps we used in our client environment:<ol><li><code>podman login registry.redhat.io</code><li><code>podman pull registry.redhat.io/rhel8/varnish-6:1-191.1655143360</code><li><code>podman tag registry.redhat.io/rhel8/varnish-6:1-191.1655143360 client registry/foldername/varnish-6:1-191.1655143360</code><li><code>podman push client registry/foldername/varnish-6:1-191.1655143360</code></ol><h3 id=create-the-vcl-file><a href=#create-the-vcl-file>Create the VCL file</a></h3><p>Create the <code>default.vcl</code> file using a starting template from Varnish website. VCL stands for Varnish Configuration Language and is the domain-specific language used by Varnish to control request handling, routing, caching, and several other things.<p>Configure backend <code>.host</code> in default segment set to pg_tileserv service object, in our case it was tileserv.<ul><li><code>.host = tileserv</code><li><code>.port = 7800</code></ul><h4 id=sample-defaultvcl><a href=#sample-defaultvcl>Sample <code>default.vcl</code></a></h4><pre><code class=language-vcl>vcl 4.0;

backend default { .host = "tileserv"; .port = "7800"; }
</code></pre><h3 id=create-varnish_deploymentyaml><a href=#create-varnish_deploymentyaml>Create <code>varnish_deployment.yaml</code></a></h3><p>I will highlight few things that are important and provide our example below.<ul><li>Configure the image in the client private repository (<code>client private_registry/ varnish-6:1-191.1655143360</code>), use image to pull the secret if the private repository requires it.<li>Mount <code>tmpfs</code>, <code>mountPath /var/lib/varnish, Volumes emptyDir: {}</code>.<li>Volume mount the varnish <code>configMap</code>, to <code>/etc/varnish/default.vcl</code>.<li>Configure <code>containerPort</code> to <code>8080</code>.<li>Configure Container <code>resources</code>, if this is not configured it will take default configuration configured at the cluster level, e.g. cpu request: 1M.<li>Configure pod commands to execute <code>varnishd</code> and <code>varnishncsa</code>. <code>varnishcsa</code> command below adds varnish request info to the pod logs, this provides cache hit or miss information.</ul><pre><code class=language-yaml>    command: ["/bin/sh"]
    args:
      - -c
      - |
      varnishd -a :8080 -a :6081 -f /etc/varnish/default.vcl -s malloc,512M;
      varnishncsa -F '%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-agent}i" % Varnish:handling}x %{Varnish:hitmiss}x'
</code></pre><h4 id=sample-varnish-deploymentyaml><a href=#sample-varnish-deploymentyaml>Sample Varnish <code>deployment.yaml</code></a></h4><pre><code class=language-yaml>---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: varnish
  labels:
  app: varnish
spec:
  replicas: 1
  selector:
  matchLabels:
    app: varnish
  template:
  metadata:
    labels:
    app: varnish
  spec:
    containers:
    - name: varnish
    image: privateclientregistry/crunchydata/varnish-6:1-191.1655143360
    imagePullPolicy: Always
    command: ["/bin/sh"]
    args:
      - -c
      - |
      varnishd -a :8080 -a :6081 -f /etc/varnish/default.vcl -s malloc,512M;
      varnishncsa -F '%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-agent}i" % Varnish:handling}x %{Varnish:hitmiss}x'
    ports:
    - containerPort: 8080
    - containerPort: 6081
    resources:
      limits:
      cpu: 200m
      memory: 1024Mi
      requests:
      cpu: 100m
      memory: 512Mi
    volumeMounts:
    - mountPath: /etc/varnish/default.vcl
      name: varnish-config
      subPath: default.vcl
      defaultMode: 0777
    - mountPath: /var/lib/varnish
      name: tmpfs
      defaultMode: 0777
    volumes:
    - name: varnish-config
    configMap:
      name: varnish-config
      defaultMode: 0777
      items:
      - key: default.vcl
        path: default.vcl
    - name: tmpfs
    emptyDir: {}
    serviceAccount: pgo-default
    securityContext:
    runAsNonRoot: true
    fsGroupChangePolicy: "OnRootMismatch"
    terminationGracePeriodSeconds: 30
</code></pre><h3 id=create-varnish_serviceyaml-for-the-kubernetes-varnish-service-with-the-clusterip><a href=#create-varnish_serviceyaml-for-the-kubernetes-varnish-service-with-the-clusterip>Create <code>varnish_service.yaml</code> for the Kubernetes varnish service with the ClusterIP</a></h3><p>Below is our example:<pre><code class=language-yaml>---
kind: Service
apiVersion: v1
metadata:
  name: varnish-svc
  labels:
  name: varnish
spec:
  selector:
  app: varnish
  ports:
  - protocol: TCP
  port: 8080
  targetPort: 8080
  type: ClusterIP
</code></pre><h3 id=create-varnish_routeyaml-for-a-secure-varnish-route><a href=#create-varnish_routeyaml-for-a-secure-varnish-route>Create <code>varnish_route.yaml</code> for a secure Varnish route</a></h3><p>In our case we have created edge route with no certificates to enable https. Our recommendation is to follow your use case security guidelines and Openshift documentation. Below is an example:<pre><code class=language-yaml>kind: Route
apiVersion: route.openshift.io/v1
metadata:
  name: varnish
  labels:
  name: varnish
spec:
  tls:
  insecureEdgeTerminationPolicy: Redirect
  termination: edge
  to:
  kind: Service
  name: varnish-svc
  weight: 100
  port:
  targetPort: 8080
  wildcardPolicy: None
</code></pre><h3 id=create-kustomizeyaml><a href=#create-kustomizeyaml>Create <code>kustomize.yaml</code></a></h3><p>Here is an example manifest:<pre><code class=language-yaml>---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- varnish_deployment.yaml
- varnish_service.yaml
- varnish_route.yaml


configMapGenerator:
  - name: varnish-config
  files:
    - default.vcl
</code></pre><h3 id=deploy><a href=#deploy>Deploy</a></h3><p>The final step is to list files and deploy kustomize manifest using Openshift cli. Assumption here is that <code>kustomize.yaml</code> is in current directory.<pre><code class=language-shell>$ ls
  # default.vcl
  # kustomization.yaml
  # varnish_deployment.yaml
  # varnish_route.yaml
  # varnish_service.yaml
</code></pre><pre><code class=language-shell>oc apply -k .
</code></pre><h2 id=troubleshooting><a href=#troubleshooting>Troubleshooting</a></h2><h3 id=adjust-default-vcl-cookies><a href=#adjust-default-vcl-cookies>Adjust Default VCL Cookies</a></h3><p>After the initial deployment the Varnish pod log was showing most of the pages as a miss on the cache hit/miss. After analyzing, I found that set-cookie header was added to the request and response. Add <code>default.vcl</code> unset the cookies:<pre><code class=language-vcl>sub vcl_recv {
  if (req.url ~ "^[^?]*\.(pbf)(\?.*)?$") {
      unset req.http.Cookie;
    unset req.http.Authorization;
      # Only keep the following if VCL handling is complete
      return(hash);
  }
  }
  sub vcl_backend_response {
  if (bereq.url ~ "^[^?]*\.(pbf)(\?.*)?$") {
      unset beresp.http.Set-Cookie;
      set beresp.ttl = 1d;
  }
  }
</code></pre><p>Redeploy Varnish, assumption kustomize.yaml is in current directory.<pre><code class=language-bash># delete the deployment
oc delete -k .
# Deploy
oc apply -k .
</code></pre><h3 id=encountered-errors-and-resolution><a href=#encountered-errors-and-resolution>Encountered Errors and Resolution</a></h3><table><thead><tr><th>Errors<th>Resolution<tbody><tr><td>Permission denied cannot create &#60some path> vsm? and pod restarting with CrashLoopbackoff error.<td>Check <code>tmpfs</code> , set <code>mountPath</code> to the path mentioned in the error, also map the Volumes to <code>emptyDir: {}</code><tr><td>503 Backend fetch failed<td><code>default.vcl</code> backend <code>.host</code> and <code>.port</code> in default segment is not mapped correctly to pg_tileserv Openshift service<tr><td>Port 80 permission denied<td>Configure ContainerPort to 8080<tr><td>Pod logs show all Cache pages are MISS<td>In default.vcl Configure vcl_recv and vcl_backend_response functions to unset req.http.Cookie and beresp.http.Set-Cookie, after our deployment we found that pages were not caching as our webserver was setting cookies on every page request this meant that every page was a new page and the hash value calculated by the Varnish cache is going to be different.</table><h2 id=closing-thoughts><a href=#closing-thoughts>Closing Thoughts</a></h2><p>Our clients have a complex mapping data. pg_tileserv was generating a fair amount of database server load and rendering could be slow at times. Implementing Varnish caching improved map rendering performance by <strong>25% to 30%</strong> and equally reduced load on our database. I hope this blog was helpful, and we at Crunchy Data wish you happy learnings. ]]></content:encoded>
<category><![CDATA[ Kubernetes ]]></category>
<author><![CDATA[ Rekha.Khandhadia@crunchydata.com (Rekha Khandhadia) ]]></author>
<dc:creator><![CDATA[ Rekha Khandhadia ]]></dc:creator>
<guid isPermalink="false">ed55d0c5521d93841a13deeda1e1af79ebbf645479bc36e7391f46423d946b8e</guid>
<pubDate>Fri, 05 Aug 2022 11:00:00 EDT</pubDate>
<dc:date>2022-08-05T15:00:00.000Z</dc:date>
<atom:updated>2022-08-05T15:00:00.000Z</atom:updated></item></channel></rss>