Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CUEを使ったJSONテンプレート管理

 CUEを使ったJSONテンプレート管理

Kazuma Arimura

December 02, 2022
Tweet

More Decks by Kazuma Arimura

Other Decks in Marketing & SEO

Transcript

  1. CUEを使った JSONテンプレート管理 [email protected] ML/Search team @pakio 2022/12/02 - mercari.go #20

  2. 2022 Agenda • 導入背景 • CUEについて ◦ JSONビルダとしての利用事例 • 他の生成方法との比較検討

    • まとめ
  3. 2022 背景

  4. 2022 導入背景 現在のMercari USの検索周りシステム構成(概略) Microservices BFF … search team Search

    Service Primary Search Engine Elasticsearch
  5. 2022 導入背景 GET /_search { "query": { "bool": { "should":

    [ { "match": { "name.first": { "query": "shay", "_name": "first" } } }, { "match": { "name.last": { "query": "banon", "_name": "last" } } } ], "filter": { "terms": { "name.last": [ "banon", "kimchy" ], "_name": "test" } } } } } Elasticsearch Query DSL • JSON型 • 複雑な条件の場合、ネストが深くなる • 条件の組み合わせによって、ブロックを挿入 する箇所を変えたいケースがある → クエリをコンポーネントで管理しつつ、 それらを組み合わせてクエリを組みたい
  6. 2022 導入背景 GET /_search { "query": { "bool": { "should":

    [ { "match": { "name.first": { "query": "shay", "_name": "first" } } }, { "match": { "name.last": { "query": "banon", "_name": "last" } } } ], "filter": { "terms": { "name.last": [ "banon", "kimchy" ], "_name": "test" } } } } } クエリビルダに求める条件 • 柔軟にJSONが構築できること • 複雑なクエリになっても可読性が高いこと • 学習コストが十分に低いこと • 型安全性が守れること ◦ ❌ interface{}の乱用
  7. 2022 CUE

  8. 2022 CUE CUEとは? CUE is an open source data constraint

    language which aims to simplify tasks involving defining and using data. 特徴 • 基本的にJSONのsuperset • CUE自体はGoで記述された言語 ◦ 文法的にはJSON + Go • 値のvalidationが詳細に定義可能 • 各種IDEのサポートが豊富 • Goのpackageが公式で提供されている Logo from: https://cuelang.org/
  9. 2022 社内事例 - Kubernetes Configuration Management with CUE https://engineering.mercari.com/en/blog/entry/20220127-kubernetes-configuration-management-with-cue/

  10. 2022 JSONビルダとしてのCUE

  11. 2022 JSONビルダとしてのCUE JSON生成までの手順 1. JSONのテンプレートを.cue形式で記述 2. Goのプログラム内で読み込み、CompileBytes()またはCompileString()でcue.Value形式にコンパイル 3. (必要に応じて)プログラムから値のバインディング/マージ等の操作 4.

    JSON等用途に応じた形式で出力
  12. 2022 Data binding / validation Goのプログラムから変数のバインディング、また型・値のバリデーションが可能 min?: *0 | number

    // 0 if undefined max?: number & >min // must be strictly greater than min if defined. minmax.cue type minmax struct { Min int `json:"min"` Max int `json:"max"` } func main() { data := minmax{Min: 10, Max: 100} v := cuecontext.New().CompileBytes(template) filled := v.FillPath(cue.ParsePath(""), data) compiled, err := filled.MarshalJSON() } bind.go Structから 値をバインド
  13. 2022 Data binding / validation min?: *0 | number max?:

    number & >min minmax.cue func main() { data := minmax{Min: 10, Max: 100} v := cuecontext.New().CompileBytes(template) filled := v.FillPath(cue.ParsePath(""), data) compiled, err := filled.MarshalJSON() fmt.Printf("err: %v\njson:%s\n", err, compiled) } bind.go err: <nil> json:{"min":10,"max":100} out
  14. 2022 func main() { data := minmax{Min: 10, Max: 5}

    v := cuecontext.New().CompileBytes(template) filled := v.FillPath(cue.ParsePath(""), data) compiled, err := filled.MarshalJSON() fmt.Printf("err: %v\njson:%s\n", err, compiled) } bind.go Data binding / validation min?: *0 | number max?: number & >min minmax.cue 不正な値 err: cue: marshal error: max: invalid value 5 (out of bound >10) json: out
  15. 2022 Reduce boilerplate in your data list/mapを渡してイテレーション #name: { first:

    string last: string } #names: [... #name] #names: [ {first: "jiro", last: "sasaki"}, {first: "taro", last: "tanaka"}, ] attendees: [ for name in #names { {"name": "\(name.first) \(name.last)"} }, ] { "attendees": [ { "name": "jiro sasaki" }, { "name": "taro tanaka" } ] }
  16. 2022 Reduce boilerplate in your data 複数のCUEを結合 { term: {"name":

    "taro tanaka"} } term.cue { query: {} } query.cue { function_score: { query: {} random_score: {} } } function_score.cue
  17. 2022 Reduce boilerplate in your data 複数のCUEを結合 ※ FillPathで指定するValue同士は同一のcuecontextから生成される必要あり func

    mergeCue() { ctx := cuecontext.New() term := ctx.CompileBytes(termTemplate) query := ctx.CompileBytes(queryTemplate) score := ctx.CompileBytes(scoreTemplate) merged := query.FillPath(cue.ParsePath("query"), term) compiled, _ := merged.MarshalJSON() merged = score.FillPath(cue.ParsePath("function_score.query"), term) compiled, _ = merged.MarshalJSON() } merge.go
  18. 2022 CUE その他サポートされているオペレーション : • Ifによる条件分岐 • 各種論理演算子・算術演算(math packageの一部) •

    len()を用いた配列長判定 • nullが渡された場合のデフォルト値定義
  19. 2022 比較/検討

  20. 2022 パフォーマンス検証 他のJSON生成方法と比較した際のパフォーマンスについて検証 比較対象 • encoding/json • text/template シナリオ •

    1層のみのシンプルなJSON • 5層のネスト、リストありなJSON • testing.Bによるベンチマークでns/opとallocs/opを比較
  21. 2022 検証結果 → us単位なので許容範囲なものの、かなり低速 かつencoding/json, text/templateと比較して、複雑度の増加に対する速度の低下が顕著

  22. 2022 総合的な検討 encoding/json text/template cue-lang/cue 可読性(小規模) ◦ ◦ ◦ 可読性(大規模)

    ☓ ◦ ◦ 開発効率 △ ☓ ◦ 学習コスト ◦ △ △ 型安全性 ◦ ◦ ◦ 処理パフォーマンス ◦ ◦ ☓
  23. 2022 総合的な検討 クエリビルダに求める条件 • 柔軟にJSONが構築できること • 複雑なクエリになっても可読性が高いこと • 学習コストが十分に低いこと •

    型安全性が守れること encoding/json text/template cue-lang/cue 可読性(小規模) ◦ ◦ ◦ 可読性(大規模) ☓ △ ◦ 開発効率 △ ☓ ◦ 学習コスト ◦ △ △ 型安全性 ◦ ◦ ◦ 処理パフォーマンス ◦ ◦ ☓ WINNER👑 → 全て条件を満たした上で、複雑になった場合の可読性・開発効率から CUEを選択
  24. 2022 まとめ

  25. 2022 まとめ • Mercari USの検索ではElasticsearch向けのJSONビルダとしてCUEを利用している • シンタックスハイライトやIDEによるオートコンプリートなどの恩恵を受けた効率的な開発を実現 • ただし、パフォーマンスについての懸念が若干あるため、ユースケース毎に検証は必要そう

  26. 2022 Thanks!