1 of 18

1:n のドキュメントの

検索で試行錯誤した話

@kaiba

2 of 18

@kaiba

  • 器用貧乏。DB設計やバックエンドが得意ですが最近はフロントエンド、GraphQLに興味がある。
  • 技術書典5で「検索だけじゃないElasticsearch入門」を書いて以来、Elasticsearch関連でお声掛けいただけることが増えた。
  • 22inc の Stampsアプリ で Elasticsearch案件を担当
    • 設計がきれい
    • データ量が多くチャレンジング
    • 裁量が大きく、フロントからインフラまでなんでも挑戦できる
    • 必要なところには金を惜しまない
    • 夜はビールとか日本酒とか飲める
    • JOIN US!

3 of 18

鰓(エラ)

サーチ!

スティック

Esの基礎から実践まで学べる本! 500円

やるぞ!

4 of 18

5 of 18

22inc の Stamps の課題

6 of 18

Stampsの課題

  • 店舗からElasticな条件で来店者にPush通知したい
    • 20代、男性で〜
    • Kaibaステーキ渋谷店に3回来た人で〜
    • 最近来てない人にまた来てほしい!
  • MySQLで実装していたが重いクエリになりサービス全体に影響

7 of 18

「Kaibaステーキ渋谷店に3回来た人」は1:n

3/1 来店

3/8 来店

3/15 来店

8 of 18

回数の検索はSQLは苦手!

group by して sum して order…

見なくてもわかる遅いやつやん!

好きなだけ条件を

足せる!

9 of 18

User : Checkins = 1: n

どんな設計にする?

ユーザを

index?

店舗を

index?

10 of 18

普通に Aggregation でいけるっしょ期

  • EsにAggregationという集計の機能があります
  • Checkinsをindexし、渋谷店3回の来店者は検索できた
  • でも追加の条件で絞り込むのは困難だった
    • できたかもしれないけどとてもメンテできないクエリになった

渋谷店に�3回きた人

20代

11 of 18

来店回数を保持すればいいっしょ期

{

"user": {

"id": "6789",

"gender": "man",

"checkins": [

{ "shop_id": "1", count: 3 },

{ "shop_id": "3", count: 1 },

{ "shop_id": "5", count: 1 },

{ "shop_id": "8", count: 1 },

・・・

来店するほど�ドキュメントが�でかくなるよ!

非正規化!

Esらしいアプローチ!

ケースによってはあり

12 of 18

2回クエリ発行期

  • Aggregationとその他分けて2回クエリ発行しよ?
    • 渋谷店に3回来た人は? => 6,23,12,4 …
    • 男性 & user_id in (6,23,12,4 …

5千兆人来店していたら…

13 of 18

Join datatype期

  • Userを親にCheckinを子にしたJoin datatype
  • join datatypeはドキュメントの中で親子関係を持たせることができる
  • これじゃね!?

Esは非正規化!�と言ったな?

あれは嘘じゃ。

14 of 18

join datatypeはRDBのように使うべきではありません。

Elasticsearchでは非正規化してドキュメントを作ることで

高いパフォーマンスが得られています

join datatypeが意味を成すのは1つのエンティティに対して

大量のデータが含まれるケースです。

例えば製品と製品の注文です。

この場合、製品を親、注文を子としてドキュメントに

することは適しています。

気になるパフォーマンス

15 of 18

{

"user": {

...

},

"checkin": {

...

},

"my_join_field": {

"name": "user or checkin"

}

}

userかcheckin、どちらかが入ります。

自分がuserなのか、checkinなのかを指定します。

Esのmapping設定であらかじめ親、子を定義してあります。

16 of 18

クエリの例: 店舗1に3回以上来たUserを探す

{

"query": {

"has_child": {

"type": "checkin",

"query": {

"term": {

"checkin.shop_id": 1

}

},

"min_children": 3

}

}}

すげー

よみやすい!

queryがかけるので

最終来店からn日経ったとか複雑な条件もいける

17 of 18

Join datatypeで何が実現できたか

  • User : Checkin = 1 : n はEsで扱いづらいが…
    • CheckinしたときUserの情報を更新する必要がない
    • Userを更新したときCheckinを更新する必要がない
  • クエリがわかりやすい! ドキュメントの構造は気持ち悪くなったが…
  • 懸念されていたパフォーマンスも問題なかった
    • クエリを複雑にすればするほど絞り込まれるためか速くなった

18 of 18

まとめ

  • 四苦八苦してjoin datatypeに落ち着きそう
  • 十分なパフォーマンスが得られた
  • 22inc tech blogにより詳しい情報を書きました
  • Es本、持ってきたので買ってくれると嬉しいです!

ありがとうございました!