水耕栽培装置届いた

会社から帰宅したら、何やら巨大な段ボールが届いていました。

欲しい物リストにあるとある品がなくなっているのを見て確信。水耕栽培装置でした。これです。

どどんとふ公式鯖管理人の欲しい物リストに入れていたので、どなたかが贈ってくださったのでしょう。マーケットプレイス業者からの直送なので、どなたからかはわかりませんでしたが、こんなに高額なものをありがとうございます。早速開封します。

ほぼパイプなわけですが、予想通り工作精度はめっちゃ悪いしなんか汚いです。まあ、屋外で使う物なので気にしません。

栽培用のカップですが、かなりの量がバッキバキだったのは予想外ですが、使えなくはない。

ポンプとタイマーは中華仕様なので、これはゴミ箱直行。リーベックスのプログラムタイマーと観賞魚用のポンプは既に持っていますので大丈夫です。

とりあえず、組み立ててみましたが組み立てがとにかく大変です。何せ工作精度が悪くてパイプの接合が堅いし歪んでいます。かといって、ハンマーとかで力尽くでやると割れそうなので、とりあえず気合いで押し込みました。風呂上がりなのに汗だく&変な力の入れ方をしたせいか節々が痛い。

で、あとは苗をスポンジに入れて水を流すのですが、まず苗を発芽させなければなりません。とりあえず手持ちのほうれん草とバジルの種を濡らしたキッチンペーパーの上で発芽させることにします。ちなみに、本体以外の付属品は、「バッキバキに割れたカップ」「200V仕様のポンプ」「中国規格の電圧不明なタイマー」「ピンセット」「説明書(中国語)」でした。うーん、ホームハイポニカとかに比べたら確かに安いし、国産品にはない立体的に72個の苗を一気に育てられるのと、塩ビ管を自分で加工する手間を考えると妥当な気がするが、2万あればかなりの量の野菜がスーパーで買えてしまうので、需要は極めて少ないと思う。

bcdice-api

※bcdice-apiに改良部分がマージされましたが、2月3日に投稿した内容とURLが変わっています

どどんとふで採用されているダイスボット(bcdice)のWebAPI版を試験公開。
WebAPI版はNKMR氏が開発・公開したものをベースにちょっと改良しています。落ち着いたらMerge依頼かけます。

改良ポイントは、Onset!互換I/Fを用意したこと。

それ以外の使い方は、上記NKMR氏のgithubリポジトリにあるReadmeと同じです。

これで、rubyが使えないレンタルサーバーでもPHPさえ動けばDiceBotも含めてOnset!が使えるようになるはずです。

使い方は、Onset!を設置した後、src/config.phpにあるBcdiceURLに公式鯖が公開したOnset!互換APIのURLを指定するだけ。
すでにOnset!(どどんとふ公式鯖版)もこれで動いています。
※現時点では試験公開なので、URLはそのうち変わるかもしれません。あくまでテスト目的でお願いします。

どどんとふのimageUploadSpaceのサムネイル対策

画像や部屋を削除したのに、smallImages以下のサムネイルファイルが残ってしまう現象に対しての対処法。
要は、smallImages以下にあるファイルの中で元ファイルが存在しないファイルを削除するShellScriptです。

どどんとふのsystemdユニットファイル

どどんとふをnginx+fcgiで動かすfcgi編で記載した起動スクリプトをsystemdユニットファイルで記述したものがこちらです。
今のどどんとふ公式鯖もこちらで動いています。これのいい所はプロセスを開始してくれているので、不意に異常停止しても勝手に再起動してくれるところと、依存関係を設定しているのでnginxとzramの初期化が完了してから自動で起動するようになっているところです。

zramの初期化部分もこのようにsystemdのユニットファイルで起動時に一度だけ実行するようにしておけば良い感じですよ。

[連載5]どどんとふが重い?

連載3の真面目な対処法をやっても、あまり効果がありませなんでしたので、いくつか追加で思い当たる対応策を実施しました。

追加対策5-1: fastcgiをTCP/IPではなくドメインソケットに変更

効果無しでした。TCPIPのオーバーヘッドってセッション数が200-300程度じゃ誤差にもならなかったです。

追加対策5-2: $refreshIntervalを死ぬほど短くする

これまでの対応でやはりnginxとfastcgi間を散々弄ってみましたが、nginxのreq/secがほぼfastcgiのプロセス数上限で頭打ちになるということから、
buffering云々ではなくDodontoFServer.rbのどっかでCPUやDiskIOに関係なく待っている箇所があると疑い、調べてみたらありました。

めっちゃsleepしてます、しかもデフォルトのCommet方式をそのまま使っているので、$refreshTimeout秒もしくは、$refreshInterval秒×saveDataが空になるまでのループ回数分もここでsleepで待っています。
refreshという一番使用頻度の高いリクエストがsleepなんかしちゃってるせいで、fastcgiのサーバープロセスが他のリクエストを処理できない待ち時間が発生してしまっていたわけです。
ただ待っているだけのsleepですからCPUもDiskIOも食うわけありません。
こんなに待たされちゃたまりませんので、refreshIntervalをとりあえず0.1秒にして$refreshTimeoutも0.1秒にしてみました。

結果:リクエスト数が3倍になりました。平日に第壱、第弐だけで試したのが幸いでした。週末に全鯖に設定入れてたら3倍どころの騒ぎじゃなく、1日で2億リクエストぐらいになってしまいログがパンクしていたでしょう。
しかし、nginxのreq/secは1400req/secまで一気に改善していました。設定値に問題はあったもののボトルネックはここで間違いなさそうです。

追加対策5-3: 疑似Commet方式を辞めて、sleepそのものをコメントアウト

そもそも、この処理ってレンタルサーバーなどでrubyプロセスを1つのリクエストがなるべく再利用するためにという思想で作られた擬似Commet方式のためにあるのですが、公式鯖はやたら大量にリクエストが来るので、リクエストはrubyプロセスをさっさと開放して次のリクエストを処理させたほうが良いのでCommet方式をまず辞めちゃいましょう。
この設定に関する箇所は、src_ruby/config.rbにあるこの部分で設定することになります。

で、実はこれも過去に試していたんですが、どちらの方式でもあまり挙動に変化がなかったことから、デフォルトのままisCommetをTrueにしていました。
なぜ、効果がなかったのかをよく見てみると、Commetを無効にしていてもrefreshLoopが呼び出されたらサーバー内で2秒もsleepしちゃってたわけです。
そりゃ、有効にしても無効にしても変わらないわけです。これはいけません。改善しましょう。
まずは、諸悪の根源sleepが入っているrefreshLoopを確認してsleepを根絶できないか調べてみます。

refreshLoopの呼び出し元は、「getWebIfChatTextFromTime getWebIfRefresh refresh」の3箇所からです。
このうち、refreshについては、Commetを無効にすれば呼ばれなくなりますので、気にしないとして、
「getWebIfChatTextFromTime getWebIfRefresh」については、Commetを無効にするとサーバー上で2秒も待ってしまうことになります。
むせる鯖のようにwebIfを無効にしちゃえば呼ばれることはなくなりますが、どろいどんとふとかスマホ用chat.htmlとか影響が大きすぎるのでWebIfは無効にはできません。
次は、この処理のsleepを丸ごと削っちゃっていいのかどうか挙動を追ってみます。

むせる鯖の中の人であるくまかば氏によると大丈夫らしいのですが、一応確認しておきましょう。
sleepをコメントアウトすると$refreshTimeoutで設定している間(公式鯖では1秒)は、saveDataが空になるまで全速力でrefreshOnceを呼ぶことになります。
refreshOnceの中身は、saveData.merge!。つまりハッシュをマージしているだけですね。
ハッシュをマージしてsaveDataが空になればループを抜けるので全速力になっても負荷が高くなる理由はないですし、
コメントアウトしても問題なさそうです。というわけでコードを変更したのはこの一カ所(sleepのコメントアウト)だけでcommet方式も無効にしました。

というわけで、今回のチューニングは、今まではやらなかったどどんとふ本体のコードにも手をつけてしまいました。
で、結果がどうなったかというと、こうなりました。(見やすくするためにグラフのスケールが近くなるようにリサイズしています)
munin_new
ログイン人数が620人の状態で、nginxの処理能力が400req/sec以上を叩き出していますし、今まで1鯖あたり100人を超えたあたりから部屋一覧の表示がもっさりするようになっていたのが、150人を超えても一瞬で表示されています。
クライアントからのリクエスト数も増えていないことから、負荷チューニングとしては成功したと思います。

公式鯖は今までどどんとふ本体には手を入れないという基本的ポリシーでしたが、ついに手を出しちゃいました。とはいえ、公式鯖用にForkなんてもってのほかなので、この記事を@torgtaitai氏が見つけてどどんとふ本体にマージされることを生温かく待つことにします。

[連載4]どどんとふが重い?

前回の投稿で、1300人のLANパーティーというフザけたネタ解決策も提案しましたが、現実的な解決策もありました。
他に考えた対策も一つ紹介したいと思います。

対処法2改:どうせ待たされるんだからSWAP承知でメモリとプロセス数を増やす

公式鯖第壱鯖、第弐鯖限定で2016年5月20日から試験的に実装しています。その後に対処法3を思いついたので今となっては微妙な対策です。
SWAP承知といってもHDDにSWAPされて大幅にレスポンスが落ちたらかえって待ちが増えてしまうので、なるべく重くならない方法として
zramにSWAPさせています。(これがzramの本来の使い方で、ファイル置き場にするほうが邪道な使い方です)
どどんとふ公式鯖稼働系が動いている仮想マシンは14GBのメモリを割り当てていますが、このうち2GBをsaveData用zram0に、
それに加えて6GBをzram1に割り当て、そこにswapを割り当てました。

そして、第壱鯖と第弐鯖のプロセス数を60→100に増やしました。合計で80のプロセス数増です。
設定前後のnginxのreq/secを比較して、結果を見てみましょう。

nginx_request-week

なんということでしょう!!
5/20以前は300req/secで頭打ちになっていたnginxの処理能力が387req/secと増やしたプロセス数の分だけ増えています!!
memory-week
cpu-week
SWAPも増えていますし当然ながらメモリをリアルタイム圧縮するためにCPU使用率は顕著に上昇していますが、まだCPUには余裕があるのでコレはコレでチューニングとしてはアリなわけですが、
対処法3であるnginxのfastcgiのバッファリングを増やすことにより待ちが改善するのであれば、プロセス/スレッド切り替えのコストが削減できるためCGIのプロセス数を減らした方がCPU使用効率は良くなります。
プロセス数が少なくて済むのであれば、メモリも必要量が減るのでzramをSWAPに割り当てる必要もないですし、saveData用のzramも圧縮を無効化してさらに速度を上げるという方法も可能となります。

これがお仕事の業務システムであれば、テストを実施して、結果をまとめて、手順書を作って、説明資料を作って、レビューを受けて、利用者との停止調整を行ってという膨大な手間と最低でも2週間はかかりそうな時間がかかるのですが、どどんとふ公式鯖は無保証な個人提供のサービスですので、管理人である私が王です。
そんな手続きはバッサリすっ飛ばして、2016年5月21日の朝に公式鯖の第壱~第四鯖までのfastcgi_buffersを1024 16kbに変更しました。その時点で30人ほど利用者が居たようですが、3秒ぐらいのサービス一時停止ぐらいなので気にせず実施です。ビバ個人鯖!!

来週の公式鯖の負荷状況を見て、zramによるSWAP設定は見直そうと思います。

[連載3]どどんとふが重い?

前回の投稿で、おそらく純粋にTRPGを快適に利用したい考えている一般利用者を99%ほどふるい落としたような気がしますが、気にせず続けます。

じゃあ、より多数の人数を終了するにはどうすれば良いのか、対処法を考えていきたいと思います。

対処法1:レイテンシを思いっきり短くする

インターネット上だと色んな速度のネットワークやPCがあり、遅いPCに引きずられてしまいます。

これに対する最強の解決策が、インターネットを使わず全ての利用者が、最新の高速なPCに買い換えて、公式鯖管理人の家に有線LANで直結するという方法です。

Client1300

つまり、この状態を

LAN_party

こうします。
これは海外ではオンラインゲームを快適に行うために使われるやりかたで、LANパーティーと呼ばれています。
確かに通信部分の平均応答時間が、0.4秒から0.02秒に改善されますから、今のリソースでも遅延はあまり発生しなくなるでしょう。
どどんとふのコードはそのままで、公式鯖も大きく構成を変更することなく、自鯖がなくても解決しますね。
しかし、1300人がPCを持ち寄るとなると、部屋には入れませんし、電源も足りませんので、ざっと思いつく限りでも以下の準備が必要そうです。

・仮設テントや机、椅子、照明などの手配と設営。

1300人分ですので、PC含めて一人あたり1畳(1.65m^2)分のスペースだとしても2145m^2。競技用の50mプール3個分ほどらしいです。
マルチモードの光ファイバー(1000base-SX)が到達できる範囲にそれだけのスペースがなければ、シングルモード(1000Base-LX)で伸ばすしかないですね。自宅のL2スイッチはSFPスロットが付いているので、SFPさえ取り付ければどちらでも対応可能です。

・電源車の手配もしくは、電力会社に工事用の仮設電源設備の設置を依頼する

PC1台が200Wだと仮定すれば合計260KVA。それにネットワーク機器と照明が追加されるのでざっくり300KVAぐらいでしょうか?
空調は計算がややこしいし屋外前提なので使わないものとして計算します。
川崎重工のガスタービン発電車(MPU300)あたりがちょうど良さそうです。レンタルの価格相場はどこも個別見積もりっぽいのでわかりませんでした。
5t車なので中型免許でなんとかなりそうですが、これ出力電圧が6600VだからそのままだとPCには使えないのでダウントランスとか分電盤が別に必要です。設営場所の端から端まで電源引くにはPC5台(1000W)ごとにドラムコ ードを分けるとしても260個必要です。一個4300円なので、ざっくり112万円ですね。

・設営場所までの屋外LAN配線およびネットワークスイッチ増設

1300人だとLANポートだけでなくIPアドレスも足りないのでをL3スイッチも必要ですね。
L2スイッチは、1300ポート分なので48ポートを30台でなんとかなりそうです。
ネットギアやD-Linkの48ポートスイッチが、1台約5万円なので30台で150万円。L3スイッチが、Ciscoの3750マルチレイヤースイッチが26万。LANケーブル代も会わせると200万で足りるかどうかという数字です。


・所轄の警察に道路使用許可の申請

・私道部分は、私道の所有者および近隣の通行権所有者への使用許可

・近隣住民への説明と同意

結論:俺が住めなくなりますので、辞めてください。普通に公民館なりレンタル会議スペースでオフセすればいいだろ!

対処法2:レイテンシが長くなっても待ちがでないぐらいプロセス数を増やしまくる

プロセス数が足りないから待ちが生じるので、公式鯖のプロセス数とメモリを大量に増やすという方法です。
1300人を捌くために1300個のプロセスとそれが入るだけのメモリを用意すればこの問題は解決します。
DodontoFServer.rbの仮想メモリ使用サイズは1プロセスあたり90MBですので、90MB×1300で117GB。
それにnginxやzramやOSなどの必要量を加味すると128~160GB搭載すれば処理できるようになります。
LGA2011-3のXeon搭載のマザーボードなら、128GBや256GBを搭載できるモデルもありますので、マザーボード、CPU、電源、ケース、メモリでざっくり50万ぐらいでしょうか?
ほぼ同じ構成の待機系もあるので2台分ですね。1300人が一人800円ずつです。利用者全体でいえば5000人ぐらいはいそうなので、それなら一人あたり200円で済みます。
ちょっと高くつきますが、完成品のサーバー(例えばIBM/Lenovoのx3650M5 メモリ最大768GB 1CPUソケットでも最大384GB)なら自作する手間もありませんし、24/365のハードウェア保守サポートがつきます。

結論:100万円ください。家族で旅行に行って、残りを住宅ローンの返済に充てます。公式鯖の増設?ちょと重いぐらい我慢してください。

まじめな対処法3:nginxでバッファリングする

次は真面目な解決策です。前回紹介した図をまず見てみましょう。
Client_Server
A-1の処理に時間がかかるのであれば、nginxはDodontoFServer.rbから処理結果が返ってきた時点でDodontoFServer.rbを開放してしまえば良いのです。幸いなことに公式鯖のnginxは32768コネクションまで同時に捌けるよう設定していますので、nginxが詰まることは当分なさそうです。これなら、相手の回線速度による待ちが発生しようがしまいが、DodontoFServer.rbはリクエストがジャンジャカ受け取り&処理できます。
で、結論からいうと実はこの設定はすでにやっていたのです。公式鯖のnginxの設定で該当箇所を見てみましょう。

上記設定にある「fastcgi_buffers 」という設定項目がそれにあたります。設定内容は、256KBのバッファを128個用意するという設定になっています。
つまり、128×4鯖ということは512。ざっくり500人程度までは待ちが生じない設定になっていたのです!!しかも、その当時はこの設定内容をよくわかっていなかったので256KBも確保していました。どう考えても多すぎます。
はい、公式鯖3rd generationをリリースしたときは、これで余裕だったのですが、今では全然足りていません。これを現状のリクエスト数に応じて調整すれば良いわけですね。
DodontoFServer.rbの平均リクエストサイズは前回の投稿で約700Byteだとわかりましたが、Analogレポートのサイズ別の内訳を見ると、バッファサイズを16KBにすれば全リクエストの99%以上をバッファリングできることになります。

req_size

今後の利用者増を考慮してもとりあえず1鯖あたりは500個ほど用意しておけば足りそうですね。これなら同時ログイン数が2000人になっても遅延はでないはずです。

ちゃんと情報を整理する事は重要ですね。実はこの記事を書いているうちにこの解決策を思いつきました。なんだ簡単にできるじゃないか。。。

ということで、次のメンテナンスでこの設定を試してみて、いずれ結果を紹介したいと思います。

[連載2]どどんとふが重い?

前回の記事に引き続き、サーバー部分のどこが重たいのか、もう少し詳しく調べていきましょう。

サーバー内部の様子

クライアントとサーバーの間のネットワークを省略して、公式鯖のリクエストが処理される流れを図示したイメージがこちらです。

servermodel2

全体を書いても訳が分からないと思いますので、とりあえず、第壱鯖だけに絞って説明します。

Client_Server

青い矢印(A)がブラウザがindex.htmlやDodontoF.swfなどの静的コンテンツを読み込むリクエストの流れ。

緑の矢印(C)がFlashプラグインが静的コンテンツ(map、キャラクター、カットイン、ダイスなど)を読み込むリクエストの流れ。

赤い矢印(B)がチャットデータの送受信などを行う部分で、最も負荷が高い部分です。

このうちAとCの処理は、nginxだけでリクエストを返すので、応答時間としてはキャッシュが有効ならA-1の応答時間、キャッシュが無効でもA1とA-2の応答時間の合計となります。

赤い矢印の部分は、A-1とB-1とB-2の応答時間の合計となります。

Bの処理をさらに細かく見る(CGIとDodontoFServer.rb)

B-1とB-2は、レンタルサーバーなどでは、CGIとしてDodontoFServer.rbが動きますが、リクエストの都度、以下のような処理が動きます。

  1. ruby本体の読み込み
  2. DodontoFServer.rbのコンパイル
  3. DodontoFServer.rbが使用する周辺ライブラリの読み込み&コンパイル
  4. DodontoFServer.rbの処理実行

公式鯖のようなFastCGIでは、1~3は公式鯖起動時に一度実行されるだけで、リクエストの都度動くのは4だけです。
しかも、4の部分は常にリクエストが受け付けられるよう各鯖毎に60個のプロセスが常に待機しています。
公式鯖が大量人数を処理できる大きな理由がこれです。

逆に公式鯖がボトルネックになっている部分もここで、一つの鯖につき60個までしか同時にリクエストを処理することはできません。
例えば1回のリクエストを1秒で返したとしても、ログイン人数が120人を超えると応答時間の遅延や再接続が発生します。
実際には1秒もかかりませんが処理内容によって応答時間はまちまちなので、何人ならという指標は適切じゃないのですが、
次に説明するレイテンシの都合により、平均応答時間は0.3~1秒程度を要するめ、1鯖あたり200人超えたあたりから遅いと感じる人が出始めるようです。

帯域も大事だけどレイテンシ(応答時間)も重要

先ほどの「4.DodontoFServer.rbの処理実行」をさらに分解すると以下のようになります。

4.1: データ読み込み
4.2: データ(コマンド)の中身解析
4.3: 処理実行
4.4: 結果を出力

このうち4.2と4.3の部分は、公式鯖内部での処理になりますが4.2はただの構文解析なのでCPUしかほとんど使いません。

4.3でsaveDataディレクトリのファイルの読み書きやダイス判定を行うため、CPUとディスクIOを使います。このディスクIOに相当する部分がB-2となりますが、CPUに比べてディスクIOは非常に遅いため、公式鯖では高速化のためにRAMDISKを使っています。

そのため、HDDだとランダムIOの平均シークタイム10msec程度、SSDでも1~2msec程度の応答時間を10μsecのオーダーまで短縮させているのです。
で、問題は残りの4.1と4.4で行われる処理です。4.1と4.4のデータ送信は、A-1の通信も含むため、クライアントとの通信に時間がかかると通信待ちが生じてしまいます。
それによってCPUやディスクが空いているにも関わらず他のクライアントが待たされてしまい待たされた分のクライアントの応答時間が長くなってしまうのです。

レイテンシいろいろ

ではここで、A-1の通信にどれぐらいの時間がかかっているのかレイテンシを調べてみましょう。

まずは、いろんなところとPINGを打ってパケットの応答時間を調べてみます。(端数適当に切り捨て)

公式鯖管理人自宅⇔どどんとふ公式鯖 0.3ms
公式鯖管理人自宅⇔eo光トップページ 3.5ms
公式鯖管理人自宅⇔さくらインターネット 4ms
公式鯖管理人自宅⇔Googleトップページ 5ms
公式鯖管理人自宅⇔OCNトップページ 12ms
公式鯖管理人自宅⇔Yahooトップページ 15ms
公式鯖管理人自宅⇔こかげ工房さんの自宅鯖 18ms
モバイルルーター(mineo)⇔どどんとふ公式鯖 50ms

厳密には違うので異論は認めますが、応答時間×パケット数が、4.1と4.4のざっくりとした合計時間になります。(やっぱGoogleさんパネェっす。)
ICMPのパケットは56byteですしチェックサムの計算や上位アプリケーションレイヤーのハンドシェークにかかる時間も加わるので
実際の通信にかかる応答時間はもっと長くなります。

DodontoFServer.rbのCGIがクライアントに返す転送量を、Analogレポートからざっくり平均で調べてみると、
過去半年で800,661,712リクエストを、535GBのトラフィックを返しています。
ということは、1リクエストあたりの転送量は、平均して706Byteです。
Analogレポートのファイルサイズ毎のグラフで見ても、1KB未満のトラフィックが約90%ですので、妥当な線だと思います。

※DNSの名前解決は自分が契約しているISPのDNSやGooglePublicDNSでしょうし、一度解決した後はローカルのキャッシュにより
解決されますので、応答時間としてはもうちょっと短くなります。
参考:DNSとは
※SSL/TLSのハンドシェイクもHTTP KeepAliveを使えば、リクエストの都度ハンドシェイクをする必要はありませんが、
ある理由により公式鯖は無効にしています。
参考:SSL/TLSセッションの確立手順

仮に自分のPCとどどんとふ公式鯖の間のレイテンシが20msでハンドシェークに20パケットの往復が必要だとすれば、
DodontoFServer.rbのデータ通信1回には400ms(0.4秒)を要します。モバイル回線だと、1秒もかかってしまいます。

DodontoF.swfからDodontoFServer.rbに更新の有無を確認する頻度は2秒なので、ダイスや他の人の発言は
送信されてから画面の表示されるまでの間に0.4~2.4秒の遅れが発生します。

1300人が2.4秒間ずつ240個のプロセスを占有するリクエストを2秒毎に出したら、絶対詰まりますよね?

サーバーのCPUやディスクIOが暇なのに重いのはそれが理由です。