iT邦幫忙

2025 iThome 鐵人賽

DAY 5
0
佛心分享-SideProject30

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

Day 5:單子條件解包與層級消除:性能與可讀性

  • 分享至 

  • xImage
  •  

單子條件解包(unwrap)是在建構 matcher tree 時,主動移除「只有一個子節點的聚合器」等多餘包裝,讓呼叫深度更淺、explain 更直觀、效能更穩定的一個技巧

為什麼要解包(unwrap)

{:age => {:$gt => 18}} 為例,直覺建出的樹可能是:

AND
  └─ Field: "age"
     └─ AND
        └─ Gt: 18

為什麼會多一層?

因為 age 對應到的條件值{:$gt => 18},型別為 hash,很自然就當作聚合層處理
但這裡的內層 AND 只有一個子 matcher,沒有資訊增量,卻會增加一層呼叫與 trace 雜訊
Mongory 在建樹期會把這層拿掉,得到:

Field: "age"
  └─ Gt: 18

何時解包,何時保留

  • 解包:
    • ConditionMatcher / AndMatcher / OrMatcher 僅有一個子 matcher
    • 不會改變語意與副作用(單純結構包裝)
  • 保留:
    • $elemMatch(陣列元素匹配需要完整上下文)
    • $every (同上)

實作策略(建樹期消除深度)

下列概念化程式碼示意建樹過程中的解包邏輯,非完整原始碼:

def build_matcher(node)
  case node
  when Hash
    if operator_hash?(node)
      matcher = build_operator_matcher(node)
      return unwrap_if_single(matcher)
    else
      and_matcher = build_and_from_hash(node) # 將 field:cond 攤平成 AND
      return unwrap_if_single(and_matcher)
    end
  else
    EqMatcher.new(node)
  end
end

def unwrap_if_single(m)
  return m if must_preserve?(m)
  return m unless m.is_a?(AbstractMultiMatcher)
  return m if m.matchers.size != 1
  m.matchers.first
end

def must_preserve?(m)
  m.is_a?(ElemMatchMatcher) || m.is_a?(EveryMatcher)
end

如想了解解包邏輯,請參考 source code enable_unwrap!

範例與 explain

require 'mongory'
Mongory.enable_symbol_snippets!
Mongory.register(Array)

records = [
  { 'name' => 'John', 'age' => 20 },
  { 'name' => 'Ann',  'age' => 17 }
]

# 單子條件(可解包)
q1 = records.mongory.where(:age.gt => 18)
q1.explain

# 需保留語意的條件
q2 = records.mongory.where(:tags.elem_match => { :name => 'ruby' })
q2.explain

輸出:

└─ Query: {"$and"=>[{"age"=>{"$gt"=>18}}]}
   └─ Field: "age" to match: {"$gt"=>18}
      └─ Gt: 18

└─ Query: {"$and"=>[{"tags"=>{"$elemMatch"=>{"name"=>"ruby"}}}]}
   └─ Field: "tags" to match: {"$elemMatch"=>{"name"=>"ruby"}}
      └─ ElemMatch: {"name"=>"ruby"}
         └─ Field: "name" to match: "ruby"
            └─ Eq: "ruby"

邊界與踩雷清單

  • $elemMatch, $every 不解包:它不是單純聚合器,而是「針對陣列逐一匹配」的語意單元
  • 解包只在「建樹期」進行,避免 runtime 行為與 explain 不一致
  • 空 AND/OR 的規格需明確:
    • 空 AND 可視作 true 或直接移除,視設計選擇
    • 空 OR 多半視作 false,避免誤通過

效能觀察(小型基準)

在條件深度 4 ~ 6、每層只有單一子條件的情境,解包可讓呼叫深度下降 30–60%,explain/trace 的輸出量也更精簡
在 Mongory 的 C core/Ruby 兩條路徑上,解包都能帶來可觀的呼叫層數縮減,越複雜的條件越有感

延伸小抄

  • 看到 Field -> And -> <單一子> 就想到可以解包
  • 看到 $elemMatch/$every 就想到要保留
  • 先用 explain 檢視樹,再用 trace 逐筆驗證匹配過程

下一篇:Query builder 與 converters(含 Rails generator 快速示範)

專案首頁(Ruby 版)


上一篇
Day 4:進階運算子與 Array 包裝
下一篇
筆者心裡話:在這個 AI 盛行的時代,要 Mongory 有何用?
系列文
Mongory:打造跨語言、高效能的萬用查詢引擎10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言