目次
この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。
がんばろう
貴方はプロジェクトにアサインされ、サーバーの負荷試験を依頼された
「頑張るぞ! ガルバンゾ!」
ここにコードがあった。それは見るからに不味そうなスパゲッティであった
「お? おお……」
ここにスケジュールがあった。それはスパゲッティが腐るまでに時間がない事を示していた
「おお……。お、おお……」
それは即ち、根本解決をするだけの時間的猶予がない事を示していた
「おおあああああっ、うっ、うぐぅっ」
貴方の使命は、せめて足掻く事であった
「……」
現状整理から始めよう
コードがスパゲッティになる理由は幾らかあれど、そんな事を調査していても始まらない。
やるべき事は罪を償わせる事ではない。罪を代わりに償う事だ。
まずは一つ一つ現状を整理し、問題解決には何をすべきか考える事である。
現状を調査した結果、まずはとても良い事が分かった。
テストを書く文化がある
これが何を意味するかというと、コードの振る舞いがテストによって保証されているという事だった。
コードが中でどんなに破茶滅茶な事をしていても、テストが通りさえすればそのコードは少なくとも挙動としては正しい。
即ち、中を書き換えてもテストが通りさえすればその正当性は保証される。最後にQAなどを通す必要はあるが、これから行う改修に対しての強力な道しるべとなる。
使用している監視ツールはデータベースの重みを調査するものと、サーバーそのものの重みを監視する二つを利用していた
確かにこれで重い部分がある程度は分かる。ある程度ではあるが。
これではコード全体の重みは一部見逃される恐れがある。レスポンスの遅さとは、データベースのクエリ以上にコードそのものの重みが原因となる場合があるからだ。
ただ、それは誰かに何かを依頼する事もなく調査、解決出来る。
そしてその監視ツールがある事自体はとても有用である、というか無ければ負荷試験は出来ない。
最も問題であるコードは1APIの中で何度も同じデータを参照する事があった
マスタデータやユーザーデータを、そのコードが呼ばれる度に参照、計算して返していた。
1回の呼び出しでユーザーデータが多数変更され、また都度都度必要となるマスタデータは変わる。それが1APIの間で何度も発生する。
そしてまた、これを根本的に解決する事ーー例えば1回の呼び出しに纏めるーーといった事などは影響範囲や再度掛かるQAへのコストなども鑑みると時間の都合上不可能。
出来るのは1回1回で掛かるコストを出来る限り減らす事、それが最善だった。
改善方針をまず固めて共有しよう
見ての通り改善すべき事はデータベース上の重みとしてもコードとしてもはっきりしている。
ただ、改善するものの規模が大きいのならば、見切り発車で改善を決めるのではなく、一旦共有して見落としが無いかなどを第三者の目からも含めて判定した方が良い。
「ここの重みを削れれば、取り敢えず重みは60%位になります」
「60%でもまだ重いな……他の部分は?」
「まだ調査していないです。ただ、ここのコードの重みとしては、このAPIの負荷の割合を見てもデータベースの重みではなく別の部分である事ははっきりしているのと、今のツールでは調査しきれない部分もあるので先にやってしまおうと思います」
「……まあ、良いか。で、どうやって削る?」
「基本的に1API間でユーザーデータを使い回す、1スレッドで同じマスタデータを使い回す、という感じですね」
「どうやって使い回す?」
「ユーザーデータに関してはのユーザーのインスタンスが1APIで使い回されているので、そこにインスタンス変数として保持しまおうかと。
マスタデータもメモ化してしまいます。
まあ、要するにユーザーデータ、マスタデータ共にスレッドのメモリにキャッシュします」
「……メモリに負荷が掛かるだろうけどそこ辺りは」
「幸いそちらはまだ余裕があります」
「オーケー」
改修したが想定までは減らなかった
「まあ、そう簡単に減らせたら苦労しないよなー……」
確かに、無駄なデータ検索量は減った。幸いマスタをメモリに持たせる施策も、強いメモリ負荷もなく実現出来た。
しかしながらAPIの重みを見ると、データベース起因ではない不明な重みが大半を占める事となっていた。
これは即ち、コード自体に問題があるという事だ(掛けている負荷が適切であれば)。
アプリケーションに対するプロファイリングツールを導入し確認してみると、膨大な配列から必要なデータを参照してくる部分に大きな問題があった。
既存の部分だけではなく、自身が改善したユーザーデータやマスタデータを使い回す処理でも、その引っ張ってきたデータから目的のデータを探り当てる処理で重みが発生してしまっていた。
「データをDBからメモリ上にデータを乗せるだけではまだ駄目か……」
こうなると、その検索処理などをどうにかするしかない。
コードを読み込み、どのようにデータが扱われているかを確認していく。
・ある条件に当てはまるユーザーデータは、特殊な条件の場合にしか参照されない事が分かり、基本的に検索から抜いても良い事が判明した。
・同じマスタの中でも種類が膨大にあり、複雑な検索条件でもメモ化の形を工夫すれば配列検索の回数を減らせる事が分かった(マスタを分けろよという愚痴は心の奥底に仕舞った)。
そんな事からユーザーデータやマスタデータを分類分けしていくと、何とかなりそうな目星がついてきた。
※ruby_on_railsだとindex_byやgroup_byがとても有用。単純にデータ検索をするにしても配列からfindを仕掛けるよりもハッシュにしてキーが存在するかを判定した方が確実に早いです。
サンプルに以下。
1.
array = (1..1000).to_a
begin
now = Time.now
1000.times do |n|
array.include?(n + 1)
end
p Time.now - now
end
=> 0.0051824
2.
hash = (1..1000).each_with_object({}){|n, res| res[n] = true}
begin
now = Time.now
1000.times do |n|
!!hash[n + 1]
end
p Time.now - now
end
=> 0.0001501
かなり改善された
「ここまで改善出来ました!」
「これなら耐えうるな、で、コードは?」
「こうなりました!」
「……複雑だな……」
「……そうなんです。まあ、テストコードがあったので振る舞いは保証されています」
「そうだな、そうか(テストコードの網羅率って100%だったかな……)」
根本原因を解決出来ずに上部だけで対応しようとすると、そうなります。
できる事はスパゲッティを美味しく作り直す事ではなく、防腐剤を振りかける事、ただそれだけだったのですから。
しかしながら、そうしなければいけない時もあるのです。きっと。
美味しいスパゲッティの作り方
- 厚切りの芽を抜いたニンニクと唐辛子をたっぷりのオリーブオイルで常温から弱火で炒めましょう
- ニンニクはきつね色になったら取り出しましょう。唐辛子も焦げる前に取り出しましょう
- 好きなキノコを好きなだけ入れて塩を振って炒めましょう
- 塩をきつめに入れた熱湯で茹で上げたスパゲッティを和えましょう
- 皿に盛り付けてから取っておいたニンニクと唐辛子と、あればパセリを意識高めに散らしましょう
- 食べよう