<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Duckdb on Tarragon</title><link>https://tarrragon.github.io/blog/tags/duckdb/</link><description>Recent content in Duckdb on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 31 Aug 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/duckdb/index.xml" rel="self" type="application/rss+xml"/><item><title>8.5 pandas 的記憶體邊界：資料與運算過程的佔用估算，推回資料庫、換引擎與分散式的代價</title><link>https://tarrragon.github.io/blog/programming/python/basic/08-data-analysis/memory-is-the-boundary/</link><pubDate>Mon, 31 Aug 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/programming/python/basic/08-data-analysis/memory-is-the-boundary/</guid><description>&lt;p>pandas 把整批資料放進記憶體才開始算，所以記憶體容量是它的硬邊界。這個邊界可以事先估算，而越過之後有三個方向可以走，各自付不同的代價。&lt;/p>
&lt;h2 id="資料本身的佔用量估算">資料本身的佔用量估算&lt;/h2>
&lt;p>數值欄位每一格佔固定的位元組數，所以列數乘欄數乘每格的位元組數就是估算值。&lt;code>int64&lt;/code> 與 &lt;code>float64&lt;/code> 各佔 8 個位元組。&lt;/p>
&lt;p>五百萬列、兩個數值欄的表格實測佔 76 MB，與 5,000,000 × 2 × 8 = 80 MB 相符。這個量級對現在的機器不構成問題，往上推一個量級（五千萬列）到 760 MB 仍然可行，再往上就要看機器。&lt;/p>
&lt;p>字串欄位不適用這個乘法，它的佔用取決於實際的字元內容與 pandas 的字串儲存方式——這一欄只能量，不能算，量的方式是 &lt;code>df.memory_usage(deep=True)&lt;/code>。&lt;/p>
&lt;h2 id="運算過程的中間結果佔用的空間">運算過程的中間結果佔用的空間&lt;/h2>
&lt;p>估算容易漏掉的是中間結果。合併與分組會產生暫時的陣列，而它們與原始資料同時存在於記憶體裡。&lt;/p>
&lt;p>實測一次 &lt;code>merge&lt;/code>：兩張表本身合計 38 MB，合併結果 46 MB，而合併期間的額外配置峰值是 136 MB——大約是那兩張表合計 38 MB 的三倍半。&lt;/p>
&lt;p>所以判斷標準是「資料量的數倍要放得下」，而不是「資料量放得下」。倍數隨操作而變，合併與排序這類要重排全部資料的操作最高。&lt;/p>
&lt;h2 id="越過記憶體邊界之後的方向推回資料庫換引擎分散式">越過記憶體邊界之後的方向：推回資料庫、換引擎、分散式&lt;/h2>
&lt;p>&lt;strong>把運算推回資料庫。&lt;/strong> 資料如果本來就在資料庫裡，先用 SQL 把量壓下來再載入。這條路不需要換工具，代價是那段運算必須用 SQL 表達得出來。&lt;/p>
&lt;p>&lt;strong>換成不需要全部載入的引擎。&lt;/strong> DuckDB 接受 SQL，而查詢的對象可以直接是磁碟上的檔案。實測查一個 129 MB 的 CSV 算出列數與平均值，Python 端的配置峰值是 0.0 MB；同一件事用 pandas 先載入再算，峰值 114.5 MB、載入後的 DataFrame 佔 76 MB。&lt;/p>
&lt;p>這裡的 0.0 MB 要照字面讀：量的是 Python 自己配置的記憶體，DuckDB 的運算發生在它自己的引擎裡、記憶體不經過 Python 的配置器。它逐段讀檔案，任何時刻只持有其中一段。&lt;/p>
&lt;p>Polars 走的是另一條類似的路：介面接近 DataFrame，而它的延遲執行模式讓整段運算先被規劃過再執行，可以只讀用得到的欄位。&lt;/p>
&lt;p>&lt;strong>換成分散式。&lt;/strong> 資料量大到單機放不下時才走這個方向。Spark 把同一份運算拆到多台機器上，代價是叢集本身的維運成本與除錯難度，兩者都比單機高一個量級。&lt;/p>
&lt;h2 id="記憶體不足與跑得慢的分辨">記憶體不足與跑得慢的分辨&lt;/h2>
&lt;p>記憶體不足有它自己的訊號：程序被作業系統終止，或者機器開始把記憶體換出到磁碟而整體速度崩落。這兩種現象都來自同一件事——需要的空間超過實體記憶體，作業系統只能終止程序或改用磁碟頂替。&lt;/p>
&lt;p>跑得慢的成因在別處。逐列套用函式而沒有用向量化的操作，慢的是每一列都要繞一趟 Python 直譯器；反覆複製整張表，慢的是記憶體頻寬。這兩種都與容量無關，所以資料量再小它們也照樣發生，換到容量更大的工具上也照樣發生。&lt;/p>
&lt;p>分開這兩者的意義在於推回資料庫、換引擎與分散式這幾個方向只解記憶體不足那一種。&lt;/p>
&lt;h2 id="延伸閱讀運算發生的位置sql-與-pandas-的寫法對應商業分析指標">延伸閱讀：運算發生的位置、SQL 與 pandas 的寫法對應、商業分析指標&lt;/h2>
&lt;p>&lt;strong>推回資料庫這個方向的另一端&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/programming/python/basic/08-data-analysis/where-computation-runs/" data-link-title="8.2 運算發生的位置：資料庫端與記憶體端的工具、分工條件與 DuckDB" data-link-desc="運算留在資料庫端或搬進記憶體端這條分界，怎麼決定工具的形狀、容量上限與失敗形態">8.2 運算發生的位置&lt;/a> 寫兩端的分工由哪三個條件決定，以及 DuckDB 為什麼同時站在兩邊。&lt;/p>
&lt;p>&lt;strong>推回去之後要用的語法對照&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/programming/python/basic/08-data-analysis/same-relational-algebra/" data-link-title="8.4 SQL 子句與 pandas 方法的對應：篩選、連接、分組彙總、視窗函數，以及空值、索引與順序的語意差異" data-link-desc="SQL 與 DataFrame 共用的關聯代數、同一份資料上兩邊寫法的實測比對，以及照著逐句翻譯會靜默失準的位置">8.4 SQL 子句與 pandas 方法的對應：篩選、連接、分組彙總、視窗函數，以及空值、索引與順序的語意差異&lt;/a> 給篩選、連接、分組彙總與視窗函數在兩邊的實測比對，以及翻譯時最容易靜默出錯的空值處理。&lt;/p>
&lt;p>&lt;strong>這批資料算出來之後拿去做什麼&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/devops/monitoring/08-business-analytics/" data-link-title="模組八：行為資料的商業利用" data-link-desc="Funnel / Cohort / Attribution / A/B test / 推薦系統 / RFM — 從 debug 工具到商業資產的翻轉">monitoring 模組八&lt;/a> 寫留存、分群與轉換率各自怎麼定義，以及事件當初的屬性設計決定了哪些指標事後算得出來。&lt;/p></description><content:encoded><![CDATA[<p>pandas 把整批資料放進記憶體才開始算，所以記憶體容量是它的硬邊界。這個邊界可以事先估算，而越過之後有三個方向可以走，各自付不同的代價。</p>
<h2 id="資料本身的佔用量估算">資料本身的佔用量估算</h2>
<p>數值欄位每一格佔固定的位元組數，所以列數乘欄數乘每格的位元組數就是估算值。<code>int64</code> 與 <code>float64</code> 各佔 8 個位元組。</p>
<p>五百萬列、兩個數值欄的表格實測佔 76 MB，與 5,000,000 × 2 × 8 = 80 MB 相符。這個量級對現在的機器不構成問題，往上推一個量級（五千萬列）到 760 MB 仍然可行，再往上就要看機器。</p>
<p>字串欄位不適用這個乘法，它的佔用取決於實際的字元內容與 pandas 的字串儲存方式——這一欄只能量，不能算，量的方式是 <code>df.memory_usage(deep=True)</code>。</p>
<h2 id="運算過程的中間結果佔用的空間">運算過程的中間結果佔用的空間</h2>
<p>估算容易漏掉的是中間結果。合併與分組會產生暫時的陣列，而它們與原始資料同時存在於記憶體裡。</p>
<p>實測一次 <code>merge</code>：兩張表本身合計 38 MB，合併結果 46 MB，而合併期間的額外配置峰值是 136 MB——大約是那兩張表合計 38 MB 的三倍半。</p>
<p>所以判斷標準是「資料量的數倍要放得下」，而不是「資料量放得下」。倍數隨操作而變，合併與排序這類要重排全部資料的操作最高。</p>
<h2 id="越過記憶體邊界之後的方向推回資料庫換引擎分散式">越過記憶體邊界之後的方向：推回資料庫、換引擎、分散式</h2>
<p><strong>把運算推回資料庫。</strong> 資料如果本來就在資料庫裡，先用 SQL 把量壓下來再載入。這條路不需要換工具，代價是那段運算必須用 SQL 表達得出來。</p>
<p><strong>換成不需要全部載入的引擎。</strong> DuckDB 接受 SQL，而查詢的對象可以直接是磁碟上的檔案。實測查一個 129 MB 的 CSV 算出列數與平均值，Python 端的配置峰值是 0.0 MB；同一件事用 pandas 先載入再算，峰值 114.5 MB、載入後的 DataFrame 佔 76 MB。</p>
<p>這裡的 0.0 MB 要照字面讀：量的是 Python 自己配置的記憶體，DuckDB 的運算發生在它自己的引擎裡、記憶體不經過 Python 的配置器。它逐段讀檔案，任何時刻只持有其中一段。</p>
<p>Polars 走的是另一條類似的路：介面接近 DataFrame，而它的延遲執行模式讓整段運算先被規劃過再執行，可以只讀用得到的欄位。</p>
<p><strong>換成分散式。</strong> 資料量大到單機放不下時才走這個方向。Spark 把同一份運算拆到多台機器上，代價是叢集本身的維運成本與除錯難度，兩者都比單機高一個量級。</p>
<h2 id="記憶體不足與跑得慢的分辨">記憶體不足與跑得慢的分辨</h2>
<p>記憶體不足有它自己的訊號：程序被作業系統終止，或者機器開始把記憶體換出到磁碟而整體速度崩落。這兩種現象都來自同一件事——需要的空間超過實體記憶體，作業系統只能終止程序或改用磁碟頂替。</p>
<p>跑得慢的成因在別處。逐列套用函式而沒有用向量化的操作，慢的是每一列都要繞一趟 Python 直譯器；反覆複製整張表，慢的是記憶體頻寬。這兩種都與容量無關，所以資料量再小它們也照樣發生，換到容量更大的工具上也照樣發生。</p>
<p>分開這兩者的意義在於推回資料庫、換引擎與分散式這幾個方向只解記憶體不足那一種。</p>
<h2 id="延伸閱讀運算發生的位置sql-與-pandas-的寫法對應商業分析指標">延伸閱讀：運算發生的位置、SQL 與 pandas 的寫法對應、商業分析指標</h2>
<p><strong>推回資料庫這個方向的另一端</strong>：<a href="/blog/programming/python/basic/08-data-analysis/where-computation-runs/" data-link-title="8.2 運算發生的位置：資料庫端與記憶體端的工具、分工條件與 DuckDB" data-link-desc="運算留在資料庫端或搬進記憶體端這條分界，怎麼決定工具的形狀、容量上限與失敗形態">8.2 運算發生的位置</a> 寫兩端的分工由哪三個條件決定，以及 DuckDB 為什麼同時站在兩邊。</p>
<p><strong>推回去之後要用的語法對照</strong>：<a href="/blog/programming/python/basic/08-data-analysis/same-relational-algebra/" data-link-title="8.4 SQL 子句與 pandas 方法的對應：篩選、連接、分組彙總、視窗函數，以及空值、索引與順序的語意差異" data-link-desc="SQL 與 DataFrame 共用的關聯代數、同一份資料上兩邊寫法的實測比對，以及照著逐句翻譯會靜默失準的位置">8.4 SQL 子句與 pandas 方法的對應：篩選、連接、分組彙總、視窗函數，以及空值、索引與順序的語意差異</a> 給篩選、連接、分組彙總與視窗函數在兩邊的實測比對，以及翻譯時最容易靜默出錯的空值處理。</p>
<p><strong>這批資料算出來之後拿去做什麼</strong>：<a href="/blog/devops/monitoring/08-business-analytics/" data-link-title="模組八：行為資料的商業利用" data-link-desc="Funnel / Cohort / Attribution / A/B test / 推薦系統 / RFM — 從 debug 工具到商業資產的翻轉">monitoring 模組八</a> 寫留存、分群與轉換率各自怎麼定義，以及事件當初的屬性設計決定了哪些指標事後算得出來。</p>
]]></content:encoded></item></channel></rss>