跳到正文
Hacker News (official RSS)(ipinfo.io)·· 3 天前精選AI 評分78

我們將 MMDB 檔案縮減 33%

How we made our MMDB files a 33% smaller

AI 導讀

mmdbshrink 將 MMDB 檔案大小縮減 31%,最高 80%,並保持查詢結果不變。

  • - 工具:mmdbshrink 及 mmdbctl 更新,已開源於 GitHub。
  • - 效能:一百萬次查詢速度略升,記憶體使用從 36 MiB 降至 23.9 MiB。
  • - 應用:已於 2026 年 7 月投入生產,API 與下載均使用最佳化檔案。
推薦理由

本研究展示了利用雜湊共用技術將 MMDB 檔案縮減至 31% 的實作與驗證,對 IP 位置與隱私資料庫的效能與儲存成本具有直接影響。

正文 · AI 翻譯

譯文尚未完整,完整內容請切換至原文。

在八月,我們告訴資料庫客戶他們的 檔案已縮小多達 80%,並承諾說明原因。這就是那份說明。執行此功能的工具現在已開源。

我們的工作

我們在 MMDB 檔案格式中發現了顯著的尺寸節省,詳情見下文。我們將其作為 mmdbctl 的更新以及獨立工具 mmdbshrink 釋出。該工具會讀取現有 MMDB 檔案並寫出更小的版本。新檔案仍為標準 MMDB:任何能開啟舊檔案的讀取程式都能開啟新檔案,並對每個 IP 地址回傳相同答案。在我們每天構建的 89 個資料庫中,目錄大小從 91.2 GB 降至 62.8 GB,減少 31%。最佳單一結果為 ipinfo_privacy,從 789 MB 降至 155 MB,減少 80%。

並未進行任何壓縮。沒有解壓步驟、沒有新讀取器、也沒有新格式。節省是真實存在於靜止狀態、網路傳輸以及記憶體中,因為 MMDB 檔案是記憶體對映的,檔案大小即為記憶體佔用。

此工具可於 https://github.com/ipinfo/mmdbctl/releases/latest。

MMDB 檔案的結構

MMDB 檔案分為兩部分。搜尋樹是以 IP 位址的位元為基礎的二元 trie。從根節點開始,逐位讀取位址,遇到 0 時往左,遇到 1 時往右,直到到達葉節點。葉節點包含一個指向第二部分 資料區 的指標,資料區存放記錄:國家、城市、ASN、隱私旗標,資料庫所載的任何資訊。

格式設計者對資料區進行了精心設計。它擁有指標型別,因而重複的記錄可只寫一次,並由多個葉節點引用,常見的寫入工具也採用此做法。

這也是為什麼在下方示範的 ipinfo_lite 執行中,資料區的大小完全不變。

搜尋樹則較少受到關注。每個節點會被寫成兩筆記錄,分別對應兩個子節點,標準寫入器會輸出它訪問到的每個節點。它不會詢問自己是否已經寫過相同的節點。

為什麼這很重要:網際網路是重複的

想像在位址空間不同區域有兩個 /24 網路,且它們的 256 個位址都回傳相同答案。在 /24 邊界以下,這兩個字首下的子樹逐位相同:相同結構、相同葉節點、相同指向資料區的指標。標準寫入器會將兩者都儲存。

現在將規模放大。隱私資料庫只包含少量判斷,適用於數百萬個範圍。位置資料庫則有數十萬個不同城市記錄,分佈於數百萬個字首。只要兩個字首解析到相同記錄,且其下方所有內容也相同,它們的子樹就是重複的。在我們的檔案中,樹的大部分都是重複子樹。

解決方案:使用 Hash-Consing 來處理樹

Hash-consing is a long-established technique from compiler and symbolic-computation work, described by Filliâtre and Conchon (2006), Type-Safe Modular Hash-Consing, and closely related to the subgraph sharing in Bryant’s binary decision diagrams (1986). The idea is simple: keep a hash table of nodes already created, and reuse an existing node whenever its contents are structurally identical to the one you are about to create. Applied bottom-up to a trie, this means processing the children first, then sharing nodes with the same pair of children. The result is a directed acyclic graph: multiple parents can point to the same subtree, which is stored only once.

應用於 MMDB 搜尋樹,程式如下:

  1. 從葉節點向上遍歷現有樹,先處理子節點再處理父節點。
  2. 對於每個節點,將對子節點的引用替換為其標準身份。對資料記錄和空結果的引用保持其含義。
  3. 使用子節點引用的有序對作為雜湊表的鍵。若該精確對已存在,則重複使用標準節點;否則新增一個新的標準節點。表格會完整檢查整對是否相等,因而雜湊衝突永不導致合併。
  4. 輸出標準節點,保持根節點為節點零,重新編號節點引用,並更新 node_count 及編碼資料引用。

This requires no change to the MMDB format. Each node contains two records, which can identify another node, a data record, or an empty result. Multiple parents can refer to the same node; the MMDB format specification already describes this kind of sharing for IPv4 aliases.

共享相同子樹可保留每個 IP 位址的分支決策順序,並得到相同的資料記錄或空結果。檔案佈局與大小會變更;查詢結果保持不變。

我們是第一個發現並發布此節省方案的人。起初,參考寫入器在每個位置輸出一個新節點,其他我們檢視的寫入器也是如此。去年在一篇 WhatsApp 漏洞論文中出現了概念相似的技術,研究人員需要儲存大量電話號碼,並自行構建自訂結構。與此不同的是,我們的輸出仍然是有效的 MMDB。

數字

在我們的生產目錄中:

查詢速度保持不變或略有提升。在下列 ipinfo_lite 基準中,一百萬次查詢在原始檔案上每次 797 ns,在最佳化檔案上 757 ns,常駐記憶體從 36.02 MiB 降至 23.92 MiB。樹節點位元組數減少意味著頁面錯誤更少。

實作範例

這是對我們免費 Lite 資料庫執行的工具,與 README 中的說明完全相同。

$ mmdbshrink --full-report ipinfo_lite.mmdb

input:   ipinfo_lite.mmdb
output:  ipinfo_lite.shrunk.mmdb

  size:   37.76 MB   ->  25.08 MB   (saved 12.68 MB, -33.58%)
  nodes:  3,790,310  ->  2,205,358  (-41.82%)
  tree:   30.32 MB   ->  17.64 MB
  data:   7.44 MB    ->  7.44 MB    (unchanged)

elapsed: 452ms

validation (seed 1):
  [OK] metadata        ip_version=6  record_size=32  binary_format=2.0
                       database_type: ipinfo bundle_location_lite.mmdb  build_epoch=1790582633
                       node_count: 3,790,310 -> 2,205,358 (-41.82%)
  [OK] fixed probes    n=39  hits=7  (0s)
  [OK] random IPv4     n=1,000,000  hits=861,887 (86.19%)  mismatches=0  (1.73s)
  [OK] random IPv6     n=1,000,000  hits=1,985 (0.20%)  mismatches=0  (39ms)

bench (1,000,000 lookups each):
  input:    size=37.76 MB    per_op= 797ns  qps=1,320,636
  output:   size=25.08 MB    per_op= 757ns  qps=1,319,347

memory (200,000 lookups each, separate processes):
  input:    mmap_cached=36.02 MiB   proc_rss=29.34 MiB   per_op= 707ns  minflt=155  majflt=0
  output:   mmap_cached=23.92 MiB   proc_rss=27.58 MiB   per_op= 695ns  minflt=168  majflt=0

一起閱讀最後三行。資料區段未被改動。每節省的位元組均來自搜尋樹,41% 的節點被發現是已寫入節點的重複。

證明輸出為相同資料庫

若單一查詢結果改變,縮小檔案毫無價值。在任何最佳化檔案送達客戶前,我們以四種方式檢查它。

  1. 雙向完整列舉。原始檔案中的每個字首都在最佳化檔案中查詢,反之亦然。對於 ipinfo_lite,這是 3,783,922 個字首,7,567,844 次查詢,全部匹配。
  2. 隨機探測。一百萬個隨機 IPv4 和一百萬個隨機 IPv6 位址,零不匹配。
  3. 現有工具。 mmdbctl verify 報告檔案有效,mmdbctl diff 報告 0 個子網路與 0 個記錄被修改,MaxMind 自己的 mmdbverify 也通過。 我們將檔案對照 12 個 MMDB 讀取 SDK 進行測試。
  4. 基準相容性 在延遲與記憶體上,與上文相同。

第一批最佳化檔案於七月投入正式環境,最先應用於一位客戶自訂資料集,該資料集已超過 2GB 平臺限制,接著再進入我們自己的 API,較小的檔案降低了每個例項所需的記憶體。如今它們已成為預設:100% 的 API 流量與 100% 的 IPinfo 資料庫下載均以相同檔名提供已最佳化的檔案。

我們為何免費提供

MMDB 是一種開放格式,可快速查詢 IP 元資料,且生態系統廣泛支援。 我們曾經釋出過開源工具,這次也延續此承諾。 我們發現的重複是格式寫入者的屬性,任何釋出 MMDB 檔案的人都會有相同的樹狀膨脹。 我們競爭的焦點在於檔案內容。 檔案的打包方式並非我們優勢所在,因此最佳化器以 Apache 2.0 授權開源。

若您自行建置 MMDB 檔案,請將其執行於您自己的檔案上。 若您只讀取 MMDB 檔案,則完全不需任何操作。

您可以在此 GitHub 倉庫中找到專案:https://github.com/ipinfo/mmdbctl/tree/master/mmdbshrink

您可以直接使用 go install github.com/ipinfo/mmdbctl/mmdbzip@latest 方便安裝,其他選項請參閱 README.md 中的安裝說明。

安裝完成後,您即可像這樣使用:mmdbshrink ipinfo_lite.mmdb

若您是 IPinfo 資料庫客戶,則不需要做任何事。您下載的每個 MMDB 檔案已經套用了這些節省。