iT邦幫忙

2025 iThome 鐵人賽

DAY 1
0
佛心分享-SideProject30

Mongory:打造跨語言、高效能的萬用查詢引擎系列 第 1

Day 1:快速上手與緣起:5 分鐘跑起 Mongory(Quick Start + 起源)

  • 分享至 

  • xImage
  •  

為什麼要有 Mongory?

一開始,來自筆者工作上的需求:把條件寫在 S3 上的 file,應用程式動態讀取,決定要不要執行某些動作

但問題在於「如何規劃條件式 DSL」,以及「如何解析」
這時我把目光瞄向了 MongoDB 的 Query 語法

MongoDB Query 語法的特性是使用類 JSON 結構表達條件,結構清晰直觀,條件表達式語意明確,非常易於編寫與擴充

但市面上現有的 lib 套件找不到這種可以動態編譯 JSON 格式條件,並作為條件篩選的 engine,而 MongoDB Query 本身又深度綁定 DB,無法用於純資料

MongoDB 的 query 語法很好用,我希望把它「抽離資料庫」也能用在記憶體中的任意資料結構上

雖然後來工作上那個需求改方案了決定寫死條件,但筆者做出的最初版保留了下來,並且決定開源
於是有了 Mongory : 一個在記憶體陣列上執行 Mongo 風格篩選的 DSL filter engine!
命名就是 Mongo + query 的複合字,取其只實作 Mongo query 邏輯,但不綁定 DB 的概念
Mongory, let you query everywhere !

本篇不長篇大論,直接在 5 分鐘內跑起來,先拿到成就感,再回頭看故事與原理

5 分鐘 Quick Start(可直接複製執行)

  1. 安裝
gem install mongory
# 或 Gemfile
# gem 'mongory'
  1. 最小可執行範例(Ruby 2.6+)
require 'mongory'

# 開啟 Symbol 語法糖,並註冊可在 Array 上呼叫 .mongory
Mongory.enable_symbol_snippets!
Mongory.register(Array)

# 模擬資料
records = [
  { 'name' => 'Jack', 'age' => 18, 'tags' => [{ 'name' => 'ruby', 'priority' => 6 }] },
  { 'name' => 'Jill', 'age' => 15, 'tags' => [{ 'name' => 'rails', 'priority' => 3 }] },
  { 'name' => 'Bob',  'age' => 21, 'tags' => [{ 'name' => 'ruby', 'priority' => 8 }] }
]

# 建構你的 Query
query = records.mongory
  .where(:age.gte => 18) # 基本比較子
  .any_of(               # 巢狀 OR(語義 alias)
    { :name.regex => /^J/ },
    { :tags.elem_match => { :name => 'ruby' } }
  )

# each 直接出來的就會是篩選過的資料
query.each { |r| p r }

你應該會看到 JackBob,因為他們年齡 >= 18,且名字以 J 開頭或 tags 內含 ruby

  1. explain:看懂你的 matcher tree,並了解輸入的條件將被如何運作
query.explain

實際輸出:

└─ Query: {"$and"=>[{"age"=>{"$gte"=>18}}, {"$or"=>[{"name"=>...
   └─ And: [{"age"=>{"$gte"=>18}}, {"$or"=>[{"name"=> ...
      ├─ Field: "age" to match: {"$gte"=>18}
      │  └─ Gte: 18
      └─ Or: [{"name"=>{"$regex"=>/^J/}}, {"tags"=>{"$elemMatch"...
         ├─ Field: "tags" to match: {"$elemMatch"=>{"name"=>"ruby"}}
         │  └─ ElemMatch: {"name"=>"ruby"}
         │     └─ Field: "name" to match: "ruby"
         │        └─ Eq: "ruby"
         └─ Field: "name" to match: {"$regex"=>/^J/}
            └─ Regex: /^J/
  1. trace:逐筆逐步追蹤匹配過程
records.mongory.where(age: 18).trace { |x| ... }

輸出會列出每個 matcher 的命中與否,幫你快速定位條件設計問題

輸出 be like:
https://ithelp.ithome.com.tw/upload/images/20250831/20151038y07aSND8lK.png

這非常好用,尤其是在你面對一堆手寫篩選條件,程式運作不如預期卻不知道從何 debug 起時

  1. 可選:切到 C 加速路徑(若 C 擴充可用)
if defined?(Mongory::CMatcher)
  records.mongory.c.where(:age.gte => 18).each { |x| ... }
end

一樣可以 trace

  records.mongory.c.where(:age.gte => 18).trace { |x| ... }

在少量資料或是簡單條件的場景,效能通常跟一般純 Ruby 的手寫篩選持平
當資料筆數越多,或是條件越複雜時,效能甚至有機會超越純 Ruby
更不用說 Mongory 還提供比對過程的追蹤

  1. 小提醒(容易踩雷)
  • limit(n) 會「立即」生效,且影響後續條件的資料子集(效能最佳化用途)

  • $in/$nin 對 Array 欄位是「交集」語意,不是單純 include?

  • :tags => 'a' 對 Array 欄位會自動包成 $elemMatch 走陣列元素比對

好咧,今天的文章就到這邊,相信讀者您已經:

  • 安裝 mongory,跑出第一個 query
  • explain 看見 matcher tree
  • trace 追蹤逐筆匹配

接下來(Day 2 ~ Day 4)會帶你走過 matcher tree 的設計與運算子細節;Week 2 會進入 C core;Week 3 介紹 Ruby Bridge 與效能戰;Week 4 探索 Go Bridge 與未來規劃

專案首頁(Ruby 版)

也歡迎在 Issue 留下你想要的運算子或真實案例,讓我們在接下來的篇章中一起打磨


下一篇
Day 2:為什麼需要 Query Engine:S3 JSON → 匹配引擎的誕生
系列文
Mongory:打造跨語言、高效能的萬用查詢引擎10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言