<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Suchit Gupta on Medium]]></title>
        <description><![CDATA[Stories by Suchit Gupta on Medium]]></description>
        <link>https://medium.com/@suchit-g?source=rss-7e9cc4326c4d------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/2*61x0u4WS0o-Bg11DR0VeDA.png</url>
            <title>Stories by Suchit Gupta on Medium</title>
            <link>https://medium.com/@suchit-g?source=rss-7e9cc4326c4d------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 03:57:53 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@suchit-g/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Spark with Prometheus monitoring]]></title>
            <link>https://suchit-g.medium.com/spark-with-prometheus-monitoring-2f91e1208eed?source=rss-7e9cc4326c4d------2</link>
            <guid isPermaLink="false">https://medium.com/p/2f91e1208eed</guid>
            <category><![CDATA[spark]]></category>
            <category><![CDATA[prometheus]]></category>
            <category><![CDATA[grafana]]></category>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[Suchit Gupta]]></dc:creator>
            <pubDate>Thu, 25 Feb 2021 06:27:09 GMT</pubDate>
            <atom:updated>2021-02-25T06:27:09.158Z</atom:updated>
            <content:encoded><![CDATA[<p>Get spark jobs running in Kubernetes with Prometheus monitoring.</p><p>A step by step guide to monitor spark jobs running in K8 via Prometheus</p><blockquote>Set up the Prometheus server using <a href="https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack">kube-prometheus-stack</a> helm chart on Docker-On-Mac</blockquote><p>Please follow my post to set up the Prometheus server on Docker-On-Mac</p><p><a href="https://suchit-g.medium.com/prometheus-on-docker-on-mac-e853df1ae4f5">Prometheus on Docker-On-Mac</a></p><blockquote>Inspect the Prometheus resource created by the <a href="https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack">kube-prometheus-stack</a> helm chart</blockquote><p>A Prometheus, defines a desired state of Prometheus deployment.</p><p>The Prometheus created by <a href="https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack">kube-prometheus-stack</a> is looking for a service monitor objects defined with label: `<strong><em>release: prometheus</em>`</strong></p><p>Lets’ check the Prometheus resource. Execute below command:</p><pre>kubectl get prometheus -oyaml</pre><p>The yaml file looks like below and check for `<strong>serviceMonitorSelector`</strong></p><pre>apiVersion: v1<br>items:<br>- apiVersion: monitoring.coreos.com/v1<br>  kind: Prometheus<br>  metadata:<br>    annotations:<br>      meta.helm.sh/release-name: prometheus<br>      meta.helm.sh/release-namespace: default<br>    creationTimestamp: &quot;2021-02-24T20:12:37Z&quot;<br>    generation: 1<br>    labels:<br>      app: kube-prometheus-stack-prometheus<br>      app.kubernetes.io/managed-by: Helm<br>      chart: kube-prometheus-stack-13.10.0<br>      heritage: Helm<br>      release: prometheus<br>    managedFields:<br>    - apiVersion: monitoring.coreos.com/v1<br>      fieldsType: FieldsV1<br>      fieldsV1:<br>        f:metadata:<br>          f:annotations:<br>            .: {}<br>            f:meta.helm.sh/release-name: {}<br>            f:meta.helm.sh/release-namespace: {}<br>          f:labels:<br>            .: {}<br>            f:app: {}<br>            f:app.kubernetes.io/managed-by: {}<br>            f:chart: {}<br>            f:heritage: {}<br>            f:release: {}<br>        f:spec:<br>          .: {}<br>          f:alerting:<br>            .: {}<br>            f:alertmanagers: {}<br>          f:enableAdminAPI: {}<br>          f:externalUrl: {}<br>          f:image: {}<br>          f:listenLocal: {}<br>          f:logFormat: {}<br>          f:logLevel: {}<br>          f:paused: {}<br>          f:podMonitorNamespaceSelector: {}<br>          f:podMonitorSelector:<br>            .: {}<br>            f:matchLabels:<br>              .: {}<br>              f:release: {}<br>          f:portName: {}<br>          f:probeNamespaceSelector: {}<br>          f:probeSelector:<br>            .: {}<br>            f:matchLabels:<br>              .: {}<br>              f:release: {}<br>          f:replicas: {}<br>          f:retention: {}<br>          f:routePrefix: {}<br>          f:ruleNamespaceSelector: {}<br>          f:ruleSelector:<br>            .: {}<br>            f:matchLabels:<br>              .: {}<br>              f:app: {}<br>              f:release: {}<br>          f:securityContext:<br>            .: {}<br>            f:fsGroup: {}<br>            f:runAsGroup: {}<br>            f:runAsNonRoot: {}<br>            f:runAsUser: {}<br>          f:serviceAccountName: {}<br>          f:serviceMonitorNamespaceSelector: {}<br>          f:serviceMonitorSelector:<br>            .: {}<br>            f:matchLabels:<br>              .: {}<br>              f:release: {}<br>          f:shards: {}<br>          f:version: {}<br>      manager: Go-http-client<br>      operation: Update<br>      time: &quot;2021-02-24T20:12:37Z&quot;<br>    name: prometheus-kube-prometheus-prometheus<br>    namespace: default<br>    resourceVersion: &quot;1383&quot;<br>    selfLink: /apis/monitoring.coreos.com/v1/namespaces/default/prometheuses/prometheus-kube-prometheus-prometheus<br>    uid: 1315fc95-3465-45a0-bcbf-1ce924252925<br>  spec:<br>    alerting:<br>      alertmanagers:<br>      - apiVersion: v2<br>        name: prometheus-kube-prometheus-alertmanager<br>        namespace: default<br>        pathPrefix: /<br>        port: web<br>    enableAdminAPI: false<br>    externalUrl: <a href="http://prometheus-kube-prometheus-prometheus.default:9090/">http://prometheus-kube-prometheus-prometheus.default:9090</a><br>    image: quay.io/prometheus/prometheus:v2.24.0<br>    listenLocal: false<br>    logFormat: logfmt<br>    logLevel: info<br>    paused: false<br>    podMonitorNamespaceSelector: {}<br>    podMonitorSelector:<br>      matchLabels:<br>        release: prometheus<br>    portName: web<br>    probeNamespaceSelector: {}<br>    probeSelector:<br>      matchLabels:<br>        release: prometheus<br>    replicas: 1<br>    retention: 10d<br>    routePrefix: /<br>    ruleNamespaceSelector: {}<br>    ruleSelector:<br>      matchLabels:<br>        app: kube-prometheus-stack<br>        release: prometheus<br>    securityContext:<br>      fsGroup: 2000<br>      runAsGroup: 2000<br>      runAsNonRoot: true<br>      runAsUser: 1000<br>    serviceAccountName: prometheus-kube-prometheus-prometheus<br>    serviceMonitorNamespaceSelector: {}<br>    <strong>serviceMonitorSelector:<br>      matchLabels:<br>        release: prometheus</strong><br>    shards: 1<br>    version: v2.24.0<br>kind: List<br>metadata:<br>  resourceVersion: &quot;&quot;<br>  selfLink: &quot;&quot;</pre><blockquote>Create a Prometheus Service Monitor that is listening to a Service with a specific label</blockquote><p>ServiceMonitor, which declaratively specifies how groups of Kubernetes services should be monitored. The Operator automatically generates Prometheus scrape configuration based on the current state of the objects in the API server.</p><p>The Prometheus resource is looking for a service monitor resources with label `<strong>release: prometheus` </strong>so we need to ensure that this label is defined in our service monitor resource.</p><p>Let’s use the below yaml file to create a service monitoring resource that is listening to services defined with <strong>label `app: spark`</strong></p><pre>apiVersion: monitoring.coreos.com/v1<br>kind: ServiceMonitor<br>metadata:<br>  name: servicemonitor-spark<br>  labels:<br>    <strong>release: prometheus</strong><br>spec:<br>  selector:<br>    matchLabels:<br>      <strong>app: spark</strong><br>  endpoints:<br>    - port: metrics</pre><p>Save the file as servicemonitor-spark.yaml and save it inside a folder name prometheus and execute following:</p><pre>kubectl apply -f prometheus/servicemonitor-spark.yaml</pre><p>Check the object via</p><pre>kubectl get servicemonitor</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/639/1*XW23TjR_86l4jOu8hx-Ijw.png" /></figure><blockquote>Create a service resource listening to pods with a specific label</blockquote><p>The service monitor resource is looking for a service resources with label `<strong>app: spark` </strong>so we need to ensure that this label is defined in our service resource.</p><p>The service resource looks for the pods with label `<strong>app: spark`</strong> and the pods should be exposing their metrics at port 8090</p><p>Save the content below in a file say spark-service.yaml and save it inside a folder prometheus.</p><pre>apiVersion: v1<br>kind: Service<br>metadata:<br>  name: spark-service<br>  labels:<br>    <strong>app: spark</strong><br>spec:<br>  ports:<br>    - name: metrics<br>      port: 8090<br>      targetPort: 8090<br>      protocol: TCP<br>  selector:<br>    <strong>app: spark</strong></pre><p>And execute</p><pre>kubectl apply -f prometheus/spark-service.yaml</pre><blockquote>Run a sample Spark Job in K8</blockquote><p>Please go through my earlier post to set up the <a href="https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/master/docs/user-guide.md#monitoring">spark-k8-operator</a></p><p><a href="https://suchit-g.medium.com/run-spark-job-on-kubernetes-using-spark-on-k8s-operator-3d08d05e8198">Run Spark Job on Kubernetes using Spark-on-k8s-Operator</a></p><p>Let’s use the below yam file to deploy a sample Spark job.</p><p>The driver and executor defines the label `<strong>app: spark` </strong>which is required by the service resource.</p><p>Save the file with name spark-sample-prometheus.yaml, inside a folder name prometheus.</p><pre>apiVersion: &quot;sparkoperator.k8s.io/v1beta2&quot;<br>kind: ScheduledSparkApplication<br>metadata:<br>  name: spark-pi-scheduled<br>  namespace: default<br>spec:<br>  schedule: &quot;@every 1m&quot;<br>  concurrencyPolicy: Allow<br>  template:<br>    type: Scala<br>    mode: cluster<br>    image: &quot;<strong>gcr.io/spark-operator/spark:v3.0.0-gcs-prometheus</strong>&quot;<br>    imagePullPolicy: Always<br>    mainClass: org.apache.spark.examples.SparkPi<br>    mainApplicationFile: &quot;local:///opt/spark/examples/jars/spark-examples_2.12-3.0.0.jar&quot;<br>    sparkVersion: &quot;3.0.0&quot;<br>    restartPolicy:<br>      type: Never<br>    driver:<br>      cores: 1<br>      coreLimit: &quot;1200m&quot;<br>      memory: &quot;512m&quot;<br>      labels:<br>        version: 3.0.0<br>      serviceAccount: spark<br>    executor:<br>      cores: 1<br>      instances: 1<br>      memory: &quot;512m&quot;<br>      labels:<br>        version: 3.0.0<br>    <strong>monitoring:<br>      exposeDriverMetrics: true<br>      exposeExecutorMetrics: true<br>      prometheus:<br>        jmxExporterJar: &quot;/prometheus/jmx_prometheus_javaagent-0.11.0.jar&quot;<br>        port: 8090</strong></pre><p>Execute the below command to deploy the spark job:</p><pre>kubectl apply -f prometheus/spark-sample-prometheus.yaml</pre><p>The metrics would be accessible at port 8090</p><blockquote>View the metrics in prometheus server</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jlhBrOGSofX8GgSruCLM5w.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2f91e1208eed" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Prometheus on Docker-On-Mac]]></title>
            <link>https://suchit-g.medium.com/prometheus-on-docker-on-mac-e853df1ae4f5?source=rss-7e9cc4326c4d------2</link>
            <guid isPermaLink="false">https://medium.com/p/e853df1ae4f5</guid>
            <category><![CDATA[prometheus]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[grafana]]></category>
            <dc:creator><![CDATA[Suchit Gupta]]></dc:creator>
            <pubDate>Thu, 25 Feb 2021 04:19:31 GMT</pubDate>
            <atom:updated>2021-02-25T06:28:07.444Z</atom:updated>
            <content:encoded><![CDATA[<p>A step by step guide to set up a prometheus server on local K8 cluster</p><blockquote>Please go through my earlier post to set up the K8 and K8 Dashboard on Docker-On-Mac</blockquote><p><a href="https://suchit-g.medium.com/running-kubernetes-dashboard-on-docker-on-mac-42c8b692fbe9">Running Kubernetes Dashboard On Docker-On-Mac</a></p><blockquote>Install the helm chart</blockquote><p>We will use <a href="https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack">kube-prometheus-stack</a> helm chart for getting Prometheus server running on our Docker-On-Mac.</p><blockquote>Add the repo</blockquote><pre>helm repo add prometheus-community https://prometheus-community.github.io/helm-charts</pre><blockquote>Update the repo</blockquote><pre>helm repo update</pre><blockquote>Install the chart</blockquote><pre>helm install prometheus prometheus-community/kube-prometheus-stack</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bSVFYCHpDfq_EZZZMoid2Q.png" /><figcaption>Output</figcaption></figure><blockquote>Check the pods</blockquote><pre>kubectl get pods</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/920/1*6q5LZNXOcyPHDNbCKR64uA.png" /><figcaption>Output</figcaption></figure><blockquote>Check the services</blockquote><pre>kubectl get svc</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iTlicbgU-yNSZ2KmB6AF5w.png" /><figcaption>output</figcaption></figure><blockquote>Expose the Prometheus and Grafana servers from localhost</blockquote><p>Save the below manifest file in a file say prometheus-service.yaml and save it inside a folder name prometheus.</p><pre>apiVersion: v1<br>kind: Service<br>metadata:<br>  name: prometheus<br>spec:<br>  type: NodePort<br>  ports:<br>    - name: web<br>      nodePort: 30900<br>      port: 9090<br>      protocol: TCP<br>      targetPort: web<br>  selector:<br>    prometheus: prometheus-kube-prometheus-prometheus<br><br>---<br>apiVersion: v1<br>kind: Service<br>metadata:<br>  name: grafana<br>spec:<br>  type: NodePort<br>  ports:<br>    - name: service<br>      nodePort: 30901<br>      port: 80<br>      protocol: TCP<br>      targetPort: 3000<br>  selector:<br>    app.kubernetes.io/name: grafana</pre><p>Execute:</p><pre>kubectl apply -f prometheus/prometheus-service.yaml</pre><p>The prometheus server can now be accessed via <a href="http://localhost:30900/">http://localhost:30900/</a> and grafana at <a href="http://localhost:30901/?orgId=1">http://localhost:30901/?orgId=1</a></p><p>Grafana username/password is admin/prom-operator</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GTsJ_Dnl830mntpYHolxvw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Oo19ZeBF_kHfiHdTBrWZoA.png" /></figure><blockquote>Check out my another post on how to create a custom resource monitors</blockquote><p><a href="https://suchit-g.medium.com/spark-with-prometheus-monitoring-2f91e1208eed">https://suchit-g.medium.com/spark-with-prometheus-monitoring-2f91e1208eed</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e853df1ae4f5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Zeppelin in Kubernetes]]></title>
            <link>https://suchit-g.medium.com/zeppelin-in-kubernetes-15ddf987725e?source=rss-7e9cc4326c4d------2</link>
            <guid isPermaLink="false">https://medium.com/p/15ddf987725e</guid>
            <category><![CDATA[zeppelin]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[spark]]></category>
            <dc:creator><![CDATA[Suchit Gupta]]></dc:creator>
            <pubDate>Wed, 13 Jan 2021 02:04:17 GMT</pubDate>
            <atom:updated>2021-01-13T02:04:17.410Z</atom:updated>
            <content:encoded><![CDATA[<p>Step to step guide to deploy Zeppelin for multi users in Kubernetes</p><h3>Objective</h3><blockquote>Deploy Zeppelin:0.9.0 in K8</blockquote><blockquote>Authenticate Zeppelin UI via Apache Shiro JDBC realm</blockquote><h3>Repo</h3><p>Code being discussed can be found in <a href="https://github.com/suchitgupta01/zeppelin-k8.git">Github</a></p><h3>Steps to meet the objective</h3><blockquote><strong>Enable Kubernetes</strong></blockquote><p>Follow below link to run K8 on Mac.</p><p><a href="https://suchit-g.medium.com/running-kubernetes-dashboard-on-docker-on-mac-42c8b692fbe9">Running Kubernetes Dashboard On Docker-On-Mac</a></p><blockquote><strong>Build the docker image</strong></blockquote><pre>docker build . -t sg/zeppelin:latest</pre><h4>Dockerfile</h4><pre><em>#https://hub.docker.com/r/apache/zeppelin/dockerfile<br></em>ARG <em>ZEPPELIN_IMAGE</em>=apache/zeppelin:0.9.0<br>FROM ${<em>ZEPPELIN_IMAGE</em>}<br><br>WORKDIR ${<em>Z_HOME</em>}<br><br>USER root<br>ADD /shiro.ini ${<em>Z_HOME</em>}/conf/<br><em>#Required for local set up. Overriding so as to make imagePullPolicy as Never<br></em>ADD /100-interpreter-spec.yaml ${<em>Z_HOME</em>}/k8s/interpreter/<br><br><em>#Once ZEPPELIN-5192 is resolved, we can remove the below command. <br></em>ADD /credentials.json ${<em>Z_HOME</em>}/conf/<br><br><em>#Required for starting the Zeppelin server. Without this Shiro won&#39;t be able to set the env configuration.<br></em>ADD https://repo1.maven.org/maven2/org/postgresql/postgresql/42.2.5/postgresql-42.2.5.jar  ${<em>Z_HOME</em>}/lib<br>ENV <em>CLASSPATH</em>=${<em>Z_HOME</em>}/lib/postgresql-42.2.5.jar:${<em>CLASSPATH</em>}<br>RUN chmod 775 ${<em>Z_HOME</em>}/lib/postgresql-42.2.5.jar<br><br>USER 1000<br><br>ENTRYPOINT [ &quot;/usr/bin/tini&quot;, &quot;--&quot; ]<br>WORKDIR ${<em>Z_HOME</em>}<br>CMD [&quot;bin/zeppelin.sh&quot;]</pre><h4>Why we need a custom image?</h4><p>We need to provide Zeppelin UI Authentication via Apache Shiro. So need to include shiro.ini as part of the docker image.</p><h4>Why we need to override 100-interpreter-spec.yaml?</h4><p>Since the docker image sg/zeppelin:latest is not in docker hub, we need to override the file to make imagePolicy as never</p><p><strong>Why credentials.json is part of the docker image?</strong></p><p>This should get resolved with <em>ZEPPELIN-5192</em></p><blockquote><strong>Build the stack for Kubernetes</strong></blockquote><p>This will build the postgresql image with Zeppelin database with following tables:</p><ol><li>zeppelin.zeppelin_users</li><li>zeppelin.zeppelin_user_roles</li></ol><p>The tables will populated with user zeppelin_ui_user and encrypted password Welcome123</p><pre>docker-compose -f docker-compose-zeppelin.yaml build</pre><blockquote><strong>Push the stack to kubernetes</strong></blockquote><pre>docker stack deploy --compose-file docker-compose-zeppelin.yaml  --orchestrator kubernetes zeppelin</pre><p>You can connect the database with zeppelin_user/Password123</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/812/1*FS5XKMPKXToMymEJzWHWOg.png" /></figure><p>Pre populated user</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4wXTc3GFpX3blhgq7gbBPg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kqZQPG9xBIBEXPsteA_p6Q.png" /></figure><p>PostgreSQL deployed in K8</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KnOFHwrhtRfYys5-4uESjQ.png" /></figure><blockquote><strong>Apply the kubernetes manifest file</strong></blockquote><pre>kubectl apply -f zeppelin-server-manifest.yaml</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wHlt6ukJK9Thd9h0z8GmlA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*osJMgkxAQd1EmcVeJfgPJg.png" /></figure><h4>Why custom manifest file?</h4><ol><li>ZEPPELIN-5191: The manifest provided by Zeppelin is pointing to wrong docker image.</li><li>We need to use a custom docker image.</li></ol><blockquote><strong>Open the proxy terminal for Zeppelin UI</strong></blockquote><p>Open a terminal and execute</p><pre>kubectl port-forward deployment/zeppelin-server 8080:8080</pre><h3>Access the Zeppelin UI</h3><p>Go to <a href="http://localhost:8080/#/?ref=%2Fnotebook%2F2FX7FXFXD">http://localhost:8080/</a> and try zeppelin_ui_user/Welcome123</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*F94qjoSAgvp2TY19HHLmbA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SKNG5oNmNOjqi1u3eq4Mhw.png" /></figure><h3><strong>Creating a new user</strong></h3><blockquote>Generate Password</blockquote><p>Execute `java -jar shiro-tools-hasher-1.5.0-cli.jar -p` and enter the password that you want to hash.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/928/1*BfhXcls8CpbQOv0uI6hv3w.png" /></figure><blockquote>Insert the user in zeppelin.zeppelin_users table</blockquote><pre>INSERT INTO zeppelin.zeppelin_users (id, username, password, created_at, updated_at) VALUES (DEFAULT, &#39;test_ui&#39;, &#39;$shiro1$SHA-256$500000$8FQTUt1U4IHPv3CZgs2k3A==$n8ZSN4zc6PJ4fMs9el6i1sOVObCez0mryQ5tNjFZ2TA=&#39;, DEFAULT, DEFAULT);</pre><blockquote>Assign a role to the user.</blockquote><pre>INSERT INTO zeppelin.zeppelin_user_roles (id, username, role_name, created_at, updated_at) VALUES (DEFAULT, &#39;test_ui&#39;, &#39;admin&#39;, DEFAULT, DEFAULT);</pre><p>Hope this was useful :)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=15ddf987725e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Spark 3.0.0 history server with minIO]]></title>
            <link>https://suchit-g.medium.com/spark-3-0-0-history-server-with-minio-23be164a8070?source=rss-7e9cc4326c4d------2</link>
            <guid isPermaLink="false">https://medium.com/p/23be164a8070</guid>
            <category><![CDATA[minio]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[spark]]></category>
            <dc:creator><![CDATA[Suchit Gupta]]></dc:creator>
            <pubDate>Thu, 22 Oct 2020 06:13:57 GMT</pubDate>
            <atom:updated>2020-10-22T06:13:57.847Z</atom:updated>
            <content:encoded><![CDATA[<h3>A step by step guide on setting up Spark history server backed by minIO in Kubernetes</h3><h3>Set up:</h3><ol><li>Docker on Mac: 2.2.0</li><li>Kubernetes: 1.15.5</li><li>macOS: 10.15.6</li><li>Spark: 3.0.0 with hadoop 3.2</li><li><a href="https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/master/sparkctl/README.md">Spark Google operator</a></li><li><a href="https://min.io/">minIO</a>: latest image</li></ol><p>Step 1: Enable K8 and set up Dashboard on local.</p><p><a href="https://suchit-g.medium.com/running-kubernetes-dashboard-on-docker-on-mac-42c8b692fbe9">Running Kubernetes Dashboard On Docker-On-Mac</a></p><p>Step 2: Set up the spark google operator</p><p><a href="https://suchit-g.medium.com/run-spark-job-on-kubernetes-using-spark-on-k8s-operator-3d08d05e8198">Run Spark Job on Kubernetes using Spark-on-k8s-Operator</a></p><p>Step 3: Get the minIO in the docker-compose and push the stack</p><blockquote>docker-compose.yaml</blockquote><pre>version: &#39;3.0&#39;<br>services:<br>  minio:<br>    image: minio/minio<br>    container_name: minio<br>    ports:<br>      - 9000:9000<br>    environment:<br>      MINIO_ACCESS_KEY: AKIAIOSFODNN7EXAMPLE<br>      MINIO_SECRET_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY<br>    entrypoint: sh<br>    command: -c &#39;mkdir -p /export/spark-events &amp;&amp; mkdir -p /export/spark-events/logs &amp;&amp; /usr/bin/minio server /export&#39;<br>    healthcheck:<br>      test: [ &quot;CMD&quot;, &quot;curl&quot;, &quot;-f&quot;, &quot;http://localhost:9000/minio/health/live&quot; ]<br>      interval: 30s<br>      timeout: 20s<br>      retries: 3</pre><blockquote>Command to push the stack:</blockquote><pre>docker stack deploy --compose-file docker-compose-local-setup.yaml --namespace default --orchestrator kubernetes spark-minio </pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*k4WWrBGZKLBD8LtlAbf6Wg.png" /></figure><blockquote>After the push, UI should be accessible at <a href="http://localhost:9000/minio/">http://localhost:9000/minio/</a></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-qWWikIRH_zJw2cukXcf4g.png" /></figure><p>Step 4: Set up the spark history server</p><blockquote>Dockerfile:</blockquote><pre>ARG <em>SPARK_IMAGE</em>=gcr.io/spark-operator/spark:v3.0.0-hadoop3<br>FROM ${<em>SPARK_IMAGE</em>}<br><em># Switch to user root so we can add additional jars, packages and configuration files.<br></em>USER root<br><br>RUN apt-get -y update &amp;&amp; apt-get install -y coreutils<br><br>RUN rm -rf $<em>SPARK_HOME</em>/jars/httpclient-4.5.6.jar<br><br><em># Add dependency for hadoop-aws<br></em>ADD https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-core/1.11.883/aws-java-sdk-core-1.11.883.jar $<em>SPARK_HOME</em>/jars<br>ADD https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-dynamodb/1.11.883/aws-java-sdk-dynamodb-1.11.883.jar $<em>SPARK_HOME</em>/jars<br>ADD https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-s3/1.11.883/aws-java-sdk-s3-1.11.883.jar $<em>SPARK_HOME</em>/jars<br><br>ADD https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-aws/3.2.0/hadoop-aws-3.2.0.jar $<em>SPARK_HOME</em>/jars<br><br>ADD https://repo1.maven.org/maven2/org/apache/httpcomponents/httpclient/4.5.9/httpclient-4.5.9.jar $<em>SPARK_HOME</em>/jars<br><br><em>#Required for minIO service discovery by name.<br></em>ADD /spark-history/spark-defaults.conf /opt/spark/conf/<br><br>ENTRYPOINT [&quot;/opt/entrypoint.sh&quot;]</pre><blockquote>spark-defaults.conf</blockquote><pre>spark.hadoop.fs.s3a.path.style.access  true</pre><blockquote>Build the docker image</blockquote><pre>docker build -f spark-history/Dockerfile -t spark-history-server:latest .</pre><blockquote>Create secret for S3</blockquote><pre>kubectl create secret generic aws-secrets --from-file=secret/aws-access-key --from-file=secret/aws-secret-key</pre><blockquote>Install stable helm repo</blockquote><pre>helm repo add stable https://kubernetes-charts.storage.googleapis.com</pre><blockquote>install the helm chart</blockquote><pre>helm install stable/spark-history-server --namespace default -f examples/spark-history-server-manifest.yaml --generate-name</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UWoXP5FLFc9WvbHEdYqwzQ.png" /></figure><blockquote>spark-history-server-manifest.yaml</blockquote><pre><em><br>#s3 config<br></em>image:<br>  repository: spark-history-server<br>  tag: latest<br>  pullPolicy: Never<br>pvc:<br>  enablePVC: false<br>  existingClaimName: nfs-pvc<br>  eventsDir: “/”<br>nfs:<br>  enableExampleNFS: false<br>  pvName: nfs-pv<br>  pvcName: nfs-pvc<br>s3:<br>  enableS3: true<br>  enableIAM: false<br>  secret: aws-secrets<br>  accessKeyName: aws-access-key<br>  secretKeyName: aws-secret-key<br>  logDirectory: s3a://spark-events/logs/<br>  endpoint: http://minio-published:9000</pre><blockquote>The server should be accessible at <a href="http://localhost:18080/">http://localhost:18080/</a></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KaSK6T4hqTC9XHtj1A7SXA.png" /></figure><p>Note: Have seen some issues with caching so will recommend incognito mode.</p><p>Step 5: Submit the spark job</p><pre>kubectl apply -f examples/spark-pi.yaml</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lWOp41IYzktmBdUXZnr9bw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sk0t7DqcjZBtqklMVkQ2yA.png" /></figure><h3>The event is minIO</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hpnfI8Qj4-19XO7-1nC89A.png" /></figure><h3>The spark history server</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nIAPz3Rs4ob6-wHaKoqQrA.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=23be164a8070" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Run Spark 3.0.0 on Kubernetes]]></title>
            <link>https://suchit-g.medium.com/run-spark-3-0-0-on-kubernetes-af995cd6b4cd?source=rss-7e9cc4326c4d------2</link>
            <guid isPermaLink="false">https://medium.com/p/af995cd6b4cd</guid>
            <category><![CDATA[spark]]></category>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[Suchit Gupta]]></dc:creator>
            <pubDate>Fri, 16 Oct 2020 06:28:27 GMT</pubDate>
            <atom:updated>2020-10-16T06:38:08.079Z</atom:updated>
            <content:encoded><![CDATA[<p>A step by step guide on running your local spark scala code on Kubernetes using <a href="https://github.com/GoogleCloudPlatform/spark-on-k8s-operator">spark-on-k8s-operator</a>.</p><blockquote>In my earlier post, I package the application jar inside the docker image. For local development, it would be easier if we can run the code on K8 without rebuilding the image each time.</blockquote><blockquote>One way of doing that would be via sharing your local development path as a Volume. In this post, we will see how to do that</blockquote><p><a href="https://suchit-g.medium.com/package-spark-scala-code-and-deploy-it-on-kubernetes-using-spark-on-k8s-operator-3e295292b672">Package Spark Scala Code and Deploy it on Kubernetes using Spark-on-k8s-Operator</a></p><p><strong>Versions:</strong></p><ol><li>Spark: 3.0.0</li><li>Scala: 2.12</li><li>SBT: 1.3.13</li><li>Docker On Mac: 2.2.0.0</li><li>Kubernetes: v1.15.5</li><li>spark-on-k8s-operator: sparkoperator.k8s.io/v1beta2</li></ol><p><strong>Step 1:</strong> <em>Set up Kubernetes:</em></p><p>Please follow my earlier post to set up Kubernetes</p><p><a href="https://suchit-g.medium.com/run-spark-job-on-kubernetes-using-spark-on-k8s-operator-3d08d05e8198">Run Spark Job on Kubernetes using Spark-on-k8s-Operator</a></p><p><strong>Step 2:</strong> <em>The Project can be cloned from </em><a href="https://github.com/suchitgupta01/spark-scaka-k8-app.git"><em>Github</em></a><em>:</em></p><p>The project structure is as below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/610/1*7_oECep1lQGNpfE94qYjmA.png" /></figure><p>Step 3: <em>Set up for packaging:</em></p><ol><li>The plugins.sbt file inside the project folder is required for building a flat jar.</li></ol><pre><em>addSbtPlugin(&quot;com.eed3si9n&quot; % &quot;sbt-assembly&quot; % &quot;0.14.10&quot;)</em></pre><p>2. The build.properties file inside the project folder is required for providing the sbt version.</p><pre><em>sbt.version=1.3.13</em></pre><p>3. In build.sbt the task for merge strategy is required to resolve any conflicts during packaging</p><pre><em>assemblyMergeStrategy in assembly := {<br>  case &quot;META-INF/services/org.apache.spark.sql.sources.DataSourceRegister&quot; =&gt; MergeStrategy.concat<br>  case PathList(&quot;META-INF&quot;, xs@_*) =&gt; MergeStrategy.discard<br>  case &quot;application.conf&quot; =&gt; MergeStrategy.concat<br>  case x =&gt; MergeStrategy.first<br>}</em></pre><p><em>Step 4: Create yaml file to deploy/run the code on K8</em></p><p>The file is available in examples/spark-scala-k8-app.yaml</p><p>Important elements in the yaml file</p><ol><li>spec.image: “gcr.io/spark-operator/spark:v3.0.0”. It’s a static image</li><li>spec.imagePullPolicy:<strong> </strong>Never . Since we are pulling image from local.</li><li>spec.mainClass:<strong> </strong>com.AppK8Demo . Provide the path to your job class. AppK8Demo is a sample Job class from my Github repo.</li><li>Update the spec.volumes as per your work directory.</li></ol><pre>apiVersion: &quot;sparkoperator.k8s.io/v1beta2&quot;<br>kind: SparkApplication<br>metadata:<br>  name: spark-scala-file-k8-app<br>  namespace: default<br>spec:<br>  type: Scala<br>  mode: cluster<br>  image: &quot;gcr.io/spark-operator/spark:v3.0.0&quot;<br>  imagePullPolicy: Never<br>  mainClass: com.AppK8Demo<br>  mainApplicationFile: &quot;local:///opt/spark/work-dir/jar/spark-scala-k8-app-assembly-0.1.jar&quot;<br>  sparkVersion: &quot;3.0.0&quot;<br>  restartPolicy:<br>    type: Never<br>  volumes:<br>    - name: &quot;app-volume&quot;<br>      hostPath:<br>        path: &quot;/Users/sugupta/Desktop/codebase/personal/spark-scala-k8-app/target/scala-2.12&quot;<br>        type: Directory<br>    - name: &quot;file-volume&quot;<br>      hostPath:<br>        path: &quot;/Users/sugupta/Desktop/codebase/personal/spark-scala-k8-app/src/main/resources&quot;<br>        type: Directory<br>  driver:<br>    cores: 1<br>    coreLimit: &quot;1200m&quot;<br>    memory: &quot;512m&quot;<br>    labels:<br>      version: 3.0.0<br>    serviceAccount: spark<br>    volumeMounts:<br>      - name: &quot;app-volume&quot;<br>        mountPath: &quot;/opt/spark/work-dir/jar&quot;<br>      - name: &quot;file-volume&quot;<br>        mountPath: &quot;/opt/spark/work-dir/src/main/resources&quot;<br>  executor:<br>    cores: 1<br>    instances: 1<br>    memory: &quot;512m&quot;<br>    labels:<br>      version: 3.0.0<br>    volumeMounts:<br>      - name: &quot;app-volume&quot;<br>        mountPath: &quot;/opt/spark/work-dir/jar&quot;<br>      - name: &quot;file-volume&quot;<br>        mountPath: &quot;/opt/spark/work-dir/src/main/resources&quot;</pre><p>Run the job</p><pre><em>kubectl apply -f examples/spark-scala-k8-app.yaml</em></pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P7yOCQcbyA6Pl4CxTPdf0A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*axQCFNj2Yq8xn8v10H4v0w.png" /></figure><p>Hoped this worked for you too</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=af995cd6b4cd" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Use Spring Scheduler In Clustered Environment]]></title>
            <link>https://suchit-g.medium.com/use-spring-scheduler-in-clustered-environment-9de27251763f?source=rss-7e9cc4326c4d------2</link>
            <guid isPermaLink="false">https://medium.com/p/9de27251763f</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[spring]]></category>
            <dc:creator><![CDATA[Suchit Gupta]]></dc:creator>
            <pubDate>Fri, 26 Jun 2020 06:26:30 GMT</pubDate>
            <atom:updated>2020-06-26T06:26:30.516Z</atom:updated>
            <content:encoded><![CDATA[<p>At most once at the same time in Spring Scheduler.</p><p>Spring Scheduler is an easy and efficient way of scheduling the tasks. However, one caveat is in case of multiple instances it fires the task on every instance. This behavior is not ideal for use cases where the expected behavior is at most once at the same time.</p><blockquote>I have been using quartz for scheduling the jobs but recently came across <a href="https://github.com/lukas-krecan/ShedLock">Shedlock</a> which makes scheduling much easier.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kr4InobvUO_zL7XqbLG-VQ.png" /><figcaption>Spring-Scheduler with Shedlock</figcaption></figure><p>Spring-Scheduler triggers all the instances, Shedlock initiates a race condition and whichever instances are able to win takes control over the task and other instances silently exit the task.</p><blockquote>How to implement Shedlock?</blockquote><p>Step 1: Create a table that would be used by Shedlock to trigger the race condition.</p><pre><strong>CREATE TABLE </strong>IF <strong>NOT EXISTS </strong>shedlock(<strong>name <em>VARCHAR</em></strong>(64) <strong>NOT NULL</strong>, locked_at <strong><em>TIMESTAMP </em>NOT NULL</strong>, lock_until <strong><em>TIMESTAMP </em>NOT NULL</strong>, locked_by <strong><em>VARCHAR</em></strong>(255) <strong>NOT NULL</strong>, <strong>PRIMARY KEY </strong>(<strong>name</strong>));</pre><p>Step 2: Enable Schedlock and create SheldLock Lock Provider.</p><pre><strong>import </strong>javax.sql.DataSource;<br><strong>import </strong>net.javacrumbs.shedlock.core.LockProvider;<br><strong>import </strong>net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;<br><strong>import </strong>net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;<br><strong>import </strong>org.springframework.context.annotation.Bean;<br><strong>import </strong>org.springframework.context.annotation.Configuration;<br><strong>import </strong>org.springframework.scheduling.annotation.EnableScheduling;</pre><pre>@Configuration<br><strong>@EnableSchedulerLock(defaultLockAtMostFor = &quot;30s&quot;)</strong><br>@EnableScheduling<br><strong>public class </strong>ShedLockConfig {<br><br>  @Bean<br>  <strong>public LockProvider lockProvider(DataSource dataSource) {<br>    return new JdbcTemplateLockProvider(dataSource, &quot;shedlock&quot;);<br>  }</strong><br>}</pre><p>Step 3 Create a Spring Scheduler task with Shedlock.</p><pre><strong>import </strong>java.text.SimpleDateFormat;<br><strong>import </strong>java.util.Date;<br><strong>import </strong>net.javacrumbs.shedlock.spring.annotation.SchedulerLock;<br><strong>import </strong>org.slf4j.Logger;<br><strong>import </strong>org.slf4j.LoggerFactory;<br><strong>import </strong>org.springframework.scheduling.annotation.Scheduled;<br><strong>import </strong>org.springframework.stereotype.Component;<br><br>@Component<br><strong>public class </strong>ScheduledCurrentTimeTask {<br><br>  <strong>private static final </strong>Logger <strong><em>log </em></strong>= LoggerFactory.<em>getLogger</em>(ScheduledCurrentTimeTask.<strong>class</strong>);<br><br>  <strong>private static final </strong>SimpleDateFormat <strong><em>dateFormat </em></strong>= <strong>new </strong>SimpleDateFormat(<strong>&quot;HH:mm:ss&quot;</strong>);<br><br>  @Scheduled(fixedRate = 5000)<br>  @SchedulerLock(name = <strong>&quot;currentTimeTask&quot;</strong>, lockAtLeastFor = <strong>&quot;5S&quot;</strong>, lockAtMostFor = <strong>&quot;10M&quot;</strong>)<br>  <strong>public void </strong>currentTimeTask() {<br>    <strong><em>log</em></strong>.info(<strong>&quot;The time is now {}&quot;</strong>, <strong><em>dateFormat</em></strong>.format(<strong>new </strong>Date()));<br>  }<br><br>}</pre><p>The three steps and now Spring Scheduler is <em>At most once at the same time in Spring Scheduler.</em></p><blockquote>Sample Code</blockquote><p>Download the sample code from <a href="https://github.com/suchitgupta01/spring-scheduler-with-shedlock.git">Github</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9de27251763f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Spring Boot with Flyway Placeholders]]></title>
            <link>https://suchit-g.medium.com/spring-boot-with-flyway-placeholders-4c3e748b9f35?source=rss-7e9cc4326c4d------2</link>
            <guid isPermaLink="false">https://medium.com/p/4c3e748b9f35</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[migration]]></category>
            <category><![CDATA[spring]]></category>
            <category><![CDATA[flyway]]></category>
            <category><![CDATA[spring-boot]]></category>
            <dc:creator><![CDATA[Suchit Gupta]]></dc:creator>
            <pubDate>Tue, 02 Jun 2020 17:19:23 GMT</pubDate>
            <atom:updated>2020-06-02T17:20:06.410Z</atom:updated>
            <content:encoded><![CDATA[<p>Play around with multiple schemas in your flyway scripts by using <a href="https://flywaydb.org/getstarted/">Flyway</a> placeholders.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*ZRdf50T7OIk1dZNU.png" /></figure><p>Requirement</p><p>In each environment, we need to support two Database schemas. The schema should not be hardcoded and take the environment name as a suffix.</p><blockquote>application.yaml</blockquote><p>Our configuration:</p><pre><strong>spring</strong>:<br>  <strong>datasource</strong>:<br>    <strong>url</strong>: jdbc:postgresql://${database_name}/${service_database_name}<br>    <strong>username</strong>: ${database_user}<br>    <strong>password</strong>: ${database_password}<br>  <strong>jpa</strong>:<br>    <strong>properties</strong>:<br>      <strong>hibernate</strong>:<br>        <strong>dialect</strong>: org.hibernate.dialect.PostgreSQLDialect<br>        <strong>default_schema</strong>: test_${spring.profiles.active}<br>  <strong>flyway</strong>:<br>    <strong>schemas</strong>: test_${spring.profiles.active}<br>    <strong>out-of-order</strong>: true<br>    <strong>baseline-on-migrate</strong>: true<br>    <strong>placeholder-prefix</strong>: $$$<br>    <strong>placeholder-suffix</strong>: $$$<br>    <strong>placeholders</strong>:<br>        <strong>raw_schema</strong>: test_raw_${spring.profiles.active}</pre><p><strong>Default Flyway Schema</strong>: This is were flyway scripts will be executed.</p><pre><strong>flyway</strong>:<br>    <strong>schemas</strong>: ${database_schema_name}</pre><p><strong>Second Schema: </strong>This is the second schema which we will use in our flyway scripts.</p><p>The default prefix is ‘${‘, which is not suitable if you want to use spring properties. So I have updated the placeholder prefix and suffix as we need to use spring properties.</p><pre><strong>spring:<br>  flyway:<br>    placeholder-prefix</strong>: $$$<br>    <strong>placeholder-suffix</strong>: $$$<br>    <strong>placeholders</strong>:<br>        <strong>raw_schema</strong>: test_raw_${spring.profiles.active}</pre><blockquote>Flyway Script</blockquote><p>Now you can use the placeholder like this:</p><pre>create schema if not exists $$$<strong>raw_schema</strong>$$$;</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4c3e748b9f35" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[MySQL to PostgreSQL using Kafka Connect]]></title>
            <link>https://suchit-g.medium.com/mysql-to-postgresql-using-kafka-connect-ec074ddc9209?source=rss-7e9cc4326c4d------2</link>
            <guid isPermaLink="false">https://medium.com/p/ec074ddc9209</guid>
            <category><![CDATA[kafka-connect]]></category>
            <category><![CDATA[debezium]]></category>
            <category><![CDATA[mysql]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[kafka]]></category>
            <dc:creator><![CDATA[Suchit Gupta]]></dc:creator>
            <pubDate>Sun, 31 May 2020 22:54:21 GMT</pubDate>
            <atom:updated>2020-05-31T22:54:21.846Z</atom:updated>
            <content:encoded><![CDATA[<p>Our objective would be to quickly set up a data pipeline and move data from MySQL to PostgreSQL.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KWxi7ZRZjXaA4448znB-kw.png" /></figure><h4>Debezium</h4><p>We would use the Debezium MySQL connector for reading MySQL Database logs. Debezium would put the events in Kafka.</p><h4>Kafka-Connect</h4><p>We would use the JDBC Connector to read the events from the Kafka Topic and put in PostgreSQL.</p><h4>Code</h4><p>The code can be downloaded from <a href="https://github.com/suchitgupta01/mysql-to-postgresql.git">Github</a>.</p><h4>Build the pipeline</h4><p>We would use docker-compose to set up the following:</p><ol><li>MySQL: The source DB.</li><li>PostgreSQL: The destination DB</li><li>Kafka-Connect(Debezium and JDBC Connector): Debezium for reading MySQL Logs and JDBC Connector for pushing the change to PostgreSQL.</li><li>Kafka</li><li>Zookeeper</li></ol><blockquote>Build the image</blockquote><p>To build the Kafka-connect, we will add the JDBC connector and PostgreSQL JDBC Driver to our Kafka-Connect image.</p><pre>docker-compose build</pre><p>Dockerfile</p><pre><strong>FROM </strong>debezium<strong>/</strong>connect:1.0<br><strong>ENV <em>KAFKA_CONNECT_JDBC_DIR</em></strong>=$<strong><em>KAFKA_CONNECT_PLUGINS_DIR</em>/</strong>kafka-connect-jdbc<br><br><strong>ARG <em>POSTGRES_VERSION</em></strong>=42.2.8<br><strong>ARG <em>KAFKA_JDBC_VERSION</em></strong>=5.5.0<br><br><em># Deploy PostgreSQL JDBC Driver<br></em><strong>RUN </strong>cd <strong>/</strong>kafka<strong>/</strong>libs <strong>&amp;&amp; </strong>curl <strong>-</strong>sO https:<strong>//</strong>jdbc.postgresql.org<strong>/</strong>download<strong>/</strong>postgresql-$<strong><em>POSTGRES_VERSION</em></strong>.jar<br><br><em># Deploy Kafka Connect JDBC<br></em><strong>RUN </strong>mkdir $<strong><em>KAFKA_CONNECT_JDBC_DIR </em>&amp;&amp; </strong>cd $<strong><em>KAFKA_CONNECT_JDBC_DIR </em>&amp;&amp;</strong>\<br> curl <strong>-</strong>sO https:<strong>//</strong>packages.confluent.io<strong>/</strong>maven<strong>/</strong>io<strong>/</strong>confluent<strong>/</strong>kafka-connect-jdbc<strong>/</strong>$<strong><em>KAFKA_JDBC_VERSION</em>/</strong>kafka-connect-jdbc-$<strong><em>KAFKA_JDBC_VERSION</em></strong>.jar</pre><blockquote>Run the containers</blockquote><pre>docker-compose up -d</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IWcybbpLbPs9HUWxb3LL8A.png" /><figcaption>Our containers</figcaption></figure><blockquote>MySQL</blockquote><p>MySQL comes with a database inventory.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/414/1*dZ--tYzfj_Vacq5MQZLGDg.png" /></figure><blockquote>Debezium Connector</blockquote><p>Execute the following curl command to set up the Debezium connector for reading the logs from MySQL.</p><p>This will create topics following topics:</p><ol><li>orders</li><li>addresses</li><li>customers</li><li>products</li><li>products_on_hand</li></ol><pre>curl -X POST \<br>  <a href="http://localhost:8083/connectors/">http://localhost:8083/connectors/</a> \<br>  -H &#39;Cache-Control: no-cache&#39; \<br>  -H &#39;Content-Type: application/json&#39; \<br>  -d &#39;{<br>    &quot;name&quot;: &quot;inventory-source&quot;,<br>    &quot;config&quot;: {<br>        &quot;connector.class&quot;: &quot;io.debezium.connector.mysql.MySqlConnector&quot;,<br>        &quot;tasks.max&quot;: &quot;1&quot;,<br>        &quot;database.hostname&quot;: &quot;mysql&quot;,<br>        &quot;database.port&quot;: &quot;3306&quot;,<br>        &quot;database.user&quot;: &quot;debezium&quot;,<br>        &quot;database.password&quot;: &quot;dbz&quot;,<br>        &quot;database.server.id&quot;: &quot;184054&quot;,<br>        &quot;database.server.name&quot;: &quot;dbserver1&quot;,<br>        &quot;database.whitelist&quot;: &quot;inventory&quot;,<br>        &quot;database.history.kafka.bootstrap.servers&quot;: &quot;kafka:9092&quot;,<br>        &quot;database.history.kafka.topic&quot;: &quot;schema-changes.inventory&quot;,<br>        &quot;decimal.handling.mode&quot;: &quot;double&quot;,<br>        &quot;snapshot.mode&quot;: &quot;when_needed&quot;,<br>        &quot;transforms&quot;: &quot;route&quot;,                                                    <br>        &quot;transforms.route.type&quot;: &quot;org.apache.kafka.connect.transforms.RegexRouter&quot;, <br>        &quot;transforms.route.regex&quot;: &quot;([^.]+)\\.([^.]+)\\.([^.]+)&quot;,                     <br>        &quot;transforms.route.replacement&quot;: &quot;$3&quot;   <br>    }<br>}&#39;</pre><blockquote>JDBC connector</blockquote><p>Execute the following curl command to set up the JDBC connector for writing the events from “orders” MySQL Table to PostgreSQL.</p><p>We have kept “auto.create”: “true” so that it automatically creates tables in PostgreSQL.</p><pre>curl -X POST \<br>  <a href="http://localhost:8083/connectors/">http://localhost:8083/connectors/</a> \<br>  -H &#39;Cache-Control: no-cache&#39; \<br>  -H &#39;Content-Type: application/json&#39; \<br>  -d &#39;{<br>    &quot;name&quot;: &quot;rtw-connector-sink&quot;,<br>    &quot;config&quot;: {<br>        &quot;connector.class&quot;: &quot;io.confluent.connect.jdbc.JdbcSinkConnector&quot;,<br>        &quot;tasks.max&quot;: &quot;1&quot;,<br>        &quot;topics&quot;: &quot;orders&quot;,<br>        &quot;connection.url&quot;: &quot;jdbc:postgresql://postgresql:5432/test_db&quot;,<br>        &quot;connection.user&quot;: &quot;test_user&quot;,<br>        &quot;connection.password&quot;: &quot;Welcome123&quot;,<br>        &quot;transforms&quot;: &quot;unwrap&quot;,                                                <br>        &quot;transforms.unwrap.type&quot;: &quot;io.debezium.transforms.UnwrapFromEnvelope&quot;,   <br>        &quot;auto.create&quot;: &quot;true&quot;,                                                   <br>        &quot;insert.mode&quot;: &quot;insert&quot;,                                                 <br>        &quot;pk.fields&quot;: &quot;order_number&quot;,                                                     <br>        &quot;pk.mode&quot;: &quot;none&quot;                  <br>     <br>    }<br>}&#39;</pre><blockquote>PostgreSQL</blockquote><p>Once the connector is set up it will automatically create a table with name orders.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/676/1*AjqVbCvTSZFcmuyNXlUYOw.png" /></figure><blockquote>Play Around</blockquote><p>Make changes in MySQL orders table and see them getting reflected in PostgreSQL orders table.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ec074ddc9209" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Integration Testing in Spark Structured Streaming]]></title>
            <link>https://suchit-g.medium.com/integration-testing-in-spark-structured-streaming-1123634ca02f?source=rss-7e9cc4326c4d------2</link>
            <guid isPermaLink="false">https://medium.com/p/1123634ca02f</guid>
            <category><![CDATA[integration-testing]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[spark-streaming]]></category>
            <category><![CDATA[spark]]></category>
            <dc:creator><![CDATA[Suchit Gupta]]></dc:creator>
            <pubDate>Tue, 26 May 2020 07:33:46 GMT</pubDate>
            <atom:updated>2020-05-26T07:33:46.392Z</atom:updated>
            <content:encoded><![CDATA[<p>A guide for writing integration test for a Spark Structured Streaming Application</p><h4>Bird’s eye view of the architecture:</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fUROuImSjcxcunUkiqLUvA.png" /><figcaption>The basic Architecture</figcaption></figure><blockquote>The focus of the post will be entirely on Integration Test</blockquote><h4>For Set Up And Running the Spark Application, please read my earlier post.</h4><p><a href="https://medium.com/@suchit.g/spark-streaming-with-kafka-connect-debezium-connector-ab9163808667">Spark Streaming with Kafka-Connect Debezium Connector</a></p><h4>The Code</h4><ol><li>Spark: 2.4.5</li><li>Scala 2.12.8</li><li>Test Containers: 0.37.0</li><li>Scala-Test: 3.0.1</li></ol><h4>Project Structure</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/734/1*0QGN-cZG31NE9ar-pVyosw.png" /></figure><h4>The Spark Application</h4><p>The Debezium Connector reads MySQL DB changes from the Binlog file and pushes the changes as Debezium events to a Kafka Topic.</p><p>The Spark Application Reads the data from the Kafka Topic and pushes the change to PostgreSQL.</p><pre><strong>package </strong>com.sg.job.streaming<br><br><strong>import </strong>com.sg.wrapper.SparkSessionWrapper<br><strong>import </strong>org.apache.spark.sql.execution.datasources.jdbc.JDBCOptions<br><strong>import </strong>org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}<br><br><strong>object </strong>StreamingJob <strong>extends </strong>App <strong>with </strong>SparkSessionWrapper {<br><br>  <strong>val </strong><em>currentDirectory </em>= <strong>new </strong>java.io.File(<strong>&quot;.&quot;</strong>).getCanonicalPath<br>  <strong>val </strong><em>kafkaReaderConfig </em>= KafkaReaderConfig(<strong>&quot;localhost:29092&quot;</strong>, <strong>&quot;dbserver1.inventory.orders&quot;</strong>)<br>  <strong>val </strong><em>jdbcConfig </em>= <em>JDBCConfig</em>(url = <strong>&quot;jdbc:postgresql://localhost:5432/test&quot;</strong>)<br>  <strong>new </strong>StreamingJobExecutor(<em>spark</em>, <em>kafkaReaderConfig</em>, <em>currentDirectory </em>+ <strong>&quot;/checkpoint/job&quot;</strong>, <em>jdbcConfig</em>).execute()<br>}<br><br><strong>case class </strong>JDBCConfig(url: String, user: String = <strong>&quot;test&quot;</strong>, password: String = <strong>&quot;Test123&quot;</strong>, tableName: String = <strong>&quot;orders_it&quot;</strong>)<br><br><strong>case class </strong>KafkaReaderConfig(kafkaBootstrapServers: String, topics: String, startingOffsets: String = <strong>&quot;latest&quot;</strong>)<br><br><strong>case class </strong>StreamingJobConfig(checkpointLocation: String, kafkaReaderConfig: KafkaReaderConfig)<br><br><strong>class </strong>StreamingJobExecutor(spark: SparkSession, kafkaReaderConfig: KafkaReaderConfig, checkpointLocation: String, jdbcConfig: JDBCConfig) {<br><br>  <strong>def </strong>execute(): Unit = {<br>    <em>// read data from kafka and parse them<br>    </em><strong>val </strong>transformDF = read()<br>      .selectExpr(<strong>&quot;CAST(key AS STRING) as key&quot;</strong>, <strong>&quot;CAST(value AS STRING) as value&quot;</strong>, <strong>&quot;topic&quot;</strong>)<br><br>    transformDF<br>      .writeStream<br>      .option(<strong>&quot;checkpointLocation&quot;</strong>, checkpointLocation)<br>      .foreachBatch { (batchDF: DataFrame, _: Long) =&gt; {<br>        batchDF.write<br>          .format(<strong>&quot;jdbc&quot;</strong>)<br>          .option(<strong>&quot;url&quot;</strong>, jdbcConfig.url)<br>          .option(<strong>&quot;user&quot;</strong>, jdbcConfig.user)<br>          .option(<strong>&quot;password&quot;</strong>, jdbcConfig.password)<br>          .option(<strong>&quot;driver&quot;</strong>, <strong>&quot;org.postgresql.Driver&quot;</strong>)<br>          .option(JDBCOptions.JDBC_TABLE_NAME, jdbcConfig.tableName)<br>          .option(<strong>&quot;stringtype&quot;</strong>, <strong>&quot;unspecified&quot;</strong>)<br>          .mode(SaveMode.Append)<br>          .save()<br>      }<br>      }.start()<br>      .awaitTermination()<br>  }<br><br>  <strong>def </strong>read(): DataFrame = {<br>    spark<br>      .readStream<br>      .format(<strong>&quot;kafka&quot;</strong>)<br>      .option(<strong>&quot;kafka.bootstrap.servers&quot;</strong>, kafkaReaderConfig.kafkaBootstrapServers)<br>      .option(<strong>&quot;subscribe&quot;</strong>, kafkaReaderConfig.topics)<br>      .option(<strong>&quot;startingOffsets&quot;</strong>, kafkaReaderConfig.startingOffsets)<br>      .load()<br>  }<br>}</pre><h4>Integration Test For the Spark Application</h4><p>For writing the integration test for the Spark Application discussed above we need the following:</p><ol><li>MySQL DB: With Binary Logging</li><li>Kafka-Connect with Debezium connector</li><li>Kafka</li><li>Zookeeper</li><li>PostgreSQL</li></ol><p>I will use <a href="https://github.com/testcontainers/testcontainers-scala">Docker Test Containers</a> to build the required infra.</p><blockquote>build.sbt</blockquote><pre><em>name </em>:= <strong>&quot;spark-streaming-with-debezium&quot;<br><br></strong><em>version </em>:= <strong>&quot;0.1&quot;<br></strong><em>scalaVersion </em>:= <strong>&quot;2.12.8&quot;<br><br>val </strong>sparkVersion = <strong>&quot;2.4.5&quot;<br>val </strong>testcontainersScalaVersion = <strong>&quot;0.37.0&quot;<br><br></strong><em>resolvers </em>+= <strong>&quot;confluent&quot; </strong>at <strong>&quot;http://packages.confluent.io/maven/&quot;<br></strong><em>resolvers </em>+= <em>Resolver</em>.jcenterRepo<br><em>resolvers </em>+= <strong>&quot;Spark Packages Repo&quot; </strong>at <strong>&quot;http://dl.bintray.com/spark-packages/maven&quot;<br><br><br></strong><em>assemblyMergeStrategy </em>in <em>assembly </em>:= {<br>  <strong>case &quot;META-INF/services/org.apache.spark.sql.sources.DataSourceRegister&quot; </strong>=&gt; <em>MergeStrategy</em>.<em>concat<br>  </em><strong>case </strong><em>PathList</em>(<strong>&quot;META-INF&quot;</strong>, xs@_*) =&gt; <em>MergeStrategy</em>.<em>discard<br>  </em><strong>case &quot;application.conf&quot; </strong>=&gt; <em>MergeStrategy</em>.<em>concat<br>  </em><strong>case </strong>x =&gt; <em>MergeStrategy</em>.<em>first<br></em>}<br><br><em>coverageEnabled</em>.in(ThisBuild, <em>IntegrationTest</em>, <em>test</em>) := <strong>true<br><br></strong><em>//skipping test cases during package<br>test </em>in <em>assembly </em>:= {}<br><br><strong>lazy val </strong>server = (project in <em>file</em>(<strong>&quot;.&quot;</strong>))<br>  .configs(<em>IntegrationTest</em>)<br>  .settings(Defaults.<em>itSettings</em>)<br><br><em>libraryDependencies </em>++= <em>Seq</em>(<br>  <strong>&quot;org.apache.spark&quot; </strong>%% <strong>&quot;spark-sql&quot; </strong>% sparkVersion,<br>  <strong>&quot;org.apache.spark&quot; </strong>%% <strong>&quot;spark-sql-kafka-0-10&quot; </strong>% sparkVersion,<br>  <strong>&quot;org.apache.kafka&quot; </strong>% <strong>&quot;kafka-clients&quot; </strong>% <strong>&quot;2.2.1&quot;</strong>,<br>  <strong>&quot;org.postgresql&quot; </strong>% <strong>&quot;postgresql&quot; </strong>% <strong>&quot;42.2.5&quot;</strong>,<br>  <strong>&quot;com.typesafe&quot; </strong>% <strong>&quot;config&quot; </strong>% <strong>&quot;1.4.0&quot;</strong>,<br>  <strong>&quot;org.slf4j&quot; </strong>% <strong>&quot;slf4j-simple&quot; </strong>% <strong>&quot;1.7.30&quot;</strong>,<br>  <strong>&quot;org.scalatest&quot; </strong>% <strong>&quot;scalatest_2.12&quot; </strong>% <strong>&quot;3.0.1&quot; </strong>% <strong>&quot;it,test&quot;</strong>,<br>  <strong>&quot;com.dimafeng&quot; </strong>%% <strong>&quot;testcontainers-scala-scalatest&quot; </strong>% testcontainersScalaVersion % <em>IntegrationTest</em>,<br>  <strong>&quot;com.dimafeng&quot; </strong>%% <strong>&quot;testcontainers-scala-postgresql&quot; </strong>% testcontainersScalaVersion % <em>IntegrationTest</em>,<br>  <strong>&quot;com.dimafeng&quot; </strong>%% <strong>&quot;testcontainers-scala-kafka&quot; </strong>% testcontainersScalaVersion % <em>IntegrationTest</em>,<br>  <strong>&quot;com.dimafeng&quot; </strong>%% <strong>&quot;testcontainers-scala-mysql&quot; </strong>% testcontainersScalaVersion % <em>IntegrationTest</em>,<br>  <strong>&quot;io.debezium&quot; </strong>% <strong>&quot;debezium-testing-testcontainers&quot; </strong>% <strong>&quot;1.0.3.Final&quot; </strong>% <em>IntegrationTest</em>,<br>  <strong>&quot;mysql&quot; </strong>% <strong>&quot;mysql-connector-java&quot; </strong>% <strong>&quot;5.1.49&quot; </strong>% <em>IntegrationTest<br></em>)</pre><h4>Integration Test Flow</h4><ol><li>At the start of the test case execution, we will start our docker test containers.</li><li>We will do some write operations on our MySQL Database.</li><li>Will add Debezium connector via a Curl command</li><li>We will wait for some time to ensure that Kafka Connector is ready to read messages from MySQL DB</li><li>Kafka-Connect(Debezium) Connector will read the MySQL changes and put them as events in Kafka.</li><li>We will execute our Spark Structured Streaming job.</li><li>The job will stream the Kafka messages and with small transformation put them in PostgreSQL.</li><li>After some time a thread will stop the streaming job.</li><li>Now, we will assert the records in PostgreSQL with the expected data.</li></ol><blockquote>Integration Test class</blockquote><pre><strong>package </strong>com.sg.job.streaming<br><br><strong>import </strong>java.sql.{Connection, DriverManager, Statement}<br><strong>import </strong>java.time.LocalDateTime<br><br><strong>import </strong>com.dimafeng.testcontainers.{ForAllTestContainer, MultipleContainers}<br><strong>import </strong>com.sg.wrapper.{ContainerTestWrapper, SparkSessionITWrapper}<br><strong>import </strong>io.debezium.testing.testcontainers.ConnectorConfiguration<br><strong>import </strong>org.apache.spark.sql.SparkSession<br><strong>import </strong>org.scalatest.{BeforeAndAfterAll, FlatSpec}<br><strong>import </strong>org.slf4j.{Logger, LoggerFactory}<br><br><em>/**<br> * A Integration Test for </em>[[<em>StreamingJob</em>]]<br> <em>*/<br></em><strong>class </strong>StreamingIT <strong>extends </strong>FlatSpec <strong>with </strong>ForAllTestContainer <strong>with </strong>BeforeAndAfterAll <strong>with </strong>ContainerTestWrapper <strong>with </strong>SparkSessionITWrapper {<br><br>  <strong>val </strong><em>sparkTest</em>: SparkSession = <em>spark<br>  </em><strong>override val </strong><em>container</em>: MultipleContainers = <em>MultipleContainers</em>(<em>kafkaContainer</em>, <em>postgresContainer</em>, <em>mySQLContainer</em>)<br>  <strong>private val </strong><em>logger</em>: Logger = LoggerFactory.<em>getLogger</em>(<strong>this</strong>.getClass.getName)<br><br>  <em>it </em>must <strong>&quot;integration test for streaming job&quot; </strong>in {<br><br>    <em>logger</em>.info(<strong>&quot;Kafka-Connect is ready to send events...&quot;</strong>)<br>    createBinLogDBRecords(<em>mySQLContainer</em>.driverClassName, <em>mySQLContainer</em>.jdbcUrl, <em>mySQLContainer</em>.username, <em>mySQLContainer</em>.password)<br><br>    <em>logger</em>.info(<strong>&quot;launching the job...&quot;</strong>)<br><br>    <strong>val </strong>checkpoint = <strong>new </strong>java.io.File(<strong>&quot;.&quot;</strong>).getCanonicalPath + <strong>&quot;/checkpoint/it&quot; </strong>+ LocalDateTime.<em>now</em>()<br>    <strong>val </strong>kafkaReaderConfig = KafkaReaderConfig(<strong>&quot;localhost:29092&quot;</strong>, <strong>&quot;dbserver1.inventory.orders&quot;</strong>)<br>    <strong>val </strong>jobcConfig = JDBCConfig(url = <em>postgresContainer</em>.jdbcUrl)<br><br>    <strong>new </strong>StreamingJobExecutor(<em>sparkTest</em>, kafkaReaderConfig, checkpoint, jobcConfig).execute()<br><br>    <em>logger</em>.info(<strong>&quot;Assert the records&quot;</strong>)<br>    Class.<em>forName</em>(<em>postgresContainer</em>.driverClassName)<br>    <strong>val </strong>connection: Connection = DriverManager.<em>getConnection</em>(<em>postgresContainer</em>.jdbcUrl, <em>postgresContainer</em>.username, <em>postgresContainer</em>.password)<br>    connection.setAutoCommit(<strong>true</strong>)<br>    connection.setSchema(<strong>&quot;test&quot;</strong>)<br>    <strong>val </strong>statement: Statement = connection.createStatement()<br><br>    <strong>val </strong>sql = <strong>&quot;select * from public.orders_it&quot;<br>    val </strong>rs = statement.executeQuery(sql)<br>    assert(rs != <strong>null</strong>)<br>    <strong>try </strong>{<br>      <strong>while </strong>(rs.next()) {<br>        <em>//Put the logic for assertion<br>        println</em>(rs.getString(<strong>&quot;key&quot;</strong>), rs.getString(<strong>&quot;value&quot;</strong>), rs.getString(<strong>&quot;topic&quot;</strong>))<br>      }<br>    } <strong>finally </strong>{<br>      rs.close()<br>    }<br>  }<br><br>  <strong>override def </strong>beforeAll() {<br>    <strong>super</strong>.beforeAll()<br>    <em>debeziumContainer</em>.start()<br>    createSnapshotDBRecords(<em>mySQLContainer</em>.driverClassName, <em>mySQLContainer</em>.jdbcUrl, <em>mySQLContainer</em>.username, <em>mySQLContainer</em>.password)<br><br>    <strong>val </strong>connector = ConnectorConfiguration.<em>forJdbcContainer</em>(<em>mySQLContainer</em>.<em>container</em>)<br>      .`with`(<strong>&quot;database.server.name&quot;</strong>, <strong>&quot;dbserver1&quot;</strong>)<br>      .`with`(<strong>&quot;connector.class&quot;</strong>, <strong>&quot;io.debezium.connector.mysql.MySqlConnector&quot;</strong>)<br>      .`with`(<strong>&quot;database.whitelist&quot;</strong>, <strong>&quot;inventory&quot;</strong>)<br>      .`with`(<strong>&quot;database.user&quot;</strong>, <strong>&quot;root&quot;</strong>)<br>      .`with`(<strong>&quot;database.password&quot;</strong>, <strong>&quot;test&quot;</strong>)<br>      .`with`(<strong>&quot;database.hostname&quot;</strong>, <em>mySQLContainer</em>.networkAliases.head)<br>      .`with`(<strong>&quot;database.server.id&quot;</strong>, <strong>&quot;123&quot;</strong>)<br>      .`with`(<strong>&quot;database.port&quot;</strong>, <strong>&quot;3306&quot;</strong>)<br>      .`with`(<strong>&quot;name&quot;</strong>, <strong>&quot;my-connector&quot;</strong>)<br>      .`with`(<strong>&quot;database.history.kafka.bootstrap.servers&quot;</strong>, <strong>s&quot;$</strong>{<em>kafkaContainer</em>.networkAliases.head}<strong>:9092&quot;</strong>)<br>      .`with`(<strong>&quot;database.history.kafka.topic&quot;</strong>, <strong>&quot;schema-changes.inventory&quot;</strong>)<br><br>    <em>logger</em>.info(<strong>&quot;Trying to connect to Kafka-Connect&quot;</strong>)<br>    <em>debeziumContainer</em>.registerConnector(<strong>&quot;my-connector&quot;</strong>, connector)<br><br>    <em>//Giving time for kafka-connect to settle down.<br>    </em>Thread.<em>sleep</em>(30000)<br><br>    <em>//A way out of stopping the streaming job<br>    </em><strong>val </strong>runnable: Runnable = () =&gt;<br>      <strong>try </strong>{<br>        Thread.<em>sleep</em>(60000)<br>        <em>logger</em>.info(<strong>&quot;Stopping the job...&quot;</strong>)<br>        <em>sparkTest</em>.streams.active.foreach(q =&gt; q.stop())<br>        <em>sparkTest</em>.sparkContext.stop()<br>        <em>sparkTest</em>.stop()<br>      } <strong>catch </strong>{<br>        <strong>case </strong>e: InterruptedException =&gt; e.printStackTrace()<br>      }<br>    <strong>val </strong>thread = <strong>new </strong>Thread(runnable)<br>    thread.start()<br>  }<br><br>  <strong>override def </strong>afterAll() {<br>    <em>sparkTest</em>.stop()<br>    <em>debeziumContainer</em>.stop()<br>    <strong>super</strong>.afterAll()<br>  }<br><br>  <em>/**<br>   * DB changes for snapshot mode<br>   *<br>   */<br>  </em><strong>def </strong>createSnapshotDBRecords(driverClassName: String, jdbcUrl: String, username: String, password: String): Unit = {<br>    Class.<em>forName</em>(driverClassName)<br>    <strong>val </strong>connection: Connection = DriverManager.<em>getConnection</em>(jdbcUrl, username, password)<br>    <strong>val </strong>statement: Statement = connection.createStatement()<br>    statement.execute(<strong>&quot;create table inventory.orders (id int not null, title varchar(255), primary key (id))&quot;</strong>)<br>    statement.execute(<strong>&quot;insert into inventory.orders values (1, &#39;Learn ADC&#39;)&quot;</strong>)<br>    statement.execute(<strong>&quot;insert into inventory.orders values (2, &#39;Learn BDC&#39;)&quot;</strong>)<br>    statement.closeOnCompletion()<br>    connection.close()<br><br>  }<br>  <em>/**<br>   * DB change for binlog mode<br>   *<br>   */<br>  </em><strong>def </strong>createBinLogDBRecords(driverClassName: String, jdbcUrl: String, username: String, password: String): Unit = {<br>    Class.<em>forName</em>(driverClassName)<br>    <strong>val </strong>connection: Connection = DriverManager.<em>getConnection</em>(jdbcUrl, username, password)<br>    <strong>val </strong>statement: Statement = connection.createStatement()<br>    statement.execute(<strong>&quot;insert into inventory.orders values (3, &#39;Learn CDC&#39;)&quot;</strong>)<br>    statement.execute(<strong>&quot;insert into inventory.orders values (4, &#39;Learn DDC&#39;)&quot;</strong>)<br>    statement.execute(<strong>&quot;update inventory.orders set title = &#39;Learn EDC&#39; where id = 4&quot;</strong>)<br>    statement.execute(<strong>&quot;delete from inventory.orders where id = 3&quot;</strong>)<br>    statement.closeOnCompletion()<br>    connection.close()<br>  }<br><br>}</pre><p>When you run the test case, it will first bring the containers</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DBjMgjapkNzieGnNSblc7A.png" /><figcaption>Logs for integration test</figcaption></figure><p>Once the containers are up, we insert or test data in MySQL.</p><p>Debezium Connector ensures that MySQL records are available in Kafka as events.</p><p>Spark Application reads the Kafka topic and after doing the required transformation inserts data to PostgreSQL.</p><p>After 90 seconds, spark application is killed and control comebacks to Integration Test.</p><p>Now, you can create a connection to PostgreSQL DB and perform the required assertion.</p><blockquote>sbt</blockquote><p>Execute the following command to run the integration test</p><pre>sbt clean coverage test coverage it:test coverageReport</pre><p>I hope this was precise and helpful. The code can be downloaded from <a href="https://github.com/suchitgupta01/spark-streaming-with-debezium.git">Github</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1123634ca02f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Spark Streaming with Kafka-Connect Debezium Connector]]></title>
            <link>https://suchit-g.medium.com/spark-streaming-with-kafka-connect-debezium-connector-ab9163808667?source=rss-7e9cc4326c4d------2</link>
            <guid isPermaLink="false">https://medium.com/p/ab9163808667</guid>
            <dc:creator><![CDATA[Suchit Gupta]]></dc:creator>
            <pubDate>Sun, 17 May 2020 03:20:59 GMT</pubDate>
            <atom:updated>2020-05-26T14:59:10.641Z</atom:updated>
            <content:encoded><![CDATA[<p>A quick guide to set up Debezium and use Spark-Streaming Job as a Kafka consumer.</p><p>Bird’s eye view of the architecture:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fUROuImSjcxcunUkiqLUvA.png" /><figcaption>The Architecture</figcaption></figure><h4><a href="https://debezium.io/">Debezium</a></h4><p>Debezium is an open-source distributed platform for change data capture. It provides a set of Kafka connector which are capable of reading from the Database Binlog files and produce the changes as events in Kafka.</p><p>As a CDC, it gives you a state of Database Row before &amp; after the commit and also tells what kind of operation it was i.e Create/ Update or Delete.</p><p>A Debezium Kafka message looks like:</p><pre>{<br>   &quot;schema&quot;:{<br>      &quot;type&quot;:&quot;struct&quot;,<br>      &quot;fields&quot;:[<br>         {<br>            &quot;type&quot;:&quot;struct&quot;,<br>            &quot;fields&quot;:[<br>               {<br>                  &quot;type&quot;:&quot;int32&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;order_number&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;int32&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;name&quot;:&quot;io.debezium.time.Date&quot;,<br>                  &quot;version&quot;:1,<br>                  &quot;field&quot;:&quot;order_date&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;int32&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;purchaser&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;int32&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;quantity&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;int32&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;product_id&quot;<br>               }<br>            ],<br>            &quot;optional&quot;:true,<br>            &quot;name&quot;:&quot;dbserver1.inventory.orders.Value&quot;,<br>            &quot;field&quot;:&quot;before&quot;<br>         },<br>         {<br>            &quot;type&quot;:&quot;struct&quot;,<br>            &quot;fields&quot;:[<br>               {<br>                  &quot;type&quot;:&quot;int32&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;order_number&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;int32&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;name&quot;:&quot;io.debezium.time.Date&quot;,<br>                  &quot;version&quot;:1,<br>                  &quot;field&quot;:&quot;order_date&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;int32&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;purchaser&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;int32&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;quantity&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;int32&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;product_id&quot;<br>               }<br>            ],<br>            &quot;optional&quot;:true,<br>            &quot;name&quot;:&quot;dbserver1.inventory.orders.Value&quot;,<br>            &quot;field&quot;:&quot;after&quot;<br>         },<br>         {<br>            &quot;type&quot;:&quot;struct&quot;,<br>            &quot;fields&quot;:[<br>               {<br>                  &quot;type&quot;:&quot;string&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;version&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;string&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;connector&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;string&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;name&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;int64&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;ts_ms&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;string&quot;,<br>                  &quot;optional&quot;:true,<br>                  &quot;name&quot;:&quot;io.debezium.data.Enum&quot;,<br>                  &quot;version&quot;:1,<br>                  &quot;parameters&quot;:{<br>                     &quot;allowed&quot;:&quot;true,last,false&quot;<br>                  },<br>                  &quot;default&quot;:&quot;false&quot;,<br>                  &quot;field&quot;:&quot;snapshot&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;string&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;db&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;string&quot;,<br>                  &quot;optional&quot;:true,<br>                  &quot;field&quot;:&quot;table&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;int64&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;server_id&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;string&quot;,<br>                  &quot;optional&quot;:true,<br>                  &quot;field&quot;:&quot;gtid&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;string&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;file&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;int64&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;pos&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;int32&quot;,<br>                  &quot;optional&quot;:false,<br>                  &quot;field&quot;:&quot;row&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;int64&quot;,<br>                  &quot;optional&quot;:true,<br>                  &quot;field&quot;:&quot;thread&quot;<br>               },<br>               {<br>                  &quot;type&quot;:&quot;string&quot;,<br>                  &quot;optional&quot;:true,<br>                  &quot;field&quot;:&quot;query&quot;<br>               }<br>            ],<br>            &quot;optional&quot;:false,<br>            &quot;name&quot;:&quot;io.debezium.connector.mysql.Source&quot;,<br>            &quot;field&quot;:&quot;source&quot;<br>         },<br>         {<br>            &quot;type&quot;:&quot;string&quot;,<br>            &quot;optional&quot;:false,<br>            &quot;field&quot;:&quot;op&quot;<br>         },<br>         {<br>            &quot;type&quot;:&quot;int64&quot;,<br>            &quot;optional&quot;:true,<br>            &quot;field&quot;:&quot;ts_ms&quot;<br>         }<br>      ],<br>      &quot;optional&quot;:false,<br>      &quot;name&quot;:&quot;dbserver1.inventory.orders.Envelope&quot;<br>   },<br>   &quot;payload&quot;:{<br>      &quot;before&quot;:{<br>         &quot;order_number&quot;:10004,<br>         &quot;order_date&quot;:16852,<br>         &quot;purchaser&quot;:1003,<br>         &quot;quantity&quot;:1,<br>         &quot;product_id&quot;:107<br>      },<br>      &quot;after&quot;:{<br>         &quot;order_number&quot;:10004,<br>         &quot;order_date&quot;:16852,<br>         &quot;purchaser&quot;:1003,<br>         &quot;quantity&quot;:3,<br>         &quot;product_id&quot;:107<br>      },<br>      &quot;source&quot;:{<br>         &quot;version&quot;:&quot;1.0.3.Final&quot;,<br>         &quot;connector&quot;:&quot;mysql&quot;,<br>         &quot;name&quot;:&quot;dbserver1&quot;,<br>         &quot;ts_ms&quot;:1589660547000,<br>         &quot;snapshot&quot;:&quot;false&quot;,<br>         &quot;db&quot;:&quot;inventory&quot;,<br>         &quot;table&quot;:&quot;orders&quot;,<br>         &quot;server_id&quot;:223344,<br>         &quot;gtid&quot;:null,<br>         &quot;file&quot;:&quot;mysql-bin.000003&quot;,<br>         &quot;pos&quot;:354,<br>         &quot;row&quot;:0,<br>         &quot;thread&quot;:12,<br>         &quot;query&quot;:null<br>      },<br>      &quot;op&quot;:&quot;u&quot;,<br>      &quot;ts_ms&quot;:1589660547884<br>   }<br>}</pre><h4>Spark Streaming</h4><p>We are using Spark-Streaming as a processing engine, it reads the Debezium events from the Kafka topic and pushes the changes to PostgreSQL.</p><h4>Sample Code</h4><p>The sample code under discussion can be cloned from <a href="https://github.com/suchitgupta01/spark-streaming-with-debezium.git">Github</a></p><h4>How to bring the infrastructure up and running</h4><blockquote>docker-compose</blockquote><pre><strong>version</strong>: <strong>&#39;3.0&#39;<br>services</strong>:<br>  <strong>zookeeper</strong>:<br>    <strong>image</strong>: confluentinc/cp-zookeeper:latest<br>    <strong>container_name</strong>: zookeeper<br>    <strong>ports</strong>:<br>      - 2181:2181<br>      - 2888:2888<br>      - 3888:3888<br>    <strong>environment</strong>:<br>      - ALLOW_ANONYMOUS_LOGIN=yes<br>      - ZOOKEEPER_CLIENT_PORT=2181<br>      - ZOOKEEPER_TICK_TIME=2000<br>  <strong>kafka</strong>:<br>    <strong>image</strong>: confluentinc/cp-kafka:latest<br>    <strong>container_name</strong>: kafka<br>    <strong>ports</strong>:<br>      - 9092:9092<br>      - 29092:29092<br>    <strong>depends_on</strong>:<br>      - zookeeper<br>    <strong>environment</strong>:<br>      - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181<br>      - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092<br>      - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT<br>      - KAFKA_INTER_BROKER_LISTENER_NAME=PLAINTEXT<br>      - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1<br>  <strong>mysql</strong>:<br>    <strong>image</strong>: debezium/example-mysql:1.0<br>    <strong>container_name</strong>: mysql<br>    <strong>ports</strong>:<br>      - 3306:3306<br>    <strong>environment</strong>:<br>      - MYSQL_ROOT_PASSWORD=debezium<br>      - MYSQL_USER=mysqluser<br>      - MYSQL_PASSWORD=mysqlpw<br>  <strong>connect</strong>:<br>    <strong>image</strong>: debezium/connect:1.0<br>    <strong>container_name</strong>: connect<br>    <strong>ports</strong>:<br>      - 8083:8083<br>    <strong>depends_on</strong>:<br>      - kafka<br>      - mysql<br>    <strong>environment</strong>:<br>      - BOOTSTRAP_SERVERS=kafka:9092<br>      - GROUP_ID=1<br>      - CONFIG_STORAGE_TOPIC=my_connect_configs<br>      - OFFSET_STORAGE_TOPIC=my_connect_offsets<br>      - STATUS_STORAGE_TOPIC=my_connect_statuses<br>  <strong>postgresql</strong>:<br>    <strong>image</strong>: sameersbn/postgresql:9.4<br>    <strong>container_name</strong>: postgresql<br>    <strong>environment</strong>:<br>      - DEBUG=false<br>      - DB_USER=test<br>      - DB_PASS=Test123<br>      - DB_NAME=test<br>    <strong>ports</strong>:<br>      - <strong>&quot;5432:5432&quot;</strong></pre><p>Execute:</p><pre>docker-compose up -d</pre><p>It will bring up the following containers:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3D_oApgPbOtsFvM91rfjLQ.png" /><figcaption>containers</figcaption></figure><blockquote>Add Debezium-Connector</blockquote><p>Once the container “connect” is up and running, execute the following command to add MySqlConnector:</p><pre>curl -X POST \<br>  <a href="http://localhost:8083/connectors/">http://localhost:8083/connectors/</a> \<br>  -H &#39;Cache-Control: no-cache&#39; \<br>  -H &#39;Content-Type: application/json&#39; \<br>  -d &#39;{<br>    &quot;name&quot;: &quot;inventory-connector&quot;,<br>    &quot;config&quot;: {<br>        &quot;connector.class&quot;: &quot;io.debezium.connector.mysql.MySqlConnector&quot;,<br>        &quot;tasks.max&quot;: &quot;1&quot;,<br>        &quot;database.hostname&quot;: &quot;mysql&quot;,<br>        &quot;database.port&quot;: &quot;3306&quot;,<br>        &quot;database.user&quot;: &quot;debezium&quot;,<br>        &quot;database.password&quot;: &quot;dbz&quot;,<br>        &quot;database.server.id&quot;: &quot;1&quot;,<br>        &quot;database.server.name&quot;: &quot;dbserver1&quot;,<br>        &quot;database.whitelist&quot;: &quot;inventory&quot;,<br>        &quot;database.history.kafka.bootstrap.servers&quot;: &quot;kafka:9092&quot;,<br>        &quot;database.history.kafka.topic&quot;: &quot;schema-changes.inventory&quot;,<br>        &quot;decimal.handling.mode&quot;: &quot;double&quot;,<br>        &quot;snapshot.mode&quot;: &quot;when_needed&quot;,<br>        &quot;table.whitelist&quot;:&quot;inventory.orders&quot;<br>    }<br>}&#39;</pre><p>The connector is now ready to read MySQL Binlog files.</p><blockquote>MySQL Database</blockquote><p>The MySQL database in the docker-compose comes with a sample database and has the required database changes which are required for the connector to read the DB logs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/1*uz892r9FBKffXKTylP-sRA.png" /><figcaption>Table in MySQL</figcaption></figure><p>If you plan to use your own database then ensure the following things:</p><ol><li>The MySQL config for ‘binlog_format’ should be ROW</li></ol><pre>show variable &#39;binlog_format&#39;; //should be ROW</pre><p>2. Binary logging should be enabled. In MySQL cnf file:</p><pre># Uncomment the following if you want to log updates<br>log-bin=mysql-bin<br>expire_logs_days  = 10<br>binlog_format     = row</pre><blockquote>Kafka Topic</blockquote><p>Once the connector is added via curl command, it starts pushing the snapshot changes to Kafka, and once that is done it changes it’s mode to Binlog where it is watching the changes in Binlog file and pushed them to Kafka.</p><p>Kafka Topic:</p><pre>kafkacat -b localhost:29092 -t dbserver1.inventory.orders</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c8nLmCo5OXurOpJsUj1QpA.png" /><figcaption>Sample Kafka Message</figcaption></figure><blockquote>Spark Streaming Job</blockquote><p>The Spark-Streaming will listen to the Kafka Message, serialize/deserialize the message and push the change to PostgreSQL.</p><pre><strong>package </strong>com.sg.job.streaming<br><br><strong>import </strong>com.sg.wrapper.SparkSessionWrapper<br><strong>import </strong>org.apache.spark.sql.execution.datasources.jdbc.JDBCOptions<br><strong>import </strong>org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}<br><br><strong>object </strong>StreamingJob <strong>extends </strong>App <strong>with </strong>SparkSessionWrapper {<br><br>  <strong>val </strong><em>currentDirectory </em>= <strong>new </strong>java.io.File(<strong>&quot;.&quot;</strong>).getCanonicalPath<br>  <strong>val </strong><em>kafkaReaderConfig </em>= KafkaReaderConfig(<strong>&quot;localhost:29092&quot;</strong>, <strong>&quot;dbserver1.inventory.orders&quot;</strong>)<br>  <strong>val </strong><em>jdbCConfig </em>= <em>JDBCConfig</em>(url = <strong>&quot;jdbc:postgresql://localhost:5432/test&quot;</strong>)<br>  <strong>new </strong>StreamingJobExecutor(<em>spark</em>, <em>kafkaReaderConfig</em>, <em>currentDirectory </em>+ <strong>&quot;/checkpoint/job&quot;</strong>, <em>jdbCConfig</em>).execute()<br>}<br><br><strong>case class </strong>JDBCConfig(url: String, user: String = <strong>&quot;test&quot;</strong>, password: String = <strong>&quot;Test123&quot;</strong>, tableName: String = <strong>&quot;orders_it&quot;</strong>)<br><br><strong>case class </strong>KafkaReaderConfig(kafkaBootstrapServers: String, topics: String, startingOffsets: String = <strong>&quot;latest&quot;</strong>)<br><br><strong>case class </strong>StreamingJobConfig(checkpointLocation: String, kafkaReaderConfig: KafkaReaderConfig)<br><br><strong>class </strong>StreamingJobExecutor(spark: SparkSession, kafkaReaderConfig: KafkaReaderConfig, checkpointLocation: String, jdbcConfig: JDBCConfig) {<br><br>  <strong>def </strong>execute(): Unit = {<br>    <em>// read data from kafka and parse them<br>    </em><strong>val </strong>transformDF = read()<br>      .selectExpr(<strong>&quot;CAST(key AS STRING) as key&quot;</strong>, <strong>&quot;CAST(value AS STRING) as value&quot;</strong>, <strong>&quot;topic&quot;</strong>)<br><br>    transformDF<br>      .writeStream<br>      .option(<strong>&quot;checkpointLocation&quot;</strong>, checkpointLocation)<br>      .foreachBatch { (batchDF: DataFrame, _: Long) =&gt; {<br>        batchDF.write<br>          .format(<strong>&quot;jdbc&quot;</strong>)<br>          .option(<strong>&quot;url&quot;</strong>, jdbcConfig.url)<br>          .option(<strong>&quot;user&quot;</strong>, jdbcConfig.user)<br>          .option(<strong>&quot;password&quot;</strong>, jdbcConfig.password)<br>          .option(<strong>&quot;driver&quot;</strong>, <strong>&quot;org.postgresql.Driver&quot;</strong>)<br>          .option(JDBCOptions.<em>JDBC_TABLE_NAME</em>, jdbcConfig.tableName)<br>          .option(<strong>&quot;stringtype&quot;</strong>, <strong>&quot;unspecified&quot;</strong>)<br>          .mode(SaveMode.<em>Append</em>)<br>          .save()<br>      }<br>      }.start()<br>      .awaitTermination()<br>  }<br><br>  <strong>def </strong>read(): DataFrame = {<br>    spark<br>      .readStream<br>      .format(<strong>&quot;kafka&quot;</strong>)<br>      .option(<strong>&quot;kafka.bootstrap.servers&quot;</strong>, kafkaReaderConfig.kafkaBootstrapServers)<br>      .option(<strong>&quot;subscribe&quot;</strong>, kafkaReaderConfig.topics)<br>      .option(<strong>&quot;startingOffsets&quot;</strong>, kafkaReaderConfig.startingOffsets)<br>      .load()<br>  }<br>}</pre><blockquote>PostgreSQL</blockquote><p>Once the job is running, you can connect to PostgreSQL DB and see a table with the name <strong>orders_it </strong>has been created.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7TNnMb5vv-iKRjK_JBJySQ.png" /><figcaption>Table order_it</figcaption></figure><p>Now, play around with MySQL DB and see the changes getting pushed to PostgreSQL.</p><p>The same can be simulated via an Integration Test.</p><p><a href="https://medium.com/@suchit.g/integration-testing-in-spark-structured-streaming-1123634ca02f">Integration Testing in Spark Structured Streaming</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ab9163808667" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>