<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Performance Tuning | Sasan Jafarnejad | AI Researcher</title><link>https://sasan.jafarnejad.io/tag/performance-tuning/</link><atom:link href="https://sasan.jafarnejad.io/tag/performance-tuning/index.xml" rel="self" type="application/rss+xml"/><description>Performance Tuning</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Wed, 02 Oct 2024 12:10:22 +0200</lastBuildDate><item><title>Solving Memory Issues When Loading Parquet Files into Pandas DataFrames</title><link>https://sasan.jafarnejad.io/post/pandas-dataframe-parquet-memory-issue/</link><pubDate>Wed, 02 Oct 2024 12:10:22 +0200</pubDate><guid>https://sasan.jafarnejad.io/post/pandas-dataframe-parquet-memory-issue/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Have you ever encountered unexpected memory issues when loading a seemingly small Parquet file into a Pandas DataFrame? You&amp;rsquo;re not alone! In this article, we&amp;rsquo;ll explore a common but often overlooked problem that can cause your Python process to consume gigabytes of memory when working with Parquet files containing dictionary columns.&lt;/p&gt;
&lt;h2 id="the-problem-unexpected-memory-consumption"&gt;The Problem: Unexpected Memory Consumption&lt;/h2&gt;
&lt;p&gt;Recently, I faced a perplexing issue while working with a 145MB Parquet file containing over 2 million rows. When attempting to load this file into a Pandas DataFrame using the standard method:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;path/to/my/file.parquet&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To my astonishment, the process consumed more than 90GB of memory before being terminated by the operating system. In 2024, a 145MB file shouldn&amp;rsquo;t be considered large, so what was causing this extreme memory usage?&lt;/p&gt;
&lt;h2 id="investigating-the-cause"&gt;Investigating the Cause&lt;/h2&gt;
&lt;p&gt;After some debugging, I discovered that the culprit was a single column with a dictionary data type. This column contained dictionaries with unpredictable keys - potentially thousands of unique keys across the dataset.&lt;/p&gt;
&lt;h3 id="understanding-parquets-dictionary-encoding"&gt;Understanding Parquet&amp;rsquo;s Dictionary Encoding&lt;/h3&gt;
&lt;p&gt;Parquet uses a technique called dictionary encoding to efficiently store and compress columns with repeated values. When storing a pandas DataFrame as a Parquet file, this encoding works well. However, when the data is sparse and most keys are not frequently reused, loading the Parquet file back into memory can cause issues.&lt;/p&gt;
&lt;p&gt;When Parquet loads a dictionary-encoded column with sparse data, it creates a map for each entry containing all unique keys, with most values set to None. For millions of rows, this results in a massive memory footprint - in our case, over 90GB!&lt;/p&gt;
&lt;h2 id="the-solution-converting-dictionary-columns-to-json-strings"&gt;The Solution: Converting Dictionary Columns to JSON Strings&lt;/h2&gt;
&lt;p&gt;Since I didn&amp;rsquo;t need to perform queries on this problematic column, the solution was surprisingly simple: convert the column to a string representation before saving to Parquet. Specifically, using &lt;code&gt;json.dumps&lt;/code&gt; to serialize the dictionaries:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# When saving the DataFrame to Parquet&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;column_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;column_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;path/to/output.parquet&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# When reading the Parquet file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;path/to/output.parquet&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# If you need the column as dictionaries again:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;column_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;column_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This one-liner drastically reduced memory usage, allowing the file to load into a DataFrame without issues.&lt;/p&gt;
&lt;h2 id="key-takeaways"&gt;Key Takeaways&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Be cautious when using dictionary columns in Parquet files, especially with sparse data.&lt;/li&gt;
&lt;li&gt;Consider converting dictionary columns to JSON strings if you don&amp;rsquo;t need to query their contents directly.&lt;/li&gt;
&lt;li&gt;Always profile your data and memory usage when working with large datasets.&lt;/li&gt;
&lt;li&gt;Understand the underlying storage mechanisms of file formats like Parquet to optimize your data pipeline.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;While Parquet&amp;rsquo;s dictionary encoding is generally beneficial for data compression and query performance, it can lead to unexpected memory issues in certain scenarios. By being aware of these potential pitfalls and applying simple solutions like converting to JSON strings, you can significantly optimize your data processing workflows.&lt;/p&gt;</description></item></channel></rss>