<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.2">Jekyll</generator><link href="https://sagaringale7.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://sagaringale7.github.io/" rel="alternate" type="text/html" /><updated>2022-10-18T04:20:57+02:00</updated><id>https://sagaringale7.github.io/feed.xml</id><title type="html">Sagar Ingale</title><subtitle>A bigdata Developer with strong Technical Knowledge</subtitle><author><name>Sagar Ingale</name><email>sagaringale7794@gmail.com</email></author><entry><title type="html">Big Data Lake in the AWS Cloud</title><link href="https://sagaringale7.github.io/big-data/aws-s3-datalake/" rel="alternate" type="text/html" title="Big Data Lake in the AWS Cloud" /><published>2018-03-17T19:38:49+01:00</published><updated>2018-03-17T19:38:49+01:00</updated><id>https://sagaringale7.github.io/big-data/aws-s3-datalake</id><content type="html" xml:base="https://sagaringale7.github.io/big-data/aws-s3-datalake/">&lt;p&gt;In this post I try to talk about my experience of building a (Big) Data Lake on the AWS Cloud using S3 as a data store, and solutions to common problems that we faced.&lt;/p&gt;

&lt;p&gt;I had worked with Philips Health Tech to build the data lake, however most of the problems we faced are universal and can be applied to almost any large scale  data engineering product built these days.&lt;/p&gt;

&lt;h2 id=&quot;moving-into-a-cloud-first-world&quot;&gt;Moving into a cloud first world&lt;/h2&gt;
&lt;p&gt;As the size of data collected by businesses increased day by day, the cost required to manage these kept going up, to a point when businesses seriously started to think of cost effective ways to store and process the data.&lt;/p&gt;

&lt;p&gt;Hadoop turned out to be a good bet for quite a few years, and almost every company had tried to use this to solve their problems. It did a great job but it didn’t solve the Ops part of the story. Hadoop as a tool required managing and maintaining data centers, replacing old servers and disks as they wear out, making sure everything is running from day to day, etc.
With Hadoop you didn’t have to worry about data security, data loss, data availability, but you were always required to have an Operations team to manage the infrastructure and keep it up and running.
However the landscape of Big Data changed rapidly with technologies getting outdated faster than ever and new processing frameworks solving newer problems every other day. So this meant upgrading clusters and up-skill teams, which required effort, experimentation and risks.&lt;/p&gt;

&lt;p&gt;The cloud first world aims to solve these problems with the use of managed services and decoupling everything. A Data Lake in the cloud aims to be a true No-Ops data store which is virtually of infinite scale, and doesn’t require managing separate teams monitoring or maintaining clusters. This is achieved by storing your data in a highly secure, durable and available shared data center. Also you have separate services for storing your data, and computing. With almost all kinds of services available for storing, caching, fast look ups, archived storage, on demand processing available, you can process almost anything without having to do anything about managing operations.&lt;/p&gt;

&lt;h2 id=&quot;huge-data-in-on-premise-servers-and-the-shift-to-public-cloud&quot;&gt;Huge data in On-Premise servers and the shift to Public Cloud&lt;/h2&gt;
&lt;p&gt;Before we could trust on cloud, software wasn’t really a service but a product you buy and keep in your office. This meant having space to stack up servers and maintaining and managing them. With data growing faster than ever, technologies becoming obsolete sooner than you think of, companies had to get into the business of managing, developing and maintaining huge data centers. It made all the sense for companies like Google, Facebook which operate at hyper scale, and face problems which are almost always unsolved. But not all business can or need to have separate potentially huge data center operations running with the primary motive being to manage servers.
This is the time, when going to a shared data center model, provides huge benefits in terms of costs, scalability and operations, and this is where the cloud steps in.&lt;/p&gt;

&lt;p&gt;This can be thought of huge data centers around the world, which you can rent from time to time when you need and pay exactly for what you use. Other than saving costs, you also save all the idle time your servers were consuming and you were paying. Also this looks more environment friendly, because as a small business you would be having (say) a 100 servers which you won’t switch off on the weekend.
On the other hand, in a cloud platform with a service based model, you pay for only the seconds you use a product. And this is from a pool of tens of thousands of servers running in a data center shared by all. On the weekend when the load is low, they turn these down to low power mode. Or at night, someone from the other side of the world gets to use the same compute power when you sleep.
With these simple optimizations the companies offering the shared data centers to you, can lower the cost, without you noticing and you save your money and the environment (our mother Earth, not dev-prod) is happy.&lt;/p&gt;

&lt;h2 id=&quot;the-world-of-hdfs&quot;&gt;The world of HDFS&lt;/h2&gt;
&lt;p&gt;With HDFS being a standard of data storage when it came to Big Data, lets try and understand what it had to offer and how it solved most problems.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;It is a File System and has all characteristics of a File System.&lt;/li&gt;
  &lt;li&gt;Provides good durability which can be controlled based on the replication you configure.&lt;/li&gt;
  &lt;li&gt;Is consistent. You would know if a write is successful in the same request you are writing the file.&lt;/li&gt;
  &lt;li&gt;Can run in high availability mode, with fail over masters.&lt;/li&gt;
  &lt;li&gt;Reliable - Has a good reputation of being capable of storing peta bytes of data without corrupting it.&lt;/li&gt;
  &lt;li&gt;Listing files is a trivial operation.&lt;/li&gt;
  &lt;li&gt;Uses blocks underneath to store files. Though the block size is configured, its wasteful for files which are very small (few bytes / mega bytes)&lt;/li&gt;
  &lt;li&gt;Moving files is an atomic operation.&lt;/li&gt;
  &lt;li&gt;Organizes files into hierarchical folders.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-world-of-object-stores&quot;&gt;The world of Object Stores&lt;/h2&gt;
&lt;p&gt;The most popular data stores in the cloud are not files systems but object stores. An object is a set of bytes with some metadata, and files are stored as objects.
Data Storage for long term and sometime short term in the cloud is mostly based on Object Stores.
There are certain properties about object stores which makes it a very efficient solution for a large number of use cases while keeping costs low and only slightly compromising on certain characteristics.&lt;/p&gt;

&lt;p&gt;Some of the common properties (ref. AWS S3):&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Store data of any size up to a fixed upper limit (usually 5 TB)&lt;/li&gt;
  &lt;li&gt;Good for very small files as well as large.&lt;/li&gt;
  &lt;li&gt;Durability guarantees which is practically “forever”.&lt;/li&gt;
  &lt;li&gt;Cost based on the exact amount of data stored.&lt;/li&gt;
  &lt;li&gt;Very highly available.&lt;/li&gt;
  &lt;li&gt;Options to create instances in the same region to process data in the same data center.&lt;/li&gt;
  &lt;li&gt;Listing files is a non-trivial operation, and becomes costly for buckets with large number of files.&lt;/li&gt;
  &lt;li&gt;Moving files is non atomic.&lt;/li&gt;
  &lt;li&gt;It does not have directory (folder) based hierarchical structure.&lt;/li&gt;
  &lt;li&gt;Keys can have similar prefixes with a ‘/’ delimiter to make it look like folders, but there is no concept of folders.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-amazing-s3-from-aws&quot;&gt;The amazing S3 from AWS&lt;/h2&gt;
&lt;p&gt;S3 is the offering from Amazon as part of the AWS and is probably the largest and most used Object Store in the internet today. So much so, that a small outage can take down literally half the internet.&lt;/p&gt;

&lt;p&gt;The use of S3 is virtually unlimited and I have noticed it being successfully used to:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;store user generated content (pdfs, file scans, images, thumbnails, videos etc)&lt;/li&gt;
  &lt;li&gt;store music or any form of digital media&lt;/li&gt;
  &lt;li&gt;host of static websites&lt;/li&gt;
  &lt;li&gt;store files and use CDN over S3 to deliver content&lt;/li&gt;
  &lt;li&gt;store web logs&lt;/li&gt;
  &lt;li&gt;store big data&lt;/li&gt;
  &lt;li&gt;as a data lake behind external tables of data warehouses (Hive)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However for companies running in Hyper Scale, and using object stores for data lake did face problems which required specialized solutions before being successful.&lt;/p&gt;

&lt;h2 id=&quot;the-caveats-of-s3&quot;&gt;The Caveats of S3&lt;/h2&gt;
&lt;p&gt;The most important things to keep in mind while using S3 is that:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;It is an eventually consistent data store. (and there is no guarantee on when this eventual becomes consistent)&lt;/li&gt;
  &lt;li&gt;It is not good for storing temporary files (files written incrementally in between stages in a job / pipelines)&lt;/li&gt;
  &lt;li&gt;It not ready to use out of the box for use cases which involve multi step writes of data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: There is Native S3, which solves a few more problems but is slow and has limitations on the file size. There is S3A, which is more suitable for a data lake however still has issues with consistency.&lt;/p&gt;

&lt;h2 id=&quot;what-i-learnt-from-using-s3-as-a-data-lake&quot;&gt;What I learnt from using S3 as a Data Lake&lt;/h2&gt;
&lt;p&gt;It is super reliable and durable and truly no-ops. For using as a Big Data Lake, where you would have potentially hundreds of pipelines reading and writing data, which are more often than not connected to each other, you would face the consistency problem.
Non Atomic move / copy operation becomes an issue when used with Map Reduce, Spark, or other popular big data processing frameworks.&lt;/p&gt;

&lt;p&gt;To get in detail, most data processing frameworks like Map Reduce and Spark first write data temporarily to the associated file system, and when the job is successfully completed, it moves the data to the correct folder, and creates a ‘_SUCCESS’ file.
Since the move operation is built as atomic in HDFS, it runs as one call from one thread. In the Object Store world, this operation essentially means copy and delete. And copy here means copying the data by reading in memory and streaming to another file in S3. It is not a managed operation by S3, and hence is handled by the client. This also makes the operation run on the master which is performing the final commit operation and is therefore not distributed. For large data sets written directly from a Spark job or a MR job to S3 results in unusually slow commit times and also is prone to network failure.&lt;/p&gt;

&lt;p&gt;One option to solve this problem is to use HDFS as a temporary store and finally have another job to copy the data to S3 (distcp).
The other options is to somehow have the listing and discovery of files handled separately and not use S3’s features to list files. This is what Netflix did (&lt;a href=&quot;https://github.com/Netflix/s3mper&quot;&gt;S3mper&lt;/a&gt;) and partly Amazon does in the implementation of S3 consistent view in an EMRFS cluster.&lt;/p&gt;

&lt;h2 id=&quot;gcs-from-google-and-how-it-compares&quot;&gt;GCS from Google and how it compares&lt;/h2&gt;
&lt;p&gt;In the Google Cloud Platform world, it offers Google Cloud Storage (GCS) which is also an object store competing with S3 in the market.
However in terms of architecture, implementation and use, it is vastly different from Amazon S3.
For instance it provides solutions to a lot more problems in the consistency aspect of a data store when compared with Amazon S3:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;GCS provides strong global consistency for read after write / delete vs eventually consistency for S3&lt;/li&gt;
  &lt;li&gt;Bucket and Object Listing are consistent GCS and not in S3&lt;/li&gt;
  &lt;li&gt;Atomic moves are not supported by GCS or S3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first point means that in S3, you may not be able to list / read an object just milliseconds after having successfully written it. And you don’t know your write was a success until you have read it, because replication is in process.
This also means that after delete, stale reads are possible. S3 offers no guarantees when a write or delete operation would be complete, and there is also no way to track that.&lt;/p&gt;

&lt;p&gt;This makes using S3 as a data store to write files and make atomic commits very difficult. To over come this Netflix uses DyanamoDB to store a secondary index for managing files in the S3 bucket. The tool is &lt;a href=&quot;https://github.com/Netflix/s3mper&quot;&gt;S3mper&lt;/a&gt; and is open sourced.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;S3 is probably one of the best products solving a very wide variety of use cases. However before using this as the best alternative to HDFS there are certain things which should be kept in mind.
In this article I try to uncover some of the things I learnt while using S3 as a Data Lake.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;https://cloud.google.com/storage/docs/consistency&lt;/li&gt;
  &lt;li&gt;https://docs.aws.amazon.com/AmazonS3/latest/dev/Introduction.html#ConsistencyModel&lt;/li&gt;
  &lt;li&gt;https://github.com/Netflix/s3mper&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Sagar Ingale</name><email>sagaringale7794@gmail.com</email></author><category term="big-data" /><category term="data" /><category term="data-lake" /><category term="cloud" /><category term="system-design" /><category term="big-data" /><category term="s3" /><category term="gcs" /><category term="aws" /><category term="gcp" /><summary type="html">Using AWS S3 as a Big Data Lake and its alternatives</summary></entry><entry><title type="html">Akka Actors vs Streams for Rest APIs</title><link href="https://sagaringale7.github.io/scala/akka-actors-streams-rest-api/" rel="alternate" type="text/html" title="Akka Actors vs Streams for Rest APIs" /><published>2017-10-09T20:38:49+02:00</published><updated>2017-10-09T20:38:49+02:00</updated><id>https://sagaringale7.github.io/scala/akka-actors-streams-rest-api</id><content type="html" xml:base="https://sagaringale7.github.io/scala/akka-actors-streams-rest-api/">&lt;p&gt;This post is about a comparison between Spray IO and Akka Http, and specifically one of the major changes in the way we design code when using actor based frameworks.
Also a small comparison of choosing between Akka Actors and Akka Streams for development of applications and what to use where.&lt;/p&gt;

&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;Before going deeper and comparing the advantages and disadvantages of the two frameworks and when to use what, let us first try and understand a few concepts from definitions. This section is by no means exhaustive and might require additional reading before one can gain a complete understanding.&lt;/p&gt;

&lt;h3 id=&quot;concurrency&quot;&gt;Concurrency&lt;/h3&gt;
&lt;p&gt;No it doesn’t mean things should run in parallel. It means that if you were to break a program into smallest of fragments and execute these out of order, the results would be same.&lt;/p&gt;

&lt;p&gt;This implies that individual operations should be stateless. (From the point of REST api, we can say that each request in a very simple service should not change the state (or behavior) of the service itself. - It can make changes to the database or some other external service though, which makes the overall application stateful)&lt;/p&gt;

&lt;p&gt;This is different from parallel execution though, which means that two operations happen at the same time, in different CPUs. Concurrency means that the operations doesn’t necessarily execute in parallel (at the same time).
&lt;a href=&quot;https://en.wikipedia.org/wiki/Concurrency_(computer_science)&quot;&gt;Wiki&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;scala-futures&quot;&gt;Scala Futures&lt;/h3&gt;
&lt;p&gt;Futures are a way of defining a particular set of operations which will produce an output at a later stage after it has been executed as part of a different thread. The calling thread is not blocked during the execution of the Future. Futures allows us to define call backs where we can pass functions which will get executed after a future has been completed.&lt;/p&gt;

&lt;p&gt;For a future to be executed we must define an execution context (or have it as an implicit). This is an abstraction over creating a Thread and running it. It offers a way to write cleaner code which abstracts out the creation and termination of Threads from the user.&lt;/p&gt;

&lt;h3 id=&quot;akka-actors&quot;&gt;Akka Actors&lt;/h3&gt;
&lt;p&gt;Actor model is a programming model which is used to implement concurrency. Actors mean the isolation of state into different components and each component communicating with others using message passing over a queue. In the world of Akka and Scala, we must keep the following properties in mind:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Actors have a mutable state stored inside the actor.&lt;/li&gt;
  &lt;li&gt;The state is mutated by sending messages. Thus an actor has a mailbox where messages come, and by default it is a java &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ConcurrentLinkedQueue&lt;/code&gt;. There can be other mailboxes as well.&lt;/li&gt;
  &lt;li&gt;The order of messages decides the order of execution. Remember, you are queueing your messages in a ConcurrentLinkedQueue for your Actor.&lt;/li&gt;
  &lt;li&gt;Processing of messages for one instance of the actor does not happen in parallel. It is concurrent by definition, not parallel. (This is the default behavior. Note that one mailbox can be shared by multiple instances of the same actor if configured - but that is not the default - and this needs a routing actor, which routes the messages to various instances of the actor, and this scheduling can be configured too.)&lt;/li&gt;
  &lt;li&gt;There is no shared mutable state across (different instances of) actors. Actors are self contained in their states.&lt;/li&gt;
  &lt;li&gt;Actors have hierarchy. Execution and state of actors are supervised by a supervisor actor.&lt;/li&gt;
  &lt;li&gt;Consumption of a message by an actor is determined by when the actor executes, this is in turn governed by the dispatcher or the execution context.&lt;/li&gt;
  &lt;li&gt;When an actor is scheduled to run, it gets a thread from the dispatcher and the thread for an actor is not reserved.&lt;/li&gt;
  &lt;li&gt;Akka doesn’t provide guarantees on messages being consistent, and messages can be dropped if the mailbox is full.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus we define actor as a state store that can be mutated by passing messages to it. It is definitely not some container for a deterministic stateless function which should be called for a response.&lt;/p&gt;

&lt;p&gt;Why? Because that is what an object or a static function is used for. Since the function is deterministic, it doesn’t change the state of an Actor, and can be called in parallel from different threads, it doesn’t need to be concurrent.&lt;/p&gt;

&lt;h3 id=&quot;akka-streams&quot;&gt;Akka Streams&lt;/h3&gt;
&lt;p&gt;Streams are a model of programming which is very functional in nature. It starts with having an input and making it flow through a series of operators or functions on the stream and producing an output. One example of streaming is the Unix &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;|&lt;/code&gt; (pipe) operator.&lt;/p&gt;

&lt;p&gt;In most Http Servers that we build, we essentially write the function which maps requests to the responses. In most frameworks like Spring Boot or other MVC frameworks, one thread used to apply the transformation from a HttpRequest to a HttpResponse for every request to the server.&lt;/p&gt;

&lt;p&gt;Akka Streams provide an abstraction over the Akka Actor model specifically in a way where we write the functions to be chained and executed on an input to produce an output.&lt;/p&gt;

&lt;p&gt;With this we are abstracted out of how the messages should be passed between actors, buffering, back pressure control and other components which largely remain same for most streaming applications using actors. This also helps us define complex transformations expressed as a directed acyclic graph, and not merely a simple function. Akka Streams API allows us to cleanly define async boundaries in a set of operations and create flows without worrying much about concurrency and execution.&lt;/p&gt;

&lt;h3 id=&quot;spray-io&quot;&gt;Spray IO&lt;/h3&gt;
&lt;p&gt;Simply put, Spray IO is a framework where you create REST API to talk and interact with Akka Actors. Your Http Routing is always contained within an Actor.&lt;/p&gt;

&lt;p&gt;Now since actors execute messages one at a time, it is very good for situations involving reads and writes where there is a state change that needs to be maintained and passed on to the next request.&lt;/p&gt;

&lt;p&gt;For situations which need only reads or only writes where each request is truly concurrent, we don’t need Actors.&lt;/p&gt;

&lt;h3 id=&quot;akka-http&quot;&gt;Akka Http&lt;/h3&gt;
&lt;p&gt;This is a framework for building Http Servers and Clients based on Akka Streams and Akka Actors.&lt;/p&gt;

&lt;p&gt;The primary design change when moving from Spray to Akka Http is that Akka-http is based on Akka Streams and not directly on Akka Actors. Akka Streams internally use Akka Actors but that is an implementation level detail for akka streams.&lt;/p&gt;

&lt;p&gt;This essentially means that akka http and spray behave differently even with the same user facing code.&lt;/p&gt;

&lt;h2 id=&quot;the-rest-api&quot;&gt;The REST API&lt;/h2&gt;
&lt;p&gt;With both Spray and Akka Http, defining REST apis is fairly simple. We define the route and then bind it to the port. It then starts accepting connections and responds. But there are a lot of differences in its implementations.&lt;/p&gt;

&lt;h3 id=&quot;spray&quot;&gt;Spray&lt;/h3&gt;
&lt;p&gt;We define the “route” as a trait extending HttpService of Spray Routing. This route is evaluated for each request to Spray. As an example lets see the following:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source code is &lt;a href=&quot;https://github.com/anish749/spray-io-vs-akka-http&quot;&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the purpose of examples in this post we would use a server which takes in a String and returns all possible permutations of the characters in the String.&lt;/p&gt;

&lt;p&gt;I choose this function for this example because it has a time complexity of O(n!) which increases to a very high value with a small change in the input.&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Spray Routing trait&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RestService&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HttpService&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JsonSupport&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;implicit&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;LoggingAdapter&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;implicit&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;executionContext&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ExecutionContext&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;route&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pathSingleSlash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;complete&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nc&quot;&gt;ServiceStatus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;OK&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Welcome to Spray IO Rest Service&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;pathPrefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sync&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;pathPrefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;permutations&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nf&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;'string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inputString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;complete&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Permutations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inputString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

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

&lt;p&gt;Now we need to define our Router Actor, and override the receive method, to reply with the route that we have defined in our Service trait.&lt;/p&gt;

&lt;p&gt;Thus the route is what gets executed when requests come to the actor. Where is the state of the actor? In this simple example, there is none. However, you can have the state inside the Router actor and this state can be updated concurrently with each request if the need arises.&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Spray Router Actor&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RestRouter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;implicit&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;ActorSystem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Actor&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RestService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;implicit&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;executionContext&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;dispatcher&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;implicit&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;log&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// the HttpService trait defines only one abstract member, which&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// connects the services environment to the enclosing actor or test&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;actorRefFactory&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Here we override the receive function of the Actor to execute our Rest Route&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;receive&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;runRoute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now that we have our service defined, we create the actors, and start the server by sending the Bind command to the Http extension.&lt;/p&gt;

&lt;p&gt;This would start a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpListener&lt;/code&gt; actor for a given port. When a request comes from client, it will first spawn a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpServerConnection&lt;/code&gt; actor. This actor is responsible for accepting the requests and sending them to our router actor which knows how to respond to a HttpRequest with a HttpResponse.&lt;/p&gt;

&lt;p&gt;Now lets execute the code. Please refer to this &lt;a href=&quot;https://github.com/anish749/spray-io-vs-akka-http&quot;&gt;GitHub Repo&lt;/a&gt; for the code, if you want to execute yourself&lt;/p&gt;

&lt;p&gt;Things to note. All requests are executed sequentially but in different threads. Remember your actors had a mailbox which is a Queue. And there is only one instance of the actor defined. We didn’t define a set of actor instances and scheduler actor to give the received requests in parallel to different instances of the same actor.&lt;/p&gt;

&lt;p&gt;Here it is easy to notice that the calls become slow as the characters in string increase, which is expected for the permutation function. However also note that requests are blocking in nature. Even though processing of requests can be made concurrent (We can process one request independent of another), the calls are blocking the server, and it cannot respond to another request concurrently.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Where can this work:&lt;/em&gt;
For cases where requests must be processed in order, where you do not need things to run in parallel. Cases where each requests modifies the state of the service and the modification should be visible to the subsequent request.
Eg: You have an API with which you want to submit a task, and check after sometime the status of it. However after submitting the task you want the server to start processing and the request to start should complete. There should be only one instance of the task running. So with a submit, you modify the state of the actor and then you query the actor using REST api for the status of the task.
An Implementation of this use case is there with Akka Http later on.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Where will this be an anti pattern:&lt;/em&gt;
If all your requests are stateless, and each request doesn’t change the state of the service, this is an anti pattern. You can possibly run these requests in parallel, but with an Actor pattern and one instance (ActorRef) of the Actor defined, you can’t run requests in different threads at the same time.&lt;/p&gt;

&lt;h3 id=&quot;spray-io-and-responding-httprequests-with-futures&quot;&gt;Spray IO and responding HttpRequests with Futures&lt;/h3&gt;
&lt;p&gt;Is there a workaround? Say for cases where there will be a high throughput required, all requests are concurrent in nature, and might take a few seconds to execute. It should not block the caller while it is executing.&lt;/p&gt;

&lt;p&gt;This is where we use Futures inside an actor. It should be noted that actors are not meant for running things in parallel. It is meant to store state, but in these cases our operations would be defined in a multi threaded way and not using actors, i.e., we won’t have multiple actors which processes the request.&lt;/p&gt;

&lt;p&gt;The route looks something like this:&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;pathPrefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;async&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;pathPrefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;permutations&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;'string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inputString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;respFut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Future&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nv&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Processing request for {} in Future&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inputString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Permutations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inputString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nf&quot;&gt;onComplete&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;respFut&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;successPermutations&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Permutations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;successPermutations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Failure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Exception while processing : {}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;StatusCodes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;InternalServerError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ServiceStatus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERROR&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Some error in service&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The code here takes an input string and returns all permutations by calculating that in a different thread.&lt;/p&gt;

&lt;p&gt;We use the callback &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onComplete&lt;/code&gt; which takes in a Future. The router actor is no longer blocked while the Future is getting executed.&lt;/p&gt;

&lt;p&gt;This makes requests to the server concurrent and non-blocking in nature.&lt;/p&gt;

&lt;h3 id=&quot;akka-http-1&quot;&gt;Akka Http&lt;/h3&gt;
&lt;p&gt;An Http Layer running on top of Akka Streams is much different from what we saw earlier. Even though the Routing remains the same, the whole semantics of execution changes.&lt;/p&gt;

&lt;p&gt;For this particular case itself we see that Akka Streams running requests concurrently just out of the box, without any coding with Futures.&lt;/p&gt;

&lt;p&gt;This is because Akka Streams defines the operations as a DAG, with a source and sink. Actors has been abstracted here.&lt;/p&gt;

&lt;p&gt;The code is exactly the same in terms of routing.&lt;/p&gt;

&lt;p&gt;The code to start the server however changes. Here we don’t have any actors and we don’t instantiate any. We create an ActorSystem and an ActorMaterializer.&lt;/p&gt;

&lt;p&gt;We call the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Http().bindAndHandle(route, host, port)&lt;/code&gt; and this gives us a binding future which will terminate once the server terminates.&lt;/p&gt;

&lt;p&gt;Actors are used and created underneath for the server to work. For user facing code and implementations, we can have Actors, and akka http will work well that as well.&lt;/p&gt;

&lt;p&gt;Here is an example where an user defined actor is used to store the state.&lt;/p&gt;

&lt;h3 id=&quot;akka-http-with-actors&quot;&gt;Akka Http with Actors&lt;/h3&gt;
&lt;p&gt;Akka Http works perfectly well with actors defined by us. In the following example we create a Task Manager.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/anish749/afe516ef0f96470cd381c1f5b0d79f59.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;It is responsible for creating and managing Tasks (these can be server side jobs). It is implemented using Actors, which stores the state of each Task.&lt;/p&gt;

&lt;p&gt;Task itself is a simple class which starts and does some operation. Its state is stored inside the instance of the class and gets updated as the Task progresses.&lt;/p&gt;

&lt;p&gt;The TaskManager is an Actor which keeps the instances of Task and can query them to understand their state. This actor runs the requests concurrently and avoids all problems which would have arose because of multiple requests coming at the same time. All TaskCreation / Querying happens one at a time because the state of all the tasks is stored in the Actor.&lt;/p&gt;

&lt;p&gt;Original Code in &lt;a href=&quot;https://github.com/anish749/spray-io-vs-akka-http/blob/master/akka-rest-service/src/main/scala/org/anish/akka/TaskManagerServer.scala&quot;&gt;Github Repo&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;https://stackoverflow.com/questions/11675422/actor-pattern-what-it-exactly-constitutes&lt;/li&gt;
  &lt;li&gt;http://blog.michaelhamrah.com/2015/01/a-gentle-introduction-to-akka-streams/&lt;/li&gt;
  &lt;li&gt;https://www.chrisstucchio.com/blog/2013/actors_vs_futures.html&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Sagar Ingale</name><email>sagaringale7794@gmail.com</email></author><category term="scala" /><category term="scala" /><category term="akka" /><category term="akka-actors" /><category term="akka-streams" /><category term="akka-http" /><category term="spray-io" /><summary type="html">A comparison of use cases for Spray IO (on Akka Actors) and Akka Http (on Akka Streams) for creating rest APIs</summary></entry><entry><title type="html">Implementing Statistical Mode in Apache Spark</title><link href="https://sagaringale7.github.io/spark/spark-udaf-statistics-mode/" rel="alternate" type="text/html" title="Implementing Statistical Mode in Apache Spark" /><published>2017-07-13T20:38:49+02:00</published><updated>2017-07-13T20:38:49+02:00</updated><id>https://sagaringale7.github.io/spark/spark-udaf-statistics-mode</id><content type="html" xml:base="https://sagaringale7.github.io/spark/spark-udaf-statistics-mode/">&lt;p&gt;In this post we would discuss how we can practically optimize the statistical function of Mode or the most common value(s) in Apache Spark by using UDAFs and the concept of monoid.&lt;/p&gt;

&lt;h3 id=&quot;what-is-mode&quot;&gt;What is mode?&lt;/h3&gt;
&lt;p&gt;In statistics, mode is defined as the value that appears most often in a set of data. Essentially it is the most common value in a given set of data.&lt;/p&gt;

&lt;h3 id=&quot;finding-the-mode-in-sql&quot;&gt;Finding the mode in SQL&lt;/h3&gt;
&lt;p&gt;This might sound simple and we might be hoping that an aggregate function is already available. However in most cases it is not available because it is pretty complex to implement the theoretical definition of mode using SQL.&lt;/p&gt;

&lt;p&gt;For example, lets say we have a data set, where all the values in a particular field is same, or there are multiple values having the same frequency. This boils down to the fact that the function should not give us one value as an aggregate, but a set of values as the output. This implies that the output should be an array. However in most real world use cases, where one would need to find mode, the attributes are usually nominal having a varied distribution and it can be assumed that there is only one value with the highest frequency. Thus for practical purposes we may assume that the output is a single value and not a set.&lt;/p&gt;

&lt;p&gt;Now, let us consider a data set such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trip_id, user_id, timestamp, city, airline, hotel_name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We want to find out the most visited city for each visitor and tag that as his home city, find the airline he uses the most, and the hotel he stays the most in, and then use this data for further computations. Now lets try and express this use case.&lt;/p&gt;

&lt;p&gt;Intuitively we would want something like this:&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;city&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;home_city&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;airline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fav_airline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hotel_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fav_hotel&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;trips&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This is simple to express, easy to read, but the catch is that we don’t have a function to calculate the most common value (mcv) for a particular group of data.&lt;/p&gt;

&lt;p&gt;Without having this function, here is how it looks like:&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;home_city&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;city&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;home_city&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ROW_NUMBER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cityFreq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rn&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;city&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;city&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cityFreq&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;trips&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;city&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Using Rank would give the true definition of mode, as there can be multiple values holding the same max frequency. But then would have to introduce another GROUP BY to convert that into a SET, and keep it in one row.&lt;/p&gt;

&lt;p&gt;So now we have home_city, we write the same thing again for airline, and again for hotel, and again for 100 other features that we may have. And we have ended up with multiple tables, so join these based on user_id.&lt;/p&gt;

&lt;p&gt;Lets see how this would work:
First it would do a grouping operation then a ROW_NUMBER or a RANK on an ordered set. Ordering the data is not the best thing we want to do especially because we would be ignoring every but the one with row number 1. Can we do a max? Yes, but that would need another join, and all this to calculate the home city.
That’s not the end, we will need one join for each feature, before we can have the final data set. Thus for 3 features that is 2 joins.&lt;/p&gt;

&lt;p&gt;So we have ended up doing 3 grouping operations, 3 partitioning and 3 ordering operations, and 2 joins to find the most common value for 3 attributes.
Pretty costly.&lt;/p&gt;

&lt;p&gt;An UDAF is what helps us in these cases. Lets understand that now.&lt;/p&gt;

&lt;h3 id=&quot;defining-custom-aggregate-functions&quot;&gt;Defining custom aggregate functions&lt;/h3&gt;
&lt;p&gt;Analysing our problem statement, we can say that our UDAF would be run for each user, and the input for one run would be all city names for that user. The output should be the most common value from that set.&lt;/p&gt;

&lt;p&gt;This sounds simple, and the steps for this function may be defined as follows:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Find the frequency of each element in the given input data.&lt;/li&gt;
  &lt;li&gt;Find the element with the max frequency.&lt;/li&gt;
  &lt;li&gt;Send that as the output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For practical use cases, we can assume that there is only one value which is the most frequent, and thus we would return a single value. This also means that the complexity of our function would now be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n)&lt;/code&gt;. (If we wanted to find all the most common values - theoretical mode - then we would have to sort based on frequencies, and it would be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n log n)&lt;/code&gt; )&lt;/p&gt;

&lt;h3 id=&quot;monoids&quot;&gt;Monoids&lt;/h3&gt;
&lt;p&gt;This might sound completely random to come in as a section here, but lets recollect the definition of Monoids. It would help us in writing much more optimized codes and understand how we can create UDAFs in Spark (or other SQL)&lt;/p&gt;

&lt;p&gt;By definition, we can say:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Given a type T, a binary operation Op:(T,T) =&amp;gt; T, and an instance Zero: T, with the properties that will be specified below, the triple (T, Op, Zero) is called a monoid. 
Here are the properties:

Neutral element: Zero Op a == a Op Zero == a
Associativity: (a Op b) Op c == a Op (b Op c)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Satisfying the associative property helps us in randomly grouping the given inputs and run these groups them in parallel and merge the outputs into one.&lt;/p&gt;

&lt;p&gt;If we want to calculate avg, the neutral element is the average itself, but average is not associative. However average can be expressed in terms of sum and count, which are associative. We can count elements in parallel and then add them up to find the total count. Similarly we can also add up elements and then add the results to find total sum. 
Using this information we can find the average at the end. Thus expressing average as a monoid helps us in calculating the average in parallel. The neutral element for sum is 0 and for count is null.&lt;/p&gt;

&lt;p&gt;It is easy to understand how we can find sum and average in parallel at the same time.&lt;/p&gt;

&lt;p&gt;Now we can see that computation of functions which can be expressed as monoids can run in parallel very easily. This helps us in writing very efficient map reduce code.&lt;/p&gt;

&lt;h3 id=&quot;spark-udaf&quot;&gt;Spark UDAF&lt;/h3&gt;
&lt;p&gt;Going by the &lt;a href=&quot;https://docs.databricks.com/spark/latest/spark-sql/udaf-scala.html&quot;&gt;user guide&lt;/a&gt; on Spark UDAF its clear that the above algorithm can’t be used as is, since we don’t even have a function that we can override where the input parameter contains the whole list of elements.
To run our most common value function in parallel we can see that if we divide a given set of data into multiple sets and calculate the most common value in each set, and subsequently find the most common value, we wont end up with the actual most common value for the whole set.&lt;/p&gt;

&lt;p&gt;This is where we try to define our function in terms of monoid, and see how it fits in the functions defined in the user guide.&lt;/p&gt;

&lt;p&gt;Considering that for practical use cases the cardinality of the attribute wont be very high, (in most cases where you would logically want to find the mode) we can say that if we know the frequency, find the element with max frequency can happen in one node, and doesn’t need to be distributed.
This leaves us with the first part: finding the frequencies. Can this be distributed? Let us try by writing a monoid which returns the frequency of each distinct element in a given set of values.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The computation would run in parallel, lets say we have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; partitions.&lt;/li&gt;
  &lt;li&gt;The frequencies would essentially be of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Map[Object, Long]&lt;/code&gt; with the item and its frequency.&lt;/li&gt;
  &lt;li&gt;Initially the Map would be empty, in each of the partitions. (This is the neutral element)&lt;/li&gt;
  &lt;li&gt;Add each element to the already maintained Map. (Update the map with one element, by increasing its frequency)&lt;/li&gt;
  &lt;li&gt;Every partition is now reduced to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Map[Object, Long]&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Merge the output of all the partitions till we get one output. (The actual function which is the monoid, this is where associativity is used)&lt;/li&gt;
  &lt;li&gt;Find out the max frequency, from the merged Map. (Some other operation at the end)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus finding the frequencies in each partition and then merging them again to find the total set of frequencies, can happen in parallel at the same time.&lt;/p&gt;

&lt;p&gt;Now let us write these logic as part of the Spark UDAF.&lt;/p&gt;

&lt;p&gt;We are given a buffer for our aggregate function to store the intermediate results
Its Schema can be defined as follows. (Considering that we are calculating the mode on a field with elements of StringType)&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;bufferSchema&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;StructType&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StructType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;nc&quot;&gt;StructField&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;frequencyMap&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;DataTypes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;createMapType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;StringType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LongType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Nil&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now for calculating we need to initialize the MapType for each partition.&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MutableAggregationBuffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The buffer is where we store intermediate results.&lt;/p&gt;

&lt;p&gt;Now when a new value comes in, we need to add it to our frequency map.&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MutableAggregationBuffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getAs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]](&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|+|&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getAs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note, the function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;|+|&lt;/code&gt; is from Scalaz and adds the frequencies for the same string. We also need to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import scalaz.Scalaz._&lt;/code&gt;
This is a SemiGroup operator from the package. This function is associative and hence we use to merge the frequency maps.&lt;/p&gt;

&lt;p&gt;After we get all the frequency maps from the parallel tasks, we need to merge them. This merges two aggregation buffers.&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer1&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MutableAggregationBuffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer2&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;buffer1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;buffer1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getAs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]](&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|+|&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;buffer2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getAs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]](&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally find the most common element from the merged Map.&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getAs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]](&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;maxBy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;_2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;_1&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With the above our Spark Code now becomes:&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;mostCommonValue&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MostCommonValue&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;groupBy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;agg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mostCommonValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;airline&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;fav_airline&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mostCommonValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;col&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;city&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;home_city&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Simple, clean and fast.&lt;/p&gt;

&lt;p&gt;The full class for the UDAF is available &lt;a href=&quot;https://gist.github.com/anish749/6a815ed281f538068a0d3a20ca9044fa&quot;&gt;here as a gist&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cheers&lt;/p&gt;</content><author><name>Sagar Ingale</name><email>sagaringale7794@gmail.com</email></author><category term="spark" /><category term="spark" /><category term="scala" /><category term="udaf" /><category term="statistics" /><category term="functions" /><summary type="html">Finding the most common value in parallel across nodes, and having that as an aggregate function.</summary></entry><entry><title type="html">Analyzing Java Garbage Collection Logs for debugging and optimizing Apache Spark jobs</title><link href="https://sagaringale7.github.io/spark/analyzing-java-garbage-collection-logs-debugging-optimizing-apache-spark/" rel="alternate" type="text/html" title="Analyzing Java Garbage Collection Logs for debugging and optimizing Apache Spark jobs" /><published>2017-05-14T20:38:49+02:00</published><updated>2017-05-14T20:38:49+02:00</updated><id>https://sagaringale7.github.io/spark/analyzing-java-garbage-collection-logs-debugging-optimizing-apache-spark</id><content type="html" xml:base="https://sagaringale7.github.io/spark/analyzing-java-garbage-collection-logs-debugging-optimizing-apache-spark/">&lt;p&gt;Recently while trying to make peace between Apache Parquet, Apache Spark and Amazon S3, to write data from Spark jobs, we were running into recurring issues.
In this post I would describe identifying and analyzing a Java OutOfMemory issue that we faced while writing Parquet files from Spark.&lt;/p&gt;

&lt;h3 id=&quot;what-we-have&quot;&gt;What we have&lt;/h3&gt;
&lt;p&gt;A fairly simple Spark job processing a few months of data and saving to S3 in Parquet format from Spark, intended to be used further for several purposes. To improve access, we got the data partitioned based on the date of generation. So far so good and in development our systems ran just fine to move forward. 
However the moment we started loading actual data, of approx 150 GB as a test, our jobs became slower and slower to a point when it started throwing Out of Memory Exception in Spark.&lt;/p&gt;

&lt;h3 id=&quot;what-the-error-looked-like&quot;&gt;What the error looked like&lt;/h3&gt;
&lt;p&gt;One of the interesting errors was a java OutOfMemoryError with GC Overhead limit exceeded.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;17/05/12 10:48:36 WARN NioEventLoop: Unexpected exception in the selector loop.
java.lang.OutOfMemoryError: GC overhead limit exceeded
...
...
17/05/12 10:48:52 ERROR InsertIntoHadoopFsRelation: Aborting job.
java.lang.OutOfMemoryError: Java heap space: failed reallocation of scalar replaced objects
17/05/12 10:48:52 ERROR ErrorMonitor: Uncaught fatal error from thread [sparkDriver-akka.remote.default-remote-dispatcher-5] shutting down ActorSystem [sparkDriver]
java.lang.OutOfMemoryError: GC overhead limit exceeded
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;reproducing-the-error&quot;&gt;Reproducing the error&lt;/h3&gt;
&lt;p&gt;To reproduce the error I wrote a small piece of code after doing the full analysis to understand what is wrong. It is available as a gist &lt;a href=&quot;https://gist.github.com/anish749/587e8a12bfdb0de36b0d47ee5cf85e56&quot;&gt;here&lt;/a&gt;. I wrote just as much as it is needed to create this issue primarily for writing this blog.
&lt;script src=&quot;https://gist.github.com/anish749/587e8a12bfdb0de36b0d47ee5cf85e56.js&quot;&gt;&lt;/script&gt;
Submit the code as follows after making suitable changes to reproduce the error. I had used Spark 1.5.2 with Scala 2.10.4&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;nohup &lt;/span&gt;spark-submit &lt;span class=&quot;nt&quot;&gt;--packages&lt;/span&gt; com.github.scopt:scopt_2.10:3.3.0 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--master&lt;/span&gt; yarn-client &lt;span class=&quot;nt&quot;&gt;--executor-memory&lt;/span&gt; 6g &lt;span class=&quot;nt&quot;&gt;--executor-cores&lt;/span&gt; 1 &lt;span class=&quot;nt&quot;&gt;--num-executors&lt;/span&gt; 40 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--driver-memory&lt;/span&gt; 2g &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--conf&lt;/span&gt; spark.storage.memoryFraction&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0.4 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--class&lt;/span&gt; org.anish.spark.gcissue.SparkParquetGcOutOfMemory &lt;span class=&quot;nt&quot;&gt;--verbose&lt;/span&gt; JarFileName.jar &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--outputTableName&lt;/span&gt; feed.temporary &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--outputTablePath&lt;/span&gt; s3a://your-bucket-name/junkData/ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--outputTableFormat&lt;/span&gt; parquet &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--outputTableSaveMode&lt;/span&gt; append &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--rows&lt;/span&gt; 1000000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--cols&lt;/span&gt; 500 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--repeat&lt;/span&gt; 20 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--parquetEnableSummaryMetadata&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt; &amp;amp;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This creates a data frame with random integers having 500 columns and 1000000 rows. And the repeat specifies how many times it should append to the final location where it is trying to write this data.&lt;/p&gt;

&lt;h3 id=&quot;debugging&quot;&gt;Debugging&lt;/h3&gt;
&lt;p&gt;A bit of background on the complexity of the logic in the Spark Job and data size:&lt;/p&gt;

&lt;p&gt;The job we were having was fairly simple with a couple of transformations and filters processing around 100GB data. There were no joins or aggregations going on. In short all the transformations can be expressed as a single stage map operation and there was absolutely no need for any data to be cached in memory. We can very well stream the input and write to output, after adding partition information. So a OutOfMemory Exception is fairly unexpected. We also do not have any collect/take/show/etc in our job. The computation is triggered by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;df.write.save&lt;/code&gt; api on the data frame, so the data should not be coming to the driver anytime.&lt;/p&gt;

&lt;p&gt;A quick Google search with the error and some skimming through JVM docs says that “the garbage collector is taking an excessive amount of time (by default 98% of all CPU time of the process) and recovers very little memory in each run (by default 2% of the heap).”
But we are yet to understand which JVM is throwing this error. There are multiple executors running and our dearest driver.&lt;/p&gt;

&lt;p&gt;To debug, the fist option was to play around with the executor memory (because the data is being processed there), which proved futile after a few attempts. Checking a few executor logs managed by YARN also doesn’t reveal any error. This seems to be a dead end.&lt;/p&gt;

&lt;h3 id=&quot;enabling-gc-logging-at-the-executors&quot;&gt;Enabling GC logging at the executors&lt;/h3&gt;
&lt;p&gt;The next approach is to understand what is there in the JVM heap that triggers this error. To understand this further we turn on GC logging so that we get to know what is going on in the JVM. Spark provides us with the configuration &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spark.executor.extraJavaOptions&lt;/code&gt; with which we can pass options to the JVM.
To start this we add the following to the spark-submit&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;--conf&lt;/span&gt; spark.executor.extraJavaOptions&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'-XX:+PrintFlagsFinal -XX:+PrintReferenceGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy -XX:+UnlockDiagnosticVMOptions -XX:+G1SummarizeConcMark'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The above would print the GC logs to the executors stdout. These logs would be really big in size. We had around 100MB logs, with INFO log level for Spark.
Now take out a few executor logs and compress them bring those out of the cluster to analyze further.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;yarn logs &lt;span class=&quot;nt&quot;&gt;-appOwner&lt;/span&gt; &amp;lt;your-user-name&amp;gt; &lt;span class=&quot;nt&quot;&gt;-applicationId&lt;/span&gt; &amp;lt;yarn-application-id&amp;gt; &lt;span class=&quot;nt&quot;&gt;-containerId&lt;/span&gt; &amp;lt;container-id&amp;gt; &lt;span class=&quot;nt&quot;&gt;--nodeAddress&lt;/span&gt; &amp;lt;executor.node:port&amp;gt; &lt;span class=&quot;nt&quot;&gt;-logFiles&lt;/span&gt; stdout &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;executor1stdout.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here the container id, and node address can be seen from the Spark UI executors tab.
This would give us the file which after compressing would be small enough to analyze further.&lt;/p&gt;

&lt;p&gt;Here I used a free GC log analyzer: &lt;a href=&quot;http://gceasy.io/&quot;&gt;GC Easy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few stats from the job is interesting and here as follows:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2017-05-15-gc-log-analysis/10_ExecutorMemoryLeak.png&quot; alt=&quot;hi&quot; class=&quot;inline&quot; /&gt;
The analysis shows that there is no Memory Leak going on.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2017-05-15-gc-log-analysis/11_ExecutorHeapUsageAfterGc.png&quot; alt=&quot;hi&quot; class=&quot;inline&quot; /&gt;
The Heap Usage after GC looks like a constant value, doesn’t look like there is any problem.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2017-05-15-gc-log-analysis/12_ExecutorGcDuration.png&quot; alt=&quot;hi&quot; class=&quot;inline&quot; /&gt;
The amount of time spent by the Garbage Collector.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2017-05-15-gc-log-analysis/13_ExecutorGcStats.png&quot; alt=&quot;hi&quot; class=&quot;inline&quot; /&gt;
Stats calculated from the GC logs&lt;/p&gt;

&lt;p&gt;From the above graphs, its clear that things are running smoothly at the executors. 
GC isn’t taking a long time and is in milliseconds. Most of the garbage is collected by Young GC, and Full GC doesn’t do a lot of work. (You might be interested in reading and understanding a bit about Java GC before interpreting these graphs. This Spark related &lt;a href=&quot;https://databricks.com/blog/2015/05/28/tuning-java-garbage-collection-for-spark-applications.html&quot;&gt;post&lt;/a&gt; from Databricks has some excellent explanations)&lt;/p&gt;

&lt;h3 id=&quot;enabling-gc-logging-at-the-driver&quot;&gt;Enabling GC logging at the driver&lt;/h3&gt;
&lt;p&gt;Now the above action was a waste of time. In fact if I had analyzed the Spark log a bit carefully I would have identified the issue faster.
It tells us two things (in the last 3 lines of the log at the beginning of the post):&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;A class that was able to see this error and act: It is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ERROR InsertIntoHadoopFsRelation: Aborting job.&lt;/code&gt; So this means that the job was aborted due to an error somewhere by the InsertIntoHadoopFsRelation class. This implies that the data was fine and was processed without exceptions being thrown, but it wasn’t able to insert into a HadoopFsRelation.&lt;/li&gt;
  &lt;li&gt;The thread which threw this uncaught exception: This thread is named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[sparkDriver-akka.remote.default-remote-dispatcher-5]&lt;/code&gt; and after the error the sparkDriver ActorSystem was shut down. A brief idea about how Spark is implemented and how Akka Actors communicate with each other helps us infer that this error is thrown in the sparkDriver Actor System which is in our driver JVM.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I did try a random shortcut to increase the driver memory from the default 1g using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--driver-memory 2g&lt;/code&gt; in my spark-submit, just to see if it resolves the issue or there is really something nasty going underneath, but this doesn’t help.
However after this, I don’t want to try with 32g driver because I know I am not collecting any data to the driver and something is wrong here.&lt;/p&gt;

&lt;p&gt;Thus as the next step we enable GC logging in the driver.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;--conf&lt;/span&gt; spark.driver.extraJavaOptions&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'-XX:+PrintFlagsFinal -XX:+PrintReferenceGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy -XX:+UnlockDiagnosticVMOptions -XX:+G1SummarizeConcMark'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These logs have been printed in the stdout (I was running with yarn-client as a master). Once again uploading these to GC Easy shows us the following results:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2017-05-15-gc-log-analysis/20_DriverMemoryLeak.png&quot; alt=&quot;hi&quot; class=&quot;inline&quot; /&gt;
The analysis shows that there is Memory Leak in the driver JVM.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2017-05-15-gc-log-analysis/21_DriverHeapUsageAfterGc.png&quot; alt=&quot;hi&quot; class=&quot;inline&quot; /&gt;
The Heap Usage after GC. Towards the end it remains constant.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2017-05-15-gc-log-analysis/22_DriverGcDuration.png&quot; alt=&quot;hi&quot; class=&quot;inline&quot; /&gt;
The amount of time spent by the Garbage Collector. It is very high towards the end.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2017-05-15-gc-log-analysis/23_DriverGcStats.png&quot; alt=&quot;hi&quot; class=&quot;inline&quot; /&gt;
Stats calculated from the GC logs showing high amount of time taken for Full GC&lt;/p&gt;

&lt;p&gt;Now we can understand the following things:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;It clearly tells us that there is a Memory Leak.&lt;/li&gt;
  &lt;li&gt;Towards the end of processing, we can see that there are multiple Full GC runs with no change in Heap Size. (This looks apt with the description in JVM docs about the Exception being thrown)&lt;/li&gt;
  &lt;li&gt;The max time of Full GC is 7 sec.&lt;/li&gt;
  &lt;li&gt;The number of Full GC is quite high towards the end before it throws the error and shuts down.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are sure that the driver is the culprit here.
Now the next question would be about the objects that are being created in the driver JVM which needs so much memory. So my next step is to analyze the JVM Heap Memory using Heap Dumps.&lt;/p&gt;

&lt;h3 id=&quot;analyzing-the-jvm-heap-dump&quot;&gt;Analyzing the JVM Heap Dump&lt;/h3&gt;
&lt;p&gt;Along with all the other driver JVM options we add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-XX:+HeapDumpOnOutOfMemoryError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This would create a Heap Dump in the working directory. Since we are running in yarn-client, the driver home directory is same as the one from which we are submitting the job. The final Spark driver JVM options looks as follows:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;--conf&lt;/span&gt; spark.driver.extraJavaOptions&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'-XX:+PrintFlagsFinal -XX:+PrintReferenceGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy -XX:+UnlockDiagnosticVMOptions -XX:+G1SummarizeConcMark -XX:+HeapDumpOnOutOfMemoryError'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now since we were running with 2g driver memory, we have a nice 2.4gb heap dump created.&lt;/p&gt;

&lt;p&gt;To analyze what is there in the Java Heap, I used a tool called &lt;a href=&quot;http://www.eclipse.org/mat/&quot;&gt;Memory Analyzer&lt;/a&gt; which is an Eclipse plugin.&lt;/p&gt;

&lt;p&gt;Now we need to run Eclipse on a JVM which is having more than 2g memory to analyze a 2g heap dump. Here I had change the JVM options of Eclipse so that it can analyze this dump.
I changed the VM option to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-Xmx4096m&lt;/code&gt; in Contents/Eclipse/eclipse.ini inside the application package of the Eclipse app for Mac OS X.&lt;/p&gt;

&lt;p&gt;Now after loading the heap dump and a bit of clicking around leads us to some interesting things.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;1.5g of total 2g is used by Thread main&lt;/li&gt;
  &lt;li&gt;This also tells us leak suspects. Going there we try to see the Thread Stack to understand which function goes on allocating memory. This stack is the key to finding the root cause. More on this later&lt;/li&gt;
  &lt;li&gt;The dominator tree tells us which are these objects. We see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;org.apache.parquet.format.RowGroup&lt;/code&gt; which also stores &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;org.apache.parquet.format.ColumnChunk&lt;/code&gt; which is internally having &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;org.apache.parquet.format.ColumnMetaData&lt;/code&gt; and then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;org.apache.parquet.format.Statistics&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These information about the object names along with the call stack points us to the root cause described below.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2017-05-15-gc-log-analysis/31_HeapDumpOverview.png&quot; alt=&quot;hi&quot; class=&quot;inline&quot; /&gt;
The overview of the Heap. We see that one thread has 1.5g of the total 2g of memory.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2017-05-15-gc-log-analysis/32_HeapDumpCallStack.png&quot; alt=&quot;hi&quot; class=&quot;inline&quot; /&gt;
The call stack of the thread which is taking up most of the memory.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/2017-05-15-gc-log-analysis/33_HeapDumpDominatorTree.png&quot; alt=&quot;hi&quot; class=&quot;inline&quot; /&gt;
The dominator tree showing us the objects which are consuming the most space in memory.&lt;/p&gt;

&lt;h3 id=&quot;the-problematic-call-stack&quot;&gt;The problematic call stack&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;main
  at org.apache.parquet.bytes.BytesUtils.intToBytes(I)[B (BytesUtils.java:221)
  at org.apache.parquet.column.statistics.IntStatistics.getMaxBytes()[B (IntStatistics.java:56)
  at org.apache.parquet.format.converter.ParquetMetadataConverter.toParquetStatistics(Lorg/apache/parquet/column/statistics/Statistics;)Lorg/apache/parquet/format/Statistics; (ParquetMetadataConverter.java:242)
  at org.apache.parquet.format.converter.ParquetMetadataConverter.addRowGroup(Lorg/apache/parquet/hadoop/metadata/ParquetMetadata;Ljava/util/List;Lorg/apache/parquet/hadoop/metadata/BlockMetaData;)V (ParquetMetadataConverter.java:167)
  at org.apache.parquet.format.converter.ParquetMetadataConverter.toParquetMetadata(ILorg/apache/parquet/hadoop/metadata/ParquetMetadata;)Lorg/apache/parquet/format/FileMetaData; (ParquetMetadataConverter.java:79)
  at org.apache.parquet.hadoop.ParquetFileWriter.serializeFooter(Lorg/apache/parquet/hadoop/metadata/ParquetMetadata;Lorg/apache/hadoop/fs/FSDataOutputStream;)V (ParquetFileWriter.java:405)
  at org.apache.parquet.hadoop.ParquetFileWriter.writeMetadataFile(Lorg/apache/hadoop/fs/Path;Lorg/apache/parquet/hadoop/metadata/ParquetMetadata;Lorg/apache/hadoop/fs/FileSystem;Ljava/lang/String;)V (ParquetFileWriter.java:433)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the third line of the above stack it is clear that Parquet is trying to calculate Column Statistics and the problem comes up when writing the MetadataFile (last line above). Also from the objects in the dominator tree, it seems that it is trying to compute column stats for each file.
A quick Google Search about Parquet and how it works helps in understanding that it creates the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_common_metadata&lt;/code&gt; file which stores metadata information from each of the file footers. And this whole merging of metadata from each of the files’ footers to the common metadata file is happening in the driver and on the main thread itself.
Thus it is trying to download the whole metadata and trying to combine that.&lt;/p&gt;

&lt;h3 id=&quot;problem-analysis-and-solution&quot;&gt;Problem analysis and Solution&lt;/h3&gt;
&lt;p&gt;Does Spark really need the common metadata? Apparently while reading, it tries to create it and initializing reads from Parquet files is also slow.
Is this really necessary? No. Things would work just fine (&lt;a href=&quot;https://forums.databricks.com/questions/6494/do-you-need-a-metadata-file-in-a-parquet-folder-to.html&quot;&gt;Read here&lt;/a&gt;). It would read the metadata while reading the files anyway. The common metadata file helps when there are multiple schemas and there are multiple nested sub directories. With our use case we were not having that at the current moment.&lt;/p&gt;

&lt;p&gt;Thus a probable candidate for fixing this issue is to disable this metadata generation.
To do this we add the following to our code, and this has to be with the hadoopConfiguration:&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;sparkContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;hadoopConfiguration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;parquet.enable.summary-metadata&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;false&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;While reading the data we also disable merging of metadata by using the following in spark-submit&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;--conf&lt;/span&gt; spark.sql.parquet.mergeSchema&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we run the code again. And yes, things work fine.&lt;/p&gt;

&lt;p&gt;If we really need to generate the MetaData, we can use the following code:
&lt;script src=&quot;https://gist.github.com/anish749/804c2f6ec97afa00c16fd94aa1ea73ea.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;

&lt;h3 id=&quot;other-optimization&quot;&gt;Other optimization&lt;/h3&gt;
&lt;p&gt;One more optimization to take note of is to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DirectParquetOutputCommitter&lt;/code&gt; which commits directly without creating a _temporary folder which speeds up processing considerably when working with Blob stores like S3. But more on this on a different post.&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;sparkContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;hadoopConfiguration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;spark.sql.parquet.output.committer.class&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;org.apache.spark.sql.parquet.DirectParquetOutputCommitter&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;notes&quot;&gt;Notes:&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Spark version used for this is 1.5.2&lt;/li&gt;
  &lt;li&gt;JVM version is 1.8.0_77 and is a Java HotSpot 64-Bit Server VM.&lt;/li&gt;
  &lt;li&gt;Scala compiler version is 2.10.4&lt;/li&gt;
  &lt;li&gt;The JVM options and tools for Heap Dump analysis may vary from different JVM distributions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;https://plumbr.eu/outofmemoryerror/gc-overhead-limit-exceeded&lt;/li&gt;
  &lt;li&gt;http://stackoverflow.com/questions/542979/using-heapdumponoutofmemoryerror-parameter-for-heap-dump-for-jboss&lt;/li&gt;
  &lt;li&gt;https://dzone.com/articles/java-heap-dump-are-you-task&lt;/li&gt;
  &lt;li&gt;https://crunchify.com/increase-eclipse-memory-size-to-avoid-oom-on-startup/&lt;/li&gt;
  &lt;li&gt;http://javaeesupportpatterns.blogspot.in/2011/11/hprof-memory-leak-analysis-tutorial.html&lt;/li&gt;
  &lt;li&gt;https://forums.databricks.com/questions/6494/do-you-need-a-metadata-file-in-a-parquet-folder-to.html&lt;/li&gt;
  &lt;li&gt;http://stackoverflow.com/questions/37327626/generate-metadata-for-parquet-files&lt;/li&gt;
  &lt;li&gt;http://stackoverflow.com/questions/34935393/append-new-data-to-partitioned-parquet-files&lt;/li&gt;
  &lt;li&gt;https://www.appsflyer.com/blog/the-bleeding-edge-spark-parquet-and-s3/&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Sagar Ingale</name><email>sagaringale7794@gmail.com</email></author><category term="spark" /><category term="spark" /><category term="scala" /><category term="aws" /><category term="s3" /><category term="parquet" /><category term="java-gc" /><summary type="html">Understanding how Spark runs on JVMs and how the memory is managed in each JVM.</summary></entry><entry><title type="html">Using HashSet based indexes in Apache Spark</title><link href="https://sagaringale7.github.io/spark/using-hashset-based-indexes-apache-spark/" rel="alternate" type="text/html" title="Using HashSet based indexes in Apache Spark" /><published>2017-05-13T20:38:49+02:00</published><updated>2017-05-13T20:38:49+02:00</updated><id>https://sagaringale7.github.io/spark/using-hashset-based-indexes-apache-spark</id><content type="html" xml:base="https://sagaringale7.github.io/spark/using-hashset-based-indexes-apache-spark/">&lt;p&gt;This post is about de-duplication of data while loading to tables using HashSet based indexes in Apache Spark.&lt;/p&gt;

&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Indexes in R-DBMS systems are used to improve query performance and access times of the data stored. But while using systems like Spark which is primarily an in-memory computation engine, we don’t have indexes. Data sources on the other hand implement indexing and in Spark we can do predicate push down to take advantage of this indexing.
Loading data to indexed tables take more time since it rebuilds the index. In most cases indexes are also not optimized to load data faster, but to enhance faster reads.
Here I try an use case of incremental loading of data, where I eliminate duplicates without using a join or a full table scan of the existing data set.&lt;/p&gt;

&lt;h3 id=&quot;the-problem-statement&quot;&gt;The problem statement&lt;/h3&gt;
&lt;p&gt;A table exists in Hive or any destination system and data is loaded every hour (or day) to this table. As per the source systems, it can generate duplicate data. However the final table should de-duplicate the data while loading. We assume that data is immutable, but can be delivered more than once, and we need a logic to filter these duplicates before appending the data to our master store.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Assumption:&lt;/em&gt; We have the data partitioned and data doesn’t get repeated across partition. This just makes the problem a simpler to optimize as it would help in reducing shuffles. For other use cases we can very well consider the whole data to be in one partition.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sample use case:&lt;/em&gt; I used this on click stream data with an at least once delivery guarantee. However since the data is immutable, the source timestamp doesn’t change, and I partition the data based on this source timestamp.&lt;/p&gt;

&lt;h3 id=&quot;sql-based-solution&quot;&gt;SQL based solution&lt;/h3&gt;
&lt;p&gt;A typical query to do this require a join of the existing and new data sets. Indexes do speed up joins in R-DBMS systems.&lt;/p&gt;

&lt;p&gt;A simple sql based solution of doing a de-duplicated insert is often found in data bases which appends new data from a source table to a destination table. However to realize this insert in a SQL query, it essentially means to do an outer join of the two data sets and then choose only the records that are actually new and discard the old. Also we must make sure we take only a distinct set of records from the new data, which means a distinct before joining or a group by after joining to completely remove duplicates.&lt;/p&gt;

&lt;p&gt;The same query in expressed in terms of the map reduce paradigm, transforms to a very costly join and multiple shuffles. The distinct or a group by to eliminate the duplicates from the new data set also means more complex reduce or shuffle and reduce operation respectively. Over all the whole query is not the most optimized way of doing things.&lt;/p&gt;

&lt;h3 id=&quot;the-data-size&quot;&gt;The data size&lt;/h3&gt;
&lt;p&gt;With a very high data size, traditional data bases don’t scale well, so we go for a map reduce based solution, but we don’t have any indexes here. Spark cannot take advantage of indexes to optimize the join even if those are defined in the data sources.&lt;/p&gt;

&lt;h3 id=&quot;a-spark-native-solution&quot;&gt;A Spark native solution&lt;/h3&gt;
&lt;p&gt;Here I will discuss a solution for managing and maintaining a HashSet of the composite keys for each partition using which we can eliminate a full table scan of the existing data to calculate the subset of the new data that should be appended after removing the duplicates.
The whole code base is &lt;a href=&quot;https://github.com/anish749/spark-indexed-dedup/&quot;&gt;here&lt;/a&gt; and can be downloaded and tested.
The main starting point of this etl is the runEtl() method in DeDuplicateMergeData class.&lt;/p&gt;

&lt;h4 id=&quot;the-logic&quot;&gt;The Logic&lt;/h4&gt;
&lt;p&gt;When the data is being loaded, create and maintain another pair RDD where the key is the partition and the value is one single HashSet of the composite keys present in that partition of the data.&lt;/p&gt;

&lt;p&gt;While loading the data first we convert it into a pair RDD and partition the incoming data in the same way as the existing data. Then for each partition we de-duplicate the data using a HashSet for each partition. (Explained with code later)&lt;/p&gt;

&lt;p&gt;In the first run, there is no data present, so we go ahead and load this de-duplicated data and generate the index to be used for the next load.&lt;/p&gt;

&lt;p&gt;If there is data existing, but the HashSet index is not available, as a recovery option, we create the HashSet of the existing data and then proceed.&lt;/p&gt;

&lt;p&gt;Now we co-group the new data along with the HashSet index and then filter the duplicate records from the new data based on the HashSet. This lookup now happens in O(1) time since we are having a HashSet present.&lt;/p&gt;

&lt;p&gt;After having the filtered data, append it to the existing data and merge the index of the new data with the index of the old data. Write the existing data first and then if successful, save the index to a file system.&lt;/p&gt;

&lt;p&gt;Now let us look at the individual steps in detail to understand further.&lt;/p&gt;

&lt;h4 id=&quot;the-hashset-based-index&quot;&gt;The HashSet based index&lt;/h4&gt;
&lt;p&gt;The HashSet based index is essentially another RDD which stores a HashSet of the composite keys for each partition of the data that has already been loaded.
Since a Sequence of multiple values form the composite key, we maintain one HashSet of these keys. So the type is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HashSet[Seq[String]]&lt;/code&gt;, and since this HashSet is for only one partition, our pair RDD for the whole data is of type  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(Int, HashSet[Seq[String]])&lt;/code&gt; where the data is partitioned based on a column of type Int.&lt;/p&gt;

&lt;p&gt;We create the HashSet index RDD from a partitioned pair RDD. (The partitioning strategy is discussed after this)&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createHashSetIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;partitionedPairRDD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;RDD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)],&lt;/span&gt;
                                 &lt;span class=&quot;n&quot;&gt;partitioner&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Partitioner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                                 &lt;span class=&quot;n&quot;&gt;primaryKeys&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;RDD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]])]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Create a HashSet (or a Bloom Filter) index for each partition&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;createCombiner&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SerializableHelpers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getKeysFromRow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;asInstanceOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;GenericRowWithSchema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primaryKeys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;mergeValue&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;SerializableHelpers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getKeysFromRow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;asInstanceOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;GenericRowWithSchema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primaryKeys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;mergeCombiners&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C1&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C2&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]])&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C2&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Pass our partitioner to prevent repartitioning / shuffle&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;partitionedPairRDD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;combineByKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;createCombiner&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createCombiner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;mergeValue&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mergeValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;mergeCombiners&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mergeCombiners&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;partitioner&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;partitioner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here the function getKeysFromRow is defined as follows and is used to extract and make a sequence of the composite keys from one Spark Row object.&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getKeysFromRow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;GenericRowWithSchema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primaryKeys&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;primaryKeys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fieldName&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getAs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fieldName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createCombiner&lt;/code&gt; function creates a HashSet from the row object.
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mergeValues&lt;/code&gt; function is used to combine a Row into a HashSet.
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mergeCombiners&lt;/code&gt; are used to merge two HashSets.
Using these three functions, Spark runs the operation of creating the HashSets for each partition in parallel. The resulting output of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rdd.combineByKey&lt;/code&gt; is one pair RDD where the key is the partition column value and the value is a HashSet of the composite keys in that partition.&lt;/p&gt;

&lt;h4 id=&quot;custom-partitioner&quot;&gt;Custom Partitioner&lt;/h4&gt;
&lt;p&gt;To make sure that every step in creation of the index or other co-group operation doesn’t not trigger a new Shuffle, we use the same partitioner everywhere and have most operations as a OneOnOneDependency on the previous RDD rather than a ShuffleDependency. Our idea is to make sure that the internal partitioning logic of RDDs and the partitioning that we want to maintain is the same. For example if we want our data to be partitioned by date, the RDD should internally keep values of the same date in the same partition. We can increase or decrease the granularity of the partitioning column to control the size of the data in one partition. 
By default Spark uses HashPartitioner which takes the data and puts to a constant number of partitions based on the hashcode of the key, in case of a key value pair RDD. This means data with multiple values of the key can end up with the same value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nonNegativeMod(key.hashcode, numPartitions)&lt;/code&gt; which means that these would be present in the same partition. We can decide on the number of partitions by using estimators described later, but it would still result in irregular partitions.&lt;/p&gt;

&lt;p&gt;For this we implement an exact value based partitioner. The would make sure that the data from one key value would only go to one partition. (One trick to implement this is to use RangePartitioner in Spark, with a very high value for the number of partitions, but this does a sampling underneath and scans a part of the data which is not necessary for our purpose.) &lt;a href=&quot;https://github.com/anish749/spark-indexed-dedup/blob/master/src/main/scala/org/anish/spark/indexeddedup/partitioner/SimpleModuloPartitioner.scala&quot;&gt;Our implentation&lt;/a&gt; is to simple do a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nonNegativeMod(key, numPartitions)&lt;/code&gt; and make sure that we are using an Integer or a Double value as the partition key. If we were to partition based on geographical regions, the HashPartitioner with a high value in the numPartitions would probably be good enough.&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getPartition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;nonNegativeMod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;asInstanceOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numPartitions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Double&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;nonNegativeMod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;asInstanceOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Double&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numPartitions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Long is not defined as numPartitions should be Int as per Spark Partitioner class&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RuntimeException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Key is not Int or Double or it is not supported yet&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In this partitioner implementation all records having the same partition column value ends up in the same partition of the RDD. This helps us in more optimized filtering after we co-group with the HashSet RDD.&lt;/p&gt;

&lt;h4 id=&quot;partition-size-estimators&quot;&gt;Partition size estimators&lt;/h4&gt;
&lt;p&gt;Our next step is to find out what should be our number of partitions. We can either to a group by or a distinct (in Spark v2.0 and later, distincts are optimized to use groupBy internally) and then count the values of the partitioning column.
Or we can use a high enough number and have some empty partitions which gets ignore while processing producing no output, and is not really a huge overhead.&lt;/p&gt;

&lt;p&gt;The other option is to get this information from the metadata of the source. In this case I used it with weblogs and extracted the date from the log file name. The idea was to keep the data originating in all the log files in one day in a single partition.
Estimating from dates in the file name somewhat looks like this in the implementation &lt;a href=&quot;https://github.com/anish749/spark-indexed-dedup/blob/master/src/main/scala/org/anish/spark/indexeddedup/estimator/PartitionEstimator.scala&quot;&gt;here&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;lazy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;estimate&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;datePatternRegex&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;datePattern&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;r&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;datesInFiles&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sparkContext&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;wholeTextFiles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inputFilePath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;_1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;datePatternRegex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ERROR : did not match given regex&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;distinct&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;collect&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toList&lt;/span&gt;

    &lt;span class=&quot;nv&quot;&gt;datesInFiles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;length&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;datePattern&lt;/code&gt; is a regex which has the date pattern in the file name. This however doesn’t look very optimized for blob stores like S3 and can be optimized further to uses FileStatus of Hadoop.&lt;/p&gt;

&lt;h4 id=&quot;finding-distinct-using-hashsets&quot;&gt;Finding distinct using HashSets&lt;/h4&gt;
&lt;p&gt;Given a pair RDD which is partitioned using the above partitioning techniques, we are sure that data of the same key is always there in only one partition and one partition has values with only one key. Finding the distinct records for each partition now becomes very easy as we don’t need a shuffle to find the distinct. Our aim is to find the distinct values in one given partition only. This means that the partition information is not needed and the logic is stateless and can run independently for each partition. Think of this as data from China can never be duplicate in India, if the data is partitioned by countries. And if the date is generated at source, the data generated on one day may be send multiple times on the same day but never on different days, because the data is immutable and we have partitioned based on the time the data was generated. Data generation time (not when a server logs it) would always be immutable.&lt;/p&gt;

&lt;p&gt;The above operation can be expressed as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mapPartitions&lt;/code&gt; on our new data RDD. However I though of implementing this as a new type of RDD extending the actual RDD implementation. Here we directly define the way we want to compute the data from the previous RDD. The core logic looks as follows as is implemented &lt;a href=&quot;https://github.com/anish749/spark-indexed-dedup/blob/master/src/main/scala/org/anish/spark/indexeddedup/internal/DeDupPairRDD.scala&quot;&gt;here&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@DeveloperApi&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;compute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Partition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;TaskContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Iterator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;K&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;iterList&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;firstParent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;K&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)].&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;iterator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toList&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;iterList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;nonEmpty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;hashSetForPartition&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;MHashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MHashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;

      &lt;span class=&quot;nv&quot;&gt;iterList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Cast to GenericRowWithSchema to enable access by field name&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;keysInRecord&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getKeysFromRow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;_2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;asInstanceOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;GenericRowWithSchema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;hashSetForPartition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keysInRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;hashSetForPartition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.+=(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keysInRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toIterator&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;firstParent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;K&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)].&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;iterator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// The partition is empty&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This is however a DeveloperAPI is Spark and should be used with caution. The method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getKeysFromRow&lt;/code&gt; is same as the one described previously.&lt;/p&gt;

&lt;h4 id=&quot;merging-data-based-on-hash-set-index&quot;&gt;Merging data based on Hash Set index&lt;/h4&gt;
&lt;p&gt;Now comes the most interesting part where we skip the full table scan but still join using the index that we had created and filter the data that was already present.&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;filterOldRecordsUsingHashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deDupedNewData&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;RDD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)],&lt;/span&gt;
                                           &lt;span class=&quot;n&quot;&gt;existingHashSetRDD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;RDD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]])],&lt;/span&gt;
                                           &lt;span class=&quot;n&quot;&gt;newDataPartitioner&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Partitioner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                                           &lt;span class=&quot;n&quot;&gt;primaryKeys&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;RDD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Step 3 - Merge - this is an incremental load, and old data is already available.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Step 3.1 - Cogroup the data. Passing the partitioner same as new data should prevent repartitioning the data&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;coGroupedRDD&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;deDupedNewData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;cogroup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;existingHashSetRDD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;partitioner&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newDataPartitioner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Step 3.2 - Remove duplicates using old data HashSet - the actual merge operation&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;coGroupedRDD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;flatMapValues&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// This is a new partition and this wasn't present in old data&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;vs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;iterator&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ws&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// This is a partition which is there in old data as well as new data&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;newRecordsIterator&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;vs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;iterator&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;existingHashSet&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ws&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;iterator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;headOption&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getOrElse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// We expect only one HashSet&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;newRecordsIterator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newRecord&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;// Filter already existing data&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;existingHashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SerializableHelpers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getKeysFromRow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;newRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;asInstanceOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;GenericRowWithSchema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;primaryKeys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// Ignore the case for only old partition with no new partition present -&amp;gt; case (Seq(), ws)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This is implemented &lt;a href=&quot;https://github.com/anish749/spark-indexed-dedup/blob/master/src/main/scala/org/anish/spark/indexeddedup/DeDuplicateMergeData.scala&quot;&gt;here&lt;/a&gt;. We first co-group the new data and the index to bring the HashSet to the partition we are working on. Now we use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rdd.flatMapValues&lt;/code&gt; to implement our actual filter operation. This is much like a left outer join operation which internally calls the cogroup and flatMapValues API of RDDs. While doing the flatMap we check if the new data is already present in the HashSet of the old data for that partition. Note that we are only using the composite keys to check this, much like the join operation would be on some primary keys in any relation.&lt;/p&gt;

&lt;h4 id=&quot;merge-hashsets&quot;&gt;Merge HashSets&lt;/h4&gt;
&lt;p&gt;This is a another operation we need because we also have to merge the new HashSet index that was added to the existing HashSet index.
We use the following implented &lt;a href=&quot;https://github.com/anish749/spark-indexed-dedup/blob/master/src/main/scala/org/anish/spark/indexeddedup/DeDuplicateMergeData.scala&quot;&gt;here&lt;/a&gt; to achieve this:&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mergeHashSetIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rdds&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;RDD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]])]],&lt;/span&gt;
                                &lt;span class=&quot;n&quot;&gt;partitioner&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Partitioner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;RDD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]])]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Apply reduce to union all the given rdds into one rdd.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// This would forget the previous partitioning and simply increase the number of partitions.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;unionedRDD&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;rdds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;union&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// The following function for merging two HashSets would work for merging values as well as Combiners&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;mergeVaulesAndCombiners&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C1&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C2&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]])&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C2&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Because after co grouping we expect only one HashSet&lt;/span&gt;

    &lt;span class=&quot;nv&quot;&gt;unionedRDD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;combineByKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;createCombiner&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]])&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;mergeValue&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mergeVaulesAndCombiners&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;mergeCombiners&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mergeVaulesAndCombiners&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;partitioner&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;partitioner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// This is required to force repartitioning as union would have likely increased the partitions&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This is fairly simple because we first do a union of all the RDDs and then partition it to bring all the HashSets to the same partition as the key, and then merge the HashSets. The function used for merging values and combiners are the same in this case.&lt;/p&gt;

&lt;h4 id=&quot;storing-and-reading-the-hash-set-index&quot;&gt;Storing and reading the Hash Set index&lt;/h4&gt;
&lt;p&gt;We also need to store and read this RDD of HashSets. For this we use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rdd.saveAsObjectFile&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sparkContext.objectFile[(Int, HashSet[Seq[String]])](hashSetIndexLocation)&lt;/code&gt; to save and read the index.
We also built some fault tolerance and around the reading to handle cases like first execution when no index would be present and cases when the we are applying the logic for the first time and data is already existing, so the index needs to be rebuilt. This logic is implemented &lt;a href=&quot;https://github.com/anish749/spark-indexed-dedup/blob/master/src/main/scala/org/anish/spark/indexeddedup/DeDuplicateMergeData.scala&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id=&quot;building-the-complete-etl-and-handling-failures&quot;&gt;Building the complete ETL and handling failures&lt;/h4&gt;
&lt;p&gt;Now we try and implement all the above logic into one ETL which should solve our initial problem. 
The steps can be described as follows:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Partitioning and de-duplicating new data
    &lt;ol&gt;
      &lt;li&gt;Prepare (make a pair rdd) and partition (spark memory partition) the new data.&lt;/li&gt;
      &lt;li&gt;Remove duplicates using Hash table for new data. (Remove duplicates present in incoming raw data)&lt;/li&gt;
      &lt;li&gt;Create a HashSet of the new data.&lt;/li&gt;
      &lt;li&gt;Check if it is the first load, then return the de-duplicated data and the HashSet. End of story&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Get ready to merge with old data. -&amp;gt; We know it is not a first load
    &lt;ol&gt;
      &lt;li&gt;Load HashSet of Old Data. If this doesn’t exist it should return empty. This might have got deleted.&lt;/li&gt;
      &lt;li&gt;Check if existing HashSet is present, rebuild HashSet.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Merge
    &lt;ol&gt;
      &lt;li&gt;If no old data is available, this is the fist load, load the new data as is.&lt;/li&gt;
      &lt;li&gt;Co group the two data sets. Remove data that was already loaded before, based on available HashSet.&lt;/li&gt;
      &lt;li&gt;Now we have the actual data that needs to be appended&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Create/Update HashSet index
    &lt;ol&gt;
      &lt;li&gt;Create the HashSet Index, for the incoming data set.&lt;/li&gt;
      &lt;li&gt;If this is the first run, store this HashSet index.&lt;/li&gt;
      &lt;li&gt;If HashSet already existed for old data, update and overwrite the new HashSet.&lt;/li&gt;
      &lt;li&gt;Handle failures for writing and storing this HashSet.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Output:&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;The de-duplicated data that should be appended&lt;/li&gt;
  &lt;li&gt;The total HashSet index that would be valid after appending the data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is implemented in the runEtl method &lt;a href=&quot;https://github.com/anish749/spark-indexed-dedup/blob/master/src/main/scala/org/anish/spark/indexeddedup/DeDuplicateMergeData.scala&quot;&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;The overall aim of this project was to speed up duplicated data loading to a master table from where data could be reported or used for various purposes. An optimization which decreases the time complexity from a full table scan to an O(1) look up is fairly complex to think, code and explain in a blog post. It would take quite a few iterations to go through this and understand the overall optimization and how it benefits by using less computation.&lt;/p&gt;

&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;http://blog.madhukaraphatak.com/extending-spark-api/&lt;/li&gt;
  &lt;li&gt;http://rahulkavale.github.io/blog/2014/12/01/extending-rdd-for-fun-and-profit/&lt;/li&gt;
  &lt;li&gt;http://codingjunkie.net/spark-combine-by-key/&lt;/li&gt;
  &lt;li&gt;http://abshinn.github.io/python/apache-spark/2014/10/11/using-combinebykey-in-apache-spark/&lt;/li&gt;
  &lt;li&gt;http://apachesparkbook.blogspot.in/2015/12/cogroup-example.html&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Sagar Ingale</name><email>sagaringale7794@gmail.com</email></author><category term="spark" /><category term="spark" /><category term="scala" /><category term="join" /><category term="optimization" /><category term="indexes" /><category term="hashset" /><summary type="html">This post is about de-duplication of data while loading to tables using HashSet based indexes in Apache Spark.</summary></entry><entry><title type="html">Exception Handling in Spark Data Frames</title><link href="https://sagaringale7.github.io/spark/exception-handling-spark-data-frames/" rel="alternate" type="text/html" title="Exception Handling in Spark Data Frames" /><published>2017-05-04T17:38:49+02:00</published><updated>2017-05-04T17:38:49+02:00</updated><id>https://sagaringale7.github.io/spark/exception-handling-spark-data-frames</id><content type="html" xml:base="https://sagaringale7.github.io/spark/exception-handling-spark-data-frames/">&lt;h3 id=&quot;general-exception-handling&quot;&gt;General Exception Handling&lt;/h3&gt;

&lt;p&gt;Handling exceptions in imperative programming in easy with a try-catch block. Though these exist in Scala, using this in Spark to find out the exact invalid record is a little different where computations are distributed and run across clusters. A simple try catch block at a place where an exception can occur would not point us to the actual invalid data, because the execution happens in executors which runs in different nodes and all transformations in Spark are lazily evaluated and optimized by the Catalyst framework before actual computation.&lt;/p&gt;

&lt;p&gt;For most processing and transformations, with Spark Data Frames, we usually end up writing business logic as custom udfs which are serialized and then executed in the executors. Without exception handling we end up with Runtime Exceptions. Sometimes it is difficult to anticipate these exceptions because our data sets are large and it takes long to understand the data completely. Also in real time applications data might come in corrupted and without proper checks it would result in failing the whole Spark job.&lt;/p&gt;

&lt;h3 id=&quot;the-runtime-exception&quot;&gt;The Runtime Exception&lt;/h3&gt;

&lt;p&gt;Lets take an example where we are converting a column from String to Integer (which can throw NumberFormatException). Our idea is to tackle this so that the Spark job completes successfully.&lt;/p&gt;

&lt;p&gt;Now this can be different in case of RDD[String] or Dataset[String] as compared to Dataframes. In most use cases while working with structured data, we encounter DataFrames. So our type here is a Row.&lt;/p&gt;

&lt;p&gt;We define our function to work on Row object as follows without exception handling. This can however be any custom function throwing any Exception.:&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DataFrame&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Convert using a map function on the internal RDD and keep it as a new column&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;simpleCastRDD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;RDD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;rdd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;memberIdStr&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getAs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;member_id&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;memberIdInt&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;memberIdStr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toInt&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;fromSeq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toSeq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memberIdInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Create schema for the new Column&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;simpleCastSchema&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StructType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;fields&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;StructField&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;member_id_int&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntegerType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)))&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Simple Casting&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;simpleCastDF&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Dataset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sparkSession&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;sqlContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;createDataFrame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;simpleCastRDD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;simpleCastSchema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;simpleCastDF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;printSchema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;simpleCastDF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The above can also be achieved with UDF, but when we implement exception handling, Spark wont support Either / Try / Exception classes as return types and would make our code more complex.
Since the map was called on the RDD and it created a new rdd, we have to create a Data Frame on top of the RDD with a new schema derived from the old schema.&lt;/p&gt;

&lt;p&gt;The above code works fine with good data where the column member_id is having numbers in the data frame and is of type String.
When an invalid value arrives, say “**” or “,” or a character “aa” the code would throw a java.lang.NumberFormatException in the executor and terminate the application.
Worse, it throws the exception after an hour of computation till it encounters the corrupt record.&lt;/p&gt;

&lt;h3 id=&quot;handling-exceptions&quot;&gt;Handling Exceptions&lt;/h3&gt;
&lt;p&gt;Here I will discuss two ways to handle exceptions. One using an accumulator to gather all the exceptions and report it after the computations are over. The second option is to have the exceptions as a separate column in the data frame stored as String, which can be later analysed or filtered, by other transformations.&lt;/p&gt;

&lt;p&gt;If the number of exceptions that can occur are minimal compared to success cases, using an accumulator is a good option, however for large number of failed cases, an accumulator would be slower.&lt;/p&gt;

&lt;h4 id=&quot;using-collection-accumulator&quot;&gt;Using Collection Accumulator&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Spark Accumulators&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spark provides accumulators which can be used as counters or to accumulate values across executors. Accumulators have a few drawbacks and hence we should be very careful while using it.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The accumulator is stored locally in all executors, and can be updated from executors.&lt;/li&gt;
  &lt;li&gt;Only the driver can read from an accumulator.&lt;/li&gt;
  &lt;li&gt;The accumulators are updated once a task completes successfully.&lt;/li&gt;
  &lt;li&gt;If a stage fails, for a node getting lost, then it is updated more than once.&lt;/li&gt;
  &lt;li&gt;If an accumulator is used in a transformation in Spark, then the values might not be reliable. If multiple actions use the transformed data frame, they would trigger multiple tasks (if it is not cached) which would lead to multiple updates to the accumulator for the same task.&lt;/li&gt;
  &lt;li&gt;In cases of speculative execution, Spark might update more than once.&lt;/li&gt;
  &lt;li&gt;The values from different executors are brought to the driver and accumulated at the end of the job. Thus there are no distributed locks on updating the value of the accumulator.&lt;/li&gt;
  &lt;li&gt;If the data is huge, and doesn’t fit in memory, then parts of might be recomputed when required, which might lead to multiple updates to the accumulator.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping the above properties in mind, we can still use Accumulators safely for our case considering that we immediately trigger an action after calling the accumulator. This prevents multiple updates.
In Spark 2.1.0, we can have the following code, which would handle the exceptions and append them to our accumulator.
We use Try - Success/Failure in the Scala way of handling exceptions.&lt;/p&gt;

&lt;p&gt;First we define our exception accumulator and register with the Spark Context&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;exceptionAccumulator&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CollectionAccumulator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Any&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Throwable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sparkSession&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;sparkContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;collectionAccumulator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Any&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Throwable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)](&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;exceptions&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We cannot have Try[Int] as a type in our DataFrame, thus we would have to handle the exceptions and add them to the accumulator. Finally our code returns null for exceptions.
While storing in the accumulator, we keep the column name and original value as an element along with the exception. This would help in understanding the data issues later.&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DataFrame&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;errorHandledRdd&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;RDD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;rdd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;memberIdStr&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getAs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;member_id&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;memberIdInt&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Try&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;memberIdStr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;integer&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Failure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;exceptionAccumulator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(((&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;member_id&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memberIdStr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Because other boxed types are not supported&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;fromSeq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toSeq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memberIdInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;errorHandledSchema&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StructType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;fields&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;StructField&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;member_id_int&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntegerType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)))&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;castDf&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Dataset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sparkSession&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;sqlContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;createDataFrame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;errorHandledRdd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;errorHandledSchema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;c1&quot;&gt;// Note: Ideally we must call cache on the above df, and have sufficient space in memory so that this is not recomputed.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Everytime the above map is computed, exceptions are added to the accumulators resulting in duplicates in the accumulator. &lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;castDf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;printSchema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;castDf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This works fine, and loads a null for invalid input.&lt;/p&gt;

&lt;p&gt;To see the exceptions, I borrowed this utility function:&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;printExceptions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acc&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CollectionAccumulator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Any&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Throwable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)])&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Unit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;acc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Throwable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// using org.apache.commons.lang3.exception.ExceptionUtils&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;--- Exception on input: $i : ${ExceptionUtils.getRootCauseMessage(e)}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ExceptionUtils.getStackTrace(e) for full stack trace&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// calling the above to print the exceptions&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;printExceptions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exceptionAccumulator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This looks good, for the example. But say we are caching or calling multiple actions on this error handled df. This would result in invalid states in the accumulator.
To demonstrate this lets analyse the following code:&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;castDf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Show has been called once, the exceptions are : &quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;printExceptions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exceptionAccumulator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Cache and show the df again&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;castDf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Now the contents of the accumulator are : &quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;printExceptions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exceptionAccumulator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The output is as follows:&lt;/p&gt;
&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;go&quot;&gt;+---------+-------------+
|member_id|member_id_int|
+---------+-------------+
|      981|          981|
|        a|         null|
+---------+-------------+

Show has been called once, the exceptions are : 
--- Exception on input: (member_id,a) :  NumberFormatException: For input string: &quot;a&quot;
Cache and show the df again
+---------+-------------+
|member_id|member_id_int|
+---------+-------------+
|      981|          981|
|        a|         null|
+---------+-------------+

Now the contents of the accumulator are : 
--- Exception on input: (member_id,a) :  NumberFormatException: For input string: &quot;a&quot;
--- Exception on input: (member_id,a) :  NumberFormatException: For input string: &quot;a&quot;

Process finished with exit code 0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It is clear that for multiple actions, accumulators are not reliable and should be using only with actions or call actions right after using the function. When a cached data is being taken, at that time it doesn’t recalculate and hence doesn’t update the accumulator.&lt;/p&gt;

&lt;h4 id=&quot;log-exceptions-to-a-new-column&quot;&gt;Log Exceptions to a new Column&lt;/h4&gt;
&lt;p&gt;Another interesting way of solving this is to log all the exceptions in another column in the data frame, and later analyse or filter the data based on this column.
In the following code, we create two extra columns, one for output and one for the exception.&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DataFrame&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;rddWithExcep&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;rdd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;memberIdStr&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getAs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;member_id&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;memberIdInt&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Try&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;memberIdStr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Failure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;fromSeq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toSeq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memberIdInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;castWithExceptionSchema&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StructType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;df&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;fields&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;StructField&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;member_id_int&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntegerType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StructField&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;exceptions&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StringType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)))&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;castExcepDf&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sparkSession&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;sqlContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;createDataFrame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rddWithExcep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;castWithExceptionSchema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;castExcepDf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;printSchema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;castExcepDf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now we have the data as follows, which can be easily filtered for the exceptions and processed accordingly&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;+---------+-------------+------------------------------------------------------+
|member_id|member_id_int|exceptions                                            |
+---------+-------------+------------------------------------------------------+
|981      |981          |null                                                  |
|a        |null         |java.lang.NumberFormatException: For input string: &quot;a&quot;|
+---------+-------------+------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Would love to hear more ideas about improving on these.&lt;/p&gt;

&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;http://danielwestheide.com/blog/2012/12/26/the-neophytes-guide-to-scala-part-6-error-handling-with-try.html&lt;/li&gt;
  &lt;li&gt;https://www.nicolaferraro.me/2016/02/18/exception-handling-in-apache-spark/&lt;/li&gt;
  &lt;li&gt;http://rcardin.github.io/big-data/apache-spark/scala/programming/2016/09/25/try-again-apache-spark.html&lt;/li&gt;
  &lt;li&gt;http://stackoverflow.com/questions/29494452/when-are-accumulators-truly-reliable&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Sagar Ingale</name><email>sagaringale7794@gmail.com</email></author><category term="spark" /><category term="data-engineering" /><category term="spark" /><category term="scala" /><category term="data-frames" /><category term="data-errors" /><summary type="html">General Exception Handling</summary></entry><entry><title type="html">SQL Analytical Functions - II - Window Frames, ROWS and RANGE</title><link href="https://sagaringale7.github.io/sql/window-frames-sql-analytical-functions/" rel="alternate" type="text/html" title="SQL Analytical Functions - II - Window Frames, ROWS and RANGE" /><published>2017-04-29T17:38:49+02:00</published><updated>2017-04-29T17:38:49+02:00</updated><id>https://sagaringale7.github.io/sql/window-frames-sql-analytical-functions</id><content type="html" xml:base="https://sagaringale7.github.io/sql/window-frames-sql-analytical-functions/">&lt;p&gt;A follow up post about specifying window frames to SQL analytical functions. This assumes you have already read my previous post where I described the use of OVER(), PARTITION BY and ORDER BY clause.&lt;/p&gt;

&lt;h3 id=&quot;rows-and-range-clause&quot;&gt;ROWS and RANGE clause&lt;/h3&gt;
&lt;p&gt;The Over clause also allows us to specify window frames on which the analytical functions get applied. This is useful in calculating things like moving average and cumulative sum using SQL.
The partition by clause allows us to group the data set before the analytical function is applied. However it doesn’t allow us to choose different sets records for each input record to apply a function.
Consider we have a rainfall data set for a city for 1 month. It has a schema like “date, precipitation”. We want to find the out the total rainfall for the past 5 dates for each day of the month. For arguments sake, lets visually ignore the first 5 values, since they wont be having data for the last 5 days.
Here we see that to find sum, we can use the SUM function but we need only the last 5 records and not all the records, for each row.
Lets start with a simpler problem, and find the sum of all records and have that as a column.&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;precipitation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;SUM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;precipitation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_precipitation&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rainfall&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next we would apply the window on the input data to the SUM function, so that only the last 5 days are taken. For this we would first sort the data and then add the window as follows&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;precipitation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;SUM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;precipitation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ROWS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BETWEEN&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PRECEDING&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PRECEDING&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_precipitation&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rainfall&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This query first orders the data based on date, and then selects the window frame for each row, which consists of the previous 5 rows based on the ordering specified. This set of records changes for every record. The analytical function is then applied on the selected window. This is particularly useful for moving averages.&lt;/p&gt;

&lt;p&gt;ROWS or RANGE requires that the ORDER BY clause be specified.&lt;/p&gt;

&lt;p&gt;Practice problem &lt;a href=&quot;https://www.hackerearth.com/problem/sql/calculate-moving-average/&quot;&gt;Calculate Moving Average on HackerEarth&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;using-partition-by-order-by-and-rowsrange-together&quot;&gt;Using PARTITION BY, ORDER BY and ROWS/RANGE together&lt;/h3&gt;
&lt;p&gt;Consider the above example but lets say now the data set contains data for multiple cities. Now the precipitation for each city would be different and the sum would also have to be calculated differently. Here we have the data set as “city, date, precipitation”. We would patition the data by city in the Over clause.&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;city&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;precipitation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;SUM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;precipitation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;city&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ROWS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BETWEEN&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PRECEDING&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PRECEDING&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_precipitation&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rainfall&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;When applied with partition by clause, the window frame is applied separately for each partition.&lt;/p&gt;

&lt;h5 id=&quot;other-keywords&quot;&gt;Other keywords&lt;/h5&gt;
&lt;p&gt;The window frame can be specified using (but not limited to) the following:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;ROWS BETWEEN 1 PRECEDING AND CURRENT ROW -&amp;gt; the current value and one previous&lt;/li&gt;
  &lt;li&gt;ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -&amp;gt; all records from start to current row in the given ordering. This is useful for cumulative sum calculation&lt;/li&gt;
  &lt;li&gt;ROWS BETWEEN 2 FOLLOWING AND 10 FOLLOWING -&amp;gt; records after the current row as per the number specified.&lt;/li&gt;
  &lt;li&gt;ROWS 5 PRECEDING -&amp;gt; This is equivalent to ROWS BETWEEN 5 PRECEDING AND CURRENT ROW&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;summary&quot;&gt;Summary&lt;/h4&gt;
&lt;p&gt;This is very useful for statistical analysis which involves calculation where the input set record for a particular analytical function changes for every record.
Applications of this technique include calculation of Year to Date sales, seasonal precipitation analysis, etc.&lt;/p&gt;</content><author><name>Sagar Ingale</name><email>sagaringale7794@gmail.com</email></author><category term="sql" /><category term="sql" /><category term="analytics" /><category term="data-exploration" /><category term="analytical-functions" /><summary type="html">A follow up post about specifying window frames to SQL analytical functions. This assumes you have already read my previous post where I described the use of OVER(), PARTITION BY and ORDER BY clause.</summary></entry><entry><title type="html">SQL Analytical Functions - I - Overview, PARTITION BY and ORDER BY</title><link href="https://sagaringale7.github.io/sql/sql-analytical-functions-overview-partition-by-order-by/" rel="alternate" type="text/html" title="SQL Analytical Functions - I - Overview, PARTITION BY and ORDER BY" /><published>2017-04-27T17:38:49+02:00</published><updated>2017-04-27T17:38:49+02:00</updated><id>https://sagaringale7.github.io/sql/sql-analytical-functions-overview-partition-by-order-by</id><content type="html" xml:base="https://sagaringale7.github.io/sql/sql-analytical-functions-overview-partition-by-order-by/">&lt;p&gt;For a long time I had faced a lot of problems while working with data bases and SQL where in order to get a better understanding of the available data, simple aggregations using group by and joins were not enough. For deep diving into data, advanced SQL features were necessarry.&lt;/p&gt;

&lt;h4 id=&quot;introduction&quot;&gt;Introduction&lt;/h4&gt;
&lt;p&gt;Aggregate functions are good with the Group By clause for most simple data related calculations. However things like having the average of a group as a column or calculating the deviation from average for each record becomes increasingly complex to express using group by and self join in a sql query.
Often while learning SQL, the group by and join are probably enough to gain a basic understanding but very difficult for most analytical operations where the aim is to understand the data.&lt;/p&gt;

&lt;p&gt;A few features of the SQL dialect available in most RDBMS or SQL interface which help in better understanding are&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;the OVER() clause&lt;/li&gt;
  &lt;li&gt;the analytical functions, for example AVG, LAG, LEAD, PERCENTILE_RANK&lt;/li&gt;
  &lt;li&gt;the PARTITION BY clause, for example PARTITION BY dept, job&lt;/li&gt;
  &lt;li&gt;the ORDER BY clause, for example order by job nulls last&lt;/li&gt;
  &lt;li&gt;the ROW or RANGE clause for the window frame extent, for example RANGE UNBOUNDED PRECEDING or ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over the course of a series of posts, I will try to explain the use of these in SQL along with solutions to few problems from HackerEarth for practice and better understanding.&lt;/p&gt;

&lt;h4 id=&quot;recap&quot;&gt;Recap&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;The aggregate function and GROUP BY:&lt;/em&gt; This post assumes that the reader has good knowledge of group by and finding aggregates for a given data set.
As a quick example, suppose we want to find the average marks scored by students from a table having “id, name, subject, marks” we can run&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AVG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The above query gives one record as output which is the average of the whole data.
Now to find AVG of marks for each subject, we can use a group by clause:&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AVG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now I have applied a group by clause and specified the column to group the data. This has produced as many groups as there are subjects, and calculated avg marks for each subject. This has returned me records exactly equal to the number of distinct subject values in the table.&lt;/p&gt;

&lt;h3 id=&quot;over-clause&quot;&gt;OVER() clause&lt;/h3&gt;
&lt;p&gt;The Over clause is used to define the partition, ordering and window extent about how a particular analytical function would act on the data set.
Analytical functions that are used can be AVG, SUM (which are aggregate functions) or RANK, ROW_NUMBER which are purely analytical functions. While using the analytical version of these functions, we don’t need a group by clause and the number of records produced are same as the input records.
Lets us take a simple example of AVG, and apply it to a dataset of “id, name, subject, marks”&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;AVG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OVER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;avg_marks&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here the OVER clause is blank and it doesn’t partition / order / window the data before applying the AVG function. Thus the output has an extra column with the avg marks of all the records as a new column.
The real application of the over clause is when we use it to specify how to handle the data before applying the AVG function and what should act as inputs to each function calls.&lt;/p&gt;

&lt;h3 id=&quot;partition-by-clause&quot;&gt;PARTITION BY clause&lt;/h3&gt;
&lt;p&gt;Lets say our aim was to find out how much each student’s marks deviates from the mean for that subject. For this we know that we don’t want to aggregate the data and want to keep the same number of rows as the input.
An easy way to approach this problem is to find the average of marks for each subject and then have that as a column and find the difference for each student and subject mark.
Now suppose we wanted to have avg marks for each subject as a column, without the over clause, would have meant grouping the data by subject, and then applying the AVG function for each group.
This would produce records equal to the number of subjects. To get the avg as another column we would have to do a self join and then find the deviation for each student making things increasingly complex.&lt;/p&gt;

&lt;p&gt;The OVER clause allows us to apply a function over a specified subset of input and have that aggregate or output as a column.
For our example, we need to find average for each subject, which means we can partition the data based on each subject and then apply our function on each partition, have the output as a column.
Lets try the query&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;AVG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;avg_subject_marks&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The above query applies the AVG of marks after partitioning the data by subject. So average is calculated for each subject, and is returned as a column.
Our initial problem to find the difference of each student from subject average is now simple and can be expressed as follows:&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;ABS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AVG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mean_subject_diff&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;order-by-clause&quot;&gt;ORDER BY clause&lt;/h3&gt;
&lt;p&gt;We all know using the Order by clause to sort the query output. But inside the Over Clause the order by is used to specify the order in which the data would be provided to the analytical function as input and processed.
This is particularly useful with ranking and percentile functions which work on ordered datasets.
Consider we have to find the rank of each student. For this we create a temporary table which has data for each student.&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_marks&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SUM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_marks&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Thus our input data set has 3 columns, and total records equal to the total number of students.
To find the rank, we simply order the data descending by total_marks, and put a rank against it.&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RANK&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_marks&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;overall_rank&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_marks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here the OVER clause specified how to order the data before passing it over to the rank function which assigns a new value for each input record.
Yes, these to queries can be combined using a sub query or a CTE, but to keep things simple I made it as a table.&lt;/p&gt;

&lt;h3 id=&quot;using-partition-by-along-with-order-by&quot;&gt;Using PARTITION BY along with ORDER BY&lt;/h3&gt;
&lt;p&gt;Consider we have to find the rank of each student for each subject. For this we partition the data for each subject and then order the students based on their ranks. Now we can easily put a number and have a rank for each student for each subject.
The query looks like&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;RANK&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject_rank&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This query is pretty readable and self explanatory. It says to partition the data first and then order it descending based on marks, and finally put a rank for each record.&lt;/p&gt;

&lt;h4 id=&quot;summary&quot;&gt;Summary&lt;/h4&gt;
&lt;p&gt;The above techniques cover most requirements for simple understandings. Coupled with functions like RANK(), DENSE_RANK(), ROW_NUMBER(), NTILE() which we would soon visit and try to understand, these techniques give a lot of simple ways to explore data.
In the future blog posts I would write about using ROWS and RANGE to specify window frames to apply analytical functions.&lt;/p&gt;</content><author><name>Sagar Ingale</name><email>sagaringale7794@gmail.com</email></author><category term="sql" /><category term="sql" /><category term="analytics" /><category term="data-exploration" /><category term="analytical-functions" /><summary type="html">For a long time I had faced a lot of problems while working with data bases and SQL where in order to get a better understanding of the available data, simple aggregations using group by and joins were not enough. For deep diving into data, advanced SQL features were necessarry.</summary></entry><entry><title type="html">Geo Location Batch Search in Spark</title><link href="https://sagaringale7.github.io/spark/geo-search-spark/" rel="alternate" type="text/html" title="Geo Location Batch Search in Spark" /><published>2017-04-22T17:38:49+02:00</published><updated>2017-04-22T17:38:49+02:00</updated><id>https://sagaringale7.github.io/spark/geo-search-spark</id><content type="html" xml:base="https://sagaringale7.github.io/spark/geo-search-spark/">&lt;p&gt;A module written in Scala for Apache Spark v2.0.0 to batch process mapping of Geo Locations in two skewed data sets.
Link to code: https://github.com/anish749/geo-search-spark&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Apache Spark application written in Scala to map given latitude longitude values to nearest latitude longitude values in a given set using functional programming simulating a map side join of two data sets.
This takes two sets of data as input. A master set of latitude longitude values (which are considered constant), and a set of latitude longitudes which are to be searched and mapped to this master set. This is expected to change on every run of the ETL/ join code.&lt;/p&gt;
&lt;h5 id=&quot;assumption&quot;&gt;Assumption:&lt;/h5&gt;
&lt;ul&gt;
  &lt;li&gt;The master data set can be cached in memory, thus not very large. This is the index being used, and only has the available pairs of latitude and longitudes. An id can also be attached to this to track each point. This data set is already sorted (or can be sorted using the code provided) based on latitudes and then optionally on longitudes.&lt;/li&gt;
  &lt;li&gt;The master set has all unique coordinates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;mapping-logic&quot;&gt;Mapping logic&lt;/h5&gt;
&lt;p&gt;The two sets of coordinates are mapped based on the distance calculated by using the Haversine formula. The max limits considered as near can be specified while using the utility:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;maxDistMiles - maximum distance that is considered as near (in miles) [used 30 here]&lt;/li&gt;
  &lt;li&gt;latLimit, lonLimit - To increase efficiency, the area in which the actual distances are calculated is narrowed down by using these limits. Eg: 0.5 latLimit means the distance of points beyond searchLat +- 0.5 are ignored, since 0.5 degree means 34.5 miles approx, the mapped lat lon values would not lie beyond this range. Similarly for longitude, till 83 deg latitude the values for 30 miles is 3.9 degree&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Geosearch in databases and SQL queries (if written in Hive) use theta joins which are not supported in Hive, thus forcing cross products. This version using broadcasting of a sorted index to reduce the need for a cross product using a map side join.&lt;/p&gt;

&lt;p&gt;A map reduce version of the code with a map side theta join implementation is available at https://github.com/anish749/geo-search-mapreduce&lt;/p&gt;

&lt;h2 id=&quot;build&quot;&gt;Build&lt;/h2&gt;
&lt;p&gt;The project is written in Scala 2.11.6, and dependencies are managed using Maven 3.3.9. This is written in Apache Spark v2.0.0&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mvn clean &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;usage&quot;&gt;Usage&lt;/h2&gt;

&lt;h3 id=&quot;adding-this-package-as-a-dependency&quot;&gt;Adding this package as a dependency&lt;/h3&gt;
&lt;p&gt;Once installed via maven, this package can be added as a dependency as follows:&lt;/p&gt;
&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.anish.spark&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;geosearch&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0-SNAPSHOT&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note: This is not available as a package in Maven Central repository.&lt;/p&gt;

&lt;h3 id=&quot;as-a-stand-alone-application&quot;&gt;As a stand alone application&lt;/h3&gt;
&lt;h5 id=&quot;create-index&quot;&gt;Create index&lt;/h5&gt;
&lt;p&gt;The first step is to create an index (a sorted array available to all executors during mapping) using the data for available latitude longitudes.
This is provided by the com.anish.spark.geosearch.Indexer class. The path where the index would be stored and where the available latitude longitudes are present are configured as variables in this class. (Should be changed / taken as arguments before usage in projects)
This class reads the files in the ‘input/availableLatLon’ folder which are configured to be ‘;’ separated pairs of latitude longitudes to which data would be mapped. It then creates an index out of this data and saves it as files in the index Path given after sorting this based on latitudes and then on longitudes.&lt;/p&gt;

&lt;h5 id=&quot;run-batch-search&quot;&gt;Run batch search&lt;/h5&gt;
&lt;p&gt;This is the actual batch job which searches for nearest latitude longitudes and maps them. This is implemented in the class named com.anish.spark.geosearch.Search.
This can be run as follows:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;java &lt;span class=&quot;nt&quot;&gt;-jar&lt;/span&gt; target/geosearch-1.0-SNAPSHOT.jar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It would automatically set the following configurations for demo as described in the Mapping Logic section:&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;latLimit&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;lonLimit&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;4.0&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;maxDistMiles&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;30.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It would take the unknownLatLons as input and map them to the nearest available lat lon as read from the index and write the output in the folder configured.&lt;/p&gt;

&lt;h3 id=&quot;batch-search-in-spark-applications-scala-api&quot;&gt;Batch Search in Spark applications (Scala API)&lt;/h3&gt;
&lt;p&gt;From a Scala/Java Application where the Spark Session is not available, we can call the following function to execute the search:&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;runSparkBatchSearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indexFilePath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unknownLatLonPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outputPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;latLimit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lonLimit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxDistMiles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If the Spark Session is available, then the actual search logic is implemented in the following function and can be called directly:&lt;/p&gt;
&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * @param sparkSession  The spark session. This is used for importing spark implicits
 * @param indexBc       Available latitude longitude Spark Broadcast variable
 * @param unknownLatLon A spark Dataset of the Coordinates that are to be mapped.
 * @return A DataSet of Row, which has all given Coordinates mapped to available Coordinates. (null for those which were not mapped)
 */&lt;/span&gt;

&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sparkSession&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SparkSession&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indexBc&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Broadcast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Coordinate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unknownLatLon&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Dataset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Coordinate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;latLimit&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Double&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lonLimit&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Double&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxDistMiles&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Double&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Dataset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;hr /&gt;</content><author><name>Sagar Ingale</name><email>sagaringale7794@gmail.com</email></author><category term="spark" /><category term="spark" /><category term="mapreduce" /><category term="scala" /><category term="spacial" /><category term="join" /><category term="optimization" /><summary type="html">A module written in Scala for Apache Spark v2.0.0 to batch process mapping of Geo Locations in two skewed data sets. Link to code: https://github.com/anish749/geo-search-spark</summary></entry></feed>