インフラを意識してコードを書くということ

チーフエンジニアの id:Songmu です。

4月に 新人エンジニア研修を行なった のですが、その際に、「インフラを意識したアプリケーションの書き方」という講義を担当しました。そこでおこなった講義の内容について整理しながら書き起こしていきたいと思います。

インフラを意識すると何が良いか

業務でWebアプリケーションを扱うと、個人ではなかなか扱えないトラフィックであったりデータ量を扱うことになります。小規模サービスでは考えなくてよかった多くのことを考慮する必要がでてきます。なかなか体験できないことでもあるので、楽しく、やりがいもあります。

また、そういった経験を通して、インフラを意識しコードをかけるスキルを身につけることは、Webエンジニアとしては大きな強みとなります。ISUCONで優勝できるかもしれません*1

インフラを意識すると何が良いか 〜 中規模ベンチャーの場合

そもそも、はてなは、長くサービスを運用することで価値を発揮してきた会社です。作ったら終わりではなく「運用に耐える」Webアプリケーションを継続的に開発していく必要があります。そういう意味でインフラを意識してコードを書くことは必須だとも言えます。インフラを意識してコードを書くことで、サーバー台数を抑える事ができ、インフラコストの削減にも繋がるというわかりやすいメリットもあります。

メリットはそれだけではありません。

はてなは、社員数100人、エンジニアが40人程度の会社です。

そのような規模の会社ですので、エンジニア全員がハードウェア/ネットワーク/OSからミドルウェア/サーバーサイド/クライアントサイドまで、下から上まで一通りある程度わかるように心がける必要があります。もちろん、完全に全部はできませんし、得手不得手があるのは当然ですが、他のエンジニアと話すときに「全く話が通じない」ということはできるだけないようにする必要があります。

たとえば、「サーバーサイドエンジニアがインフラのことがわからない」「インフラエンジニアがサーバーサイド開発のことがわからない」「クライアントアプリ開発者がWebアプリケーションのことがわからない」「サーバーサイドエンジニアがクライアントアプリを考慮できない」というようなことがあります。そういう場合にお互いの「共通言語」が無いため、コミュニケーションが成り立たず、開発スピードに影響を及ぼしてしまったり、もっとひどくなると険悪になってしまう、ということが起き得ます。

大きな組織であれば、ロール毎に縦割りのチームを編成することでチーム単体の生産性を高め、ロール間の多少のオーバーヘッドは許容する、というような組織編成もありでしょう。

ただ、はてなくらいの規模ですと、少なくとも自分の専門領域と隣接する領域のスキルはある程度把握しておいてもらいたいと考えています。そのように、お互いのスキルをオーバーラップさせることで、コミュニケーションを円滑にし、開発スピードを高めようとしています。

自分の専門領域と隣接する領域のスキルを学ぶことで、そこを専門領域にしている人の凄さが本当に分かる、という効果もあります。そうすることで、お互いの専門性に対して、より敬意を払うこともでき、関係性も良くなることでしょう。

ですので、Webアプリケーションエンジニアがインフラを意識するメリットは大いにあります。組織の規模が今より大きくなったとしてもそこは大事にしていきたいと思っています。

インフラを知ることは強みになる

Webアプリケーションエンジニアとして、インフラを知ることは強みになります。

AWS等、パブリッククラウドや各種マネージドサービスが充実してきている昨今、それほどインフラを意識せずにWebサービスを開発することも可能になってきました。

そういったIaaSは多くのことを隠蔽してくれますが、よく見ると、隠蔽化しきれないほころびがあったりします。そういったところを手がかりにして、隠蔽化されている部分の技術について想像して考察することは有益ですし楽しくもあります。実際に問題が起こった時に、問題解決の手がかりになったりもします。

ですので、幾らパブリッククラウドが充実してきたからといって、インフラの知識は無駄になることはありませんし、むしろ強みになります。幾ら未来になっても、世界のどこかには物理サーバーがあり、消えることはありません。

はてなは現在、データセンターでのオンプレ環境と、AWS上でのパブリッククラウド環境を利用したハイブリッド運用をしています。新しいサービスはAWS上で構築することが多いですが、AWS上でサービスを運用する上でも、オンプレ環境で培ってきた運用ノウハウが活かされているように感じます。

はてなが独自のデータセンターを持ち、物理インフラが身近にあるというのは、エンジニアがインフラ勘を持ち、知識を深める上でもメリットになっていると考えています。研修の最後にデータセンター見学を行ったのですが、なかなかできない体験であることもあり、非常に好評だったようです。

さわりでいい

「インフラを知る」と言っても、Webアプリケーションエンジニアが知る必要があるのはとりあえずさわりだけで構いません。例えば以下の様なことです。

  • コンピューターの基礎的な仕組みを知る
  • TCP/IPをなんとなく知る
  • Unixのプロセスモデルを理解する
  • データベース。RDBMS/SQLを理解する
  • HTTPを理解する。HTTP/1.0をtelnetで話す
  • Infrastructure as Code に関する知識

Infrastructure as Codeに関しては、近年進化が目覚ましい分野です。アプリケーションエンジニアはコードを書くのは得意なので「アプリケーションエンジニアでも簡単にサーバー構築ができるようになった」というという点で、アプリケーションエンジニアにとってお得な技術だと言えるでしょう。

僕もそれほどインフラに詳しくなかったので、その昔サーバー構築を任された際に、サーバーに対しておっかなびっくりコマンドを一個一個打ち込んでいた時代からすると、良い時代になったと感じます。

Webアプリケーションの特性とデータベース

Webアプリケーションの特性として、I/Oメインであるというものがあります。処理の中でデータの書き込み・読み込みが支配的であるということです。逆に、ユーザーから受けたリクエストに対して素早くレスポンスを返す必要があるため、それ以外の処理は比較的単純です。単純である必要があるともいえ、時間のかかる複雑な演算が必要な場合には別のシステムに任せる必要があるでしょう。

データの読み書きには、多くの場合データベースが利用されます。データベースに対する理解は、Webアプリケーション開発において必須といえます。

なぜデータベースを使うのでしょうか。それは、データベースにはACIDなトランザクション処理を実現したり、データの堅牢性が担保されていたり、複数台のサーバーから同じデータを参照したり、逆に複数台のサーバーにデータをコピーや分散したり、インデックスを用いて効率よくデータを読み出す機能などが整っているからです。

リレーショナルデータベース

はてなではほぼすべてのサービスでOSSのリレーショナルデータベース(RDB)を利用しています。主にMySQLであり、MackerelではPostgreSQLを利用しています。

RDBが使われているのは、理論が枯れており、はてなに限らず、多くのWebサービスで歴史的にRDBが使われてきたため、業界レベルでノウハウが充実しているというところが大きいように感じます。

アプリケーション開発において、データのモデリングは非常に重要ですが、SQLのDDLはデータモデリング言語として、業界標準の共通言語とも言えます。きちんと設計されたSQLのスキーマ定義を見れば、開発言語にかぎらずどういうアプリケーションなのか大体わかります。

Webアプリケーションにおいてデータベースは最終防衛ラインです。「スキーマレス」がもてはやされることもありますが、実運用においては、きちんとしたスキーマ定義ができ、データに制約をかけられるデータベースを利用するべきでしょう。まかり間違ってデータベースに変なデータが入ってしまうと、下手をすればそのデータは10年以上残ることになり、喉に刺さった小骨のようにシステム運用を苦しめ続けることになります。変なデータが入ってしまうよりかは、一時的にサーバーエラーを返したほうがマシと言えます。

リレーショナルデータベースを使うことでよく言われるネガについて

RDBを使うことに関してネガティブなことが言われることがあります。曰く、古い、複雑、であるとか、パフォーマンスが出ない、などです。多くの場合、それは発言している人の不勉強に起因します。「RDBよりもオンメモリKVSの方が爆速」とか言われても「はあ、そうですか…」としか言いようがありません。RDBとオンメモリKVSでは使いどころが異なります。

ただ、中にはもっともな意見もあり、RDBを使う上で耳を傾けたほうが良いものもあります。

例えば「アプリケーションとDBが相互に依存してしまう」という意見があります。データベース定義を変更するときにアプリケーションを気にしないといけないしその逆もある。互いに依存しており疎結合じゃない、というものです。これは実際にその通りですが、データベースもシステムの一部だとみなすことができれば気にならないでしょう。スキーママイグレーション時に気をつけないといけないことも出てきますが、データの堅牢性を保つためであれば、アプリケーション開発時に多少の制約が出てきてしまうのは許容の範囲だと言えます。

ただ、複数のコンポーネントが一つのDBに依存してしまうことは危険な徴候だと言えます。これは、マイクロサービスアーキテクチャーにおいて「共有データベース」アンチパターンと呼ばれているものです。DBに依存している複数のアプリケーション間による依存スパゲッティの問題で、例えば、システムの変更に伴って、アプリケーションコードと、データベース定義の変更を行ったら、そのデータベースに依存している別のアプリケーションが動かなくなってしまった、というような話です。

ちなみに、「共有データベース」アンチパターンは、昔はよく使われていたパターンです。はてなでも複数サービスから一つのユーザーデータベースを参照する、という作りになっているところがあります。時代の流れに伴い、昔は良いと言われていたことがアンチパターンに変化することはこの業界ではよくあることです。

他には「データサイズが肥大しやすく、分割も難しい」といった意見もあります。これも実際その通りで、RDBの制約は便利なので、ちゃんとアプリケーションを設計すればドメイン分割可能で別のデータベースに入れられるようなデータであっても、ついつい、単一のデータベースに入れてしまいがちです。

そうなると、データサイズが肥大してしまいますが、多くのRDBはその性質上マスターデータベースの分割が難しい構造になっており、サーバー1台の力に頼らざるをえない部分があります。

とは言え、アプリケーション開発者側としては一つのDBで頑張りたいところもあります。そちらのほうが、開発スピードも速く、構成もわかりやすいからです。

このようにデータベースにかぎらず、システムがモノリシックが良いか、適切に分割されている方が良いか、というところは常に議論に上がるところです。これは一般的な解はなく、場合に寄る、としか言えません。

巨大なRDBを負荷分散する手法

前項の最後で述べたように、RDBは肥大しがちですし、システムとしてもデータベースへの依存度は高くなりがちです。となると、データベースの負荷を軽減する方法を幾つか知っておく必要があるでしょう。

よく使われるのはレプリケーションです。レプリケーションとはリアルタイムにマスターデータベースの情報を別のホスト(Slaveと呼ばれる)にコピーする仕組みで、主要なデータベースにはこの機能が備わっています。データベースの冗長度を上げ、可用性を高める為に必ず使われます。

レプリケーションを利用して複数のSlaveを用意し、データの参照をSlaveに向けるのはよく使われるテクニックです。Webアプリケーションでは、多くの場合、データの更新よりも、参照の方が多くなります。また、データの更新は必ずマスターデータベースに対して行う必要がありますが、データの参照はその限りではないため、Slaveを並べて負荷分散するのは有効な対策です。

memcachedやRedisなどを使ってキャッシュをするのも有効な対策です。ただ、キャッシュは制御が難しいため、効果が高い部分にピンポイントで使うのがおすすめです。

それでも負荷やデータ量の関係で、1台のマスターデータベースでは耐えられない、となった場合は、いよいよデータベースを分割する必要が出てきます。データベース分割のことを「シャーディング」といいます。ただし、シャーディングは特に水平分割をする場合、データベースが守ってくれていたACIDな特性の多くを捨てることになるため、アプリケーションの開発が格段に難しくなります。なるべく避けたほうが良いでしょう。

近年はハードウェアの性能が上がってきているため、シャーディングまで必要になることはなかなか無いと言ってよいでしょう。マスターデータベースサーバーは、なるべくスケールアウトではなくスケールアップで凌ぎたい箇所だと言えます。

データ保全やモデリングにおける時代の変遷について

一昔前は、WebアプリケーションはLL(Lightweight Language)と呼ばれる、PerlやRuby、Pythonのような、コンパイルが不要で軽快に開発できるスクリプト言語で素早く開発を行い、データストアを堅牢にするという開発が主流でした。

もちろん、これらは今でも通用しない考えではありませんが、近年では、表現力の高い言語を用い、Webアプリケーションレイヤーでも高度な抽象化とデータモデリングをおこなうことが求められるようになってきています。

これは、作成するアプリケーションに求められる要件が複雑化していることが大きな要因です。単にHTMLを出力するだけではなく、JavaScriptやスマートフォンアプリなどとのデータの受け渡しを受け持つようにもなってきたため、そういった、各種クライアントとの共通データモデリング手法のデファクトが求められてきています。例えば、JSON Schema等があります。

今後、この辺りの技術がどのように成熟していくか、注視してく必要があるでしょう。

RDB設計

RDBに話を戻します。RDBの設計に関しては座学的なことも学んでおくと良いでしょう。以下の様なことを知っておいたり意識したほうが良いでしょう。

  • 正規化
  • primary keyはUNIQUEである他に、immutableでもあるか
  • primary keyにサロゲートキーを使うか。その場合に採番をどのようにするか
  • データ型を何にするか。データサイズがどれくらいになるか
  • 外部キーをどのように扱うか
  • インデックスについて
    • 主にどういうデータ構造が使われるか
    • B Treeインデックスが、ハッシュインデックスに比べて優れている点
  • 想定されるクエリを発行した場合にどのくらいのI/Oを引き起こすか
    • 刺さるようなクエリを発行しないように

この辺りを書き始めると、また一エントリ出来てしまうのでここでは割愛します。実際の講義ではもう少し詳しく話しています。

Webアプリケーションはミドルウェアやインフラアーキテクチャまで含めて一つのソフトウェア

僕はWebアプリケーションはミドルウェアやインフラアーキテクチャまで含めて一つのソフトウェアだと考えています。インフラアーキテクチャーを含めて設計することが、Webアプリケーション開発の面白さの一つでもあります。スケールアウト・アップ戦略まで考えておく必要がありますし、新規プロジェクトの場合、本番投入前に負荷試験ができると望ましいでしょう。

アーキテクチャ設計

アーキテクチャを含めた設計を行う場合、先ずセオリーやパターンを知り、それから理解することが大事です。これは設計に限らず技術習得全般に言えることでもあります。

ちなみに「早すぎる最適化を避ける」とはソフトウェアの設計でよく言われることですが、インフラでも例外ではありません。ハードウェアの進化が進んだ結果、不要になる技術もあります。

技術の持ち駒を増やす

技術の持ち駒を増やせば、設計力に幅がでます。

技術と言うと、プログラミング言語習得のことばかり考えてしまうかもしれませんが、似たようなパラダイムの言語をいくつも学ぶ必要はそれほどありません。幾つか習熟したプログラミング言語があれば、別のプログラミング言語はいざとなれば覚えられるものです。システムプログラミング言語、スクリプト言語、アプリケーション開発言語でそれぞれ手駒を持っておくくらいで良いでしょう。

各種ミドルウェアについても学ぶと良いでしょう。はてなでは、様々なミドルウェアが使われています。MySQL, PostgreSQL, memcached, Redis, Nginx, Apache HTTP Server, Haproxy, LVS, Keepalived, Squid, Varnish, Apache Trafic Server, Elasticsearchなどです。もちろんここでも似たレイヤーの技術を重複して学ぶ必要はあまりありません。

それぞれがどういう特性があってどういう時に使ったら良いのかを知っておくと、頭のなかでそれぞれをパズルのように組み立てることができるようになり、システム設計が楽しくなります。

技術選定

どういうミドルウェアを使うか技術選定が必要になることがあるでしょう。これも、プログラミング言語同様に運用を見据えて選択する必要があります。

理想的には、適度に枯れていて、把握可能で、OSSコミュニティの中でノウハウを共有できるような、コントロールできる技術を選ぶのが望ましいです。巨大でブラックボックス化していて手出しができないような技術を採用して、それに振り回されることは避けたいものです。

はてなでは「小さいものを組みわせる」というUnix的な考え方を重要視していますが、インフラアーキテクチャでもそれは同様です。小さな分かりやすいシステムを組み合わせることで、いざ障害が起こった時でも、素早く対応が可能になります。

そういう意味で、社内では複雑な技術を用いた分散システムよりは、振る舞いが分かりやすいMaster-Slave構成が取れるミドルウェアが好まれる傾向にあります。わかりやすい技術を組み合わせて独自の分散システムを作っているとも言えるでしょう。ただ、分散データベースなどは、クラウド技術と共に進化している分野なので、トレンドを追いかけておく必要はありそうです。

最近読んだ、O'Reillyのマイクロサービスアーキテクチャという本に以下の言葉が書いてありました。

ミドルウェアを愚かなままにし、(アプリケーションの)エンドポイントに知性を保持する

これはなかなか示唆に富んでいて面白い言葉です。

危機察知能力

手元では動くけど、いざ本番に投入してみると障害を引き起こしてしまうようなコードがあります。そういったコードを書かないためにもインフラ勘を磨く必要があります。これも幾つかのパターンがあります。

一度に何百・何千万レコードを更新・削除してしまってディスクI/Oの限界を超えてしまったり、データの参照量が多すぎてネットワークの帯域を埋め尽くしてしまったり、クライアントコードの書き方がまずくて同時に大量アクセスが走ってサーバーのワーカーが埋まってしまう、といったことが典型例です。

基本的には、大量のデータ更新、転送などが一度に発生しないように設計することが大事です。そのために、そもそもそういうことが起こらないような仕様に変更したり、キャッシュを利用したり、タイミングをずらしたりするなどのテクニックがあります。

まず、そういう観点で「なんかこのコードやばそうだな」とか思えるようになれば一歩成長で、そう感じた時は、チームの他のエンジニアやインフラのメンバーに相談してみてください。

結局、どんなコードを書いても、最終的には物理的なサーバーがあり、その物理的な性能限界を超えることはできないので、意識する必要があります。

障害対応

Webサービスを運用していると、障害はどうしても起こってしまいます。チームに配属されたばかりの段階だと、そういう時に何もできないのが普通だと思います。

障害時は、どうしたって、システムを深く把握している人が素早く対応して障害を収束させてしまい、それ以外の人は置いてけぼりになってしまいがちなところがあります。

じゃあ、そういう時に自分は何もしなくていいか、というとそういうわけではありません。きちんとその場にいて、自分ができることを探すと同時に、他の人が何をやっているのかを理解しようと心がけましょう。障害が収束した後に、何が起こっていたかを理解することも大事です。

障害時に何も出来ないことは、悲しいことです。何も出来なかった無力感や悔しさをバネに、障害対応を学んで行きましょう。もちろん、障害を対応した側も、対応の共有に努め、そんな精神論が不要になるくらい仕組み化することが一番大事です。

はてなにはいませんが「障害対応はインフラの仕事でアプリケーションエンジニアの仕事ではない」というような考え方をしてしまう人もいます。それはやはり、組織としてアプリケーションエンジニアとインフラの間の垣根が高くて関係性が悪かったり、手出しできないから諦めてしまうといったところが背景にあるんじゃないかと思っています。

そういうことにならないように組織づくりをしていく必要もありますが、エンジニア一人ひとりが垣根を作らないようにお互いの領域を学ぶ心がけも大事だと思います。

手塩にかけて開発したシステムが障害で動かない時、なんとかしたいと思う気持ちは、多くのアプリケーションエンジニアが共通で持つものだと僕は信じています。そこで何も出来ないからと諦めてしまって、インフラに丸投げしてしまうようなことが起きて、それが常態化してしまうようなことがないようにしていきたいと思っています。

技術の深め方

最後に技術の深め方の話をします。今日取り上げた中でも、いろいろな技術があり、目移りしてしまうかもしれません。

ただ、今日の話とは矛盾してしまうように感じるかもしれませんが、いろいろなものをつまみ食いするよりかは、その前に一箇所を深めに掘ることをおすすめします。1mを10箇所掘るよりかは、10mを1箇所掘る方がおすすめです。もちろん10m掘るほうが大変ではあります。一箇所深く掘ることにより以下のようなメリットがあります。

  • 横を掘って広げられる
  • 掘る能力が身につき、他のところを浅く掘ってつまみ食いするのも楽になる
  • 他の箇所を深く掘っている人とgive and takeで情報交換できる

個人サービスを持つ

また、個人サービスを持つことも技術を学ぶ上でおすすめです。僕は デイリーポータルViewer というサービスを個人的に運営しています。基本放置なのですが、たまに作り変えて遊んでいたりします。最初期はApache+CGI+テキストファイルでしたが、今はNginx+Plack+SQLiteになっています。こういうサービスを砂場的に利用して、新しい技術を試してみるのもよいと思います。

はてなにはラボサービスがあり、そこは社員はある程度自由にサービスが公開できるようになっています。そこで何かサービスを出してみてもよいかもしれません。

例えば、 はてなカウンティング というラボサービスがありますが、これは実質、社長の id:chris4403 が個人でやっています。今でもたまに社長がコードを書いて、はてなカウンティングに機能追加をしている様子が社内で観測されたりします。

まとめ

以上のようなことを研修では話しました。Developer Blogに書くにあたって膨らませた部分もあります。

実際の講義にあたっては、参加者に質問しながら進めたりもしましたが、どうしてもこちらが話している時間が長かったので、参加者を少し退屈させてしまったかもしれないとか思っています。もう少し参加者に手を動かしてもらうパートを増やしても良かったかな、とか思っています。

また、若干トピック選別が場当たり的な部分もあったかな、と思っているので、いろいろ整理しながら来年の講義にも活かして行く予定です。

PR

はてなでは、インフラを意識しながら10年以上続くサービスを開発・運用したいエンジニアを積極募集しています。

また、はてなのエンジニアによる講義を受けたいと思った学生の方は是非、はてなインターンに応募してみてください。

f:id:daiksy:20160525121351p:plain

どちらもご応募お待ちしております!

*1:ちなみに、今年の ISUCON出題を担当 します。皆様是非ご参加ください