Edited at

2019年にもなって未だに非同期I/Oを使わずPHP、Python、Ruby等でProcessを浪費しているサービスが増える理由とは!


はじめに

間違えている箇所があれば指摘していただきたい

特にPHP,Python、Rubyを本格的に開発した経験が少なく

間違ってたら私のために教えていただきたい

ただ1つ 私の中でも正しい用語定義がわからないので

非同期と書いたときは 非同期I/O、ノンブロッキングI/O 両方のことをさし

マルチスレッドは並列などと表記する


現在の状況

2019年。Webサービスはどんどんローンチされている

Java、nodeといった非同期のサービスも増えてきたが

未だに PHP、Python、Rubyといった非同期ではなくプロセスを立ち上げるサーバが多い

(asyncioとかeventmachene等の非同期機能はあるが、未だに非同期を使わないフレームワークが使われる事が多い)

まずは大雑把な年表だ

https://qiita.com/legokichi/items/1f3b1bd51e206ffdd2a6

がキレイにまとめてくれているので そちらを読んでほしい


2002年以前

WebサーバといえばApacheだった

当時はPerl、PHP などでWebサービスが開発されることも多かった(もちろんJavaも)

Apacheではクライアントからのリクエストが来たらプロセスを立ち上げ(Fork) そこでCGIを実行する

ところが インターネットの普及やコンピュータの処理性能が向上するに伴い問題が明るみに出た

まず プロセスの立ち上げに時間がかかりレスポンスが悪い事

そして メモリ使用量

更に プロセス上限数(Linuxカーネルだと標準で1万までとなっている)

これが俗にいう C10K問題だ


nginx

2002年にnginxが登場し状況はかわった

Apacheと違い クライアントからの接続を Processより軽いThreadで受けたり

非同期処理で受けることも出来た

ただし PHP、Python、Ruby等のスクリプト言語は スレッドが使えない

スレッドが言語仕様にある! と反論する人もいるかも知れないが 残念ながら本物のスレッドではない

これらのスクリプト言語はGILという致命的な問題があるため、マルチスレッドを行なうのは非常に制限が多い


FastCGI/Unicorn

そこで スクリプト言語は色々工夫をして少しでもプロセス起動コストを減らそうとした

例えば 予めWorkProcessを立ち上げておき、接続がきたら 既に立ち上がってるProcessに渡したり

クライアントとの接続が終わっても一定時間Processを終了させず 接続を待ったり

色々な工夫をした


2008年 nodeの登場

2008年にnode.jsが登場した

もちろんそれ以前に 非同期I/O、ノンブロッキングI/Oを使ったライブラリはあったが

Open系で C言語、C++、Java等でサーバを書く人は少ないく

JavaScriptであるnodeでノンブロッキングを簡単に書けるようになった

今は2019年 もう10年も経っている!

ちなみに私はその時期は ゲームのフロントが多くサーバ開発は少ないが

C++で selectを使ったノンブロッキングI/Oを主に使っていた

JavaだとServletを使いマルチスレッドで書いていたので、ちゃんと非同期プログラムはしてなかった


非同期ライブラリの実装

非同期プログラミングを行なうと コールバック地獄に陥る

https://qiita.com/LightSpeedC/items/7980a6e790d6cb2d6dad

https://qiita.com/KDKTN/items/4c6986049d204f0645d8

このように コールバック地獄はプログラマを悩ませていたが

promise/futureやGenerator、async/await 等を使うことで

コールバック地獄を回避出来る

今やJavaScriptのフロントでも当然知っている事である

非同期を使うことで従来のプロセスを立ち上げるタイプのWebサーバと比較して10倍以上のパフォーマンスが出るようになった


その他の言語

node以外でも当然 非同期なフレームワークは出た


Java

SpringやらPlayframworkやら 非同期が当然になっている


Scala

JavaVM上で動く関数型言語。こちらも非同期が当たり前に


Erlang、Elixir

非同期処理を目的としたVM上の言語

Erlangがよく使われる言語と文法が違い難しいため

比較的よくある言語と似た文法のElixirが作られた

これらの言語は 非同期型の弱点である 堅牢性の弱点が回避されている

(非同期プログラミングだとクライアントの接続を 同じスレッド上で複数対応するため、1つのスレッドの致命的エラーで全部落ちる可能性がある)


C++

おまけ

BoostAsioを使えば非同期I/Oを使うことが出来る

C++20? では標準ライブラリに std::networking が追加される予定である

めったに使う人はいないとおもうが、私は BoostAsioによる非同期サーバを少なくとも3案件行った


Go

ここで新参者のGo言語の登場

個人的にはGo言語は文法が古臭く(C言語の置き換え)

なんのために産まれてきたのか、なぜ流行っているのか理解できなかったが

ここに来てやっと理解できた

文法がシンプルで覚えやすく簡単に非同期が使えるからだ

この事は 私の最終結論に最も影響をあたえる


人はなぜPHP、Python、RubyをWebサーバで使い続けるのか

非同期フレームワークの方がパフォーマンスが出る事は明確である

が、なぜ未だに プロセスを無駄に立ち上げるのか?

これがnodeが産まれてから10年もの間謎だった

もちろん古いシステムの修正やリプレイスで古い言語を使う事はあっても

新規でもいまだにプロセスを作りまくるのだ

が しばらく考えて 下記の考察がうまれた


非同期を知らない人orパフォーマンス気にしない人

非同期が速い事、非同期そもそも知らない、あるいは パフォーマンスに無頓着な人は仕方がない

これを機に知って頂きたい

また パフォーマンス悪くても オートスケーリングすればいいという意見もあるが

サーバコストがその分多くなる事も考えるべきだ


新しい言語、フレームワークを覚えたくない人

これは最悪だ

サーバを非同期にすることのメリットを理解したら今すぐ勉強してほしい

特にリーダークラスでライブラリの選択権を持った人は

これ以上新人に 無駄なプロセスを起動する人を育てないで欲しい


特定言語にこだわる人

上記と同じで、パフォーマンスの高い言語を覚えてほしい

Railsだと開発効率がいいから使ってる と主張する人が多いが

たしかに キミのRailsは開発効率は高い

だがそれは Railsが優れているわけではなく、キミの知識が偏っているからだ

是非つぎは 下記にあげる 非同期可能なフレームワークの知識を高め

非同期サーバを高効率で開発してほしい

日本の宝だ


非同期対応の言語を覚えられない人

これが盲点だった

私は プログラマなら誰でも非同期フレームワークを勉強すれば使えると思っていたのだが

この間正直にいわれた

nodeとか勉強したが理解できなかったと。

nodeはJavaScriptなので比較的覚えやすい言語ではあるが、Java、Elixir、C++などは拒絶する人も多いだろう

PHP、Python、Rubyはそれらの人でも覚えやすい言語になっているらしい


DBがフルマネージドNoSQLでないから

DBがフルマネージドでなく、インスタンスやコンテナでオートスケールする場合

非同期のコネクション増加と、DBのスケーリングの速度ギャップがありDBが死ぬ

このため、非同期プログラムではコネクション増加を管理するか、フルマネージドのNoSQLを使うが

フルマネージドNoSQLをすべての案件に使う事は難しいし、コネクション管理に開発コストかかるため

DB性能が低い場合は、あえて同期を使う事が考えられる


結論

今だにパフォーマンスの悪い プロセス生成型のWebサーバを作る人は

パフォーマンスに興味ないか 非同期がパフォーマンスが高いことを知らない、覚えられない

DBが遅いので不要

という結論になった

まず 知識不足に関しては、非同期サーバの話をもっとアウトプットしなければと私も責任を感じている

特定の言語が好きという人は 是非その言語の非同期フレームワークを使ってみてほしい

DBのスループットにより非同期の利用が難しい場合は、フルマネージドNoSQLや非同期Queue(SQSやKinesis)、シャーディング、DB高速化、NoSQLの検討、非同期コネクション増加の制御 等を一度でも検討してみては?と思う

最後にのこった 非同期プログラムが難しくて理解できない人

これが問題だ

Java、C++、Elixir、node のどれかを覚えられたら良いのだが それすら難しい人には

ずばり

Goがオススメである

Goは文法が比較的簡単かつ、他の言語よりも簡単に非同期処理が行える。そしてパフォーマンスもそこそこ良い

非同期プログラムに一度挫折した人 もう一度Goで勉強してほしい


あとがき

非同期サーバは色々あるが、パフォーマンス等を考えると私は

C++、Java、C#、Goをおすすめしている

Goは文法的に・・・ と思ってるが、非同期サーバは書きやすい

nodeを外した理由は、やはり速度を考えるなら 他の言語の方が分があるかなという事

Elixirは堅牢性が必要なら良いが ErlangVM自体がパフォーマンスあまり良くないので除外してる

C#はベンダーロックインちっくなのが気になるが、言語は圧倒的に洗練されていると思うので推した

間違えているところあったら教えてください。


追記

コメントより、Pythonには非同期Webフレームワークが存在することを教えてもらいました!

ありがとうございます

フルマネージドでないDBを使っている場合、非同期だとDBのスケールアウトが間に合わずDBが死ぬ件は

サーバ側でコネクションを制御する必要が出てコストがかかるというのも、うっかりでした

気づかせてくれてありがとう