inductor's blog

nothing but self note :)

WebエンジニアとWeb技術とシステムの話 (sadnessOjisanのWebサーバーアーキテクチャ進化論2023を読んだ感想)

sad記事の勉強と実践のボリュームがすごい

https://blog.ojisan.io/server-architecture-2023/ を読んで、その前身とも言える https://blog.yuuk.io/entry/2015-webserver-architecture を含めてこれらのような記事を書く知識や経験が僕には無いから素直にすごいと思った。ただ、その一方でこの内容を普通に理解できる「Webエンジニア」はどのくらいいるんだろう?というのも同時に気になった。

ゆううきさんの記事は「序論」とあるがWebエンジニアとしてキャリアを積む人間が「序論」として読むには文量や背景知識が重すぎると正直思うし、システム・計算機工学を勉強した人間が背景に感じ取れる。事実、sadさん(おじさん)も昔は内容が分からなかったと本人記事内で言及しているため、僕の気のせいではないと思う。じゃあsad版がその問題を解決できているかというと、そんなこともないなと思う。「Webサーバーアーキテクチャ」のタイトルに対してなんの前置きもなくシステムプログラミングや並行処理(スレッド、プロセス)の話がどかどか登場するので、前置きの話が欲しいのが世の中の普通のWebエンジニアの気持ちなんじゃないかと思う。

そうした温度感から、記事を最初だけ眺めて置き去りにされてしまった層も一定数いるのではないかと思う(これは批判ではない)。現に元記事では

当時は「動けばそれで OK」といった感じであまり技術の仕組みとか勉強していなかったと思う。でもいつか理解したいという気持ちはずっと持っていたし、現にその気持ちを今日まで持って勉強を続けていた。このブログでもやたら並行プログラミングや非同期処理について書いているのはまさしくそういう原体験があるからだ。そんな中、最近初心に戻ってそのブログをあらためて読み返して見ると、今だと記事に書いてあることを理解できたりその記事の未来版・続きを書けることに気づいた。なので書いてみようと思う。

のように序盤で既に書かれているわけで、背景知識がない人間にとって読みやすく書くことは、恐らく記事の意図ではなかったであろう。よってこの話は「分からんなら勉強しましょうね」で終わってしまうのだけど、それじゃ記事に起こす意味がないので自分なりにWebエンジニアがなんとか食らいつけるような取っ掛かりをここに置いておこうと思う。

呼び方

単に元記事の引用をしたい場合、今回は少なくとも2つの文章が独立して存在する。これがわかりにくいので名前をつけてしまうことにする。

これ以降、二つの元記事を引用する場合、それぞれ「sad版」、「ゆううき版」と呼ぶことにする。

最初のつまづき: いきなりシステムプログラミングから会話が始まる

sad版をそのまま読もうとした時、Webサーバーのアーキテクチャの話になぜシステムプログラミングが介入するのか、まずそこで迷子になった人がいると思う。実に正しい。これは読解力が無いのではなくむしろ正しく日本語が読めている証拠だと僕は思う。何故なら間に大きな論理の飛躍(前提の省略)があるからだ(日本語にケチをつけたいわけでも無いのでそれはこの辺で置いとくとして、本人の思うままに書いたものなのでその辺を補完するのが読者やこうしたリポストであれば良いと思う)。裏を返せば、僕はこの記事を書くことで「僕の理解した範囲での説明」を新たに言語化する機会があるし、そのような集合地への広がりがもっと促進されるべきだと思っている。なのでこれを読んでいる人の中でもし興味があれば、同じように感じたことを発信してもらえるといいと思う。

ゆううき版の記事を流れで見ていくと、Webサーバーの実装には大きく分けてシンプルかつ低機能で性能もスケールしにくいシリアルモデルから、次第にプロセス、スレッド、プロセスと処理の分離レイヤがOSに近い処理になっていき、それぞれの仕組みがどのような機構で成り立っているかを図解や例を用いて説明している。もっとも、OSのシステムコールの話やLinuxにおけるネットワーク実装の話などは、特にフロントエンジニアやバックエンドでも歴の浅い人にとってはちんぷんかんぷんだと思う。少なくともサーバー実装を選定したりある程度のチューニングを行うまでであれば概念程度でもわかっていれば問題ない気もするが、まあ実際に手を動かしてみないと分からないことも多いだろう。だからこそシステムプログラミングに入門するといいよ、ということをsadさんは伝えたいのだと思う。

僕にとってこれまでのエンジニアキャリアの8割以上がWeb技術への理解と仕組みづくりで成り立っているので、僕自身がシステムプログラミングに手を出したことはない。ただ、並行処理をどう実現するかやネットワーク越しあるいは同一ホスト上の別のプロセスとの通信をLinux上でどのように実現するかなど、システム管理をする立場として当たり前の事実は、自分が作ったソフトウェアの面倒を見るために必要な責任範囲の一部であり、知っておくことに大きな意義がある。

Everything is a file

どちらの記事にも言及はないが大事だと思う考え方として「Everything is a file」という考え方がある。このWikipediaページにある通り、Linux(UNIX)ではあらゆるものがファイルになっているため、デバイスだろうがツールだろうがなんだろうが、共通の形式で処理が行える。例えば繋がっているUSBデバイスのデータを読み取る、とか、家の中にある無線LAN経由のプリンタに何かを印刷させる、といった処理がコマンドラインで完結するのはこのおかげだ。デバイスやネットワークへの接続を確保するためのファイルと、プログラムがそれにアクセスする際に生成するファイルディスクリプタによって実現される。

これは、プログラマーの視点で言えば、何らかのデータの塊を目的の場所に送るための入り口として共通でファイルシステムを使って良いことになる。普段あまり意識することはないかもしれないが、MySQLのサーバーがunix:///var/run/mysql.sockで待ち受けているのか、tcp://127.0.0.1:3306で待ち受けているのかという違いも、ソケットファイルとプロトコルという交換可能な仕組みによって抽象化されていることに他ならない。PHP、Python、Rubyなどで CGIサーバーを用いる場合でも理屈はおなじのはずである。

ソケットの存在や実態に触れる機会が多かったサーバーエンジニアにとってみれば、例えばファイルディスクリプタ制限数が甘くてエラーになっていた、といった運用上の問題はそんなに珍しい話でもないとは思うが、フロント専業でやっていた人間にとってはそもそもソケットというものがLinuxで何をしているのかが不明瞭なのはOSに向き合う時間が少ないので自然な疑問とも言える。sad版を読んでいて一番違和感があったというか遠回りだなと感じたのはそこで、結局システムプログラミングにいきなり入門するよりは、Webエンジニアの知識の範囲で首を突っ込めるくらいの規模でサーバーを立てて動かしてみるという行為をするだけでも、同等の感性を身につけること自体は可能だと思うし、少なくとも圧倒的にそちらのほうが期間は短期間だと思う(あくまで実践・実用性に振った場合の話)。なのでISUCONの過去問でも解くといいんじゃないと本人には個別にコメントした。

ここで言及する内容が現代のシステムでどこまで実際に必要かというのは正味蓋を開けてみないとわからない。Lambdaなどのサーバーレスモデルが普及した今、コンテナよりもさらに上のレイヤで戦うプログラマーにとって、カーネルのパラメーターなんて見るはずのない世界だし、全てのビルディングブロックがVPCあるいはHTTP API越しに制御されることを想定した作りになっているのであれば、まあ出番はないだろう。それはそれとしてシステムの勘みたいなものに効くとは思っているが確証があるわけでもない。

Webとシステム

Web技術の組み合わせによって作られたシステムは、図らずしてみんなどこかしら分散システムに片足を突っ込んでいると僕は思う。世間ではシステムアーキテクチャの名前としてモノリスだのマイクロサービスだのDDDだのオニオンアーキテクチャだのいろいろ名前は様々いろんな呼び方があるが、何らかの機能を提供する「系」として生きるものは全てシステムでありサービスだ。それはどんな新しいものでも古いものでも同じで、それをどう呼び出すか、データをどう取り出すかの世界がそこにあるだけの話である。

Web APIという仕組みはそれを大きく代表するものの一つだ。Google Mapが公開されたのは2005年ということでもう18年も昔のことらしいが、AjaxだのRESTだので一世を風靡した結果、「API」といえばWeb APIという認識がなんとなくWeb業界では定着したように思う。無論IT全体でみたらそんなことはなくて、例えばOSのシステムコールだってAPIだ。UNIXだろうとWindowsだろうとAPIを用いてプログラムからOSに命令を送ることができるし、そうして送る命令のことをRPCと呼ぶこともある。gRPCってやつも当たり前のようにみんな呼んでいる気がするけど、こうした背景を曖昧にしたままプログラマーをやっている人たちも当然いるだろう。

関数とシステム

僕は数学が苦手なのであんまり数学に踏み込んだ話をここでするつもりはないし、正確性を保証もするつもりはない。なので軽く流してもらっていいけど書きたいので書く。

文系で数学を触れずに生きてきた人もいるとは思うけど、ここでは比例の関数と放物線(2乗)くらいまでは使わせて欲しい。なんで数学の話を出すかというと、見通しが良くなるからだ。

Webプログラミングをしていて数学的な要素を意識するときは何かというと、僕は共通化の処理が圧倒的多数だと思う。呼び出し回数が多いから共通化して記述量を削減する、という側面もあるけど、例えばループ処理でやっている箇所をもっと賢いやり方で解決したい、みたいになった時に「どのように処理させたら重くならないか」と考えると思うが、もう少し具体的に考えてみると

  • 呼び出し回数が増えてもパフォーマンスが落ちにくい
  • そもそも呼び出し回数が増えても一発で取ってこれるようにする(みたいな具体的な改善案)
  • リファクタリングの前と後で機能上破壊的な変更がない

みたいなことを意識するだろう。最初のやつは純粋にシステムをどうしたいかという目的だけど、あとの2つは完全に数学的な考え方で、前後で等価ってことは、その機能を数学的な関数としてみた時に  f(x) = x ならば絶対に入力値と出力値が同じものが返ってくる(1なら1、1000なら1000)ってことが期待されるみたい話だからだ。

上記は具体的な実装の話だけど、もっと広くみてそのシステム全体で考えてみるとこうだ。あるシステムが数年稼働するにつれてユーザー数も増大し、次第にパフォーマンス劣化が目立つようになってきた。どこを直したら良いか。みたいなところでいうと、本来理想は  f(user, contents) = f (x, y) という具合にユーザーとコンテンツが両方いくら増えようが関係なく無限に増殖していきたいところだが、現実には難しかった、みたい話があってそこからボトルネックを見つけていくことが運用上の課題になるはずだ。

つまるところ、プログラム上の関数だろうがAPIやエンドポイント単位だろうが、システム全体だろうが、別にどんな単位でもモデル化してしまえばシステムは数学的に考えることができるというのが僕の考えだ。そういう別の系を持つシステム間でやり取りをするためにプロトコルってやつが決まっていて、APIやらRPCやらをソケットやらネットワーク越しやらでぶん投げると何かが返ってくるというのは、別にGoogle Mapに場所を聞くときだろうがMySQLにSELECT文を発行するときだろうが全く同じだと思う。

このとき、ユーザー数が増えるとその2乗に比例してパフォーマンスが落ちることがわかっているシステムがあったときに、それをなんとかただの1乗分に抑えたいと思うことがあってもいいだろう。それがパフォーマンスチューニングだ。

なんかこの辺の混み入った話をごちゃごちゃ理解するのには、プログラミング能力そのものってよりはシステムについて考えることとものづくりの目的について意識するほうが僕は大事だと思っているけど、特に経験の浅いうちはそんなこと言われても周りが見えてないので難しいという場合もありそう。抽象的な世界と具体的な世界を行ったり来たりできる能力の高い人間は特にこういうメタ知識を身につけやすいし、学生の時なんとなくうまくいかなかった人でもうまくいっている人の多くはそういう能力に長けてるんじゃないかって気がしてる(本来とめっちゃズレた話になってきた)。

結論どうしたいん

いきなりシステムプログラミングに足突っ込めは結構大変だと思うんでと思っていろいろ書いてみたけど、自分もまあまあまとまりのない結果になった。多分数年後にはもうちょっと綺麗に文章が書けるようになっている気がするので将来の自分に期待。

他の人の意見も読みたいのでいろんな人のブログ待ってます。