<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>aviyehuda.com</title>
	<atom:link href="https://aviyehuda2.wordpress.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://aviyehuda2.wordpress.com</link>
	<description>Software, Data, Tech ...</description>
	<lastBuildDate>Mon, 05 Jan 2026 16:59:57 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<site xmlns="com-wordpress:feed-additions:1">237945424</site><cloud domain='aviyehuda2.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>https://s0.wp.com/i/buttonw-com.png</url>
		<title>aviyehuda.com</title>
		<link>https://aviyehuda2.wordpress.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="https://aviyehuda2.wordpress.com/osd.xml" title="aviyehuda.com" />
	<atom:link rel='hub' href='https://aviyehuda2.wordpress.com/?pushpress=hub'/>
	<item>
		<title>Merge + liquid clustering &#8211; common issues</title>
		<link>https://aviyehuda2.wordpress.com/2026/01/03/merge-liquid-clustering-common-issues/</link>
					<comments>https://aviyehuda2.wordpress.com/2026/01/03/merge-liquid-clustering-common-issues/#respond</comments>
		
		<dc:creator><![CDATA[avi yehuda]]></dc:creator>
		<pubDate>Sat, 03 Jan 2026 09:34:45 +0000</pubDate>
				<category><![CDATA[Data Lake]]></category>
		<category><![CDATA[Spark]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Big Data]]></category>
		<category><![CDATA[Databricks]]></category>
		<category><![CDATA[Deltalake]]></category>
		<category><![CDATA[Liquid Clustering]]></category>
		<guid isPermaLink="false">http://aviyehuda2.wordpress.com/?p=5556</guid>

					<description><![CDATA[As a Spark support engineer, I still encounter many cases where MERGE or JOIN operations on Delta tables do not perform as expected even when liquid clustering is used. While liquid clustering is a significant improvement over traditional partitioning and offers many advantages, people still sometimes struggle. There is often an assumption that enabling liquid [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img width="1024" height="456" data-attachment-id="5579" data-permalink="https://aviyehuda2.wordpress.com/2026/01/03/merge-liquid-clustering-common-issues/image-11/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-6.png" data-orig-size="1104,492" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-6.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-6.png?w=1024" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-6.png?w=1024" alt="" class="wp-image-5579" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-6.png?w=1024 1024w, https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-6.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-6.png?w=300 300w, https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-6.png?w=768 768w, https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-6.png 1104w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>As a Spark support engineer, I still encounter many cases where MERGE or JOIN operations on Delta tables do not perform as expected even when liquid clustering is used. While liquid clustering is a significant improvement over traditional partitioning and offers many advantages, people still sometimes struggle. There is often an assumption that enabling liquid clustering will automatically result in efficient merges, but in practice this is not always true and the reason is lack of understanding.&nbsp;</p>



<p>Here are the most common issues when executing merge on a liquid clustering table.&nbsp;</p>



<p><strong>1. Clustering by a key, merging by another</strong></p>



<p>This is one of the most straightforward and common problems. If a table is clustered by one key but the MERGE condition uses a different key, liquid clustering cannot help.</p>



<p>When the merge key is not part of the clustering keys, Spark is generally unable to prune files when scanning the target table. Each incoming key must be searched across the entire dataset. As a result, Spark often performs a full scan and falls back to a Sort Merge Join or, when the dataset is small enough, a Broadcast Nested Loop Join, both of which can be expensive at scale.</p>



<p><strong>2. Clustering by multiple keys, merging by only one</strong><strong><br></strong>In some cases, users do merge on a clustering key but still observe little or no pruning. A frequent cause is clustering on multiple columns.</p>



<p>Liquid clustering relies on indexing, but adding more clustering keys dilutes the effectiveness of the index for each individual column. Instead of having a strong index on a single dimension, the data layout is optimized across combinations of keys. When a merge filters on only one of those keys, pruning becomes much less effective.</p>



<p></p>



<figure class="wp-block-image size-large"><img width="579" height="352" data-attachment-id="5568" data-permalink="https://aviyehuda2.wordpress.com/2026/01/03/merge-liquid-clustering-common-issues/image-9/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-4.png" data-orig-size="579,352" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-4.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-4.png?w=579" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-4.png?w=579" alt="" class="wp-image-5568" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-4.png 579w, https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-4.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-4.png?w=300 300w" sizes="(max-width: 579px) 100vw, 579px" /></figure>



<p class="has-small-font-size" style="border-style:none;border-width:0px">Clustering by a single index</p>



<p></p>



<figure class="wp-block-image size-large"><img width="561" height="388" data-attachment-id="5559" data-permalink="https://aviyehuda2.wordpress.com/2026/01/03/merge-liquid-clustering-common-issues/image-6/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2.png" data-orig-size="561,388" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2.png?w=561" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2.png?w=561" alt="" class="wp-image-5559" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2.png 561w, https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2.png?w=300 300w" sizes="(max-width: 561px) 100vw, 561px" /></figure>



<p class="has-small-font-size">Clustering by multiple indexes</p>



<p></p>



<p></p>



<p><strong>3. Clustering and merging on the same key, but the key has no natural order</strong></p>



<p>Liquid clustering is well suited for high-cardinality dimensions, but the <em>type</em> of key still matters. If the clustering key does not have a logical or natural order, it may not behave well as a merge key.</p>



<p>Date or timestamp columns are usually excellent candidates. For example, merging data from only the previous month into a table clustered by date allows Spark to prune most of the files efficiently.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="579" height="352" data-attachment-id="5569" data-permalink="https://aviyehuda2.wordpress.com/2026/01/03/merge-liquid-clustering-common-issues/image-10/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-5.png" data-orig-size="579,352" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-5.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-5.png?w=579" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-5.png?w=579" alt="" class="wp-image-5569" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-5.png 579w, https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-5.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-5.png?w=300 300w" sizes="(max-width: 579px) 100vw, 579px" /></figure>



<p>By contrast, consider a user ID represented as a UUID. Even if the table is clustered by this column, pruning will likely be minimal. Liquid clustering indexes data using value ranges. Dates have a natural ordering, so filtering by a range maps cleanly to those indexes. UUIDs, however, are effectively random, causing most ranges to overlap and forcing Spark to scan a large portion of the table.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="579" height="352" data-attachment-id="5560" data-permalink="https://aviyehuda2.wordpress.com/2026/01/03/merge-liquid-clustering-common-issues/image-7/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2-1.png" data-orig-size="579,352" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2-1.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2-1.png?w=579" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2-1.png?w=579" alt="" class="wp-image-5560" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2-1.png 579w, https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2-1.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2-1.png?w=300 300w" sizes="(max-width: 579px) 100vw, 579px" /></figure>



<p><strong>4. Clustering and merging by an ordered key, but it has a very wide merge range</strong></p>



<p>There are cases where users correctly cluster and merge on an ordered key, such as a creation date, yet the merge still takes a long time. In these scenarios, the issue often lies in the incoming dataframe rather than the target table.</p>



<p>Liquid clustering uses min–max ranges to index data files. If the incoming dataset contains values spanning a very large range, for example, dates from three years ago through today, those ranges overlap many indexed files. This overlap significantly reduces pruning and results in large scans despite correct clustering.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="579" height="352" data-attachment-id="5557" data-permalink="https://aviyehuda2.wordpress.com/2026/01/03/merge-liquid-clustering-common-issues/image-4/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image.png" data-orig-size="579,352" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image.png?w=579" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image.png?w=579" alt="" class="wp-image-5557" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image.png 579w, https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image.png?w=300 300w" sizes="(max-width: 579px) 100vw, 579px" /></figure>



<p><strong>5. Complex or non-pushdown-friendly filtering conditions</strong></p>



<p>One of the common ways to force pruning, is to use hard coded filtering inside the merge condition or right before doing the merge. That is indeed a very good solution to help Spark AQE to understand which files can be skipped, however it doesn’t always work. Even when it appears that Spark <em>should</em> be able to skip files, it sometimes cannot. A common reason is overly complex filtering logic.</p>



<p>Filters involving functions, casts, nested expressions, or non-deterministic logic can prevent predicate pushdown and make it difficult for Spark to reason about value ranges. When Spark cannot translate filters into simple range predicates, file skipping becomes ineffective.</p>



<p>Since this post is already quite long, I’ll write a separate one with examples of such complex filtering conditions.</p>



<p>In case you do use complex conditions though, one of the things that can help is to enable Photon on the cluster. Photon has some performance improvements that work in such cases.</p>



<p><strong>Recommendations</strong></p>



<p>The most important advice is when designing a merge on a liquid clustered table, keep in mind the 2 sides of the merge &#8211; the target table and the merged dataframe. Try to see how you make sure that the range of values of key in the merged dataframe will be pulled by spark from only a limited number of files.</p>



<p>Here are some practical practices you can follow:</p>



<ul class="wp-block-list">
<li>Use a merge key that is also a clustering key.</li>



<li>Minimize the number of clustering keys.</li>



<li>Prefer clustering keys with a logical order (such as dates or timestamps).</li>



<li>Keep the value range of the merge key in the incoming dataframe as narrow as possible.</li>



<li>Apply filters early, ideally before the merge or directly in the merge condition. Keep filtering conditions as much as possible.</li>



<li>Regularly optimize the table to maintain an efficient layout.</li>
</ul>



<p>Liquid clustering is a powerful optimization, but its effectiveness depends heavily on how well the clustering strategy aligns with merge patterns and data characteristics. Understanding these trade-offs is key to avoiding unexpected performance bottlenecks.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://aviyehuda2.wordpress.com/2026/01/03/merge-liquid-clustering-common-issues/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5556</post-id>
		<media:content url="https://2.gravatar.com/avatar/58277fd5adf41424400f6241b432f5cbe201500ba80bc32cd8a25cf94285f3dc?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">aviyehuda555</media:title>
		</media:content>

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-6.png?w=1024" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-4.png?w=579" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2.png?w=561" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-5.png?w=579" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image-2-1.png?w=579" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2026/01/image.png?w=579" medium="image" />
	</item>
		<item>
		<title>Let Users Ask Questions to Your Data Lake Using AI and Spark</title>
		<link>https://aviyehuda2.wordpress.com/2025/07/06/let-users-ask-questions-to-your-data-lake-using-ai-and-spark/</link>
					<comments>https://aviyehuda2.wordpress.com/2025/07/06/let-users-ask-questions-to-your-data-lake-using-ai-and-spark/#respond</comments>
		
		<dc:creator><![CDATA[avi yehuda]]></dc:creator>
		<pubDate>Sun, 06 Jul 2025 15:04:12 +0000</pubDate>
				<category><![CDATA[ai]]></category>
		<category><![CDATA[Data Lake]]></category>
		<category><![CDATA[Spark]]></category>
		<category><![CDATA[DataLake]]></category>
		<category><![CDATA[Data_engineering]]></category>
		<category><![CDATA[llm]]></category>
		<guid isPermaLink="false">http://aviyehuda.com/?p=5036</guid>

					<description><![CDATA[In my past roles as a data infrastructure engineer, building systems that enabled analysts to query the data warehouse in a simple, efficient, and cost-effective way was always a challenge. With the rise of AI, this task has become much easier. However, letting AI query datasets can be costly, and there’s always the risk of [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="447" data-attachment-id="5109" data-permalink="https://aviyehuda2.wordpress.com/2025/07/06/let-users-ask-questions-to-your-data-lake-using-ai-and-spark/chatgpt-image-jul-6-2025-11_48_54-am-3/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/chatgpt-image-jul-6-2025-11_48_54-am-2.png" data-orig-size="1530,669" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="ChatGPT Image Jul 6, 2025, 11_48_54 AM" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/chatgpt-image-jul-6-2025-11_48_54-am-2.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/chatgpt-image-jul-6-2025-11_48_54-am-2.png?w=1024" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/chatgpt-image-jul-6-2025-11_48_54-am-2.png?w=1024" alt="" class="wp-image-5109" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/chatgpt-image-jul-6-2025-11_48_54-am-2.png?w=1024 1024w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/chatgpt-image-jul-6-2025-11_48_54-am-2.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/chatgpt-image-jul-6-2025-11_48_54-am-2.png?w=300 300w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/chatgpt-image-jul-6-2025-11_48_54-am-2.png?w=768 768w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/chatgpt-image-jul-6-2025-11_48_54-am-2.png?w=1440 1440w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/chatgpt-image-jul-6-2025-11_48_54-am-2.png 1530w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>In my past roles as a data infrastructure engineer, building systems that enabled analysts to query the data warehouse in a simple, efficient, and cost-effective way was always a challenge. With the rise of AI, this task has become much easier. However, letting AI query datasets can be costly, and there’s always the risk of hallucinations. More importantly, exposing sensitive data to large language models is often not acceptable.</p>



<p>By incorporating Spark into the workflow, costs are significantly reduced, hallucinations become far less likely, and most importantly, there&#8217;s no need to send confidential data to an external LLM.</p>



<h2 class="wp-block-heading">Querying a specific dataset with Spark 4</h2>



<p>Recently, Spark introduced a great new feature that allows you to ask questions in natural language directly on a DataFrame.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
spark_ai = SparkAI(llm = chatOpenAI)
spark_ai.activate()
res_df = df.ai.transform("what product id has the highest revenue")
res_df.show()
</pre></div>


<p><br>This is a neat feature. However, the limitation is that it only works on a specific DataFrame. It would be far more useful if users could ask questions across the entire data catalog, allowing the AI to determine what data to query and how.</p>



<h2 class="wp-block-heading">Querying the entire data catalog with LLM + Spark</h2>



<p>While this can be achieved using an LLM alone, feeding the entire dataset to the model would be very expensive—and, as mentioned, potentially undesirable from a security perspective. A better approach is to describe the data to the LLM and ask it to generate a Spark SQL query that I can use for the actual execution using Spark. This way, although we might spend a bit more on compute, we save significantly on LLM token costs. It also greatly reduces the chances of mistakes.</p>



<p>To do this, I will use<a href="https://python.langchain.com/docs/integrations/chat/openai/"> langchain&#8217;s ChatOpenAI model</a>. I used <a href="https://cursor.com/">Cursor IDE</a> for generating the code for me. </p>



<p>The gist of the instuctions is:</p>



<ol class="wp-block-list">
<li>Describe the datasets to LLM.</li>



<li>Ask LLM to generate Spark SQL query based on the user&#8217;s question.</li>



<li>Trigger the query using Spark.sql()</li>
</ol>



<p>Here are the full instructions prompt I gave cursor:</p>



<p style="height: 230px;overflow: auto;padding: 10px;background-color:black;color:#AAA;border: 3px #AAA solid;font-size: 13px">&gt;&gt;&gt;create a new python file.<br><br>Create a program in spark that holds a map of dataframes.<br>The map has a key, which is the name of the dataframe ,
<br>the value is the dataframe, but also a description of the dataframe.
<br><br>Create 3 dataframes of data &#8211; produce description, product reviews and product sales.<br><br>The program get input from the program user, the user is asking a question in human text ,<br>the question related to the data in the dataframes.<br><br>The question goes through 2 phases.<br>First we send it to langchain ChatOpenAI.<br>In the prompt we ask explain to ChatOpenAI about our dataset,<br>but we don&#8217;t send the actual data, so not to lose money, we only send the name,<br>the description and the columns of the datasets.<br>We also ask ChatOpenAI to give us the result in a structure manner.<br>We will ask ChatOpenAI to give us the correct query in spark sql syntax to query the datasets.<br><br>The response can either be a follow up question or a query in spark sql syntax.<br><br>If the response is a follow up question than we will ask the user the followup question to the user and do the whole process again,<br>if the response is a query, than we will query our dataframes the query using spark.sql(query) and present the result to the user.<br><br>If the result has only one record and one column, we will display the value of that one cell.<br>Otherwise we will display it as a table to the user.<br><br>if there is more than 1 rows in the result,<br>after showing the result, ask the user whether they want to see a graph , if they do, than do df.plot() to display the graph</p>



<p><a href="https://github.com/aviyehuda/datalake_query/blob/main/datalake_ai_query.py"><img loading="lazy" width="100" height="100" data-attachment-id="5117" data-permalink="https://aviyehuda2.wordpress.com/2025/07/06/let-users-ask-questions-to-your-data-lake-using-ai-and-spark/images/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/images.jpeg" data-orig-size="225,225" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="images" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/images.jpeg?w=225" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/images.jpeg?w=225" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/images.jpeg?w=100" alt="" class="wp-image-5117" style="width:34px;height:auto" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/images.jpeg?w=100 100w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/images.jpeg?w=200 200w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/images.jpeg?w=150 150w" sizes="(max-width: 100px) 100vw, 100px" /> <font size="+1">Here is the generated code</font></a></p>



<p>And this is what I got back from Cursor: after asking a question about my datasets, the system understood what needed to be queried and even generated a graph.</p>



<div data-carousel-extra='{&quot;blog_id&quot;:237945424,&quot;permalink&quot;:&quot;https://aviyehuda2.wordpress.com/2025/07/06/let-users-ask-questions-to-your-data-lake-using-ai-and-spark/&quot;}'  class="wp-block-jetpack-tiled-gallery aligncenter is-style-rectangular"><div class=""><div class="tiled-gallery__gallery"><div class="tiled-gallery__row"><div class="tiled-gallery__col" style="flex-basis:100.00000%"><figure class="tiled-gallery__item"><img data-attachment-id="5136" data-permalink="https://aviyehuda2.wordpress.com/2025/07/06/let-users-ask-questions-to-your-data-lake-using-ai-and-spark/screenshot-2025-07-06-at-16-42-33/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png" data-orig-size="1928,662" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot 2025-07-06 at 16.42.33" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png?w=1024" data-attachment-id="5136" data-permalink="https://aviyehuda2.wordpress.com/2025/07/06/let-users-ask-questions-to-your-data-lake-using-ai-and-spark/screenshot-2025-07-06-at-16-42-33/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png" data-orig-size="1928,662" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot 2025-07-06 at 16.42.33" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png?w=1024" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png?strip=info&#038;w=600 600w,https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png?strip=info&#038;w=900 900w,https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png?strip=info&#038;w=1200 1200w,https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png?strip=info&#038;w=1500 1500w,https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png?strip=info&#038;w=1800 1800w,https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png?strip=info&#038;w=1928 1928w" alt="" data-height="662" data-id="5136" data-link="https://aviyehuda2.wordpress.com/?attachment_id=5136" data-url="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png?w=1024" data-width="1928" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png" /></figure></div></div><div class="tiled-gallery__row"><div class="tiled-gallery__col" style="flex-basis:100.00000%"><figure class="tiled-gallery__item"><img data-attachment-id="5137" data-permalink="https://aviyehuda2.wordpress.com/2025/07/06/let-users-ask-questions-to-your-data-lake-using-ai-and-spark/screenshot-2025-07-06-at-16-42-52/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.52.png" data-orig-size="1794,866" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot 2025-07-06 at 16.42.52" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.52.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.52.png?w=1024" data-attachment-id="5137" data-permalink="https://aviyehuda2.wordpress.com/2025/07/06/let-users-ask-questions-to-your-data-lake-using-ai-and-spark/screenshot-2025-07-06-at-16-42-52/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.52.png" data-orig-size="1794,866" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot 2025-07-06 at 16.42.52" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.52.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.52.png?w=1024" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.52.png?strip=info&#038;w=600 600w,https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.52.png?strip=info&#038;w=900 900w,https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.52.png?strip=info&#038;w=1200 1200w,https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.52.png?strip=info&#038;w=1500 1500w,https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.52.png?strip=info&#038;w=1794 1794w" alt="" data-height="866" data-id="5137" data-link="https://aviyehuda2.wordpress.com/?attachment_id=5137" data-url="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.52.png?w=1024" data-width="1794" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.52.png" /></figure></div></div><div class="tiled-gallery__row"><div class="tiled-gallery__col" style="flex-basis:100.00000%"><figure class="tiled-gallery__item"><img data-attachment-id="5141" data-permalink="https://aviyehuda2.wordpress.com/2025/07/06/let-users-ask-questions-to-your-data-lake-using-ai-and-spark/screenshot-2025-07-06-at-16-45-51/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.45.51.png" data-orig-size="1542,668" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot 2025-07-06 at 16.45.51" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.45.51.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.45.51.png?w=1024" data-attachment-id="5141" data-permalink="https://aviyehuda2.wordpress.com/2025/07/06/let-users-ask-questions-to-your-data-lake-using-ai-and-spark/screenshot-2025-07-06-at-16-45-51/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.45.51.png" data-orig-size="1542,668" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot 2025-07-06 at 16.45.51" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.45.51.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.45.51.png?w=1024" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.45.51.png?strip=info&#038;w=600 600w,https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.45.51.png?strip=info&#038;w=900 900w,https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.45.51.png?strip=info&#038;w=1200 1200w,https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.45.51.png?strip=info&#038;w=1500 1500w,https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.45.51.png?strip=info&#038;w=1542 1542w" alt="" data-height="668" data-id="5141" data-link="https://aviyehuda2.wordpress.com/?attachment_id=5141" data-url="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.45.51.png?w=1024" data-width="1542" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.45.51.png" /></figure></div></div></div></div></div>



<div style="height:125px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-large"><img loading="lazy" width="800" height="450" data-attachment-id="5132" data-permalink="https://aviyehuda2.wordpress.com/2025/07/06/let-users-ask-questions-to-your-data-lake-using-ai-and-spark/attachment/5/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/5.gif" data-orig-size="800,450" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="5" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/5.gif?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/5.gif?w=800" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/5.gif?w=800" alt="" class="wp-image-5132" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/5.gif 800w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/5.gif?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/5.gif?w=300 300w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/5.gif?w=768 768w" sizes="(max-width: 800px) 100vw, 800px" /></figure>



<p>Here the program asks for a followup question:</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="800" height="450" data-attachment-id="5134" data-permalink="https://aviyehuda2.wordpress.com/2025/07/06/let-users-ask-questions-to-your-data-lake-using-ai-and-spark/attachment/6/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/6.gif" data-orig-size="800,450" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="6" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/6.gif?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/6.gif?w=800" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/6.gif?w=800" alt="" class="wp-image-5134" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/6.gif 800w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/6.gif?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/6.gif?w=300 300w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/6.gif?w=768 768w" sizes="(max-width: 800px) 100vw, 800px" /></figure>



<p><strong>Notes:</strong></p>



<ul class="wp-block-list">
<li>This approach requires you to provide clear and accurate descriptions for every dataset in your catalog. The more detailed the descriptions, the lower the risk of errors.</li>



<li>Additionally, to improve the quality of the results, you can include a few sample (dummy) records for each dataset. This gives the LLM a better understanding of the data’s structure and semantics.</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://aviyehuda2.wordpress.com/2025/07/06/let-users-ask-questions-to-your-data-lake-using-ai-and-spark/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5036</post-id>
		<media:content url="https://2.gravatar.com/avatar/58277fd5adf41424400f6241b432f5cbe201500ba80bc32cd8a25cf94285f3dc?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">aviyehuda555</media:title>
		</media:content>

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/chatgpt-image-jul-6-2025-11_48_54-am-2.png?w=1024" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/images.jpeg?w=100" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.33.png" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.42.52.png" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/screenshot-2025-07-06-at-16.45.51.png" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/5.gif?w=800" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/07/6.gif?w=800" medium="image" />
	</item>
		<item>
		<title>Vector Search in Databricks</title>
		<link>https://aviyehuda2.wordpress.com/2025/05/25/vector-search-in-databricks/</link>
					<comments>https://aviyehuda2.wordpress.com/2025/05/25/vector-search-in-databricks/#respond</comments>
		
		<dc:creator><![CDATA[avi yehuda]]></dc:creator>
		<pubDate>Sun, 25 May 2025 07:16:47 +0000</pubDate>
				<category><![CDATA[ai]]></category>
		<category><![CDATA[Data Lake]]></category>
		<category><![CDATA[Databricks]]></category>
		<category><![CDATA[DataLake]]></category>
		<category><![CDATA[Data_engineering]]></category>
		<category><![CDATA[vector_search]]></category>
		<guid isPermaLink="false">http://aviyehuda.com/?p=4815</guid>

					<description><![CDATA[A short presentation I gave to my team in Databricks]]></description>
										<content:encoded><![CDATA[
<p>A short presentation I gave to my team in Databricks</p>



<iframe src="https://docs.google.com/presentation/d/e/2PACX-1vSFHgKlZ_W5ePAO8mR8sSpalCuTKZAC5tDTQpj95E8WwAgNrRE0oe3emz6Ld5Yzd4o7K2QH4gwlXlZw/pubembed?start=false&#038;loop=false&#038;delayms=3000" frameborder="0" width="860" height="569" marginheight="0" marginwidth="0" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://aviyehuda2.wordpress.com/2025/05/25/vector-search-in-databricks/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4815</post-id>
		<media:content url="https://2.gravatar.com/avatar/58277fd5adf41424400f6241b432f5cbe201500ba80bc32cd8a25cf94285f3dc?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">aviyehuda555</media:title>
		</media:content>
	</item>
		<item>
		<title>Databricks: Using DECLARE VARIABLE to overcome a file pruning issue in the SQL editor</title>
		<link>https://aviyehuda2.wordpress.com/2025/01/18/databricks-using-declare-variable-to-overcome-a-file-pruning-issue-in-the-sql-editor/</link>
					<comments>https://aviyehuda2.wordpress.com/2025/01/18/databricks-using-declare-variable-to-overcome-a-file-pruning-issue-in-the-sql-editor/#respond</comments>
		
		<dc:creator><![CDATA[avi yehuda]]></dc:creator>
		<pubDate>Sat, 18 Jan 2025 06:14:49 +0000</pubDate>
				<category><![CDATA[Spark]]></category>
		<category><![CDATA[Databricks]]></category>
		<category><![CDATA[SparkSQL]]></category>
		<guid isPermaLink="false">http://aviyehuda.com/?p=3954</guid>

					<description><![CDATA[File pruning is an optimization process in Spark that skips unneeded files from being read during query execution, based on the query&#8217;s filter condition. It is a critical performance optimization in distributed data processing systems like Spark, especially when working with large datasets stored in partitioned file formats such as Parquet, ORC, or Delta Lake. [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img data-attachment-id="3961" data-permalink="https://aviyehuda2.wordpress.com/code-lego-2/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/code-lego-edited.png" data-orig-size="1024,576" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="code-lego" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/code-lego-edited.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/code-lego-edited.png?w=1024" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/code-lego-edited.png" alt="" class="wp-image-3961" /></figure>



<p>File pruning is an optimization process in Spark that skips unneeded files from being read during query execution, based on the query&#8217;s filter condition. It is a critical performance optimization in distributed data processing systems like Spark, especially when working with large datasets stored in partitioned file formats such as Parquet, ORC, or Delta Lake.</p>



<p>In this query:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
SELECT * from my_table where my_key=123
</pre></div>


<p></p>



<figure class="wp-block-image size-large"><img loading="lazy" width="920" height="554" data-attachment-id="3968" data-permalink="https://aviyehuda2.wordpress.com/2025/01/18/databricks-using-declare-variable-to-overcome-a-file-pruning-issue-in-the-sql-editor/screenshot-2025-01-18-at-07-27-31-2/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.27.31-1.png" data-orig-size="920,554" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot 2025-01-18 at 07.27.31" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.27.31-1.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.27.31-1.png?w=920" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.27.31-1.png?w=920" alt="" class="wp-image-3968" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.27.31-1.png 920w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.27.31-1.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.27.31-1.png?w=300 300w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.27.31-1.png?w=768 768w" sizes="(max-width: 920px) 100vw, 920px" /></figure>



<p>Spark can skip reading files that do not contain the value 123 for my_key. This optimization reduces disk I/O and improves query performance.</p>



<p>File pruning is automatically enabled whether the user writes Spark code programmatically or uses SQL syntax in a notebook or the SQL editor.</p>



<p>However, when a user employs a user defined function (UDF) as a filter in a query, it may interfere with file pruning. For example:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
CREATE FUNCTION get_key_value_udf()

RETURNS INT

RETURN (SELECT key_value FROM config_table WHERE config_name = 'target_key' LIMIT 1); 

SELECT * from my_table where my_key = get_key_value_udf()
</pre></div>


<p>In most cases, the above query will prevent file pruning from working because Spark treats UDFs as black boxes and cannot infer their logic for optimization. As a result, Spark may read all the files, potentially degrading query performance.</p>



<h2 class="wp-block-heading">Cause</h2>



<p>Using a UDF prevents file pruning in Spark because UDFs are treated as black boxes by the Spark optimizer. Since the output of a UDF is determined only at runtime, it becomes nearly impossible for Spark to leverage UDFs for file pruning.</p>



<p>The solution should be easy when using Spark python/scala syntax. You would simply need to execute the UDF separately to get the result and use it in the filter.&nbsp;</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
from pyspark.sql.functions import udf
from pyspark.sql.types import IntegerType

@udf(IntegerType())
def get_key_value_udf():
    result = spark.sql("SELECT key_value FROM config_table WHERE config_name = 'target_key' LIMIT 1").collect()    
    return result&#91;0]&#91;'key_value'] if result else None

key_value = get_key_value_udf()
filtered_df = spark.table("my_table").filter(f"my_key = {key_value}")
</pre></div>


<p>The solution in SQL editor is not quite straight forward.&nbsp;</p>



<p>Using views or temp tables will not as well as Spark will execute them together with the actual application.</p>



<h2 class="wp-block-heading">Solution</h2>



<p>The solution is straightforward and quite elegant—you can accomplish this using <strong>`DECLARE VARIABLE`</strong>.</p>



<p>Example:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
DECLARE VARIABLE myvar INT DEFAULT 123;

SET VAR myvar = (SELECT key_value FROM config_table WHERE config_name = 'target_key' LIMIT 1)

SELECT * FROM my_table WHERE my_key = myvar;
</pre></div>


<p><a href="https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-ddl-declare-variable.html">More about DECLARE VARIABLE</a>.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="977" height="648" data-attachment-id="3969" data-permalink="https://aviyehuda2.wordpress.com/2025/01/18/databricks-using-declare-variable-to-overcome-a-file-pruning-issue-in-the-sql-editor/screenshot-2025-01-18-at-07-28-16/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.28.16.png" data-orig-size="977,648" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot 2025-01-18 at 07.28.16" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.28.16.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.28.16.png?w=977" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.28.16.png?w=977" alt="" class="wp-image-3969" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.28.16.png 977w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.28.16.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.28.16.png?w=300 300w, https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.28.16.png?w=768 768w" sizes="(max-width: 977px) 100vw, 977px" /></figure>
]]></content:encoded>
					
					<wfw:commentRss>https://aviyehuda2.wordpress.com/2025/01/18/databricks-using-declare-variable-to-overcome-a-file-pruning-issue-in-the-sql-editor/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3954</post-id>
		<media:content url="https://2.gravatar.com/avatar/58277fd5adf41424400f6241b432f5cbe201500ba80bc32cd8a25cf94285f3dc?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">aviyehuda555</media:title>
		</media:content>

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/code-lego-edited.png" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.27.31-1.png?w=920" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2025/01/screenshot-2025-01-18-at-07.28.16.png?w=977" medium="image" />
	</item>
		<item>
		<title>Data Engineering: Strategies for data retrieval on multi-dimensional data</title>
		<link>https://aviyehuda2.wordpress.com/2023/11/20/data-engineering-strategies-for-data-retrieval-on-multi-dimensional-data/</link>
					<comments>https://aviyehuda2.wordpress.com/2023/11/20/data-engineering-strategies-for-data-retrieval-on-multi-dimensional-data/#respond</comments>
		
		<dc:creator><![CDATA[avi yehuda]]></dc:creator>
		<pubDate>Mon, 20 Nov 2023 18:45:55 +0000</pubDate>
				<category><![CDATA[Data Lake]]></category>
		<category><![CDATA[Spark]]></category>
		<category><![CDATA[Big Data]]></category>
		<category><![CDATA[data engineering]]></category>
		<guid isPermaLink="false">http://www.aviyehuda.com/?p=3225</guid>

					<description><![CDATA[You&#8217;ve likely heard about the benefits of partitioning data by a single dimension to boost retrieval performance. It&#8217;s a common practice in relational databases, NoSQL databases, and, notably, data lakes. For example, a very common dimension to partition data in data lakes is by date or time. However, what if your data querying requirements involve [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image aligncenter size-large"><img loading="lazy" width="963" height="323" data-attachment-id="3510" data-permalink="https://aviyehuda2.wordpress.com/2023/11/20/data-engineering-strategies-for-data-retrieval-on-multi-dimensional-data/lego-2/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2023/11/lego-1.png" data-orig-size="963,323" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="lego" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2023/11/lego-1.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2023/11/lego-1.png?w=963" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2023/11/lego-1.png?w=963" alt="" class="wp-image-3510" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2023/11/lego-1.png 963w, https://aviyehuda2.wordpress.com/wp-content/uploads/2023/11/lego-1.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2023/11/lego-1.png?w=300 300w, https://aviyehuda2.wordpress.com/wp-content/uploads/2023/11/lego-1.png?w=768 768w" sizes="(max-width: 963px) 100vw, 963px" /></figure>



<p>You&#8217;ve likely heard about the benefits of partitioning data by a single dimension to boost retrieval performance. It&#8217;s a common practice in relational databases, NoSQL databases, and, notably, data lakes. For example, a very common dimension to partition data in data lakes is by date or time. However, what if your data querying requirements involve multiple dimensions. Let&#8217;s say you wish to query your&#8217;e data by field A and also by field B or sometimes by field A but other times by field B.<br>In this post I&#8217;ll go over several common options for such case.<br>For the sake of connivance I&#8217;ll give examples on how to implement it on the data lake using standard folder names and parquet to hold the data. You should know however that the paradigms are also valid for other areas like DBs, NoSQL DBs, memory storage and so on.</p>



<div style="height:47px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">The default: micro partitions</h2>



<p>Micro-partitions is a technique used to sub-partition data within a dataset. Each micro-partition contains metadata for individual fields, providing valuable information for data consumption performance optimization.</p>



<p>For instance, consider a scenario where data is organized into daily partitions stored in Parquet files. </p>



<pre class="wp-block-preformatted">&lt;dataset_root&gt;/day=20240101/data1.parquet
&lt;dataset_root&gt;/day=20240101/data2.parquet
&lt;dataset_root&gt;/day=20240101/data3.parquet</pre>



<p>In this setup, each Parquet file (or even each page within a Parquet file) can be referred to as a micro-partition. Parquet files inherently <a href="https://parquet.apache.org/docs/file-format/metadata/">store metadata</a> per file and per page, which can enhance data consumption performance.</p>



<p>Snowflake, also <a href="https://docs.snowflake.com/en/user-guide/tables-clustering-micropartitions">employs micro-partitions</a> by default, only it uses richer metadata and superior indexing capabilities than the simple parquet files. This enhanced metadata and indexing within Snowflake&#8217;s micro-partitions contribute to significant performance gains, making micro-partitions a highly beneficial feature within the platform.</p>



<p></p>



<p></p>



<h2 class="wp-block-heading">The obvious approach: nested partitions</h2>



<p>Let&#8217;s start with nested partitions. In a typical Hive partitions structure, it looks like this:</p>



<pre class="wp-block-preformatted">&lt;dataset_root&gt;/&lt;FieldA&gt;=&lt;value&gt;/&lt;FieldB&gt;=&lt;value&gt;/data.parquet</pre>



<p>While this works well for consistent queries involving both Field A and Field B, it falls short when you need the flexibility to query either field separately. For instance:</p>




<div class="wp-block-syntaxhighlighter-code "><pre class="brush: scala; title: ; notranslate">
//Good for this:
Spark.sql("select * from my_data_set where FieldA=11 and FieldB=22 ")

//Not so good for this:
Spark.sql("select * from my_data_set where FieldA=11")
Spark.sql("select * from my_data_set where FieldB=22")
</pre></div>


<p>The reason this method is not usfull for these cases is that for the 2nd type query, all partitions need to be scanned which makes it not as usuful.</p>



<div style="height:57px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">The opposite approach: data duplication with separate partitions</h2>



<p>Another approach involves duplicating the data and partitioning it once by Field A and once by Field B. The directory structure in a hive like structure might look like this:</p>



<pre class="wp-block-preformatted">&lt;dataset_root&gt;/&lt;FieldA&gt;=&lt;value&gt;/data.parquet</pre>



<p>and</p>



<pre class="wp-block-preformatted">&lt;dataset_root&gt;/&lt;FieldB&gt;=&lt;value&gt;/data.parquet</pre>



<p></p>



<p>It represents the opposite of the previous option, meaning:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: scala; gutter: false; title: ; notranslate">
// Good for this:
Spark.sql("select * from my_data_set where FieldA=11"); 
Spark.sql("select * from my_data_set where FieldB=22");

// Not good for this:
Spark.sql("select * from my_data_set where FieldA=11 and FieldB=22 ");
</pre></div>


<p>Also, maintaining data consistency becomes more challenging in this scenario.</p>



<div style="height:45px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Best of Both Worlds? Partitioning by field A + externally indexing by field B </h2>



<p>A widely adopted strategy in databases. The advantage here is that the index serves as a reference to the data, not a copy of it.</p>



<p>In the data lake world it means partition the data by fieldA, same as before</p>



<pre class="wp-block-preformatted">&lt;dataset_root&gt;/&lt;FieldA&gt;=&lt;value&gt;/data.parquet</pre>



<p>And in addition mantaining a slim dataset which reffrances the same data files by fieldB values.</p>



<p>In datalakes It&#8217;s possible to implement it yourself, although usually it is implemented using some additional data catalog. This is also one of the advantages of using lakehouses(like databricks data lakehouse) since you get it out of the box.<br>It&#8217;s ideal for cases where you need to query the data based on specific values for field B.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: scala; gutter: false; title: ; notranslate">
Spark.sql("select * from my_data_set where FieldB=22");
</pre></div>


<p>However, it&#8217;s less suitable for queries involving a range of values for field B,</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: scala; gutter: false; title: ; notranslate">
Spark.sql(&quot;select * from my_data_set where FieldB&gt;22&quot;);
</pre></div>


<p>The reason it is not as useful is since the indexed keys are not stored in a continuous manner on the machine like partitions usually do.</p>



<div style="height:52px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="option4">Often useful: partitioning by field A + clustering or sorting by field B:</h2>



<p>This is an improvement over the micro partitions approach. Here we partition the data by fieldA as you normally do, but you make sure that inside each partition the data is clustered by fieldB. <br><br>Here is one example of how to implement it using Spark:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: scala; gutter: false; title: ; notranslate">
    // partition the data partitioned by A and inside partitioned by B
    val sortedDF = df.repartitionByRange($"fieldA", $"fieldB")

    // than write the data in a partitioned manner
    sortedDF.write
      .mode(SaveMode.Overwrite) 
      .partitionBy("fieldA")
      .parquet("/dataset_root")
 
</pre></div>


<p>In the example above, data will be written partitioned by field A. But inside each partitioned the data will be divided to files (micro-partitioned) also by field B. </p>



<p>The used theologies need to support this of course. In case of parquet it works well since parquet holds metadata for each field which includes min and max values. Most of the technologies (like apache spark) take this into account so they are able to skip files which do not include the required values for field-B.</p>



<p>This is a solid choice for various use cases, while it is not the best approach for queries like this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: scala; gutter: false; title: ; notranslate">
Spark.sql("select * from my_data_set where FieldB=22 ");
</pre></div>


<p>or</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: scala; gutter: false; title: ; notranslate">
Spark.sql(&quot;select * from my_data_set where FieldB&gt;22 &quot;);
</pre></div>


<p>Since it means going over all partitions. However since the data is grouped by fieldB within the partitions at least some of the files may be skipped.<br>This approach is particularly useful when field B contains a wide range of possible values (high cardinality). It can also be a beneficial design when field B&#8217;s values are unevenly distributed (skwed).<br><br>This is why this paradigm is very common in multiple technologies, for example: clustering in BigQuery, sort key in DynamoDB. clustering inside micro partitions in snowflake and so on.</p>



<div style="height:45px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">The secret weapon &#8211; Z-order </h2>



<p>A less common but important option is to index or partition by a Z-order. In this case, the data is also sorted, but instead of being partitioned by field A and sorted by fields B, it will be sorted by  a key which is a composite of both fields A and B:</p>



<pre class="wp-block-preformatted">&lt;dataset_root&gt;/&lt;A combination of FieldA+fieldB&gt;
</pre>



<p>This method is actually ideal for all of the query types mentioned so far.<br>The secret is in the method which combines the 2 fields together, it makes sure that keys with similar values are stored in proximity to one another, and this holds true for both fields that make up the partition. So no matter wether your&#8217;e retrieving data based on one of the fields or both, whether you need a precise value or range of values, this method will surely help. Also as the previous method, this method is good for high cardinality and skews as well.</p>



<p>The implementation of this is not very common though and quite complex. Currently the most common implementations are by hosted environments like Databricks lakehouse.</p>



<div style="height:53px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Conclusion:</h2>



<p>Choosing the right strategy for multi-dimensional data querying depends on your specific use case. Each approach has its strengths and trade-offs. Whether you go for nested partitions, data duplication, external indexing, sorting, or Z-indexing, understanding these strategies equips you to make informed decisions based on your data lake architecture and querying needs.<br></p>



<div style="height:31px" aria-hidden="true" class="wp-block-spacer"></div>



<p>See also:  <a href="http://www.aviyehuda.com/blog/2023/10/13/parquet-data-filtering-with-pandas/">Parquet data filtering with Pandas</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://aviyehuda2.wordpress.com/2023/11/20/data-engineering-strategies-for-data-retrieval-on-multi-dimensional-data/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3225</post-id>
		<media:content url="https://2.gravatar.com/avatar/58277fd5adf41424400f6241b432f5cbe201500ba80bc32cd8a25cf94285f3dc?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">aviyehuda555</media:title>
		</media:content>

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2023/11/lego-1.png?w=963" medium="image" />
	</item>
		<item>
		<title>Parquet data filtering with Pandas</title>
		<link>https://aviyehuda2.wordpress.com/2023/10/13/parquet-data-filtering-with-pandas/</link>
					<comments>https://aviyehuda2.wordpress.com/2023/10/13/parquet-data-filtering-with-pandas/#comments</comments>
		
		<dc:creator><![CDATA[avi yehuda]]></dc:creator>
		<pubDate>Fri, 13 Oct 2023 19:37:41 +0000</pubDate>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[data engineering]]></category>
		<category><![CDATA[pandas]]></category>
		<category><![CDATA[parquet]]></category>
		<guid isPermaLink="false">http://www.aviyehuda.com/?p=3174</guid>

					<description><![CDATA[When it comes to filtering data from Parquet files using pandas, several strategies can be employed. While it&#8217;s widely recognized that partitioning data can significantly enhance the efficiency of filtering operations, there are additional methods to optimize the performance of querying data stored in Parquet files. Partitioning is just one of the options. Filtering by [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img data-attachment-id="3280" data-permalink="https://aviyehuda2.wordpress.com/pandas_parquet-2/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/pandas_parquet-edited.png" data-orig-size="768,432" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="pandas_parquet" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/pandas_parquet-edited.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/pandas_parquet-edited.png?w=768" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/pandas_parquet-edited.png" alt="" class="wp-image-3280" /></figure>



<p>When it comes to filtering data from Parquet files using pandas, several strategies can be employed. While it&#8217;s widely recognized that partitioning data can significantly enhance the efficiency of filtering operations, there are additional methods to optimize the performance of querying data stored in Parquet files. Partitioning is just one of the options.</p>



<p class="has-medium-font-size"><strong>Filtering by partitioned fields</strong></p>



<p>As previously mentioned, this approach is not only the most familiar but also typically the most impactful in terms of performance optimization. The rationale behind this is straightforward. When partitions are employed, it becomes possible to selectively exclude the need to read entire files or even entire directories of files (aka, predicate pushdown), resulting in a substantial and dramatic improvement in performance.<br></p>



<pre class="brush: python; title: ; notranslate">import pandas as pd
import time
from faker import Faker

fake = Faker()

MIL=1000000
NUM_OF_RECORDS=10*MIL
FOLDER="/tmp/out/"
PARTITIONED_PATH=f"{FOLDER}partitioned_{NUM_OF_RECORDS}/"
NON_PARTITIONED_PATH_PREFIX=f"{FOLDER}non_partitioned_{NUM_OF_RECORDS}.parquet"

print(f"Creating fake data")
data = {
    'id': range(NUM_OF_RECORDS),  # Generate IDs from 1 to 100
    'name': [fake.name() for _ in range(NUM_OF_RECORDS)],
    'age': [fake.random_int(min=18, max=99) for _ in range(NUM_OF_RECORDS)],
    'state': [fake.state() for _ in range(NUM_OF_RECORDS)],
    'city': [fake.city() for _ in range(NUM_OF_RECORDS)],
    'street': [fake.street_address() for _ in range(NUM_OF_RECORDS)]
}

df = pd.DataFrame(data)

# writing without partitions
df.to_parquet(path=NON_PARTITIONED_PATH)

# writing partitioned data
df.to_parquet(path=PARTITIONED_PATH, partition_cols=['state'])

# reading non partitioned
start_time = time.time()
df1 = pd.read_parquet(path=NON_PARTITIONED_PATH)
df1 = df1[df1['state']=='California']
runtime1 = (time.time()) - start_time  # 37 sec

# reading partitioned data
start_time = time.time()
df2 = pd.read_parquet(path=PARTITIONED_PATH, filters=[('state','==','California')])
runtime2 = (time.time()) - start_time # 0.20 sec</pre>



<p>The time improvement (along with reduced memory and CPU usage) is substantial, decreasing from 37 seconds to just 0.20 seconds.</p>



<p class="has-medium-font-size"><strong>Filtering by non partitioned fields</strong></p>



<p>In the example above, we observed how filtering based on a partitioned field can enhance data retrieval. However, there are scenarios where data can&#8217;t be effectively partitioned by the specific field we wish to filter. Moreover, in some cases, filtering is required based on multiple fields. This means all input files will be opened, which can be harmful to performance.</p>



<p>Thankfully, Parquet offers a clever solution to mitigate this issue. Parquet files are split to row groups , within each row group Parquet stores metadata. This metadata includes the minimum and maximum values for each field.<br><br>When writing Parquet files with Pandas you can select what will be the number of records in each control group.</p>



<p>When using Pandas to read Parquet files with filters, the Pandas library leverages this Parquet metadata to efficiently filter data loaded into memory. If the desired field falls outside the min/max range of a row group, that entire row group is gracefully skipped.<br></p>



<pre class="brush: python; title: ; notranslate">df = pd.DataFrame(data)

# writing non partitioned data, specifying the size of the row group
df.to_parquet(path=PATH_TO_PARQUET_FILE, row_group_size=1000000)

# reading non partitioned data and filtering by row groups only
df = pd.read_parquet(path=DATASET_PATH, filters=[('state','==','California')])</pre>



<p>Viewing the metadata inside Parquet files can be done using PyArrow.</p>



<pre class="wp-block-code has-text-color has-background has-small-font-size" style="color:#42af32;background-color:#505050"><code>&gt;&gt;&gt; import pyarrow.parquet as pq

&gt;&gt;&gt; parquet_file = pq.ParquetFile(PATH_TO_PARQUET_FILE)

&gt;&gt;&gt; parquet_file.metadata
&lt;pyarrow._parquet.FileMetaData object at 0x125b21220&gt;
  created_by: parquet-cpp-arrow version 11.0.0
  num_columns: 6
  <strong>num_rows: 1000000
  num_row_groups: 10</strong>
  format_version: 2.6
  serialized_size: 9325

&gt;&gt;&gt; parquet_file.metadata.row_group(0).column(3)
&lt;pyarrow._parquet.ColumnChunkMetaData object at 0x125b5b180&gt;
  file_offset: 1675616
  file_path: 
  physical_type: BYTE_ARRAY
  num_values: 100000
  path_in_schema: state
  is_stats_set: True
  statistics:
    &lt;pyarrow._parquet.Statistics object at 0x115283590&gt;
      has_min_max: True
      <strong>min: Alabama
      max: Wyoming</strong>
      null_count: 0
      distinct_count: 0
      num_values: 100000
      physical_type: BYTE_ARRAY
      logical_type: String
      converted_type (legacy): UTF8
  compression: SNAPPY
  encodings: ('RLE_DICTIONARY', 'PLAIN', 'RLE')
  has_dictionary_page: True
  dictionary_page_offset: 1599792
  data_page_offset: 1600354
  total_compressed_size: 75824
  total_uncompressed_size: 75891

</code></pre>



<p>Notice that the number of row groups in mentioned in the metadata of the entire file and the minimum and maximum values mentioned inside the statistics section of each column for each row group.<br><br>However, there is a method to further harness this Parquet feature for even more optimized results: <strong>sorting</strong>.</p>



<p></p>



<p class="has-medium-font-size"><strong>Filtering by sorted fields</strong></p>



<p>As mentioned in the previous section, part of the metadata stored by Parquet includes the minimum and maximum values for each field within every row group. When the data is sorted based on the field we intend to filter by, Pandas has a greater likelihood of skipping more row groups.</p>



<p>For example, let&#8217;s consider a dataset that includes a list of records, with one of the fields representing &#8216;state.&#8217; If the records are unsorted, there&#8217;s a good chance that each state appears in most of the row groups. For example, look at the metadata in the previous section, you can see that the 1st row group alone holds all the states from &#8216;Alabama&#8217; to &#8216;Wyoming&#8217;.</p>



<p>However, if we sort the data based on the &#8216;state&#8217; field, there&#8217;s a significant probability of skipping many row groups.</p>



<pre class="brush: python; title: ; notranslate">df = pd.DataFrame(data)

# sorting the data based on 'state'
df.sort_values("state").to_parquet(path=NON_PARTITIONED_SORTED_PATH)</pre>



<p>Now let&#8217;s look again at the metadata and see how it changed</p>



<pre class="wp-block-code has-text-color has-background has-small-font-size" style="color:#418d2f;background-color:#505050"><code>&gt;&gt;&gt; parquet_file = pq.ParquetFile(PATH_TO_PARQUET_FILE)

&gt;&gt;&gt; parquet_file.metadata.row_group(0).column(3).statistics.min
'Alabama'
&gt;&gt;&gt; parquet_file.metadata.row_group(0).column(3).statistics.max
'Kentucky'


&gt;&gt;&gt; parquet_file.metadata.row_group(1).column(3).statistics.min
'Kentucky'
&gt;&gt;&gt; parquet_file.metadata.row_group(1).column(3).statistics.max
'North Dakota'


&gt;&gt;&gt; parquet_file.metadata.row_group(2).column(3).statistics.min
'North Dakota'
&gt;&gt;&gt; parquet_file.metadata.row_group(2).column(3).statistics.max
'Wyoming'
</code></pre>



<p>As you can see, after sorting by state the min max values are effected accordingly, each row groups hold part of the states instead of all of the states. This means reading with filters should be a lot quicker now.<br><br>Now let&#8217;s see how it affects the performance of reading the data. The code for reading the data hasn&#8217;t change.</p>



<pre class="brush: python; title: ; notranslate"># reading non partitioned data and filtering by row groups, the input is sorted by state
start_time = time.time()
df = pd.read_parquet(path=DATASET_PATH, filters=[('state','==','California')])
runtime = (time.time()) - start_time # 0.24 seconds</pre>



<p>Astonishingly the performance here is almost as good as using partitions.</p>



<p>This principle applies to both partitioned and non-partitioned data, we can use both methods at the same time. If we sometimes want to filter the data based on field A and other times base on field B, then partitioning by field A and sorting by field B could be a good option. <br>In other cases, for instance, where the field we want to filter by is a field with a high cardinality, we could partition by some hash of the value (bucketing) and sort the data inside it by the actual value of the field, in this way we will enjoy the advantages of both methods &#8211; partitioning and row groups. <br></p>



<p class="has-medium-font-size"><strong>Reading <strong>a subset of the</strong> columns</strong></p>



<p>Although less commonly used, another method for achieving better results during data retrieval involves selecting only the specific fields that are essential for your task. This strategy can occasionally yield improvements in performance. This is due to the nature of Parquet format. Parquet is implemented in a columnar format, which means it stores the data column by column inside each row group. Reading only some of the columns means the other columns will be skipped.</p>



<pre class="brush: python; title: ; notranslate">start_time = time.time()
df = pd.read_parquet(path=NON_PARTITIONED_SORTED_PATH, columns=["name", "state"])
runtime = (time.time()) - start_time # 0.08 seconds</pre>



<p>Unsurprisingly, the improvement in performance is great.</p>



<p class="has-medium-font-size"><strong>Conclusion</strong></p>



<p>While partitioning data is typically the optimal approach, it is not always a possibility. Sorting the data can lead to significant improvements, we may skip more row groups this. Additionally, if feasible, selecting only the necessary columns is always a good choice.</p>



<p>I this post helped you understanding how to harness the power of parquet and pandas for better performance.<br><a rel="noreferrer noopener" href="https://github.com/aviyehuda/PandasParquetTests/blob/main/parquet_test.py" target="_blank">Here is a script</a> containing all the previously mentioned examples, complete with time comparisons.</p>



<p></p>



<p></p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://aviyehuda2.wordpress.com/2023/10/13/parquet-data-filtering-with-pandas/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3174</post-id>
		<media:content url="https://2.gravatar.com/avatar/58277fd5adf41424400f6241b432f5cbe201500ba80bc32cd8a25cf94285f3dc?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">aviyehuda555</media:title>
		</media:content>

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/pandas_parquet-edited.png" medium="image" />
	</item>
		<item>
		<title>Spark and Small Files</title>
		<link>https://aviyehuda2.wordpress.com/2022/03/12/spark-and-small-files/</link>
					<comments>https://aviyehuda2.wordpress.com/2022/03/12/spark-and-small-files/#respond</comments>
		
		<dc:creator><![CDATA[avi yehuda]]></dc:creator>
		<pubDate>Sat, 12 Mar 2022 11:12:00 +0000</pubDate>
				<category><![CDATA[Data Lake]]></category>
		<category><![CDATA[Spark]]></category>
		<category><![CDATA[DataLake]]></category>
		<guid isPermaLink="false">http://www.aviyehuda.com/?p=3075</guid>

					<description><![CDATA[In my previous post I have showed this short code example: And I asked what may be the problem with that code, assuming that the input ( my_website_visits ) is very big and that we filter most of it using the &#8216;where&#8217; clause. Well the answer is of course, is that that piece of code [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img data-attachment-id="3277" data-permalink="https://aviyehuda2.wordpress.com/spark_firework-4/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/spark_firework-1-edited-1.jpg" data-orig-size="1627,915" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;1.7&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;DMC-GH4&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;20&quot;,&quot;iso&quot;:&quot;200&quot;,&quot;shutter_speed&quot;:&quot;0.0025&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="spark_firework" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/spark_firework-1-edited-1.jpg?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/spark_firework-1-edited-1.jpg?w=1024" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/spark_firework-1-edited-1.jpg" alt="" class="wp-image-3277" /></figure>



<p>

In my <a href="http://www.aviyehuda.com/blog/2022/01/10/coalesce-with-care/">previous post</a> I have showed this short code example:

</p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<pre class="brush: python; title: ; notranslate">sparkSession.sql("select * from my_website_visits where post_id=317456")
   .write.parquet("s3://reports/visits_report")</pre>
</div>



<p>


And I asked what may be the problem with that code, assuming that the input ( my_website_visits ) is very big  and that we filter most of it using the &#8216;where&#8217; clause.
</p>



<p>Well the answer is of course, is that that piece of code may result in a large amount of small files.</p>



<p>Why?<br>
Because we are reading a large input, the number of tasks will be quite large. When filtering out most of the data and then writing it, the number of tasks will remain the same, since no shuffling was done. This means that each task will write only a small amount of data, which means small files on the target path.


</p>



<figure class="wp-block-image aligncenter size-large"><img src="https://i0.wp.com/www.aviyehuda.com/images/coalesce/coalesce7.png" alt="" /></figure>



<p></p>



<p>If in the example above Spark created 165 tasks to handle our input. That means that even after filtering most of the data, the output of this process will be at least 165 files with only a few kb in each.</p>



<figure class="wp-block-image size-large"><img src="https://i0.wp.com/www.aviyehuda.com/images/coalesce/coalesce1.png" alt=""></figure>



<p><strong>What is the problem with a lot of small files?</strong><br>Well, first of all, the writing itself is inefficient. More files means unneeded overhead in resources and time. If you&#8217;re storing your output on the cloud like AWS S3, this problem may be even worst, since Spark files committer stores files in a temporary location before writing the output to the final location. Only when all the data is done being written to the temporary location, than it is being copied to the final location. <br></p>



<p>But perhaps worst than the impact a lot of small files have on the writing process, is the impact that they have on the consumers of that data. Since data is usually written once but read multiple times. So when creating the data in multiple small files, you&#8217;re also hurting your consumers.</p>



<p>But that&#8217;s not all. Sometimes you need to store the output in a partitioned manner.
Let&#8217;s say that you want to write this data partitioned by country.

<pre class="brush: python; title: ; notranslate">sparkSession.sql("select * from my_website_visits where post_id=317456")
.write.partitionBy("country").parquet("s3://reports/visits_report")</pre>

That will make things even worst right?
Since now each of those 165 tasks can potentially write a file to each of those partitions. So it can reach up to 165 * (num of countries).

</p>



<p>
<b>What can be done to solve it? </b><br>

Well, the obvious solution is of course to use repartition or coalesce. But like I mentioned in my last post make sure to be careful if you&#8217;re planning to use coalesce. 

If you do partition the data when writing, like we saw above, there is something else that you can do.
<br>

 <pre class="brush: python; title: ; notranslate">sparkSession.sql("select * from my_website_visits where post_id=317456")
.partition("country", 1).write.partitionBy("country").parquet("s3://reports/visits_report")</pre>

In the example above we partitioned the data by country even before writing. Therefore I am expecting to have only 1 file per country.

</p>
]]></content:encoded>
					
					<wfw:commentRss>https://aviyehuda2.wordpress.com/2022/03/12/spark-and-small-files/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3075</post-id>
		<media:content url="https://2.gravatar.com/avatar/58277fd5adf41424400f6241b432f5cbe201500ba80bc32cd8a25cf94285f3dc?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">aviyehuda555</media:title>
		</media:content>

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/spark_firework-1-edited-1.jpg" medium="image" />

		<media:content url="http://www.aviyehuda.com/images/coalesce/coalesce7.png" medium="image" />

		<media:content url="http://www.aviyehuda.com/images/coalesce/coalesce1.png" medium="image" />
	</item>
		<item>
		<title>Coalesce with care&#8230;Coalesce Vs. Repartition in SparkSQL</title>
		<link>https://aviyehuda2.wordpress.com/2022/01/10/coalesce-with-care/</link>
					<comments>https://aviyehuda2.wordpress.com/2022/01/10/coalesce-with-care/#comments</comments>
		
		<dc:creator><![CDATA[avi yehuda]]></dc:creator>
		<pubDate>Mon, 10 Jan 2022 08:01:27 +0000</pubDate>
				<category><![CDATA[Spark]]></category>
		<category><![CDATA[Big Data]]></category>
		<category><![CDATA[SparkSQL]]></category>
		<guid isPermaLink="false">http://www.aviyehuda.com/?p=2838</guid>

					<description><![CDATA[Here is a quick Spark SQL riddle for you; what do you think can be problematic in the next spark code (assume that spark session was configured in an ideal way)? Hint1: the input data (my_website_visits) is quite big. Hint2: we filter out most of the data before writing. I&#8217;m sure that you got it [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img data-attachment-id="3285" data-permalink="https://aviyehuda2.wordpress.com/spark_lighter-2/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/spark_lighter-edited.jpg" data-orig-size="5760,3243" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="spark_lighter" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/spark_lighter-edited.jpg?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/spark_lighter-edited.jpg?w=1024" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/spark_lighter-edited.jpg" alt="" class="wp-image-3285" /></figure>



<p>Here is a quick Spark SQL riddle for you; what do you think can be problematic in the next spark code (assume that spark session was configured in an ideal way)?</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
sparkSession.sql("select * from my_website_visits where post_id=317456")
.write.parquet("s3://reports/visits_report")
</pre></div>


<h4 class="wp-block-heading"><b>Hint1:</b> the input data (my_website_visits) is quite big.</h4>



<p><b>Hint2:</b> we filter out most of the data before writing.</p>



<p>I&#8217;m sure that you got it by now; if the input data is big, and spark is configured in an ideal way, it means that my spark job has a lot of tasks.<br>Which means that the writing is also done from multiple tasks.<br>This probably means that the output of this will be a large amount of a very small parquet files.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="616" height="367" data-attachment-id="3287" data-permalink="https://aviyehuda2.wordpress.com/2022/01/10/coalesce-with-care/coalesce7/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce7.png" data-orig-size="616,367" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="coalesce7" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce7.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce7.png?w=616" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce7.png?w=616" alt="" class="wp-image-3287" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce7.png 616w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce7.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce7.png?w=300 300w" sizes="(max-width: 616px) 100vw, 616px" /></figure>



<p>Small files is a known problem in the big data world. It takes an unnecessary large amount of resources to write this data, but more importantly, it takes a large amount of resources to read this data (more IO, more memory, more runtime&#8230;).<br>This is how it looks in Spark UI.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="417" data-attachment-id="3288" data-permalink="https://aviyehuda2.wordpress.com/2022/01/10/coalesce-with-care/coalesce1/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce1.png" data-orig-size="1144,466" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="coalesce1" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce1.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce1.png?w=1024" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce1.png?w=1024" alt="" class="wp-image-3288" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce1.png?w=1024 1024w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce1.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce1.png?w=300 300w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce1.png?w=768 768w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce1.png 1144w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>In this case we have 165 tasks, which means that we can have up to 165 output files.</p>



<p>How would you improve this?<br>Instead of writing from multiple workers, let&#8217;s write from a single worker.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="932" height="371" data-attachment-id="3290" data-permalink="https://aviyehuda2.wordpress.com/2022/01/10/coalesce-with-care/coalesce8/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce8.png" data-orig-size="932,371" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="coalesce8" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce8.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce8.png?w=932" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce8.png?w=932" alt="" class="wp-image-3290" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce8.png 932w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce8.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce8.png?w=300 300w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce8.png?w=768 768w" sizes="(max-width: 932px) 100vw, 932px" /></figure>



<p>How this is done in spark?</p>



<h3 class="wp-block-heading">Coalesce vs. Repartition</h3>



<p>In Spark there are two common transformation to change the number of tasks; <b>coalesce</b> and <b>repartition</b>. They are very similar but not identical.<br>Repartition in spark sql triggers a shuffle, where coalesce doesn&#8217;t. And as we know, shuffle can be expensive (note: this is true for DataFrames/DataSets. In RDDs the behaviour is a bit different).<br>So let&#8217;s try to use coalesce.</p>



<pre class="wp-block-preformatted"></pre>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
sparkSession.sql("select * from my_website_visits where post_id=317456")
.coalesce(1).write.parquet("s3://reports/visits_report")

</pre></div>


<p>That took <b>3.3 minutes</b> to run, while the original program took only <b>12 seconds</b> to run.</p>



<p>Now let&#8217;s try it with repartition:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
sparkSession.sql("select * from my_website_visits where post_id=317456")
.repartition(1).write.parquet("s3://reports/visits_report")
</pre></div>


<pre class="wp-block-preformatted"></pre>



<p>That took only <b>8 seconds</b> to run.</p>



<p>How can that be?! repartition adds a shuffle, so it should be more expensive.<br>Let&#8217;s look at Spark UI</p>



<p>This is how it looks when using coalesce.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="995" height="476" data-attachment-id="3291" data-permalink="https://aviyehuda2.wordpress.com/2022/01/10/coalesce-with-care/coalesce2/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce2.png" data-orig-size="995,476" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="coalesce2" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce2.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce2.png?w=995" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce2.png?w=995" alt="" class="wp-image-3291" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce2.png 995w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce2.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce2.png?w=300 300w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce2.png?w=768 768w" sizes="(max-width: 995px) 100vw, 995px" /></figure>



<p>And this is when using repartition:</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="319" data-attachment-id="3292" data-permalink="https://aviyehuda2.wordpress.com/2022/01/10/coalesce-with-care/coalesce3/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce3.png" data-orig-size="1291,403" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="coalesce3" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce3.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce3.png?w=1024" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce3.png?w=1024" alt="" class="wp-image-3292" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce3.png?w=1024 1024w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce3.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce3.png?w=300 300w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce3.png?w=768 768w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce3.png 1291w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>The reason should be clear. Using coalesce reduces the number of tasks for the <u>entire stage</u>, also for the part which comes before calling the coalesce. This means that the reading of the input and the filtering was done using only a single worker with a single task, as oppose to 165 tasks with the original program.</p>



<p>Repartition on the other hand creates a shuffle, and this indeed adds to the runtime, but since the 1st stage is still done using 165 tasks the total runtime is much better than coalesce.</p>



<p>Does this means that coalesce is evil? Definitely not.<br>Let&#8217;s see an example where coalesce actually a better choice.</p>



<h3 class="wp-block-heading">Limit with Coalesce</h3>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
sparkSession.sql("select * from my_website_visits").limit(10)
.write.parquet("s3://reports/visits_report")
</pre></div>


<pre class="wp-block-preformatted"></pre>



<p>This ran for 2.1 minutes, but when using coalesce:</p>



<pre class="wp-block-preformatted"></pre>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
sparkSession.sql("select * from my_website_visits")
.coalesce(1).limit(10).write.parquet("s3://reports/visits_report")
</pre></div>


<p>It ran for only 3 seconds(!)</p>



<p>As you can see coalesce helped a lot here. To understand why, we need to understand how does the <b>limit</b> operator work.</p>



<p>Limit is actually dividing the program to 2 stages with a shuffle in between. In the 1st stage, there is a <b>LocalLimit</b> operation, which is executed in each of the partitions. The filtered data from each of the partitions is then combined into a single partition where another limit operation is executed on that data. This operation is defined as the <b>GlobalLimit</b>.</p>



<p>This is how it looks in the SQL tab in Spark UI:</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="307" height="933" data-attachment-id="3293" data-permalink="https://aviyehuda2.wordpress.com/2022/01/10/coalesce-with-care/coalesce4/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce4.png" data-orig-size="307,933" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="coalesce4" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce4.png?w=99" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce4.png?w=307" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce4.png?w=307" alt="" class="wp-image-3293" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce4.png 307w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce4.png?w=49 49w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce4.png?w=99 99w" sizes="(max-width: 307px) 100vw, 307px" /></figure>



<p>Notice that the local limit and global limit are on separate stages:</p>



<p>Now if this data was ordered in some way, that would have make sense to execute local limit on each partitions before doing the global limit. But since there is no ordering here at all, this is obviously a wasteful operation, we could&#8217;ve just taken those 10 records randomly from one of the partitions, logically it wouldn&#8217;t make a difference and it would&#8217;ve been much faster.</p>



<p>When using <code>coalesce(1)</code> though it helps in 2 ways.<br>First, as seen, it sets the tasks number to be 1 for the entire stage. Since limit also reduces the number of tasks to 1, then that extra stage and shuffle which limit adds are not needed anymore.<br>But there is another, more important reason why <code>coalesce(1)</code> helps here. As seen, <code>coalesce(1)</code> reduces the number tasks to 1 for the entire stage (unlike repartition which splits the stage), the local limit operation is done only on a single partition instead of doing it on many partitions. And that helps the performance by a lot.</p>



<p>Looking at this in spark UI when using coalesce, you can clearly see that the local and global limit are executed on the same stage.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="267" height="785" data-attachment-id="3295" data-permalink="https://aviyehuda2.wordpress.com/2022/01/10/coalesce-with-care/coalesce5/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce5.png" data-orig-size="267,785" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="coalesce5" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce5.png?w=102" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce5.png?w=267" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce5.png?w=267" alt="" class="wp-image-3295" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce5.png 267w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce5.png?w=51 51w" sizes="(max-width: 267px) 100vw, 267px" /></figure>



<p>And what about Repartition for this case?</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
sparkSession.sql("select * from my_website_visits").repartition(1).limit(10)
.write.parquet("s3://reports/visits_report")
</pre></div>


<p>It takes 2.7 seconds. Even slower than the original job.</p>



<p>This is how it looks in the SQL tab in spark UI:</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="350" height="911" data-attachment-id="3296" data-permalink="https://aviyehuda2.wordpress.com/2022/01/10/coalesce-with-care/coalesce6/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce6.png" data-orig-size="350,911" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="coalesce6" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce6.png?w=115" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce6.png?w=350" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce6.png?w=350" alt="" class="wp-image-3296" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce6.png 350w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce6.png?w=58 58w, https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce6.png?w=115 115w" sizes="(max-width: 350px) 100vw, 350px" /></figure>



<p>We see that in this case local and global limit are also executed on the same stage on that single task, like coalesce. so why it is slower here?</p>



<p>Repartition as oppose to coalesce, doesn&#8217;t change the number of tasks for the entire stage, instead it creates a new stage with the new number of partitions. This means that in our case it is actually taking all the data from all the partitions and combining them into a single big partition. This of course has a huge impact on performance.</p>



<h3 class="wp-block-heading">Conclusion</h3>



<p>As seen, both coalesce and repartition can help or hurt the performance of our applications, we just need to be careful using them.</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://aviyehuda2.wordpress.com/2022/01/10/coalesce-with-care/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2838</post-id>
		<media:content url="https://2.gravatar.com/avatar/58277fd5adf41424400f6241b432f5cbe201500ba80bc32cd8a25cf94285f3dc?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">aviyehuda555</media:title>
		</media:content>

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2024/10/spark_lighter-edited.jpg" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce7.png?w=616" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce1.png?w=1024" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce8.png?w=932" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce2.png?w=995" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce3.png?w=1024" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce4.png?w=307" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce5.png?w=267" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2022/01/coalesce6.png?w=350" medium="image" />
	</item>
		<item>
		<title>Quick tip: Easily find data on the data lake when using AWS Glue Catalog</title>
		<link>https://aviyehuda2.wordpress.com/2021/01/15/quick-tip-easily-find-data-on-the-data-lake-when-using-aws-glue-catalog/</link>
					<comments>https://aviyehuda2.wordpress.com/2021/01/15/quick-tip-easily-find-data-on-the-data-lake-when-using-aws-glue-catalog/#respond</comments>
		
		<dc:creator><![CDATA[avi yehuda]]></dc:creator>
		<pubDate>Fri, 15 Jan 2021 06:23:53 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[Data Lake]]></category>
		<guid isPermaLink="false">http://www.aviyehuda.com/?p=2807</guid>

					<description><![CDATA[Finding data on the data lake can sometimes be a challenge. At my current workplace (ZipRecruiter) we have hundreds of tables on the data lake and it&#8217;s growing each day. We store the data on AWS S3 and we use AWS Glue Catalog as meta data for our Hive tables. But even with Glue Catalog, [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image aligncenter is-resized is-style-rectangular"><img loading="lazy" width="465" height="374" data-attachment-id="3301" data-permalink="https://aviyehuda2.wordpress.com/2021/01/15/quick-tip-easily-find-data-on-the-data-lake-when-using-aws-glue-catalog/glue0/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue0.png" data-orig-size="465,374" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="glue0" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue0.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue0.png?w=465" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue0.png?w=465" alt="" class="wp-image-3301" style="width:454px;height:auto" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue0.png 465w, https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue0.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue0.png?w=300 300w" sizes="(max-width: 465px) 100vw, 465px" /></figure>



<p>Finding data on the data lake can sometimes be a challenge. At my current workplace (<a href="https://www.ziprecruiter.com/">ZipRecruiter</a>) we have hundreds of tables on the data lake and it&#8217;s growing each day. We store the data on AWS S3 and we use AWS Glue Catalog as meta data for our Hive tables.</p>



<p>But even with Glue Catalog, finding data on the data lake can still be a hustle. Let&#8217;s say I am trying to find a certain type of data, like &#8216;clicks&#8217; for example. It would be very nice to have an easy way to get all the clicks related tables (including aggregation tables, join tables and so on..) so i could choose from. Or perhaps I would like to know which tables were generated by a specific application. There is no easy way to find these table by default.<br><br>But here is something pretty cool that I recently found about Glue Catalog that can help.<br>If you add properties to glue tables, then you can search tables based on those properties.</p>



<p>For example, if you would add the property &#8220;clicks&#8221; to all the job related tables, then you can get all of those tables as a result by searching the phrase “clicks” in GlueCatalog. </p>



<p>You can also add property like “Application: ClicksGenerator” to all of the tables that were generated by the ClicksGenerator application. <br>Other ideas for labels may be: team names, last update date, data lag, data update frequency, and so on&#8230;<br> </p>



<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="578" data-attachment-id="3298" data-permalink="https://aviyehuda2.wordpress.com/2021/01/15/quick-tip-easily-find-data-on-the-data-lake-when-using-aws-glue-catalog/glue1/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue1.png" data-orig-size="1116,631" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="glue1" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue1.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue1.png?w=1024" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue1.png?w=1024" alt="" class="wp-image-3298" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue1.png?w=1024 1024w, https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue1.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue1.png?w=300 300w, https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue1.png?w=768 768w, https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue1.png 1116w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" width="931" height="344" data-attachment-id="3300" data-permalink="https://aviyehuda2.wordpress.com/2021/01/15/quick-tip-easily-find-data-on-the-data-lake-when-using-aws-glue-catalog/glue2/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue2.png" data-orig-size="931,344" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="glue2" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue2.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue2.png?w=931" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue2.png?w=931" alt="" class="wp-image-3300" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue2.png 931w, https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue2.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue2.png?w=300 300w, https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue2.png?w=768 768w" sizes="(max-width: 931px) 100vw, 931px" /></figure>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://aviyehuda2.wordpress.com/2021/01/15/quick-tip-easily-find-data-on-the-data-lake-when-using-aws-glue-catalog/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2807</post-id>
		<media:content url="https://2.gravatar.com/avatar/58277fd5adf41424400f6241b432f5cbe201500ba80bc32cd8a25cf94285f3dc?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">aviyehuda555</media:title>
		</media:content>

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue0.png?w=465" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue1.png?w=1024" medium="image" />

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2021/01/glue2.png?w=931" medium="image" />
	</item>
		<item>
		<title>The right way to use Spark and JDBC</title>
		<link>https://aviyehuda2.wordpress.com/2018/12/17/the-right-way-to-use-spark-and-jdbc/</link>
					<comments>https://aviyehuda2.wordpress.com/2018/12/17/the-right-way-to-use-spark-and-jdbc/#respond</comments>
		
		<dc:creator><![CDATA[avi yehuda]]></dc:creator>
		<pubDate>Mon, 17 Dec 2018 09:17:08 +0000</pubDate>
				<category><![CDATA[Spark]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Scala]]></category>
		<category><![CDATA[Sqoop]]></category>
		<guid isPermaLink="false">http://www.aviyehuda.com/?p=2775</guid>

					<description><![CDATA[A while ago I had to read data from a MySQL table, do a bit of manipulations on that data and store the results on the disk.The obvious choice was to use Spark, I was already using it for other stuff and it seemed super easy to implement. This is more or less what I [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" width="465" height="158" data-attachment-id="3305" data-permalink="https://aviyehuda2.wordpress.com/2018/12/17/the-right-way-to-use-spark-and-jdbc/spark_jdbc/" data-orig-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2018/12/spark_jdbc.png" data-orig-size="465,158" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="spark_jdbc" data-image-description="" data-image-caption="" data-medium-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2018/12/spark_jdbc.png?w=300" data-large-file="https://aviyehuda2.wordpress.com/wp-content/uploads/2018/12/spark_jdbc.png?w=465" src="https://aviyehuda2.wordpress.com/wp-content/uploads/2018/12/spark_jdbc.png?w=465" alt="" class="wp-image-3305" srcset="https://aviyehuda2.wordpress.com/wp-content/uploads/2018/12/spark_jdbc.png 465w, https://aviyehuda2.wordpress.com/wp-content/uploads/2018/12/spark_jdbc.png?w=150 150w, https://aviyehuda2.wordpress.com/wp-content/uploads/2018/12/spark_jdbc.png?w=300 300w" sizes="(max-width: 465px) 100vw, 465px" /></figure>



<p>A while ago I had to read data from a MySQL table, do a bit of manipulations on that data and store the results on the disk.<br>The obvious choice was to use Spark, I was already using it for other stuff and it seemed super easy to implement.</p>



<p>This is more or less what I had to do (I removed the part which does the manipulation for the sake of simplicity):</p>



<pre class="brush: scala; title: ; notranslate">spark.read.format(&quot;jdbc&quot;). 
   option(&quot;url&quot;, &quot;jdbc:mysql://dbhost/sbschhema&quot;). 
   option(&quot;dbtable&quot;, &quot;mytable&quot;). 
   option(&quot;user&quot;, &quot;myuser&quot;). 
   option(&quot;password&quot;, &quot;mypassword&quot;).
 load().write.parquet(&quot;/data/out&quot;)</pre>



<p>Looks good, only it didn&#8217;t quite work. Either it was super slow or it totally crashed depends on the size of the table.<br>Tuning Spark and the cluster properties helped a bit, but it didn&#8217;t solve the problems.</p>



<p>Since I was using <a href="http://www.aviyehuda.com/blog/tag/emr/">AWS EMR</a>, it made sense to give <a href="http://sqoop.apache.org/">Sqoop</a> a try since it is a part of the applications supported on EMR.</p>



<pre class="brush: bash; title: ; notranslate">sqoop import --verbose 
--connect jdbc:mysql://dbhost/sbschhema 
--username myuser --table opportunity 
--password  mypassword --m 20 --as-parquetfile --target-dir /data/out</pre>



<p>Sqoop performed so much better almost instantly, all you needed to do is to set the number of mappers according to the size of the data and it was working perfectly.</p>



<p>Since both Spark and Sqoop are a based on Hadoop map-reduce framework, it was clear that Spark can work at least as good as Sqoop, I only needed to find out how to do it. I decided to look closer at what Sqoop does to see if I can imitate that with Spark.</p>



<p>By turning on the verbose flag of Sqoop, you can get a lot more details.<br>What I found was that Sqoop is splitting the input to the different mappers which makes sense, this is map-reduce after all, Spark does the same thing.<br>But before doing that, Sqoop does something smart that Spark doesn&#8217;t do.</p>



<p>It first fetches the primary key (unless you give him another key to split the data by), it then checks it&#8217;s minimum and maximum values. Then it lets each of its mappers to query the data but with different boundaries for the key, so that the rows are split evenly between the mappers.</p>



<p>If for example the key maximum value is 100, and there are 5 mappers, than the query of the 1st mapper will look like this::</p>



<pre class="brush: sql; title: ; notranslate">SELECT * FROM mytable WHERE mykey &gt;= 1 AND mykey &gt;= 20;</pre>



<p>and the query for the second mapper will be like this:</p>



<pre class="brush: sql; title: ; notranslate">SELECT * FROM mytable WHERE mykey &gt;= 21 AND mykey &gt;= 40;</pre>



<p>and so on..</p>



<p>This totally made sense. Spark was not working properly because it didn&#8217;t know how to split the data between the mappers.</p>



<p>So it was time to implement the same logic with Spark.<br>This means I had to do these actions on my code to make Spark work properly.<br>1. Fetch the primary key of the table<br>2. Find the key minimum and maximum values<br>3. Execute spark with those values</p>



<p>This is the code I ended up with:</p>



<pre class="brush: scala; title: ; notranslate">def main(args: Array[String]){

// parsing input parameters ...

val primaryKey = executeQuery(url, user, password, s&quot;SHOW KEYS FROM ${config(&quot;schema&quot;)}.${config(&quot;table&quot;)} WHERE Key_name = 'PRIMARY'&quot;).getString(5)
val result = executeQuery(url, user, password, s&quot;select min(${primaryKey}), max(${primaryKey}) from ${config(&quot;schema&quot;)}.${config(&quot;table&quot;)}&quot;)
val min = result.getString(1).toInt
val max = result.getString(2).toInt
val numPartitions = (max - min) / 5000 + 1

val spark = SparkSession.builder().appName(&quot;Spark reading jdbc&quot;).getOrCreate()

var df = spark.read.format(&quot;jdbc&quot;).
option(&quot;url&quot;, s&quot;${url}${config(&quot;schema&quot;)}&quot;).
option(&quot;driver&quot;, &quot;com.mysql.jdbc.Driver&quot;).
option(&quot;lowerBound&quot;, min).
option(&quot;upperBound&quot;, max).
option(&quot;numPartitions&quot;, numPartitions).
option(&quot;partitionColumn&quot;, primaryKey).
option(&quot;dbtable&quot;, config(&quot;table&quot;)).
option(&quot;user&quot;, user).
option(&quot;password&quot;, password).load()

// some data manipulations here ...

df.repartition(10).write.
   mode(SaveMode.Overwrite).parquet(outputPath)

}</pre>



<p>And it worked perfectly.</p>



<p>Remarks:<br>1. The numPartitions I set for Spark is just a value I found to give good results according to the number of rows. This can be changed, since the size of the data is also effected by the column size and data types of course.<br>2. The repartition action at the end is to avoid having small files.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://aviyehuda2.wordpress.com/2018/12/17/the-right-way-to-use-spark-and-jdbc/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2775</post-id>
		<media:content url="https://2.gravatar.com/avatar/58277fd5adf41424400f6241b432f5cbe201500ba80bc32cd8a25cf94285f3dc?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">aviyehuda555</media:title>
		</media:content>

		<media:content url="https://aviyehuda2.wordpress.com/wp-content/uploads/2018/12/spark_jdbc.png?w=465" medium="image" />
	</item>
	</channel>
</rss>
