28.3K Views
November 12, 22
スライド概要
新卒で地元製本工場に入社。 システム営業としてBtoC事業の開発やカスタマーに従事し 時には自ら体を張りマスコットキャラクターとして活動することで、業界に今まで無かった風を吹かせ話題を作ることに成功したが、 運営が進むにつれ保守開発がメインとなってきてしまっていたことから、新しい技術を習得するために転職を決意。 2016年6月から地元SIerで働き始め、プロに囲まれた切磋琢磨の環境で自分が所詮井の中の蛙であった事を悟り、修行に入る。 学生時代から「多機能君」と呼ばれていた幅広さを、広く浅くな器用貧乏で終わらせず業務レベルに持っていき名を残すために奮闘している。
2019年 1
当社4名応募中3名 CfP採択される中、 一人だけ不採択になり自虐LTを披露 2
2020年 3
コロナ禍により中止 初の長野開催だったということもあり、運営の方々は勿論、現地バックアッ プとして動いていた当社にとっても無念でした。 4
2021年 5
長野開催リベンジ成功! 私はスタッフということもあってかそっとCfPに落ちていた。(と思いたい) 6
長野開催リベンジ成功! 7
時は2022年 8
Djangoで管理する全文検索エンジン DjangoCongress JP 2022 (株)日本システム技研 Yuki.Takino 9
お前、誰よ? 滝野 優紀 - Yuki Takino 長野県を拠点とする日本システム技研所属 普段の業務ではPython/Djangoをよく使う 3歳と6歳の娘を持つ2児のパパエンジニア 現在よく乾く洗濯物の干し方を修行中 PyCon JP 2018で登壇したとき月1万円だった小遣いがついに月0円になっ た。 DjangoCongress JP 2022の資料が完成したことで気が緩み寝坊した moonwalkerpoday 他 ・PyCon JP 、PyCon Kyushu等での登壇 moon̲in̲nagano ・Django Congress JP 2021といったイベントのスタッフ業 ・小規模イベントでの突LT jsl-takino など細かーく活動中 10
前提🗣 • このトークはPython/Djangoは使ってるけど全文検索はこれからデ ビューだよ、という方向けです。 • 全文検索エンジンはElasticsearchをベースにお話します。 • 息抜き程度に聞いてもらう感じのトークですので、ガチ目の話をご 所望の方はROOM 2でGeoDjangoのトークをお聞きすることをお すすめします。 11
目標 🏁 • 全文検索のあらましを理解する • 「検索なんてDjango ORMでcontainsを使えばいいじゃない」とい う思考からの卒業 • Djangoプロダクトで全文検索を導入するという選択肢を増やす 12
話さないこと • 全文検索の技術や方式などに焦点を当てた深い話 • ElasticsearchとOpenSearchという似て非なる存在の話 • 小遣いが0円になってどうやって生活しているのかという話 13
Django ORM便利ですよね😄 14
このような担当者モデルがありまして。 from django.db import models from django.utils.translation import gettext_lazy as _ class Staff(models.Model): last_name = models.CharField(_('last name'), max_length=50) first_name = models.CharField(_('first name'), max_length=50) profile = models.TextField(blank=True, default='') ... class Meta: verbose_name = verbose_name_plural = 'staff' indexes = ( models.Index(fields=('last_name',)), models.Index(fields=('first_name',)), ... ) ... 姓名はindexを指定していますが、この段階ではなんか検索が早くなる という雰囲気だけで使用していると仮定します。 15
MySQLでデータ管理しているプロダクトにて ケース1 名字が一致する人を検索したいのです が、可能ですか? はい!可能です! 顧客 T君 16
完全一致😃 >>> Staff.objects.filter(last_name=‘滝野’) <StaffQuerySet [<Staff: 滝野 太郎>, <Staff: 滝野 二郎>, <Staff: 滝野 三郎>, …]> >>> 完全一致はindexも問題なく動作し、 クエリもシンプルな一番平和なパターン 17
MySQLでデータ管理しているプロダクトにて ケース2 名字か名前が前方一致する人を検索し たいのですが、可能ですか? はい。(まだ)可能です。 顧客 T君 18
前方部分一致..😐 >>> Staff.objects.filter( Q(last_name__istartswith=‘滝野’) | Q(first_name__istartswith=‘滝野’)) <StaffQuerySet [<Staff: 滝野 太郎>, <Staff: 滝野 二郎>, <Staff: 滝野 三郎>, …]> >>> 前方一致であればindexの特性上問題なく動作するため、 多少クエリが怪しくなったがまだ引き返せるパターン 19
MySQLでデータ管理しているプロダクトにて ケース3 姓名で部分一致する人を検索したいの ですが、可能ですよね? はい…技術上は可能です。 顧客 T君 20
部分一致...🫠 >>> from django.db.models.functions import Concat >>> Staff.objects.annotate(full_name=Concat(‘last_name’, ‘first_name’)) .filter(full_name__icontains=‘滝野’) <StaffQuerySet [<Staff: 滝野 太郎>, <Staff: 滝野 二郎>, <Staff: 滝野 三郎>, …]> >>> icontainsによるLIKE %xxx% 検索により indexは星となりました。 データ量が増えてくるとパフォーマンスが低下します 21
MySQLでデータ管理しているプロダクトにて ケース4 人物のプロフィールに含まれる文 字を検索して特定したいので 対応してください。 ただ検索するだけだから特に苦労せず できるでしょ? ゑ・・・・? T君 22 顧客
・・・・???🤮 >>> Staff.objects.filter(profile__icontains=‘私の出身地は長野です’) <StaffQuerySet [<Staff: 滝野 太郎>,]> >>> テキストフィールドにicontainsによるLIKE %xxx% 検索というとんでもない実装に行き着きました。 パフォーマンスがどうなるか想像したくありません。 23
脳死でDjango ORMを使用 == チューニング地獄 今は良くてもデータが増えてきたら確実に問題と なってしまう。。。はて。。。 昨今のエンジニアが必ず通ると行って も過言ではない試練。 24 T君
そこで全文検索の可能性を閃いたT君 T君 25
目次 1.全文検索とは 逐次検索型 索引検索型 2.全文検索エンジン「Elasticsearch」とは 3.PythonでElasticsearchを使ってみる 4.Django Likeな実装を考えてみる 5.まとめ [尺余り] DB標準搭載の全文検索機能 VS 全文検索エンジン 26
全文検索とは?🤔 27
全文検索とは?🤔 全文検索とは、コンピュータにおいて、複数の文書(ファイル)から特定の文字列を検索す ること。「ファイル名検索」や「単一ファイル内の文字列検索」と異なり、「複数文書にま たがって、文書に含まれる全文を対象とした検索」という意味で使用される。 出典: フリー百科事典『ウィキペディア(Wikipedia)』 28
全文検索の検索手法は大きく分けて2つ 1.逐次検索(grep)型 目次がない本から目的の文章を探すようなこと。 全てを検索するためレイテンシはデータ量に比例する。 2. 索引検索(インデックス)型 目次から該当の項目を探すようなこと。 目的の箇所に到達するためノイズが少ないため早い。 ただしDjangoでいうcontains LookUpのような %LIKE%検索だと機能しない 29
逐次検索(grep)型 ざっくり解説 30
逐次検索(grep)型 石井の名字を持つデータを調べるよ ID Last Name First Name Pro le タワー残る探査再現する。\nキャビネット意図ブレーキトーン普通の部隊狭い フェミニス ブラケット明らかにするバーゲン埋め込む鉱山コミュニティ索引。試してみる擁 する鉱山改善今擁するプラスチックホイール。オークション合計擁する出演 ジャム腐った参加するソース電話私。革新パイオニア索引サワー。\n不自然な 私じぶんの軸ヒットベルベット葉。再現するホイール彼部隊自体コミュニティ主 1 中川 和也 2 小林 篤司 3 小林 亮介 … … … 10000 石井 裕太 10001 山口 康弘 10002 山口 聡太郎 … … … … サンプル器官トレーナーパーセント拡張。午前日曜日障害ダニ。クロス月狭い リンク。\nメニュー溝カラム敵合計ログあった。ヘアシュガーハードウェア今 動物ノート近代化するチーズ。厳しい指名参加するスペル倫理葉倫理。反射自 体トーン仕上げ屋根裏行進ダッシュ狭い。\nパーセント野球ヒットジャムカレッ トリビュート〜証言する運仕上げリフト。葉ないリフト発生する。\n連続部隊 ダッシュじぶんの。持ってる証言するバーゲンバーゲン欠乏。見落とす感謝する … fi 31
逐次検索(grep)型 まずテーブルの全データにアクセスするよ ID Last Name First Name Pro le タワー残る探査再現する。\nキャビネット意図ブレーキトーン普通の部隊狭い フェミニス ブラケット明らかにするバーゲン埋め込む鉱山コミュニティ索引。試してみる擁 する鉱山改善今擁するプラスチックホイール。オークション合計擁する出演 ジャム腐った参加するソース電話私。革新パイオニア索引サワー。\n不自然な 私じぶんの軸ヒットベルベット葉。再現するホイール彼部隊自体コミュニティ主 1 中川 和也 2 小林 篤司 3 小林 亮介 … … … 10000 石井 裕太 10001 山口 康弘 10002 山口 聡太郎 … … … … サンプル器官トレーナーパーセント拡張。午前日曜日障害ダニ。クロス月狭い リンク。\nメニュー溝カラム敵合計ログあった。ヘアシュガーハードウェア今 動物ノート近代化するチーズ。厳しい指名参加するスペル倫理葉倫理。反射自 体トーン仕上げ屋根裏行進ダッシュ狭い。\nパーセント野球ヒットジャムカレッ トリビュート〜証言する運仕上げリフト。葉ないリフト発生する。\n連続部隊 ダッシュじぶんの。持ってる証言するバーゲンバーゲン欠乏。見落とす感謝する … fi 32
逐次検索(grep)型 全体にアクセスしたテーブルから改めて検索を行うよ ID Last Name First Name Pro le タワー残る探査再現する。\nキャビネット意図ブレーキトーン普通の部隊狭い フェミニス ブラケット明らかにするバーゲン埋め込む鉱山コミュニティ索引。試してみる擁 する鉱山改善今擁するプラスチックホイール。オークション合計擁する出演 ジャム腐った参加するソース電話私。革新パイオニア索引サワー。\n不自然な 私じぶんの軸ヒットベルベット葉。再現するホイール彼部隊自体コミュニティ主 1 中川 和也 2 小林 篤司 3 小林 亮介 … … … 10000 石井 裕太 10001 山口 康弘 10002 山口 聡太郎 … … … … サンプル器官トレーナーパーセント拡張。午前日曜日障害ダニ。クロス月狭い リンク。\nメニュー溝カラム敵合計ログあった。ヘアシュガーハードウェア今 動物ノート近代化するチーズ。厳しい指名参加するスペル倫理葉倫理。反射自 体トーン仕上げ屋根裏行進ダッシュ狭い。\nパーセント野球ヒットジャムカレッ トリビュート〜証言する運仕上げリフト。葉ないリフト発生する。\n連続部隊 ダッシュじぶんの。持ってる証言するバーゲンバーゲン欠乏。見落とす感謝する … fi 33
逐次検索(grep)型 テーブルが肥大化すればスキャンの負荷も高くなるね ID Last Name First Name Pro le タワー残る探査再現する。\nキャビネット意図ブレーキトーン普通の部隊狭い フェミニス ブラケット明らかにするバーゲン埋め込む鉱山コミュニティ索引。試してみる擁 する鉱山改善今擁するプラスチックホイール。オークション合計擁する出演 ジャム腐った参加するソース電話私。革新パイオニア索引サワー。\n不自然な 私じぶんの軸ヒットベルベット葉。再現するホイール彼部隊自体コミュニティ主 1 中川 和也 2 小林 篤司 3 小林 亮介 … … … 10000 石井 裕太 10001 山口 康弘 10002 山口 聡太郎 … … … … サンプル器官トレーナーパーセント拡張。午前日曜日障害ダニ。クロス月狭い リンク。\nメニュー溝カラム敵合計ログあった。ヘアシュガーハードウェア今 動物ノート近代化するチーズ。厳しい指名参加するスペル倫理葉倫理。反射自 体トーン仕上げ屋根裏行進ダッシュ狭い。\nパーセント野球ヒットジャムカレッ トリビュート〜証言する運仕上げリフト。葉ないリフト発生する。\n連続部隊 ダッシュじぶんの。持ってる証言するバーゲンバーゲン欠乏。見落とす感謝する … fi 34
つかいどころ👋 •データ量が少ないマスタデータなど •パフォーマンスをそこまで重視しない社内システムなど 35
索引検索 (インデックス)型 ざっくり解説 36
索引検索(インデックス)型 インデックス データを登録するときに インデックスへ 解析した文字と場所を格納する よ 37 Name ドキュメントID 中川 1 和也 1 小林 2, 3 篤司 2 亮介 2 … … 石井 10000 裕太 10000 山口 10001, 10002 康弘 10001 聡太郎 10002 … …
索引検索(インデックス)型 インデックス 解析方法はいくつか種類がある けど、それを説明すると Djangoの話をせずに終わるの で割愛するよ 38 Name ドキュメントID 中川 1 和也 1 小林 2, 3 篤司 2 亮介 2 … … 石井 10000 裕太 10000 山口 10001, 10002 康弘 10001 聡太郎 10002 … …
索引検索(インデックス)型 石井の名字を持つデータを調べるよ インデックス Name ID 中川 1 和也 1 小林 2, 3 篤司 2 亮介 2 … … 石井 10000 裕太 10000 山口 10001, 10002 康弘 10001 聡太郎 10002 … … ID Last Name First Name 1 中川 和也 2 小林 篤司 3 小林 亮介 … … … 10000 石井 裕太 10001 山口 康弘 10002 山口 聡太郎 … … … fi 39 Pro le タワー残る探査再現する。\nキャビネッ ト意図ブレーキトーン普通の部隊狭いフ ブラケット明らかにするバーゲン埋め込 む鉱山コミュニティ索引。試してみる擁 ジャム腐った参加するソース電話私。革 新パイオニア索引サワー。\n不自然な私 … サンプル器官トレーナーパーセント拡 張。午前日曜日障害ダニ。クロス月狭い 動物ノート近代化するチーズ。厳しい指 名参加するスペル倫理葉倫理。反射自体 トリビュート〜証言する運仕上げリフ ト。葉ないリフト発生する。\n連続部隊 …
索引検索(インデックス)型 石井さんのデータには10000という IDが関連づいていたよ インデックス Name ID 中川 1 和也 1 小林 2, 3 篤司 2 亮介 2 … … 石井 10000 裕太 10000 山口 10001, 10002 康弘 10001 聡太郎 10002 … … ID Last Name First Name 1 中川 和也 2 小林 篤司 3 小林 亮介 … … … 10000 石井 裕太 10001 山口 康弘 10002 山口 聡太郎 … … … fi 40 Pro le タワー残る探査再現する。\nキャビネッ ト意図ブレーキトーン普通の部隊狭いフ ブラケット明らかにするバーゲン埋め込 む鉱山コミュニティ索引。試してみる擁 ジャム腐った参加するソース電話私。革 新パイオニア索引サワー。\n不自然な私 … サンプル器官トレーナーパーセント拡 張。午前日曜日障害ダニ。クロス月狭い 動物ノート近代化するチーズ。厳しい指 名参加するスペル倫理葉倫理。反射自体 トリビュート〜証言する運仕上げリフ ト。葉ないリフト発生する。\n連続部隊 …
索引検索(インデックス)型 データから10000というIDで見つけたよ インデックス Name ID 中川 1 和也 1 小林 2, 3 篤司 2 亮介 2 … … 石井 10000 裕太 10000 山口 10001, 10002 康弘 10001 聡太郎 10002 … … ID Last Name First Name 1 中川 和也 2 小林 篤司 3 小林 亮介 … … … 10000 石井 裕太 10001 山口 康弘 10002 山口 聡太郎 … … … fi 41 Pro le タワー残る探査再現する。\nキャビネッ ト意図ブレーキトーン普通の部隊狭いフ ブラケット明らかにするバーゲン埋め込 む鉱山コミュニティ索引。試してみる擁 ジャム腐った参加するソース電話私。革 新パイオニア索引サワー。\n不自然な私 … サンプル器官トレーナーパーセント拡 張。午前日曜日障害ダニ。クロス月狭い 動物ノート近代化するチーズ。厳しい指 名参加するスペル倫理葉倫理。反射自体 トリビュート〜証言する運仕上げリフ ト。葉ないリフト発生する。\n連続部隊 …
索引検索(インデックス)型 データにあってもインデックスに格納され てなかったら検索できないよ インデックス Name ID 中川 1 和也 1 小林 2, 3 篤司 2 亮介 2 … … 石井 10000 裕太 10000 山口 10001, 10002 康弘 10001 聡太郎 10002 … … ID Last Name First Name 1 中川 和也 2 小林 篤司 3 小林 亮介 … … … 10000 石井 裕太 10001 山口 康弘 10002 山口 聡太郎 … … … fi 42 Pro le タワー残る探査再現する。\nキャビネッ ト意図ブレーキトーン普通の部隊狭いフ ブラケット明らかにするバーゲン埋め込 む鉱山コミュニティ索引。試してみる擁 ジャム腐った参加するソース電話私。革 新パイオニア索引サワー。\n不自然な私 … サンプル器官トレーナーパーセント拡 張。午前日曜日障害ダニ。クロス月狭い 動物ノート近代化するチーズ。厳しい指 名参加するスペル倫理葉倫理。反射自体 トリビュート〜証言する運仕上げリフ ト。葉ないリフト発生する。\n連続部隊 …
つかいどころ👋 •データ量が多いデータなど •検索を多用して且つ横断的な場合など 43
目次 1.全文検索とは 逐次検索型 索引検索型 2.全文検索エンジン「Elasticsearch」とは 3.PythonでElasticsearchを使ってみる 4.Django Likeな実装を考えてみる 5.まとめ [尺余り] DB標準搭載の全文検索機能 VS 全文検索エンジン 44
全文検索エンジン 「Elasticsearch」とは?🤔 45
全文検索エンジン 「Elasticsearch」とは?🤔 オランダ・アムステルダムに本社を置くElastic社が中心になって開発が進められている全文 検索エンジン。 全文検索に特化しており、他のソリューションと比較しても圧倒的な全文検索スピードと利 便性を誇る 。Elasticsearchの内部ではApache Luceneが提供する超高速全文検索をフル 活用しており、スケーラブル、スキーマレス、マルチテナントを特長とする。 [4] 出典: フリー百科事典『ウィキペディア(Wikipedia)』 46
今回は「Elasticsearch」を使って 全文検索を実装してみます • Elasticsearchのversionは7.x (最新は8.xだが、ライブラリ対応の関係上) • Elasticsearch自体は既に用意できていると仮定(今回はDockerで用意) • Elasticsearchを使う理由として宗教的に深い理由はなし(重要) • 仕様や仕組みを話すと1日が終わるので、 詳しく知りたい方は後日自己研鑽でお願いします 47
目次 1.全文検索とは 逐次検索型 索引検索型 2.全文検索エンジン「Elasticsearch」とは 3.PythonでElasticsearchを使ってみる 4.Django Likeな実装を考えてみる 5.まとめ [尺余り] DB標準搭載の全文検索機能 VS 全文検索エンジン 48
インデックス作成して検索までの流れ🤔 1. マッピングを定義する データベースでいうカラム定義 2. インデックスを作成する データベースでいうテーブル作成 3. ドキュメントを登録する データベースでいうレコード作成 4. JSON形式のクエリで検索する 49
マッピングを定義する
1. まずElasticsearchのクライアントをpip install
(venv) $ pip install elasticsearch
2. 次に辞書型でmappingを定義
>>> from elasticsearch import Elasticsearch
>>> client = Elasticsearch('http://localhost:9200')
>>> mappings = {
...
'properties': {
...
'last_name': {'type': 'text'},
...
'first_name': {'type': ‘text'},
...
'profile': {
...
'type': ‘text’,
...
'analyzer': ‘kuromoji’,
...
},
...
}
...}
50
インデックスを作成する
3. インデックスを作成し、追加されていることを確認する
>>> client.indices.create(index='test_index', mappings=mappings)
ObjectApiResponse({'acknowledged': True, 'shards_acknowledged': True, 'index': 'test_index'})
>>> client.cat.indices(index='*', h=‘index').splitlines()
[‘.geoip_databases’, ‘test_index']
# .geoip_databasesはデフォルトのindex
4. マッピングも確認する
>>> client.indices.get_mapping(index='test_index')
ObjectApiResponse({'test_index': {'mappings': {'properties': {‘first_name': {'type': ‘text'},
'last_name': {'type': 'text'}, 'profile': {'type': 'text'}}}}})
51
ドキュメントを登録する
6. ドキュメントを作成後、全件取得して追加されていることを確認する
>>> client.create(index='test_index', document=document, id='a2827b2fc341-4424-846a-6e39ed08f1d2')
ObjectApiResponse({'_index': 'test_index', '_type': '_doc', '_id': 'a2827b2fc341-4424-846a-6e39ed08f1d2', '_version': 1, 'result': 'created', '_shards': {'total': 2,
'successful': 1, 'failed': 0}, '_seq_no': 0, '_primary_term': 1})
>>> client.search(index='test_index')
ObjectApiResponse({'took': 2, 'timed_out': False, '_shards': {'total': 1, 'successful': 1,
'skipped': 0, 'failed': 0}, 'hits': {'total': {'value': 1, 'relation': 'eq'}, 'max_score':
1.0, 'hits': [{'_index': 'test_index', '_type': '_doc', '_id': 'a2827b2fc341-4424-846a-6e39ed08f1d2', '_score': 1.0, '_source': {'last_name': '滝野', 'first_name':
'優紀', 'profile': 'はじめまして。私の名前は滝野優紀と申します。長野県出身で二児の父親です。これからもがん
ばります。'}}]}})
52
JSON形式のクエリで検索する
7. 部分一致のクエリで検索がヒットすることを確認する
>>> query = {
...
'match': {
...
'profile': '長野県'
...
...}
}
>>> client.search(index='test_index', query=query1)
ObjectApiResponse({'took': 3, 'timed_out': False, '_shards': {'total': 1, 'successful': 1,
'skipped': 0, 'failed': 0}, 'hits': {'total': {'value': 1, 'relation': 'eq'}, 'max_score':
1.3862942, 'hits': [{'_index': 'test_index', '_type': '_doc', '_id': '7b6ee73a-1150-4924b9be-6235060d25b0', '_score': 1.3862942, '_source': {'last_name': '滝野', 'first_name': '優
紀', 'profile': 'はじめまして。私の名前は滝野優紀と申します。長野県出身で二児の父親です。これからもがんば
ります。'}}]}})
53
clientも実装しないといけないし、 Elasticsearchのインデックス登録や 検索をうまいことmixinにしておかな いとゴリッゴリのオレオレコードがで きあがるな。。。 T君 54
目次 1.全文検索とは 逐次検索型 索引検索型 2.全文検索エンジン「Elasticsearch」とは 3.PythonでElasticsearchを使ってみる 4.Django Likeな実装を考えてみる 5.まとめ [尺余り] DB標準搭載の全文検索機能 VS 全文検索エンジン 55
django-elasticsearch-dsl を使う考え • ググったらだいたい1ページ目の上位に出てくるライブラリ。 • 比較的直感的に扱えるが、実装が複雑でない場合にはオーバーエンジニ アリングになりやすい • 高レベルクライアントであるelasticsearch-dslのDjangoラッパーで あるため、リリース頻度は高く新機能やバグの対応が早い 56
準備
1. Elasticsearchのクライアントをpip install
(venv) $ pip install django-elasticsearch-dsl
2. 次にsettings.pyへ接続情報を定義
ELASTICSEARCH_DSL={
'default': {
'hosts': 'localhost:9200'
},
}
57
まずモデルを元に定義が必要 from django_elasticsearch_dsl import Document from django_elasticsearch_dsl.registries import registry from .models import Staff @registry.register_document class StaffDocument(Document): class Index: # Name of the Elasticsearch index name = 'staffs' # See Elasticsearch Indices API reference for available settings settings = { 'number_of_shards': 1, 'number_of_replicas': 0, } class Django: model = Staff # The model associated with this Document # The fields of the model you want to be indexed in Elasticsearch fields = [ 'last_name', 'first_name', 'profile', ] 58
ドキュメント作成まではDjango感ある 1. マッピングを設定してインデックスを作成 (venv) $ ./manage.py search_index --rebuild ※ search̲index --rebuildを行うとモデルデータが全てインデックスに登録される 2. モデルデータを作成するとシグナルでインデックスにも格納される staff = Staff( last_name=’滝野’, first_name=’優紀’, profile=’私の名前は滝野です。長野県出身です。’, ) staff.save() 59
検索あたりからちょっとDjango感薄れる 1. Elasticsearchのデータが返される StaffDocument.search().filter(’match’, profile='長野県') # or StaffDocument.search().query(’match’, profile='長野県') 2. そして結果がqueryと lterで違う謎 >>> [f'{s.last_name} {s.first_name}' for s in StaffDocument.search().filter('match', profile='長野県')] [] >>> [f'{s.last_name} {s.first_name}' for s in StaffDocument.search().query('match', profile='長野県')] ['滝野 優紀’,] fi 60
elasticsearch-dslのラッパーだか らElasticsearchの知識がかなり要求さ れるな。。。 T君 61
django-haystack を使う考え • ググったらだいたい1ページ目に出てこないライブラリ • Elasticsearchの他にSolrなど他の全文検索エンジンに対応している。 • Django ORM的感的で扱えるため、学習コストは低い。 • リリース頻度はかなり緩やかのため、Elasticsearchの新機能は使えな いことが多い • 1インデックス == 1全文検索エンジン 62
準備
1. django-haystackのelasticsearchパッケージをpip install
(venv) $ pip install django-haystack[elasticsearch]
2. 次にsettings.pyへ接続情報を定義
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.elasticsearch7_backend.Elasticsearch7SearchEngine',
'URL': ‘http://localhost:9200/',
'INDEX_NAME': 'haystack',
},
}
63
こちらもインデックスの定義が必要 from haystack import indexes from .models import Staff class StaffIndex(indexes.SearchIndex, indexes.Indexable): text = indexes.CharField(document=True, use_template=True) last_name = indexes.CharField(model_attr='last_name') first_name = indexes.CharField(model_attr='first_name') def get_model(self): return Staff • SearchIndexは、document=Trueのフィールドを1つのみ持つ • use̲template=Trueとすることでテンプレートを使い、 エラーを起こりにくくする(らしい) • textというのはデフォルトの規則(settingsから変更可) 64
インデックスを作成する 1. マッピング用のテンプレートファイルを作成 templates/search/indexes/{app̲name}/sta ̲text.txt {{ object.last_name }} {{ object.first_name }} {{ object.profile }} 2. インデックスを作成 (venv) $ ./manage.py rebuild_index ※ rebuild̲indexを行うとモデルデータが全てインデックスに登録される 3. インデックスを更新(別途シグナルも実装可) (venv) $ ./manage.py update_index —age=<num_hours> ff 65
Django感ある 1. Elasticsearchのデータが返される SearchQuerySet.filter(content='長野県') # or SearchQuerySet.filter(first_name=‘優紀’) 2. 他にもDjango ORMと似たようなメソッドが多い SearchQuerySet.all() SearchQuerySet.none() SearchQuerySet.exclude(…) SearchQuerySet.values(…) SearchQuerySet.values_list(…) SearchQuerySet.order_by(…) 66
導入の障壁は低そうだな。。。 でも集計とか複雑なことを今後要求され たら。。。いや、でも。。。 T君 67
Tくんは結局学習コストの少なさと要求がそれほど多 くないことからdjango-haystackを採用しました。 プロフィールの文字から 検索できるようにしました!! ありがとうございます! T君 68 顧客
しかし問題は尽きないようです ついでに過去の担当者の○○について 集計したデータを貰いたいです! ・・・ヱ? 顧客 T君 69
まとめ • 検索なら全文検索使えってわけではなく、検索対象が長文や多岐要素に渡らなけ ればDBのgrep検索で十分といえる。 • ただし、コストがかかるからと安直にDBのgrep検索にしてしまうと、データが増 えて来た際や仕様追加などで痛い目を見てしまうため慎重になる。 • インデックスへの格納はリアルタイム性を重視した同期的処理(シグナル)、負荷を 一定時間内に制限したバッチ処理、celeryといったキューで登録といった方法が ある。 • 全文検索の導入はdjango側というより全文検索エンジン側の理解や設定のほうが 何倍も大変。 70
[余談] そもそも去年までdjango-haystackは ES 5までしか対応していなかったが… https://github.com/Surgo 以前一緒に仕事をしていたときにSurgo氏がES7の対応を行ってくれた。 71
私は騒いでいただけだった… ヒエェ~~~~!!! ES 5までしか対応していない! あららら…残念。 今対応しておくか 暫くして ヒエェ~~~~!!! ES 7に対応してる!! これか?まあなんだ。 ちょっと、やんちゃし過ぎちまったんだよ 72 みたいな
ご静聴ありがとうございました 73
目次 1.全文検索とは 逐次検索型 索引検索型 2.全文検索エンジン「Elasticsearch」とは 3.PythonでElasticsearchを使ってみる 4.Django Likeな実装を考えてみる 5.まとめ [尺余り] DB標準搭載の全文検索機能 VS 全文検索エンジン 74
# TODO 75