Kouhei Sutou
kou****@clear*****
2017年 1月 23日 (月) 14:28:19 JST
須藤です。 In <A15CE****@gmail*****> "[groonga-dev,04251] Mroongaの同値でのfast_order_limitでoffsetの結果が壊れる" on Sat, 21 Jan 2017 19:52:22 +0900, murata satoshi <murat****@gmail*****> wrote: > fast_order_limitが有効な場合にoffsetの結果が壊れるケースがありましたので報告します。 > 同値でのorder byの場合に発生するようです。 ありがとうございます! 再現用データもあってとても助かります。 うーん、これはどうするのがいいんでしょうねぇ。 原因はソートキーが同じレコードの順番がGroongaのソートとMySQL のソートで違うことです。ソートキーが同じなのでどの順番になっ てもソートに問題はありません。単にGroongaとMySQLで違うことが 原因だというだけです。 MroongaはORDER BY LIMITを最適化するとき、Groongaでソートして 必要最小限のレコードだけ返します。その後、MySQLは必要最小限 のレコードをさらにMySQLでソートして返します。 必要最小限のレコードというのは、ソート後のレコードのうち、 LIMITで指定したオフセット+行数分のレコードです。たとえば、 オフセットが2で行数が7なら先頭9レコードです。 今回の例ではMySQLはレコードが追加された順にソートしているよ うに見えます。つまり、この順番です。 > +-----+ > | _id | > +-----+ > | 1 | > | 2 | > | 3 | > | 4 | > | 5 | > | 6 | > | 7 | > | 8 | > | 9 | > | 10 | > +-----+ 一方、Groongaは次の順にソートしていそうです。 > +-----+ > | _id | > +-----+ > | 1 | > | 10 | > | 3 | > | 5 | > | 4 | > | 6 | > | 9 | > | 8 | > | 7 | > | 2 | > +-----+ このデータの場合、LIMIT 0, 5だとGroongaは次のレコードを返し ます。 +-----+ | _id | +-----+ | 1 | | 10 | | 3 | | 5 | | 4 | +-----+ これをMySQLがソートしてこうなります。 +-----+ | _id | +-----+ | 1 | | 3 | | 4 | | 5 | | 10 | +-----+ オフセットが0で行数が5なのでこれをそのままユーザーに返します。 これがLIMIT 5, 5だとGroongaは次のレコードを返します。全部で すね。 +-----+ | _id | +-----+ | 1 | | 10 | | 3 | | 5 | | 4 | | 6 | | 9 | | 8 | | 7 | | 2 | +-----+ これをMySQLがソートしてこうなります。 +-----+ | _id | +-----+ | 1 | | 2 | | 3 | | 4 | | 5 | | 6 | | 7 | | 8 | | 9 | | 10 | +-----+ オフセットが5で行数が5なので後ろの5レコードをユーザーに返します。 +-----+ | _id | +-----+ | 6 | | 7 | | 8 | | 9 | | 10 | +-----+ そうすると、ユーザーには2が消えて10が重複しているようにみえ ます。 MroongaがMySQLにGroongaのソート結果を返すとき、オフセット以 下のレコードのソートキーの値をNULLとして返すといいんですか ねぇ。こんな感じで。 +-----+-------+ | _id | price | +-----+-------+ | 1 | NULL | | 10 | NULL | | 3 | NULL | | 5 | NULL | | 4 | NULL | | 6 | 500 | | 9 | 500 | | 8 | 500 | | 7 | 500 | | 2 | 500 | +-----+-------+ そうすればNULLのレコードは必ず先頭にまとまってMySQLがソート してもこうなるはず。 +-----+-------+ | _id | price | +-----+-------+ | 1 | NULL | | 3 | NULL | | 4 | NULL | | 5 | NULL | | 10 | NULL | | 2 | 500 | | 6 | 500 | | 7 | 500 | | 8 | 500 | | 9 | 500 | +-----+-------+ で、オフセットが5で行数が5なので後ろの5レコードをユーザーに 返すことになってこう。 +-----+ | _id | +-----+ | 2 | | 6 | | 7 | | 8 | | 9 | +-----+ MySQLに返すデータも少なくなってパフォーマンス的にもメリット があるかも?だれか実装にチャレンジしてみませんか? ha_mroonga::storage_store_fields()に値として必ずNULLを設定す るモードみたいなのをつけて、ha_mroonga::storage_ft_read()で オフセット以前の場合はそれを呼び出す、みたいにするといけると 思うんですよねぇ。 -- 須藤 功平 <kou****@clear*****> 株式会社クリアコード <http://www.clear-code.com/> Groongaベースの全文検索システムを総合サポート: http://groonga.org/ja/support/ パッチ採用 - プログラミングが楽しい人向けの採用プロセス: http://www.clear-code.com/recruitment/ OSS開発支援サービス: http://www.clear-code.com/blog/2016/6/27.html