Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ii: improve performance on buffer_new's worst case #617

Closed
wants to merge 2 commits into from

Conversation

kou
Copy link
Member

@kou kou commented Dec 27, 2016

See commit messages of 5b6129d and dd67962 about description of this case in English.

転置インデックスを更新するときにbuffer_new()をすることがありますが、データの内容・更新順序によって、すごく遅く、かつ、インデックスサイズがすごく増えることがあります。

どのような内容・更新順序かというと次のようなケースです。

table_create a TABLE_NO_KEY
column_create a x COLUMN_SCALAR ShortText
table_create b TABLE_PAT_KEY ShortText
column_create b index COLUMN_INDEX a x

load --table a
[
{"x": "00000000"},
{"x": "00000001"},
...
{"x": "00486036"},
{"x": "00000000"},
{"x": "00000001"},
...
{"x": "00413962"}
]

Rubyスクリプトだと次のようになります。

puts "table_create a TABLE_NO_KEY"
puts "column_create a x COLUMN_SCALAR ShortText"
puts "table_create b TABLE_PAT_KEY ShortText"
puts "column_create b index COLUMN_INDEX a x"

puts "load --table a"
puts "["
n = 900_000
n.times do |i|
  i %= 486_037
  puts "{\"x\": \"#{i.to_s.rjust(8, '0')}\"},"
end
puts "]"

buffer_new()の中で語彙表をカーソルで回して既存のbufferがないか探していますが、↑のケースだとカーソルを回しきっても既存のbufferが見つかりません。00486036のレコードが終わって2週目(2回目の00000000)に入ると、レコードが増えるごとにカーソルが返すレコード数が増えるのでbuffer_new()の時間がどんどん増えてインデックス更新が遅くなります。

また、既存のbufferが見つからないので毎回新しくbufferを作ります。このためインデックスサイズも大きくなります。

解決策として以下を提案します。

  • 既存のbufferが見つからなかった場合に過去にgrn_ii_update_one()したときに新しく作ったbufferを使いまわせないかチェックする。
    • 既存のbufferが見つからないケースを減らす。これでインデックスサイズの増加を抑えられる。
  • カーソルで回すレコード数を制限する。
    • 既存のbufferを見つけられるケースが減ることがあるが、buffer_new()にかかる時間が一定以下に抑えられるので前述のケースでの速度劣化が抑えられる。
    • 既存のbufferを見つけられない場合は↑の使いまわせないかチェックでフォローするので影響は軽微なはず。

前者の実装は 5b6129d です。

grn_iiに追加しているnext_buffer_candidate_tidが使いまわせないかチェック対象のbuffer(に紐付いているtid)です。

buffer_open_by_tid()buffer_open()のコードの一部を抜き出しただけでロジックは変えていません。

SPLIT_COND()が変わっているのはbufferがb以外の変数にも入れられるようにです。

後者の実装は dd67962 です。

EXISTING_BUFFER_FIND_LIMIT10000はなんとなくで決めました。特に計測したわけではありません。

この変更を入れる前と入れた後では実行時間・サイズは以下のように変わります。データは前述のRubyスクリプトを実行した結果です。それをgroongaコマンドで処理しました。

項目 変更なし 変更あり
実行時間 30分以上(*) 約4分30秒
サイズ 約15GB(1GB * 15 + 4.5MB + 4KB) 約51MB(22MB + 29MB)

(*) 30分を過ぎたくらいからエラー発生。50万件後半くらいから発生する。45分くらいたって65万件くらいしかロードできていない。

以下は生結果です。

変更なし:

[[0,1482816822.219858,0.04200315475463867],true]
[[0,1482816822.261966,0.05740857124328613],true]
[[0,1482816822.319439,0.04283547401428223],true]
[[0,1482816822.362304,0.07676839828491211],true]
# select a --limit 0
[[0,1482821246.286818,0.0002453327178955078],[[[649709],[["_id","UInt32"],["x","ShortText"]]]]]
-rw-r----- 1 kou kou 4.0K Dec 27 14:33 db
-rw-r----- 1 kou kou  21M Dec 27 15:47 db.0000000
-rw-r----- 1 kou kou 8.1M Dec 27 15:47 db.0000100
-rw-r----- 1 kou kou  17M Dec 27 15:07 db.0000101
-rw-r----- 1 kou kou  13M Dec 27 15:47 db.0000102
-rw-r----- 1 kou kou 1.0G Dec 27 15:47 db.0000103
-rw-r----- 1 kou kou 1.0G Dec 27 14:38 db.0000103.001
-rw-r----- 1 kou kou 1.0G Dec 27 14:40 db.0000103.002
-rw-r----- 1 kou kou 1.0G Dec 27 14:42 db.0000103.003
-rw-r----- 1 kou kou 1.0G Dec 27 14:44 db.0000103.004
-rw-r----- 1 kou kou 1.0G Dec 27 14:46 db.0000103.005
-rw-r----- 1 kou kou 1.0G Dec 27 14:48 db.0000103.006
-rw-r----- 1 kou kou 1.0G Dec 27 14:51 db.0000103.007
-rw-r----- 1 kou kou 1.0G Dec 27 14:53 db.0000103.008
-rw-r----- 1 kou kou 1.0G Dec 27 14:55 db.0000103.009
-rw-r----- 1 kou kou 1.0G Dec 27 14:57 db.0000103.00A
-rw-r----- 1 kou kou 1.0G Dec 27 14:59 db.0000103.00B
-rw-r----- 1 kou kou 1.0G Dec 27 15:01 db.0000103.00C
-rw-r----- 1 kou kou 1.0G Dec 27 15:03 db.0000103.00D
-rw-r----- 1 kou kou 1.0G Dec 27 15:05 db.0000103.00E
-rw-r----- 1 kou kou 1.0G Dec 27 15:07 db.0000103.00F
-rw-r----- 1 kou kou 4.5M Dec 27 15:07 db.0000103.010
-rw-r----- 1 kou kou 4.0K Dec 27 14:33 db.0000103.c
-rw-r----- 1 kou kou 1.0M Dec 27 14:33 db.001
-rw-r----- 1 kou kou  13M Dec 27 14:33 db.conf
-rw-r--r-- 1 kou kou 177M Dec 27 15:48 log
-rw-r--r-- 1 kou kou  634 Dec 27 14:33 query.log

エラーログ:

2016-12-27 15:07:09.642377|A| [ii][update][one] failed to create a buffer2: <b.index>: <65537>:<1>:<65537>: size:<16>
2016-12-27 15:07:09.691508|A| /tmp/local/lib/libgroonga.so.0(grn_ii_update_one+0x2229) [0x7fb0403ff47e]
2016-12-27 15:07:09.691530|A| /tmp/local/lib/libgroonga.so.0(grn_ii_column_update+0x2740) [0x7fb040408be2]
2016-12-27 15:07:09.691534|A| /tmp/local/lib/libgroonga.so.0(grn_obj_default_set_value_hook+0x2f9) [0x7fb040270a1e]
2016-12-27 15:07:09.691536|A| /tmp/local/lib/libgroonga.so.0(+0xfe4a2) [0x7fb0402964a2]
2016-12-27 15:07:09.691538|A| /tmp/local/lib/libgroonga.so.0(+0xfe979) [0x7fb040296979]
2016-12-27 15:07:09.691540|A| /tmp/local/lib/libgroonga.so.0(grn_obj_set_value+0x3ad) [0x7fb040299ce0]
2016-12-27 15:07:09.691543|A| /tmp/local/lib/libgroonga.so.0(+0x122519) [0x7fb0402ba519]
2016-12-27 15:07:09.691545|A| /tmp/local/lib/libgroonga.so.0(+0x122dad) [0x7fb0402badad]
2016-12-27 15:07:09.691547|A| /tmp/local/lib/libgroonga.so.0(grn_load_+0xc42) [0x7fb0402bdf66]
2016-12-27 15:07:09.691549|A| /tmp/local/lib/libgroonga.so.0(+0x402282) [0x7fb04059a282]
2016-12-27 15:07:09.691552|A| /tmp/local/lib/libgroonga.so.0(grn_proc_call+0x426) [0x7fb0402cf606]
2016-12-27 15:07:09.691554|A| /tmp/local/lib/libgroonga.so.0(grn_command_run+0xce) [0x7fb040253187]
2016-12-27 15:07:09.691556|A| /tmp/local/lib/libgroonga.so.0(grn_expr_exec+0x1a5) [0x7fb0402cfd22]
2016-12-27 15:07:09.691558|A| /tmp/local/lib/libgroonga.so.0(grn_ctx_send+0x4d0) [0x7fb04025c122]
2016-12-27 15:07:09.691560|A| /tmp/local/bin/groonga(+0x634b) [0x56001bf9334b]
2016-12-27 15:07:09.691563|A| /tmp/local/bin/groonga(+0x18008) [0x56001bfa5008]
2016-12-27 15:07:09.691581|e| [table][load] failed to set column value: [ii][update][one] failed to create a buffer2: <b.index>: <65537>:<1>:<65537>: size:<16>: key: <(NULL)>, column: <x>, value: <"00065536">

変更あり:

[[0,1482814433.974035,0.04183840751647949],true]
[[0,1482814434.015956,0.05599784851074219],true]
[[0,1482814434.072021,0.04459285736083984],true]
[[0,1482814434.116677,0.08618617057800293],true]
[[0,1482814434.20292,272.1374449729919],900000]
-rw-r----- 1 kou kou 4.0K Dec 27 13:58 db
-rw-r----- 1 kou kou  21M Dec 27 13:58 db.0000000
-rw-r----- 1 kou kou 8.1M Dec 27 13:58 db.0000100
-rw-r----- 1 kou kou  17M Dec 27 13:58 db.0000101
-rw-r----- 1 kou kou  13M Dec 27 13:58 db.0000102
-rw-r----- 1 kou kou  22M Dec 27 13:58 db.0000103
-rw-r----- 1 kou kou  29M Dec 27 13:58 db.0000103.c
-rw-r----- 1 kou kou 1.0M Dec 27 13:53 db.001
-rw-r----- 1 kou kou  13M Dec 27 13:53 db.conf
-rw-r--r-- 1 kou kou  13K Dec 27 13:58 log
-rw-r--r-- 1 kou kou  787 Dec 27 13:58 query.log

It doesn't change the default existing buffer search algorithm. It's
used only when the default existing buffer search algorithm doesn't find
an existing buffer. If any existing buffer isn't found by the default
existing buffer search algorithm, new buffer is allocated. It grows
index file size.

Existing buffer isn't found case is caused by the following case:

    table_create a TABLE_NO_KEY
    column_create a x COLUMN_SCALAR ShortText
    table_create b TABLE_PAT_KEY ShortText
    column_create b index COLUMN_INDEX a x

    load --table a
    [
    {"x": "00000000"},
    {"x": "00000001"},
    ...
    {"x": "00486036"},
    {"x": "00000000"},
    {"x": "00000001"},
    ...
    {"x": "00413962"}
    ]

buffer_open_by_tid() is a function that is just extracted code from
buffer_new(). It doesn't change any logic.
If cursor for searching existing buffer doesn't find any existing buffer
and has many records (= the worst case), many times are spent. It means
that index construction time is slow for the case.

This change limits the number of records for searching existing buffer.

The following case is the worst case:

    table_create a TABLE_NO_KEY
    column_create a x COLUMN_SCALAR ShortText
    table_create b TABLE_PAT_KEY ShortText
    column_create b index COLUMN_INDEX a x

    load --table a
    [
    {"x": "00000000"},
    {"x": "00000001"},
    ...
    {"x": "00486036"},
    {"x": "00000000"},
    {"x": "00000001"},
    ...
    {"x": "00413962"}
    ]
@daijiro
Copy link
Member

daijiro commented Dec 30, 2016

別の実装になりましたが f578810 をpushしました。
これでどうでしょうか。。(今回のケースについては更新・検索とも問題なく動作することを確認しています)

もともとbuffer_new()の中でgrn_table_cursor()を呼んでいるのは、語彙表の中で値が近いキーについては、索引の中でもなるべく近くに配置してやりたいという意図があったのですが、そのためにはキーを完全に昇順に取り出さなくても、既存のキーの中でなるべく近いものを取り出せれば十分ですので、GRN_CURSOR_PREFIX|GRN_CURSOR_SIZE_BY_BITを使うように変えてみました。

@kou
Copy link
Member Author

kou commented Dec 30, 2016

さすがです!
[groonga-dev,04219]のケースでも速く省サイズになっています!

@kou kou closed this Dec 30, 2016
@kou kou deleted the ii-improve-performance-on-buffer-new-worst-case branch December 30, 2016 12:40
@kou
Copy link
Member Author

kou commented Dec 31, 2016

すみません、この実装だと、key_size_for_retry--するたびに同じレコードを何度もチェックしてしまう(key_size_for_retryにはkey_size_for_retry + 1のときの対象IDがすべて含まれるから)ので、なかなか見つからないときに遅いです。。。さらにキーサイズが大きい(数百バイトとか数キロバイトとか)とより遅いです。。。

重複なくカーソルを回せるといいのですが。。。

@kou
Copy link
Member Author

kou commented Jan 1, 2017

既存の挙動が壊れているのでそもそもこのアプローチはよくなさそうです。。。
https://travis-ci.org/groonga/groonga/jobs/187655662

@kou
Copy link
Member Author

kou commented Jan 3, 2017

キーにマルチバイト文字が含まれているときに1bitずつ減らしてgrn_table_open_cursor()すると不正なエンコーディングの文字列でノーマライズしようとしてエラーになるので一旦revertしました。

@daijiro
Copy link
Member

daijiro commented Jan 5, 2017

とりあえずnormalizeを通さずにカーソルを回すのがいいんじゃないでしょうか。
あと、bit単位じゃなくてbyte単位でも良いかもですね。

@kou
Copy link
Member Author

kou commented Jan 16, 2017

その方向でやってみます!

kou added a commit that referenced this pull request Jan 16, 2017
GitHub: #617

It improves the buffer_new's worst case.
@kou
Copy link
Member Author

kou commented Jan 16, 2017

その方向で実装してみたので動作チェックとパフォーマンス測定をしてみる。

単体テストは失敗しなくなった。パフォーマンスはここに書いてあるケースは速くなった。

@kou
Copy link
Member Author

kou commented Jan 16, 2017

↑はとりあえずの結果。後でちゃんとやる。

@kou
Copy link
Member Author

kou commented Jan 18, 2017

キーが大きくて(たとえば256バイトとか1KiBとか)さらに見つからないケースだと1bitではなく1バイトずつ減らしていっても結構な時間がかかってしまうので、ずらしていく量は倍々で増やす(1, 2, 4, 8, ...)ことにした。

通常は元のキーサイズのプレフィックスサーチか元のキーサイズ-1のプレフィックスサーチで見つかるはずなので従来のケースへの影響はないはず。

@daijiro
Copy link
Member

daijiro commented Jan 18, 2017

いいと思います!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants