任意に並び替えができる機能(ranked-model)でElasticsearchを使ったらハマった

  • 4
    Like
  • 0
    Comment

背景

私が開発しているサービスに、いわゆる「Trello的な」並び替え機能があります。

これと Elasticsearch を使った全文検索を併用しようとして若干ハマったので記事にしました。

主に Ruby on Rails の話になります。

formrun-drag.gif

並び替えの仕組み

並び替えには、 ranked-model という gem を利用しています。

これは、 データに row_order というカラムを持たせて、それによってソートをするのですが、差し込んだ時の全体の再配置やリバランスを自動的に行なってくれるものです。

差し込む場所は、ちょうど中間地点にくるように計算されます。

ちなみに、開発はそんなにアクティブではありません。

ボード | formrun.zwe9m.2016-12-21.午後8時-42-10.png

並び替えの難点

row_order というカラムは MySQLで言う Integer に保存されるのですが、ご存知の通りいろいろと問題があります。

  • 隣接した整数の間に差し込むことができない(例: 100と101の間に整数はない)
  • 最大値と最小値があるのでそれより外側に配置できない(例: 2147483647より大きい整数はない)

これを解決するために、ranked-modelは、差し込もうとしている位置に 無理 がある場合に、全体を1つずつ移動させたり、差し込めるように両側の値を外側に移動させたりしてくれるヘルパーを持っています。

この gem の価値はここのロジックにあります。これをライブラリ中では rearrangerebalance と呼んでいます。
以降、この動作のことを記事では「リバランス」と呼びます。

リバランスの問題点

ところが、このリバランスはElasticsearchと併用しようとすると、問題点が出てきます。

それは、リバランスの動作が update_all で行われているので、コールバックを指定できないという点です。

その為、リバランス後の値をElasticsearchに反映させるタイミングがわからなくなります。

しかも、リバランスは常に行われるわけでは無いので、「変更があれば全てElasticsearchに変更を送る」というのは無駄が多すぎます。

パッチで対応

そこで、リバランスを行うメソッドにコールバックを設定できるようなパッチを当てることで対応しました。

これを利用すると、モデルに after_rearrange_ranksafter_rebalance_ranks というメソッドがある時に、コールバックとして実行してくれるようになります。

ranked-model-patch.rb
module RankedModel::MapperExtensions
  private

  def rearrange_ranks
    super
    if instance.respond_to?(:after_rearrange_ranks)
      instance.after_rearrange_ranks
    end
  end

  def rebalance_ranks
    super
    if instance.respond_to?(:after_rebalance_ranks)
      instance.after_rebalance_ranks
    end
  end
end

class RankedModel::Ranker::Mapper
  prepend RankedModel::MapperExtensions
end
model.rb
class Model < ActiveRecord::Base
  # Sidekiqなどでテキトウに更新させる
  def after_rearrange_ranks
    Model::RebalanceRanksWorker.perform_async(...)
  end

  def after_rebalance_ranks
    after_rearrange_ranks
  end
end

考察

Trelloはどうやって解決しているのかいろいろ試してみましたが、そもそも検索結果と並び替えは併用していませんでした。
しかも、検索の反映まで数秒以上はかかるみたいです。
やっぱり、こういった検索・フィルタリングの精度とリアルタイム性はトレードオフの関係にあるのかなぁ

▼Trelloの検索はあくまでも「検索結果」で、ボードのフィルタリングとしては作用しない

* テスト | Trello.yiimy.2016-12-21.午後9時-07-48.png

▼自分のサービスは「フィルタリング」として作用する

ボード | formrun.kfcrt.2016-12-21.午後9時-10-50.png