参加メモ:「Integrate Full-text Search service with Django」 #PyConJP

Integrate Full-text Search service with Django

PyCon JP 2018のセッション「Integrate Full-text Search service with Django」の参加メモです。

ElasticsearchをDjangoアプリケーションに導入したときのノウハウなどを中心に紹介されたセッションでした。
スライド

django x elasticsearch

登壇者

池内孝啓さん(@iktakahiro)
株式会社 catabira - Founder & CEO
PyDataTokyo - Organizer

内容

Elasticsearchを活用したDjangoのWebサービスの活用方法や苦労など

実現したいこと

宿泊事業者向けの運用管理システムで、ゲスト-ホスト間のメッセージを全文検索できるようにしたい。

稼働中サービスに検索機能を導入する場合に考えること

  • 既存のデータ(RDBに入ってる)
  • Elasticsearchのindex設計
  • 過去のデータの同期
  • 未来のデータの同期
  • 既存アーキテクチャの非破壊

index設計

設計案1: ROA

Resourceを中心とした設計アプローチ。
今回のケースでは「スレッド」と「メッセージ」というResource起点に設計する。
RESTfulに設計しようとすると自ずとこうなる。

各DBのテーブルに対してIndexを設計する。
(thread/message/guest)

この方法の課題

ElasticsearchではRDBでいうところのJOINがやりにくい。(同一Index内のDocumentのみが対象になるなど)

設計案2: SOA

サービスを1つの単位とした設計アプローチ。
そもそもユーザーに必要なサービスは何か?という観点からスタートする。
(今回の場合、ホストが受信したメッセージを検索したい)

  • SOAでElasticsearchのIndexを設計する
    • 機能として必要な情報をすべて Indexに登録する
    • RDBのテーブルをJOINしてElasticsearchのIndexを作る
    • (機能を満たすIndexを作成)
この方法の課題

基本的に非正規化することになるのでデータサイズが大きくなる。
またマスターが更新された場合、関連するDocumentを全部更新しないといけなくなる。

データの同期設計

RDBとElasticsearchをどのように同期するか。
- そもそも誰がやる?
- バッチ?
- Webアプリ?
- どのタイミングで同期する
- リアルタイム?
- 一定時間おき?

同期ツールの選択肢
go-mysql-elasticsearch

MySQLのbinlogを監視して、Insert/Update/Deleteが有ったらデータ転送イベントを発火する。
- pros
- 物理削除も対象にできる
- テーブル更新からラグが少ない
- const
- テーブル単位で転送対象が決まるのでROAの場合はいいが、SOAだとつらい

Logstash + JDBC Plugin

LogstashからSELECT文を定期的に発行して最終発行より新しいタイムスタンプのレコードがあればデータ転送を発火
- pros
- Selectを自分で組み立てられる→非正規化に対応できる
- cons
- 物理削除されたのはわからない

Elasticsearch + Django

Elasticsearch + Python

elasticsearch-dsl
- High-level APIを提供するPythonのパッケージ
- Indexをクラスとして定義
- IndexやMappingの生成も行う
(たぶん、そこまで頭のいいMigrationはできないが、Pythonコード上でMappingとかができるのはデカい)

Serializer

  • できるだけ柔軟な検索インターフェースを提供したい
  • q?keyword=ham|egg&name=masaとかのQueryは組み立てもParseも苦しい

-> ElasticsearchのSearch APIを透過的に提供しよう
とはいえIndexを勝手に見られるとまずい。。。
→ということでSerializer(Djangoの)を使う
 → 不要なキーを削除したり、権限のない情報が見られないようにする
 → ElasticsearchのAPIを使うときのホワイトリストをDjangoのSerializerで作るイメージ

#### 同期時間の短縮化
Logstashの場合、走査はcronで行われるのでタイムラグが起こる。
Webアプリで同期的にElasticsearchを使う場合は、Signalを使えば良いのでは?

post_saveやpost_deleteを使っておくと、Modelが更新されたら「XXをやる」みたいなイベントハンドリングを簡単にできる。

##### この方法の課題
- エラー処理とか色々と考える必要がある。。。
- 非正規化されたdocumentに対して更新すると、定義箇所が多くなって複雑に。。。
- Elasticsearch側のパフォーマンス

結論というかお勧め

  • 同期はLogstash(ダウンしたりIndexが変わったときの同期にドキドキしなくていいのでとりあえずこれで)
  • ラグの許容できない箇所はSignalsを検討。

質問

この辺、ボーッとしてたので異同あるかも
Q. go-mysql-elasticsearchのように、イベントのトリガーとしてMySQLのbinlogを監視するのが良かったのでは?
A. 検討はした。処理が色々なところに散らばっちゃったり処理が複雑になるのが怖い
Q. Djangoでデータを保存したときにSelect文を非同期で発行する仕組みにすればエラー箇所とかも特定しやすくてよさそう
A. 将来リアルタイムにやるのであればそれがよさそう。Logstashに任せると安心な部分が多いので今はそうしている。

感想

将来こういう機能要件が出てきたときに、「こんなのElasticsearchで簡単にできるやろ」みたいな感じで甘く考えてると、ヤバいことになるな、と思った。

特にデータの同期をちゃんと考えて設計しないと炎上する。

要件決めの時に「どの程度のラグだったら許せるのか」とか「データによっては多少結果がズレてもいいか」みたいなことをちゃんと確認したい。


iotas𓆡創作支援アプリ運営中𓅬

創作支援ツールとかを作ってます。(Python) https://www.world-type.com 歴史成分多め。

Crieitはαバージョンで開発中です。進捗は公式Twitterアカウントをフォローして確認してください。 興味がある方は是非記事の投稿もお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか
関連記事

コメント