2017年のエンジニアリング振り返り

はてなに入社して4年経った。

2017年のエンジニアリング活動を一言でまとめてみよう。

時系列データベースの開発にはじまり、なぜかIPSJ-ONEで登壇し、その後IPSJ-ONEでの構想をベースにはてなシステム構想を考え始め、ウェブサイエンス研究会でストーリーとしてまとめ上げつつ新たな可能性に気づき、それを実践していく場としてウェブシステムアーキテクチャ(WSA)研究会を立ち上げた。

一方で、仕事では、昨年の振り返りに書いているように、エンジニアとしての専門性を発揮する機会が薄れてきたという問題意識が、いよいよ深刻な課題へと変貌したように感じている。それも残念ながら自分一人だけの問題ではなくなってきた。 この課題をエンジニアリングそのものではなく、人間のスケールアウトでは解決できない、組織アーキテクチャの課題であると捉えている。 組織アーキテクチャの課題を解くための鍵は、今のところ「未来を定義する」「未来に向かって集中して取り組める環境をつくる」ことだろうと仮定している。 前者の未来については前述のシステム構想があり、後者の方法論として今年実践する機会のあったプロジェクトマネジメントがある。

したがって、来年は、今年構想したビジョンを技術と組織の両輪を回し、実現し始める、ということが目標になる。 そしてその裏には、エンジニア個人としては技術を作る技術をやっていきたいと思いつつ、生活時間の大半である業務の課題はマネジメントであるというギャップをどう埋めて一つのストーリーとしていくかが重要になるだろう。

ここまで、いきなりまとめに入ったのだけれど、2017年に力を注いだ各トピックについて細かく振り返ってみる。

  • 時系列データベースの設計・開発・運用
  • 学術研究のアプローチとの出会いとビジョンの構想
  • プロジェクトマネジメント
  • アウトプット

時系列データベースの設計・開発・運用

開発した時系列データベース(TSDB)は、運用にのることに成功し、今のところクリティカルな問題を起こすことなく動いている。実装・運用面では、同僚のid:itchynyさん、id:astj さん、id:kizkoh さんの力によるところが大きい。

このTSDBのオレオレ実装を、半年毎日コード書いて頑張っていたのだけど、リリース前に疲弊しまくって睡眠もうまくとれなくなってしまったので、途中までになってしまった。https://github.com/yuuki/diamondb/ 個人でやるプロジェクトとしては、ひと通り動くまでに時間がかかりすぎ、細かくロードマップを引きづらかったので、サーベイを続けて、新たな課題を発見し、その課題を小さく解決する手法を編み出したい。

TSDBの開発は、大きな成果だと思っており、この知見を横展開するために、より汎用的なアーキテクチャにできないかと考えたのが、TimeFuzeアーキテクチャ構想だ。

時系列データベースに限らず、大規模な計算機システムのモニタリングを支えるデータ処理アーキテクチャが好きなので、今後のライフワークとしていきたい。

学術研究のアプローチとの出会いとビジョンの構想

突然だけど、id:matsumoto_r (まつもとりー)さんの昨年の振り返りをみてみよう。

さらに、id:y_uuki さんとは今年1年非常に仲良くさせていただいて、12月はなぜか毎週会って何かイベントごとをこなすようなぐらい、企業・アカデミア方面で関わることが多かったように思います。いつか、企業とアカデミアの両方の要素を含む新しい研究会みたいなものを一緒に作っていけるといいな、ぐらいに一方的に信頼しており、色々と今年は無茶をお願いしましたが、できるだけその無茶をちゃんと責任をもってサポートできるようにしたいと思います。

エンジニア・研究者とはどうあるべきか - 2016年振り返りと新生ペパボ福岡基盤チームの紹介 - 人間とウェブの未来

今年は、まつもとりーさんのおかげもあって、学術研究コミュニティとの関わりが深くなった年だった。 前述の 高度に発達したシステムの異常は神の怒りと見分けがつかない - IPSJ-ONE2017 - ゆううきブログ に始まり、まつもとりーさんの5年間の挑戦の最後を見届け matsumotoryさんの博士学位論文公聴会に参加し、見て聞いて考えたこと - ゆううきブログペパボ・はてな技術大会では考えたビジョンをみてもらって、自分なりにウェブシステム全体をみてストーリー化し、それに対して議論していただいて、最後は「企業とアカデミアの両方の要素を含む新しい研究会」としてウェブシステムアーキテクチャ研究会を一緒に立ち上げることができた。

その中で気づいたのは、学術研究のアプローチにより、ウェブシステムの分野におけるある種の限界を突破できるかもしれないということ。 ここでの限界というのは、同じことの繰り返しで積み上げによる進化をしていないじゃないか感と、シリコンバレーの巨人の西洋技術を取り入れるだけで自分たちの存在価値とはなんなのだろう感を指している。 それらに対するアプローチは既にあり、前者は体系化、後者は徹底したサーベイと自分なりの思考からの新規性ということになる。 このあたりのよもやまについてはTwitterにあれこれ書いていた

ちなみに、学術研究のアプローチを企業でのエンジニアリングに導入することで何が起きるかについては、まつもとりーさんの下記の2つの記事にすべて書かれている。*1

なにより重要なのは、そもそもなにがやりたいのかという自分の欲求と、それが実現したらどんな世界になるのかをイメージすることだとまつもとりーさんは何度もおっしゃっていた。*2 前者はともかく後者はまあいいんじゃないと思いがちだし、実際何度もそう思った。 特に現場の泥臭い運用をやっていれば、なおさらそう思う。 しかし、前者だけであれば個人の趣味でしかないので、食べていくために仕事をしないといけないとなると費やせる時間は限られる。 そこで、欲求が世界にどう作用するかを考えることで、やりたいことを仕事にできる...はず*3

こういうことをずっと考えていた1年だった。 こうしてちょっとずつ自分なりの道をつくりつつあるのも、まつもとりーさんのおかげだなあとしみじみ思います。いつもありがとうございます。

そういえば、同じように同僚の id:masayoshi にも僕のほうからいろいろ無茶を投げつけたのだけど、彼は僕よりもアカデミックのアプローチに精通していたり、技術力も上なのでもっと無茶振りしていこうと思った。来年こそはなんとかしたいねいろいろと。

プロジェクトマネジメント

時系列データベース開発とクラウド移行のプロジェクトを丸ごと任された結果、物事を前に進めるための条件というものがあることを体感で理解したように思う。 これをウェブオペレーションチームの一部で実践しはじめ、小さな範囲でうまくできそうだということがわかってきたので、来年はチームというか部署全体で適用していきたい。 これらについては、まだアウトプットしていないのでどこかでアウトプットしたい。daiksyさんの薦めで、RGST2018に応募していたのだけど、残念ながら選考で落ちてしまった。

SREの分野のマネジメントって、SRE本以外に世の中に知見があまりなく、逆にチャンスだと思うが、自分が追求することではないと思っているので、誰かこれをはてなで追求したい人がいないかを探している。

アウトプット

OSS

以前開発していたdrootが、[capze](https://github.com/yuuki/capze]とともにオンプレミス上の大きめのサービスで稼働しはじめ、明らかになったバグを修正したりしていた。

アーキテクチャ設計に関わった GoとMySQLを用いたジョブキューシステムを作るときに考えたこと - ゆううきブログ では、id:tarao さんにより Fireworq として実装され、公開された。今、1200 starsとかになっていてすごい。

ブログ

登壇内容をベースにそれをできるかぎりしっかり文章にまとめるということをやり続けている。 なぜかというと、アウトプットのスケーラビリティを非常に強く重視していて、登壇資料はあくまで当日その場のためのものであり、文章として後に残すことが自分のためにも重要だと考えているためだ。 最小のインプットで最大のアウトプットが鉄則。

登壇

過去最多の11本。とはいっても、数とかどこで登壇したかは問題ではなく、何を考え何を話したか、それぞれの登壇に何かしらの挑戦があったかが重要だと思う。

あとがき

エンジニアとしてやりたいことと、仕事で実際にやっていることとの乖離が大きくなってきた。 そもそも、仕事ではだいたいその場しのぎの解決しかできず、あとは家に帰ってがんばるということがこれまでほとんどではなかったかとすら思う。 世の中のすごいエンジニアもみんなそんなもので、もっとたくさんのプライベート時間を費やしているか、技術力が圧倒的だから少ない時間できれいに解決できるに違いない、自分はまだまだなのだという気持ちでいたのだけど、どうやら必ずしもそういうわけではなさそうだと感じはじめた。 人が増えれば解決するはずだと、信じていたが脆くもその願望が打ち砕かれつつある。サーバ増やしてもスケールしないのと同じでアーキテクチャの問題。

乖離を埋めるためには、どうしたらいいのか。 技術力を発揮するというより、組織やプロジェクトをマネジメントすることが必要だと自分で導出してしまったため、矛盾しているような気もする。 マネジメント、あれほどやりたくないと言っていたのだけど、それが課題となればやるしかない。 マネジメントといっても、人と調整したり協調したりすることはあまり得意ではない*4ため、自分の資質にしたがい、ビジョンとか戦略をつくって、それを達成するアーキテクチャを考え、アイディアをだして、最上志向だから不得手な部分は仲間(にどんどん任せていくというようにしてやっていきたい。

自分の技術力向上については、WSA研を目安に研究志向でアイデアをOSSとして実現しつつ、より高みを目指したアウトプットとして論文を書いていきたい。

最後にid:tomomiiさんの縁側トーク 道をつくるを載せておき、来年また思い出せるようにしておく。tomomiiさんに道の概念を話してもらってから、自分の道とはなにかということを頭のなかでずっと考えている気がする。

*1:前者は僭越ながらはてなブログ大賞に選出させていただきました。

*2:イメージすることについては、以前書いた記事で紹介させていただいた、イメージできることを実践するを連想する。

*3:本当はもうすこし深い意味があると思うのだけど、現状はこういう理解でいる

*4:id:dekokun に任せる!

TimeFuzeアーキテクチャ構想 - 処理とデータとタイマーを一体化したデータパイプライン

この記事は第1回ウェブシステムアーキテクチャ(WSA)研究会の予稿です。

cronのようなタイムスケジューラーにより、定期的に実行されるバッチ処理の課題を解決するアーキテクチャを最近考えている。 この記事では、単一のタイムスケジューラによるcronベースの手法に代えて、データに対してタイマーと処理を仕込むことでスケールさせやすい構造にできないか、という提案を試みる。

はじめに

Webサービスにおいて、リクエストに対してHTMLのレスポンスを返却する以外のワークロードの多様化が進んでいる。 最近であれば、機械学習による時間周期による大規模なデータ処理が求められることも多い。 その他、月次の課金バッチ処理や、ランキングの定期更新など、一定の時間間隔で任意の処理を実行したいケースは多い。

このような定期的なデータ処理パターンは、SRE本[Bet17]の25.1節「パイプラインのデザインパターンの起源」にて、データパイプラインと定義されるデザインパターンに分類できる。

データ処理に対する旧来のアプローチは、データを読み取り、希望する何らかの方法でそのデー タを変換し、新しいデータを出力するプログラムを書くというものでした。通常こういったプログラムは、cron のような定期スケジューリングを行うプログラムの制御の下でスケジュール実行されました。このデザインパターンはデータパイプラインと呼ばれます。 「SRE サイトリライアビリティエンジニアリング ――Googleの信頼性を支えるエンジニアリングチーム」

データパイプライン処理を実現する一般的な方式は、特定サーバ上のcronなどのタイムスケジューラーにより、バッチ処理を実行させる方式である。

一般のデータパイプラインの欠点として、25.3節「定期的なパイプラインパターンでの課題」にて、期限内に実行が終わらないジョブ、リソースの枯渇、処理の進まないチャンクとそれに伴う運用負荷が挙げられている。 この記事では、一般のデータパイプラインの課題は以下の4つであると仮定する。

  • a) データ量増大にあわせたスケーリング運用の難しさ
  • b) バッチ処理途中のエラー処理の難しさ
  • c) タイムスケジューラそのものの運用の煩雑さ
  • d) 実行頻度設定の柔軟性の低さ

一般のデータパイプラインの課題を、データと処理とタイマーを一体化するアーキテクチャにより解決できると考えている。 提案するアーキテクチャでは、データに紐付いたタイマーがジョブを起動する。 これにより、一般手法と比較し、スケーラブルかつ細かい粒度で制御しやすいデータパイプライン処理ができる。 起爆装置と爆薬とタイマーを一体化した時限信管(Time Fuze)に似ていることから、このアーキテクチャを「TimeFuzeアーキテクチャ」と名付けてみた。

課題aについては、最初から並行処理を前提としたアーキテクチャにより解決できる。 課題bについては、バッチ処理ではなくレコード単位でジョブ定義することにより解決できる。 課題cについては、データストアのTTLによりスケジューラホストが不要となる。 課題dについては、タイマー設定の粒度をレコード単位で設定することで解決できる。

以前開発した時系列データベースアーキテクチャ[yuu17]では、特性の異なる3種類の分散データストアを組み合わせ、古いデータを遅いディスクへ徐々に逃していくことにより、性能とコストを最適化した。データストア実装として、インメモリDB(Redis Cluster)、オンディスクDB(Amazon DynamoDB)、オブジェクトストレージ(Amazon S3)を採用している。 このアーキテクチャを考える上で重要なのは、どのようにデータを移動させるかということだった。 前述の一般的な手法により、データレコードを走査し、一定以上古いタイムスタンプをもつレコードを読み出し、次のデータストアへ書き込むという素朴な手法をまず考えた。 時系列データベースのアーキテクチャでは、これをデータストアのレコード単位のTTLを利用して解決した。 具体的には、データストアに書き込むときに、レコードに対してTTL(Time To Live)を設定し、TTLが期限切れになったタイミングで、トリガーを起動し、期限切れレコードを別のデータストアへ書き込む。 実装として、DynamoDB StreamsとLambda Triggersの組み合わせ[dyn01]とDynamoDBのTTL[dyn02]を利用し、DynamoDBからS3へデータを移動させた。 これにより、パイプライン処理を、バッチ処理ではなく、レコード単位のイベント駆動型処理に置き換えられた。

のちに、TTLを用いたデータパイプラインアーキテクチャを時系列データベース以外のデータパイプラインに適用できないかを考えた。 例えば、機械学習の学習モデルの定期更新や、マルチテナント環境における大量のSSL/TLS証明書[mat17]の定期更新などがある。 以降では、TimeFuzeアーキテクチャの詳細と実装手段、アプリケーション適用について議論する。

提案手法

TimeFuzeアーキテクチャ

TimeFuzeアーキテクチャの動作フローを以下の図に示す。

DataSourceはデータパイプラインの読み出し側を指し、DataDestinationは書き出し側を指す。 TriggerはDataSourceレコード単位のパイプライン処理を実行する。

まず、DataSourceに対して、Triggerを実行したい時刻を表すTTLと共にデータレコードを書き込む。 次に、TTLの期限切れ(expire)を検知し、Triggerに期限切れしたレコードを渡す。 さらに、Triggerが渡されたレコードをもとに所定の処理を実行し、DataDestinationに対して、結果を書き込む。 Triggerでは、その他のAPIやデータストアからデータを取得することもある。 DataDestinationは、DataSourceと同一のデータストアでもよい。 前述の学習モデルの更新や証明書更新の例では、DataSourceとDataDestinationは同一のデータストアを利用することを想定している。

TimeFuzeのメリット

TimeFuzeアーキテクチャには以下のメリットがある。

  • a) 並行処理を前提としたアーキテクチャであり、スケールさせやすいこと
  • b) エラーからの回復処理が容易であること
  • c) タイムスケジューラとバッチ処理のためのホストもしくはクラスタの構築・運用が不要であること
  • d) レコード単位でタイマーをセットするため、ジョブの実行頻度をレコード単位で調整可能

a) について、バッチ処理の場合、マルチコア・マルチホストスケールさせるまたはI/O多重化するための並行処理実装を開発者に要求する。 TimeFuzeでは、レコード単位でトリガー処理を記述するため、スケールさせやすい。 b) について、TimeFuzeでは、1レコード分の処理を書けばよいため、エラー発生時にリトライする場合、どこまで処理を終えたかを記録するといった回復処理のための実装が必要がない。

TimeFuzeの実装

実装として、Amazon DynamoDBとAmazon Lambdaを利用する例と、Redisを利用する例をあげる。

Amazon DynamoDBおよびAmazon Lambda

Amazon DynamoDBは、フルマネージド型のNoSQLデータベースサービスである。 Amazon Lambdaは、任意のイベントを入力として任意の処理をFunctionとして登録しておくと、イベント発火を契機にFunctionを呼び出せる。 これらを組み合わせ、DynamoDB上のレコードに対する登録、更新、削除のイベントを契機に、Lambda Functionをトリガーとして実行できる。 DynamoDBはTTLをサポートしており、TTL expiredイベントを契機にLambda Functionを実行できる。

DynamoDBをDataSourceとして、トリガー処理にLambdaを利用することで、TimeFuzeアーキテクチャを素直に実装できる。

Redis

Redisは多彩なデータ構造をもつインメモリDBであり、昨今のWebアプリケーションのデータストアの一つとして、広く利用されている。 RedisはKeyspace通知[rednot]機能をもち、キーに対するイベントをPub/Subにより、購読者にメッセージ通知できる。 Keyspace通知を利用し、DynamoDB同様にトリガー処理を実現できる。

ただし、実運用のためのトリガー処理を実現するには、イベント通知以外に、通知するイベントの永続化とイベントを受信し処理するトリガーの実装が必要となる。 Redis自体は、後者の2つをサポートしないため、アプリケーション開発者が汎用的に利用できる実装を提示したい。

そこで、以前筆者が開発したジョブキューシステム[yuu14]Fireworq[gitfir]に着目する。 Fireworqでは、ジョブの永続化をサポートし、所定のインタフェースを満たしていれば任意の言語で開発したWebアプリケーションサーバに対してジョブ実行を依頼できる。 RedisのKeyspace通知とFireworqを組み合わせることにより、メッセージの永続化をサポートしつつ、任意の言語によるトリガー処理を実現できる。 ただし、RedisにKeyspace通知を受信し、Fireworqへ投稿するコンポーネントを新たに実装する必要がある。

TimeFuzeの具体的なアプリケーションへの適用

自分の最近の業務経験から、時系列データの異常検知と大規模SSL/TLS証明書管理アプリケーションへのTimeFuzeアーキテクチャの適用を考えた。

時系列データの異常検知

機械学習をWebサービスに導入すると、最新の投稿データに追従するために、定期的に学習モデルを更新することがある。 TimeFuzeにより、DataSource上のモデルの更新処理をTriggerとして登録し、モデル更新時にTTLをセットして再書き込みすることにより、データパイプラインとして機能する。

時系列データによる異常検知では、収集したメトリック系列もしくはメトリック系列の集合に対して、学習モデルを構築する。 学習モデル構築後に収集されるメトリックに対応するため、学習モデルを定期的に更新する必要がある。

このアプリケーションにTimeFuzeアーキテクチャを適応することを考える。 学習モデルを更新するのみで、学習モデルを他のデータストアに移動させる必要はないため、DataSourceとDataDestinationは同一のデータストアとなる。 各学習モデルのレコードにTTLを設定し、Triggerが外部データストアからメトリックを取得し、学習処理を実行したのちにDataDestinationに対して上書き更新する。

大規模SSL/TLS証明書管理

ブログサービスやレンタルサーバーサービスのようなマルチテナント環境[pep17]にて、大量のSSL/TLS証明書を管理し、数ヶ月ごとに更新するケースがある。Let's Encrypt[letenc]の場合、証明書の期限は3ヶ月となる。

[mat17]では、サーバ証明書と秘密鍵をWebサーバ上のファイルシステムに格納するのではなく、データベースに格納し動的取得するアーキテクチャが提案されている。 データベース上の証明書と秘密鍵を定期的に更新するために、TimeFuzeアーキテクチャを適用することを考える。 各ドメインに対する証明書および秘密鍵のレコードにTTLを設定し、Triggerが証明書を再取得し、DataDestinationへ上書きする。 Let's Encryptの場合、ACMEプロトコルにより証明書を自動発行できる。

考察

TimeFuzeの適用条件

現在のところ、TimeFuzeの適用条件は、y_uukiの経験に頼っており、明らかでない。*1 既存のデータパイプラインアプリケーションの課題をサーベイし、適用条件を整備することは今後の課題である。 現段階では、以下のデメリットにあてはまるケースに加えて、少なくともデータレコード数が一定以上大きくなければメリットを享受しづらいことがわかっている。

TimeFuzeのデメリット

TimeFuzeアーキテクチャはあらゆるデータパイプラインに適用できるわけではない。

アーキテクチャレベルでは、Triggerがレコード単位でジョブを実行するため、DataSource上の複数のレコードをマージする必要がある処理をしづらいというデメリットがある。 従来手法であれば、DataSource上のレコードをグループ化して読み出し、マージ処理することは容易である。 一方、TimeFuzeアーキテクチャでは、マージする必要がないように最初から1つのレコードにまとめて書き込む必要がある。

さらに、実装レベルでは、TTLを利用することのデメリットとして、データの一貫性と、ジョブ実行タイミング制御の困難性がある。 前者では、レコードが削除されてから更新されるまでレコードを参照できない期間があり、アプリケーションに工夫を要求する。 後者は、TTLの期限切れを過ぎてから実際に削除されるまでのディレイが大きいと、開発者が意図したタイミングよりも遅れてジョブが実行されることがありえる。 DynamoDBのTTL実装の場合、ベストエフォートベースでTTL期限切れ以降2日以内に削除することを目指している[dyn03]

TimeFuzeのデメリットの解決

TTLのデータの一貫性の問題は、TTLをタイマーとして利用するのではなく、設定した時刻を経過したイベントのみを発行し、レコードを削除しない機能をデータストアに組み込むことで解決できる。

ジョブ実行タイミング制御の問題は、RedisやCassandraなどTTLをサポートしたデータベース実装を調査し、ディレイ時間を計測する必要がある。

アーキテクチャレベルの課題について、リレーショナルモデルのようなデータレコード間の関係の概念を導入することにより、複数のレコードを前提としたアーキテクチャへ昇華できるかもしれない。 *2

むすび

データパイプライン処理は、Webサービス開発ではよく採用されるデザインパターンである。 単一のタイムスケジューラによる従来手法では、レコード数に対するスケーラビリティ・エラー回復処理・サーバ運用効率・ジョブ実行頻度の細かい粒度での制御に課題があった。 そこで、この記事では、処理とデータとタイマーを一体化したTimeFuzeアーキテクチャを提案し、従来手法の課題の解決を構想した。 さらに、時系列データベースアーキテクチャ、時系列データの異常検知、大規模SSL/TLS証明書管理の各アプリケーションへの適用可能性を示した。

今後の取り組みとして、既存のデータパイプラインの課題をサーベイし整理し、既存データストア実装のTTLディレイ時間の調査、TTLではなくTime to Eventを既存のデータストアへ組み込むことを考えている。

参考文献

発表スライド

発表時のフィードバック

  • TimeFuzeアーキテクチャの適用条件について (matsumotoryさん)
  • 関係性の概念の導入について (monochromeganeさん)
  • モバイルエージェントとの関連について (61503891さん)
  • 適応的なパラメータ決定ポイントについて (matsumotoryさん、syu_creamさん、suma90hさん)

あとがき

提案するアーキテクチャ名がTimeFuze(時限信管)であることにはそれなりの意味があります。 表題を考えていたときに、まつもとりーさんのFastContainerアーキテクチャ構想を眺めていました。 FastContainerってかっこいいしずるいみたいな感じです。 自分もかっこいい名前にするぞ、とあれこれ考えました。 最初はDataCronやData-Driven Cronといった名前を考えていました。 このような名前であれば、この分野の人であればなんとなく想像はつきそうであるものの、多義的な用語になりそうだったため、あまりピンときていませんでした。 このアーキテクチャに対して、なんとなく時限爆弾っぽいイメージとARMORED COREの近接信管のイメージをもっていたため、ググってみると、TimeFuze(時限信管)という用語があることを知りました。 Wikipediaによると信管は、「起爆時期を感知する機能」「所望の時期以外では絶対に起爆させないための安全装置」「安全装置の解除機構」「弾薬の起爆装置」の4つの機能が統合された装置だそうです。 ここで、このアーキテクチャのアイデアって、「データ」「処理」「タイマー」の一体化ではないかということに思いあたりました。 記事中では、しれっとでてくるこの表現ですが、この表現のおかげで、最終的にはデータが意思を持って動いて欲しい(by takumakumeさん)という話まで発展させることができました。 そうすると自分の中でのFastContainerの見方がかわってきました。 FastContainerは現在のところプロセスに着目されていますが、データ処理に着目する発想もあるんじゃないかと考えました。 議論では、さらに関係性の概念やモバイルエージェントの関連など、さらにTimeFuzeを深めるための着想をいただき、ひさびさにおもしろい、楽しいと思いました。

言葉にこだわった結果、抽象化が進み、他の技術や研究と結び付け、新たな着想を得ることができる、というのがここで言いたかったことでした。

あとがきは以上です。wsa研自体の振り返りは別記事として書きます。

*1:発表後の質疑にて、matsumotoryさんに指摘されたところ

*2:発表後の質疑にて、monochromeganeさんに指摘されたところ

ウェブシステムの運用自律化に向けた構想 - 第3回ウェブサイエンス研究会

はてなエンジニア Advent Calendar 2017の2日目です。 昨日は、id:syou6162 さんによるAWS Lambda上で鯖(Mackerel)の曖昧性問題を機械学習で解決しよう - yasuhisa's blogでした。

この記事は、人工知能学会 合同研究会2017 第3回ウェブサイエンス研究会の招待講演の内容を加筆修正したものです。 講演のテーマは、「自然現象としてのウェブ」ということでそれに合わせて、「自然のごとく複雑化したウェブシステムの運用自律化に向けて」というタイトルで講演しました。 一応、他の情報科学の分野の研究者や技術者に向けて書いているつもりですが、その意図がうまく反映されているかはわかりません。

概要

ウェブシステムの運用とは、信頼性を制約条件として、費用を最小にする最適化問題であると考えています。 費用を最小にするには、システム管理者の手を離れ、システムが自律的に動作し続けることが理想です。 クラウドやコンテナ技術の台頭により、ウェブシステム運用技術の自動化が進んでおり、自律化について考える時期になってきたと感じています。 自律化のために、観測と実験による「Experimentable Infrastructure」という構想を練っています。 Experimentable Infrastructureでは、監視を超えた観測器の発達、実験による制御理論の安全な導入を目指しています。

1. ウェブシステムの信頼性を守る仕事

ここでのウェブシステムとは、ウェブサービスを構成する要素と要素のつながりを指しており、技術要素とは「ブラウザ」「インターネットバックボーン」「ウェブサーバ」「データベース」「データセンター内ネットワーク」などのことを指します。 特に、データセンター内のサーバ・ネットワークおよびその上で動作するウェブアプリケーションを指すことがほとんどです。 総体としてのウェブというよりは、単一組織内の技術階層としてのシステムに焦点をあてています。

ウェブシステムの最も基本的な機能として、「信頼性」があります。 信頼性を守る役割は「Site Reliability Engineer(SRE)」が担います。 SREはウェブ技術者界隈で市民権を得ている概念であり、Googleのエンジニアたちによって書かれた書籍「Site Reliability Engineering」[Bet17]に詳細が記されています。

信頼性にはいくらかの定義のしようがあります。 [Bet17]では[Oco12]の定義である「システムが求められる機能を、定められた条件の下で、定められた期間にわたり、障害を起こすことなく実行する確率」を採用しています。 信頼性というとつい100%を目指したくなりますが、信頼性と費用はトレードオフなため、信頼性を最大化するということはあえてしません。

さらに、信頼性をたんに担保することが仕事なのではなく、費用を最小化することが求められます。 この記事では、費用=コンピューティングリソース費用 + 人件費用 + 機会費用 *1としています。 SREは費用をエンジニアリングにより削減できます。例えば、コンピューティングリソース費用はソフトウェアの実行効率化、人件費用と機会費用はソフトウェア自動化などにより削減できます。 以上より、SREの仕事のメンタルモデルは、「目標設定された信頼性*2 *3を解くことであると言い換えられると考えます。 特にSREでは、定型作業(トイルと呼ばれる)を自動化し、スケールさせづらい人間ではなくコンピュータをスケールさせることが重要な仕事となります。

一般の人々からみれば、ウェブシステムは、十分自律動作していると言えます。 内部的に障害が発生したとしても、人々は何もせずとも、障害から回復し普段通りサービスを利用できます。 ただし、その裏では、信頼性を担保するために、数多くの人間の手作業や判断が要求されているのが現実です。 人間がなにもしなければ、およそ1週間程度*4で信頼性は損なわれることもあります。

我々のゴールは、最終的にコンピュータのみでシステムを運用可能な状態にすることです。 IOTS2016の開催趣旨が、このゴールを端的に言い表しています。

インターネット上では多種多様なサービスが提供されている。このサービスを提供し続け、ユーザに届けることを運用と呼ぶ。サービスが複雑・多様化すれば運用コストは肥大する。このコストを運用担当者の人的犠牲により「なんとか」してしまうことが「運用でカバーする」と揶揄される。日本では大規模構造物を建造する際に、破壊されないことを祈願して人身御供 (人柱) を捧げる伝統があるが、現在のインターネットの一部はこのような運用担当者の人柱の上に成立する前時代的で野蛮な構造物と言うことができる。

運用担当者を人柱となることから救う方法の一つが運用自動化である。運用の制御構造における閾値を明らかにすることにより、人は機械にその仕事を委託することができる。機械学習や深層学習により、この閾値を明らかにすることをも機械に委託することが可能となることも期待される。不快な卑しい仕事をやる必要がなくなるのは、人間にとってひじょうな福祉かもしれないが、あるいはそうでないかもしれない。しかし機械に仕事を委託することにより空いた時間を人間が他のことに使うことができるのは事実である。

本シンポジウムは、インターネットやネットワークのサービスの運用の定量的な評価を通じて、積極的に制御構造を計算機に委託することで人間の生産性を向上させ社会全体の収穫加速に結びつけることを目的とする。

IOTS2016 開催の趣旨より引用 http://www.iot.ipsj.or.jp/iots/2016/announcement

2. ウェブシステム運用の現状

国内のウェブシステムの運用技術の変遷

2010年以前は自作サーバ時代[rx709]でした。 秋葉原でパーツを購買し、組み立てたサーバをラックに手作業で配置していました。 自作だと壊れやすいこともあり、冗長化機構やスケールアウト機構など信頼性を高める基礎機能が浸透しました。[ito08] のちにベンダー製サーバやクラウドへ移行が進みます。 このころから、XenやKVMなどのハイパーバイザ型のサーバ仮想化技術の台頭により、徐々にサーバをモノからデータとして扱う流れができはじめます。

クラウド時代

2011年にAWS東京リージョンが開設され[awshis]、有名ウェブ企業が移行しはじめます[coo15]。 クラウドの利用により、必要なときにすぐにハードウェアリソースが手に入り、従来の日または月単位のリードタイムが一気に分単位になりました。 さらに、単にハードウェアリソースが手に入るだけでなく、クラウドのAPIを用いて、サーバやデータベースをプログラムから作成することが可能になりました。 これを利用し、負荷に応じて自動でサーバを増やすなどの動的なインフラストラクチャを構築できるようになってきました。

コンテナ型仮想化技術

2013年のDocker[doc13]の登場により、ソフトウェアの動作環境を丸ごとパッケージ化し、さまざまな環境に配布できるようになりました。 コンテナ型仮想化技術そのものは古くから存在しますが、Dockerはコンテナの新しい使い方を提示しました。 コンテナ環境は、ハイパーバイザ環境と比べてより高速に起動する*5ため、より動的なインフラストラクチャの構築が可能になりました。

この頃より、Immutable Infrastructure[imm13][miz13][mir13][sta13]またはDisposable Infrastructureの概念が登場し、サーバを使い捨てるという発想がでてきました。

サーバレスアーキテクチャ

2015年ごろから、サーバレスアーキテクチャ[mar16] [nek16]という概念が登場しました。 これは本当にサーバがないわけではなく、サーバの存在を意識しなくてよいような状態へもっていくためのアーキテクチャと技術選択を指します。

サーバレスアーキテクチャでは、具体的なサーバやコンテナの存在を意識することなく、抽象化された複数のサービスを組み合わせて、ビジネスロジックを実装するようになります。 ここでのサービスは、フルマネージドサービスと呼ばれることが多く、データベースサービス(Amazon DynamoDB、Google BigQueryなど)、CDNサービス(Akamai、Amazon Cloudfrontなど)、Functionサービス(AWS Lambda、Google Function)などを指します。 理想的なフルマネージドサービスは、裏側にあるサーバの個数や性能ではなく、APIの呼び出し回数や実行時間、データ転送量といったよりアプリケーションに近い単位でスケーリングし、課金されます。 ウェブサービス事業者がこれらの抽象層を実装するというよりは、クラウドベンダーがサービスとして提供しているものを利用することがほとんどです。

Site Reliability Engineering(SRE)の登場

2015年から日本のウェブ業界にSREの概念が浸透し始めました[mer15]。 SREにより、ウェブシステムの「信頼性」を担保するエンジニアリングという、何をする技術やエンジニアなのかがはっきり定義されました。 それまでは、システム管理者、インフラエンジニアや下回りといったざっくりした用語で何をする技術で何をする人なのかが曖昧な状態であることに気付かされました。 トレードオフである信頼性を損なわずに変更の速度の最大化する*6上で、ソフトウェアエンジニアリングを特に重視しているのも特徴的です。

SRE自体は技術そのものではなく、SREによりウェブシステムの運用分野に、体系的な組織開発・組織運用の概念が持ち込まれたことが、大きな変化だと考えています。

変遷まとめ

昔はハードウェアを調達し、手作業でラッキングしていました。 現在では、サーバの仮想化技術の発達とアプリケーション動作環境パッケージングの概念、さらにサーバレスアーキテクチャの考え方とサービスの浸透により、よりダイナミックで抽象的なインフラストラクチャが構築可能になりました。 このように、インフラストラクチャがソフトウェアとして扱いやすくなり、ソフトウェアエンジニアリングを重視した組織文化の浸透と合わさり、この10年でソフトウェアによる運用自動化が大幅に進んできたと言えます。

しかし、これでめでたしめでたしかというとそういうわけではありません。

3. ウェブシステム運用の課題

本当は怖いウェブシステム運用

このテーマについては、今年のIPSJ-ONEにて「高度に発達したシステムの異常は神の怒りと見分けがつかない」[yuu17]で話しました。

ウェブシステムでは、異常が発生しても原因がわからないことがあります。 原因がわからず再現させることも難しい場合、システムの振る舞いがまるで「自然現象」に感じられます。*7 自動化が進んでいるからこそ、不明であることが異常に対する「恐怖」をうみます。 これは、システムに対する変更が安全かどうかを保証することは難しいためです。 その結果、振る舞いの解明に多くの時間をとられたり、わざと自動化を避けて、人間の判断をいれようとします。

実際、異常のパターンは様々です。 講演では、以下の2つの例を話しました。

  • ハードウェアのキャパシティにはまだ余裕があるにもかかわらず、1台あたりの処理能力が頭打ちになり、自動スケールするが自動スケールしない別のコンポーネントが詰まるケース
  • データベース*8のアクティブ・スタンバイ構成*9において、ネットワーク分断により、確率的にどちらかのマスターにデータが書き込まれ、データの一貫性を損失するケース

前者は、自動化していても自動で復旧できず、後者は自動化したがゆえに新たな問題が発生したケースです。 書籍「Infrastructure As Code」[kie17]では、1.3.5節 オートメーション恐怖症にて、"オートメーションツールがどういう結果を生むかについて自信が持てないため、オートメーションツールに任せきりになるのは怖かった。"と書かれています。

ウェブシステムの複雑性

ここまできて、なぜウェブシステムは自然現象のように感じられるか、複雑さの要因はなにかについて、体系的に整理し、分析しようと真剣に考えたことはありませんでした。 そのための最初の試みとして、自分の経験を基に以下の3点を挙げてみます。

  • ソフトウェア依存関係の複雑さ
  • 分散システムとしての複雑さ
  • 入力パターンの複雑さ

ソフトウェア依存関係の複雑さ

ウェブシステムは、多数のソフトウェアの重ね合わせにより構成されます。 言語処理系、OS、ドライバ、共有ライブラリ、ミドルウェア、アプリケーションライブラリ、アプリケーションなどです。 さらに、これらがネットワーク越しに接続され、さまざまなプロトコルにより通信します。

このような状況では、ソフトウェアの依存関係や組み合わせの問題が発生します。 具体的にはバージョンアップ問題やプロトコル互換問題、依存地獄問題[yuu15]などを指します。

例えば、あるソフトウェアをバージョンアップすると、そのソフトウェアに依存したソフトウェアが動作しなくなることがあります。 データベースのバージョンアップにより、プロトコルなどの仕様変更に追従していないアプリケーションが動作しなくなるなどは典型的な例でしょう。 動作したとしても、性能が低下し障害につながるということもあります。

分散システムとしての複雑さ

信頼性のあるウェブシステムは、基本的に分散システムとして構成されています。 複数のノードが相互に通信するということは、単純にノードやリンクが増加すればするほど、システムとしては複雑になります。 さらに、分散システムは、ハードウェア故障、ネットワークの切断・遅延といった物理的制約がある中で、信頼性を担保しなければいけません。 部分的な故障を許容しつつ、自動でリカバリする仕組みは複雑です。 分散システムの難しさについて書かれた文献として、「本当は恐ろしい分散システムの話」[kum17]が非常に詳しいです。

入力パターンとしての複雑さ

ウェブシステムの入力パターン(ワークロード)は一定でもなければ、ランダムでもないことがほとんどです。 ただし、サービスの特性により、朝はアクセスは少ないが、夜に向けてアクセスが徐々に増加するといった一定の傾向はあります。 大まかな予測はできることがあるものの、むずかしい。 人間、検索クローラ、スパマーなどの活動に応じてシステムへの入力パターンは突発的に変化します。これは、制御工学でいうところの外乱に相当します。 実際、この突発的な変化が障害の原因になることはよくあります。

入力パターンの突発的変化(外乱)は、人間や社会の変化によることもあるため、システム側での予測は困難です。

システムの複雑さの度合い

複雑さを分析するのは、要因に加えて度合いの評価も必要です。 度合いについてもせいぜいサーバ台数やサービス数程度でしかみてきませんでした。

そもそも一般的に複雑さをどう定義しているかについて、複雑性科学の書籍[mel11]を参考にしました。 [mel11]には、複雑さについての一般的な定義はなく、これまでいくつかの指標が提案されたことが書かれていました。 提案された指標は、サイズ、エントロピー、アルゴリズム情報量、論理深度、熱力学深度、計算能力、統計的な複雑性、フラクタル次元、階層度がありました。 この中で、比較的ウェブシステムに当てはめやすいのは、サイズと階層度です。

前述の分散システムとしての複雑さに対して、サイズと階層度*10の概念を適応してみます。

サイズについては、例えば、はてなのシステムサイズは以下のようなものです。GoogleやAmazonであれば、おそらくこの100~1000倍の規模でしょう。

  • サービス数: 100+ (内部向け含む)
  • ロール数: 1000+
  • ホスト数: 1000+
  • プロセス/スレッド数: 10000+
  • SRE数*11: 10人弱

階層度を特に入れ子のレベルによって定義した場合、プログラム実行単位については10年前であれば例えば以下のようになります。

  • レベル1: プロセス/スレッド
  • レベル2: サーバ (複数のプロセスの集合体)
  • レベル3: ロール (クラスタやロードバランサ配下のサーバ群)
  • レベル4: サービス: (ロールまたはマイクロサービスの集合体)
  • レベル5: プラットフォーム: (複数のサービスの集合体)
  • レベル6: ウェブ

一方、現在であれば例えば以下のようになります。 ただし、サーバレスアーキテクチャを採用していれば、レベル1~3までは無視できる一方で、マイクロサービスなど層を増やす変化もあります。

  • レベル1: プロセス/スレッド
  • レベル2: コンテナ
  • レベル3: サーバ (複数のプロセスの集合体)
  • レベル4: ロール (クラスタやロードバランサ配下のサーバ群)
  • レベル5: マイクロサービス
  • レベル6: サービス: (ロールまたはマイクロサービスの集合体)
  • レベル7: プラットフォーム: (複数のサービスの集合体)
  • レベル8: ウェブ

実際には、複雑さを評価するためには、複雑さの要因ごとに複数の指標と複数の観点をもつ必要があると考えます。 ウェブシステムがどのような複雑さをもつのか、特に他分野の方に伝えるには、SREの分野では、蓄積が不足していると感じます。

4. ウェブシステムの自律運用へのアプローチ

ここまで、ウェブシステム運用技術の変遷とウェブシステムの複雑さについて書いてきました。 ここでは、これらを踏まえ、課題を解決するためのビジョンである観測と実験による「Experimentable Infrastructure」について述べます。

観測

前述の運用技術の進歩により、インフラストラクチャが抽象化され、プログマブルかつシンプルに扱えるようになってきました。 しかし、要素数を増やす方向へ技術が進んでいるため、依然としてシステム全体の挙動を人が理解することが難しいと感じます。 したがって、系全体の精緻な理解を助ける観測器が必要です。

ここでの観測とは、ウェブシステムの「過去と現在」の状況を人間またはコンピュータが自動的かつ継続的に把握することです。 20年近く前からサーバ・ネットワークを「監視」するためのツール*12が開発されてきました。

従来や現在の監視ツールは、サーバに対する定期的なpingやメトリックの時系列グラフ化をサポートしています。 しかし、これだけでシステムの振る舞いを分析できるかというとそうではありません。 監視ツールを頼りにしつつも、SREはシステムのネットワークグラフ構造を調べ、ログを眺め、アプリケーションコードをgrepし、脳内でシステムに対してどのような変更があったかを思い出すといったことをやっています。

このように、まだまだ監視ツールだけではわからないことがあるのが現状です。 従来の監視はもちろん、ログやイベントデータの収集に加えて、構成要素と要素間の関係の把握などが求められます。*13

自分が開発に関わっているサーバ監視サービスであるMackerel[mac]については、[yuu17-2]に書いています。Mackerelには世の中のウェブシステムの観測結果が集約されているデータベースとしてとらえると解析対象としておもしろいんじゃないかと思います。

制御

システムが自律的に動作し続けるためには、システムの異常を自動で制御する必要があります。

ナイーブな制御

例えば、クラウドの台頭によりサーバの生成と廃棄がプログラマ化されたため、メトリックの変動に応じてサーバの個数や性能を自動調整できます。 さらに、なんらかの理由により不調なプロセスやサーバをすぐ捨てて、新しいものを生成することも可能です。 現在では、このあたりがウェブシステムの現場で浸透中の制御になるかと思います。 しかし、制御のためのパラメータはエンジニアの経験を元に値を設定しており、制御がうまく動くかどうかはエンジニアの技芸に頼っていると言えます。 ナイーブな自律制御のままでは、すべての課題はクリアできません。

待ち行列による制御

よくあるアイデアの一つに、待ち行列理論の利用があります。情報ネットワークの分野では、よく利用されている理論です。 ウェブシステム全体と、サブシステムをそれぞれ入れ子構造の待ち行列としてモデル化できます。 待ち行列解析により、到着分布と処理分布を観測しつづけることで、必要なコンピューティングリソースを割り出し、自動的にリソースを配分し続けられます。 例えば、最も簡単なリトルの法則を応用すると、[myu16]のように中期的なキャパシティプランニングに利用できます。

しかし、待ち行列理論の課題は、仮定する分布の範囲外の予測のできない突発的な外乱に対応しづらいことです。 到着分布は、前述したように入力の複雑さにより、予測することは難しいでしょう。 さらに、到着分布が予測可能であっても、ネットワークのパケット処理とは異なり、ウェブシステムでは、処理の重たい入力とそうでない入力の差が大きい*14ため、処理分布が予測できない可能性もあります。

外乱は事業機会となることがあるため、分布から外れた異常値だからといって無視はできないという事情があります。 そこで、最近はフィードバック制御に着目しています。*15

フィードバック制御

フィードバック制御は、大規模で複雑なシステムを、たとえシステムが外乱に影響を受けようとも、あるいは、限られた資源を有効利用しつつ、その性能を保って動作させるための手法です。 [phi14]より引用

フィードバック制御に着目した理由は、制御対象はブラックボックスであり、中身は不明でよいという点です。 これは、SREがアプリケーションの中身を知らずに観測結果だけをみて障害対応する様子に似ていると感じました。 待ち行列理論ではできないダイナミクスを扱えるのも特徴です[ohs13]。 現実のウェブシステムでは、解析的にモデルを導出するのは難しいため、パラメータの決定には「実験」による計測が必要です。

ウェブシステムに対して、フィードバック制御の導入イメージは例えば、以下のようなものです。 制御入力は、明示的に変更可能なパラメータです。例えばサーバの台数やサーバのキャッシュメモリ量などがこれに相当します。 制御出力は、制御対象パラメータです。例えば、応答時間やエラーの数などになります。 制御出力を監視し続け、目標値から外れたら制御入力を変更し、元に戻すような操作を、システムモデルに基づいて行います。 具体的には、制御入力に対して伝達関数を適用し、制御出力を得ます。 ただし、伝達関数の同定やチューニングは、実システムで応答をみる必要があります。

ただし、ここで述べているのは古典的な制御理論の話であり、単一入出力しか扱えません。 実際には、複数の入力と出力を扱ったり、階層的なシステムに対する制御をやりたくなるでしょう。 そちらは、現代制御理論やポスト現代制御理論と呼ばれる発展的な理論の範疇のようです。

制御理論をウェブシステムの運用に組み込む研究には、[jen16]などがあります。 [jen16]は、データベースクラスタ内のサーバのスケーリングについて、フィードバック制御、強化学習、パーセプトロン学習の3つの手法を比較しています。 さらに、フィードバック制御(PID制御)をApache Sparkのバックプレッシャー機構に組み込む実装もあります[spapid]

実験

待ち行列にせよ、フィードバック制御にせよ、機械学習的なアプローチにせよ、実システムの応答結果を継続的に得ることが必要です。 おそらく、統一的なモデルなどはなく、システムやサブシステムごとに異なるモデルとなると考えています。

しかし、単純に平常時のシステムを観測し続けるだけでは、良好な制御モデルが得られない可能性があるのではないかと考えています。 というのは、システムごとのモデルとなると、過去に限界値や異常値に達したデータの数が少ないため、学習データが足りない可能性があります。 *16 特に新システムであればデータはゼロなので、本番環境の蓄積データだけでは、制御パラメータを決定できません。

ワークロードのない状態では、例えば1台あたりのサーバの限界性能というのは実際に限界まで負荷をかけないとわからないことが多いでしょう。 素朴に考えると、手動で実験してデータをとることになってしまいます。

そこで、実験の自動化を考えます。 システムには日々変更が加えられるため、継続的な実験が必要であり、手作業による実験は人手が必要で結局長続きしないためです。

分散システムの自動実験の概念としてNetflixが提唱するChaos Engineering[cha17]があります。 Chaos Engineeringは、本番環境にて故意に異常を起こす逆転の発想です。 例えば、サーバダウンなどわざと異常を起こすことで、システムが異常に耐えられるのかをテストし続けます。 Chaos Engineering自体は、先に述べたように制御モデルのパラメータ推定についての言及はありませんが、ビジョンの構想に大きく影響を受けました。*17

しかし、一般に言われていることは、本番環境で異常を起こしたり、限界まで負荷をかけるのは不安であるということです。 実際、書籍「Chaos Engineering」[cas17]では、監視の環境整備、異常時の挙動の仮説構築、実験環境での手動テスト、ロールバックなどの基盤を整えた上で十分自信をもった状態で望むようにと書かれています。 したがって、実験そのものは自動化されていても、まだまだそれに至るまでに人間による判断を多く必要とするようです。

ここで、クラウドやコンテナ技術など限りなく本番に近い環境をオンデマンドに構築する技術が発達してきていることを思い出します。 自分の考えでは、実験環境を高速に作成することで安全に実験を自動化する技術を突き詰め、効率的にデータを取得し、パラメータを決定するというアプローチを考えています。*18

これら以外に手動で実験するケースについても、実験という概念に内包し扱おうしています。 動作テスト、負荷テスト、パラメータチューニング(OSやミドルウェアのパラメータ)などです。*19

Experimentable Infrastructure

以上のような観測と実験の自動化アプローチには、近代科学の手法の自動化であるというメタファーが隠されています。

書籍「科学哲学への招待」[noe15]に近代科学の歴史とともに、仮説演繹法について紹介されています。

  • (1) 観察に基づいた問題の発見 (観測)
  • (2) 問題を解決する仮説の提起
  • (3) 仮説からのテスト命題の演繹
  • (4) テスト命題の実験的検証または反証 (実験)
  • (5) テストの結果に基づく仮説の受容、修正または放棄

仮説演繹法のループを高速に回し、自律的に変化に適応し続けるシステムを「Experimentable Infrastructure」と呼んでいます。 コンピュータに仮説の提起のような発見的な手法を実行させるのはおそらく難しいと思います。 しかし、ウェブシステムの観測と実験により判明することは、世紀の大発見ということはなく、世の中的には既知のなにかであることがほとんどなので、仮説提起をある程度パターン化できるのではないかと考えています。

5. 自律運用の壁とウェブサイエンス

仮に前述のアプローチがうまくいったとしても、さらにその先には壁があります。 自律運用の壁は、「ハードウェアリソース制約」と「予想できない外乱の大きな変化」です。

前者は、予め用意したリソースプールでさばける以上の負荷には耐えられないことです。 クラウドにも上限は存在します。

後者は、サーバの増加などには必ずディレイが存在するため、外乱の大きさによっては、フィードバックが間に合わないケースもありえます。 これについては、以下のようにウェブサイエンスの研究成果である状態予測をフィードフォワード制御に利用するといったアイデアもあるかもしれません。

自発的に発展するサービスの特徴を捉え、例えば、Web サービスが今後発展していくのか、元気をなくしていくのか、そうした状態予測を目指し、自律的な人工システムのダイナミクスを捉える普遍的な方法論をつくり、自然科学としての人工システム現象という分野の確立を目指している。

106 人 工 知 能 31 巻 1 号(2016 年 1 月)「ウェブサイエンス研究会(SIG-WebSci)」 発足

自律運用

ここまでの「自律」の定義は「自動修復」「自律運用」でした。 自律運用では、与えられた制約条件=信頼性 を満たすように自律動作することを目指しています。 信頼性を自律的に満たせれば、費用のうち人件費はある程度削減できます。 しかし、信頼性の条件設定、アーキテクチャの決定、ソフトウェアの効率化などは依然として人の仕事です。

自律開発

自律運用に対して、自律開発*20という考え方があります。 これは、この記事の文脈では例えば、自律的に費用を最小化するようなシステムを指します。

自律開発には、進化・適応の概念が必要であり、分散システムアーキテクチャの設計・改善やソフトウェア効率改善の自律化などを含みます。 おそらくソフトウェア進化の研究[oom12]などで進んでいる分野だと思います。

運用から解放されたその先

ここまではウェブシステムの運用という工学的モチベーションの話でした。

IPSJ-ONEの記事[yuu17]にて、以下のような宿題がありました。

しかし、最終的に自分のやっていることが世界にとってどういう意味があるかということを加えるとよいという話もあったのですが、ここは本番では組み込めなかった部分です。自動化されて、運用から解放されて、遊んで暮らせるようになるだけなのか?という漠然とした疑念はありました。科学の歴史をみてみると、例えば未知であった電気現象を逆に利用して、今ではコンピュータのような複雑なものを動かすことができるようになっています。そこからさらにメタなレイヤで、同じようなことが起きないかといったことを考えているのですが、これはこれからの宿題ということにします。

いまだにこの宿題に対する自分の中の答えはありませんが、ウェブサイエンス研究会発足の文章を拝読して、ぼんやりと基礎科学の貢献という道もあるのだなと思いあたりました。 人の手を介さずに動き続けるウェブシステムを探究することで、複雑な系に対する統一的な法則を発見し、基礎科学へ貢献できないかどうかといったことを考えながら、老後を過ごすのも、昔からシステムが好きな自分にとってよいのかもしれません。

ネットワークの技術階層を含む Webの存在そのものを新しい「自然現象」として捉え、例えば、その「生態系」としての構造を明らかにすることで、普遍的なダイナミクスやパターンを明らかにし、従来の自然科学・人文科学の考えを発展させることを目指している。

106 人 工 知 能 31 巻 1 号(2016 年 1 月)「ウェブサイエンス研究会(SIG-WebSci)」 発足

6. 議論

講演後にいただいた質問や議論から、SREの分野は、まだまだサイエンスというより、エンジニアの技芸の上に成り立っているなと感じました。

例えば、以下のようなデータ解析の内容をSREの分野で書かれた文献を今のところ僕は知りません。

  • ウェブシステムの複雑さの定義と解析
  • ウェブシステムの階層構造の変化の解析
  • ウェブシステムの階層ごとの到着分布、処理分布の解析
  • ウェブシステムの自律度合いの定義と解析
  • 異常のパターン分析と体系化

これは、以下の2点が要因としてあると考えています。

  • SREとデータ解析の関心やスキルセットのミスマッチ
  • データ収集が困難
    • 数年前までは、自社システムを自社内で観測してデータを溜め込んでいるだけであったため、多数のウェブシステムの情報をまとめて解析することができなかった

後者については、Mackerelを持っていることは強みなので、うまく活用していきたいと思います。 このようなデータ解析の観点でみると、観測には前述した項目よりもっと先があることを想起させられます。

7. まとめ

ウェブシステムの運用は、ここ10年で自動化が進んでおり、物理的な世界からソフトウェアの世界になってきました。 ウェブシステムは複雑ではあるが、現代のところコンピュータだけで自律した系ではありません。 ウェブシステムという人工物を自然のように振る舞わせ、人間を運用から解放したいというのが最終的な目標です。

参考文献

発表スライド

あとがき

そもそも、ウェブサイエンス研究会に招待していただいたきっかけは、IPSJ-ONE 2017 高度に発達したシステムの異常は神の怒りと見分けがつかない - IPSJ-ONE2017 - ゆううきブログ の登壇にてご一緒した鳴海先生に声をかけていただいたことです。 さすがに、場違いではとも思いました。というのも、僕が実際やっていることは、時系列データベースの開発であったり、10年前から続くウェブシステムの運用効率化などであり、技芸であって科学ではない*21からです。 しかし、はてなシステムを構想するにあたって、地に足がついてなくてもいいから、無理やり未来を考えるいい機会になると捉え、登壇を引き受けさせていただきました。 今回の研究会のテーマは、「自然現象としてのウェブ」ということで、本当に何を話したらよいかわからないテーマで相当苦戦しましたが、その結果、IPSJ-ONE登壇で考えたことの言語化を進められました。*22 途中、妄想のような話もあり、他の分野の専門家からみれば眉をひそめるような表現もあるかもしれませんが、一度考えたことを言語化しておくことでまた次のステップに進めると考えています。

普段はどうしても目の前の課題に熱中しがちで、未来のことを考えようとはなかなか思いません。 概念や思想だけではなかなかそれを取り入れようとは考えず、それを実現するソフトウェアなりハードウェアが目の前にあらわれ使える状態になってはじめて目を向けることになります。 例えば、AWSもDockerもないと仮定して、Immutable Infrastructureの考え方に触れたとしても、到達までの道筋がすぐにはみえないため、諦めて考えないようにしてしまいそうです。

発表後に、研究会の幹事である橋本先生に、技術者はどこまで先を考えているものなのか、と質問をいただきました。 少なくとも、日本のウェブの技術者界隈で、未来の技術ビジョンを設定し、それに進もうとしている様子が外からみえることはなかなかありません。 ペパボ研究所が掲げるなめらかなシステム *23が僕の知る唯一の例です。 Real Worldでは、一歩前に進むだけでも本当に様々な問題が起き、とにかく目の前のことを倒すことが求められるので、とても未来どころではなくなるというのが現状かもしれません。

未来を考えるのは、研究者の場合は当たり前に求められるという印象があります。 しかし、SREの分野では、研究者のコミュニティが他と比べて未発達なようにも思います。 近い分野である情報ネットワークに関しては、日本でも様々な研究会がありますが、僕の知る限りでは、日本では直接的にSREの分野を扱う研究会は存在しないようです。*24

そこで、ウェブシステムアーキテクチャ研究会、(#wsa研)というものを立ち上げようとしています。 第1回は京都開催にもかかわらず、全員発表型で10人以上の参加者が既に集まっています。 今回の講演の準備をするにあたって、我々の分野で未来を議論するための既存の枠組みや土台があまりないことを改めて実感しました。*25 WSA研では、未来を考えるために、現状を体系化し、そこから新規性や有用性を追求していこうと思います。

*1:発表時には機会費用を含めていませんでしたが、機会費用を大幅に増加させて前者2つを削減できるため、この記事では含めました

*2:厳密にはService Level Objective(SLO)))を制約条件として、費用を最小にする最適化問題」((信頼性と費用はトレードオフであり、信頼性を定期的に見直す必要があります。高すぎる信頼性のために想定より費用が最小化できないとなれば、例えば四半期ごとに信頼性目標を下げるといった運用が必要です

*3:[Bet17]では、「サービスのSLOを下回ることなく、変更の速度の最大化を追求する」という表現になっています。今回は、自分自身のメンタルモデルに近い、費用の最小化という表現を選択しました。

*4:これはサービスによって大きく異なります。 これまで運用してきたサービスの肌感覚では、1週間程度。運が良くて1ヶ月程度。

*5:実質OSのプロセスの起動

*6:前述の信頼性を維持し費用を最小化するという話

*7:#wakateinfraの中でも、超常現象などと呼んでいました

*8:ここではMySQLとPostgreSQLを想定

*9:ここでは、VRRPによるクラスタリングを想定

*10:書籍「システムの科学」[her99]の第8章 階層的システム

*11:実際の職種名はWebオペレーションエンジニア

*12:NagiosやZabbixなど

*13:6. 議論 にてさらなる観測の可能性について記述

*14:いわゆる地雷URLなど

*15:[yuu17-3]でも紹介した

*16:異常検知であれば平常時だけを知っておけば問題なさそうだが、その他のアプローチの場合はどうか

*17:書籍[cas17]では、奇しくもフィードバック制御の伝達関数の話が例としてでてくるが、予測モデルを構築することは困難なので、実験しましょうということが書かれているのみでした。

*18:本番環境の負荷の再現が壁になるだろうとは思います。

*19:パラメータチューニングの自動化については、おもしろい例[mir13-2]があります。

*20:おそらく一般的に使われている言葉がソフトウェア工学の世界などにあるかもしれません

*21:John Allspaw、Jesse Robbins編、角 征典訳,ウェブオペレーションーーサイト運用管理の実践テクニック,オライリージャパン より引用

*22:発表自体は、タイムコントロールに久々に大失敗して、途中スライドスキップして残念な感じになってしまいましたが、この記事で補完できればと思います。

*23:研究所なので技術者界隈とは呼ばないかもしれません

*24:海外ではUSENIXのLISAやSREconなどがあります

*25:SRE本は本当に稀有な存在

サーバ「管理」ツールとしてのMackerelの起源

この記事は、SaaSのサーバ監視サービスMackerelを起源を遡り、そこから現在の姿に至った経緯をはてな社内のエンジニアに共有するためのものです。 なお、ここに書かれていることは、Mackerel開発チームの公式見解ではありません。

概要

Mackerelは、もともとは2007年ごろに開発されたはてなの社内のサーバ管理ツールであり、動的なインフラストラクチャに対応するために、現在でいうところのInfrastructure As Codeを目指したものです。 そこから2013年にSaaSのサービスとして開発され、コードベースとアーキテクチャは全く新しくなり、監視機能を備え、サーバ「監視」サービスと呼ばれるようになりました。 しかし、はてな社内では、プログラマブルなAPIを備えたサーバ「管理」サービスとして、Mackerelを中心にしたインフラストラクチャを構築しています。 Mackerelの起源は、サーバ「監視」というよりはむしろサーバ「管理」にあったということをここに書き残します。

社内Mackerelの誕生

社内Mackerelは、はてな前CTOのstanakaさんを中心に2007年ごろに開発が始まりました。 社内Mackerelのコードベースは、mackerel.ioのそれとは全く別のものであり、社内Mackerelにしかない機能もあれば、Mackerelにしかない機能もあります。 しかし、共通する思想はあり、その思想は現代のインフラストラクチャ管理にも通ずるものであると考えています。

はてなでは、2007年前後にXen Hypervisorによるサーバ仮想化技術が導入[1]され、物体としてのサーバに加えて、データとしてのサーバを管理し始め、人手による管理コストが増大しました。 具体的には、サーバの増減に合わせて、人がIPアドレス管理表を更新したり、ホスト名やIPアドレスが記載された各種設定ファイルを更新して回るとといった手間が増えたことを指します。

これらについて、全体のサーバ数が少ないかつ、個数の増減頻度が小さければ、人手による管理にもコストはかかりません。 一方で、クラウド環境のように、データとしてサーバを扱うのであれば、プログラマブルに管理するためのデータベースが必要です。 そこで、社内Mackerelが誕生しました。

このように、社内Mackerelはサーバ監視というより、プログラマブルなインフラストラクチャ管理を目指したツールでした。 実際、社内Mackerelのことをサーバ「監視」ツールではなく、サーバ「管理」ツールと呼んでいました。

社内Mackerelの特徴

構成レジストリ

社内Mackerelの主な管理単位は、「ホスト」であり、ホスト名やIPアドレスはもちろんOSの種別やCPU数、メモリ量などのソフトウェア、ハードウェア情報も含みます。 加えて、ホストは以下の属性情報を持ちます。

  • 「サービス」「ロール」
  • ホストステータス ('working', 'standby', 'maintenance', 'poweroff', 'destroyed')
  • 拠点、ラック、電源、ネットワークなど

これらはビューとして人が参照するだけでなく、REST APIにより各種ツールと連携し、インフラストラクチャ要素の情報を一元化しています。 具体的には、以下のように各種ツールのパラメータを、MackerelのAPI経由で動的取得し、ホスト名やIPアドレスの多重管理を防いでいます。

  • 1: 死活監視ツールの設定ファイルの自動生成[2]
  • 2: 内部DNSのゾーンファイルの自動生成
  • 3: デプロイツールからのデプロイ対象ホストの動的取得
  • 4: 構成管理ツール(Chef)の適用cookbookを対象ホスト名から動的解決

1について、サーバ監視のうち、死活監視についてはNagiosを利用します。同一ロールであれば、同じ監視設定を流用できることと、ホストステータスがworking以外に変更すれば監視を外すといった動的なプラットフォームに適した運用が可能になります。1 2について、ホスト単位のレコード以外に、ロール名ベースのDNSラウンドロビン用FQDNとVIP用FQDNがあり、サービスディスカバリに利用します。 3について、Capistranoでホスト名を静的に設定したところをAPIによりロール名だけ設定しておけば、対象にデプロイできます。ホストステータスをmaintenanceにしておけば、デプロイ対象から外れます。 4について、ホスト名を与えれば、ホストに紐付いたロールから適用するcookbookを自動解決できるようになります。

このようなツールは、CMDB[3]に似ていますが、資産管理機能は含みません。 どちらかといえば、書籍「Infrastructure As Code」[4] 3.4節 構成レジストリに近いものです。 構成レジストリは、インフラストラクチャ要素についての情報集積庫であり、書籍では、構成レジストリと例として、Zookeeper/Consul/etcdや、Chef Server/PuppetDB/Ansible Towerなどが挙げられています。

メトリックの時系列グラフ

社内Mackerelには、前述の構成レジストリ機能に加えて、メトリックのクローリングと、メトリックを時系列グラフ表示する機能があります。 メトリッククローリングはPrometheusなどと同様にPull型のアーキテクチャであり、クローラーがOSの基本メトリックはSNMP、MySQLなどのミドルウェアについてはミドルウェアのプロトコルでメトリックを取得し、RRDtoolに保存します。

社内Mackerelの時系列グラフ機能の実装については、過去の発表資料[5]で詳しく説明しています。

今から振り返ると、メトリックの収集、保存、可視化については、Repiarableにするために、専用のツールに任せ、ビューだけ統合したほうが良かったと考えています。 社内Mackerelの開発開始が2007年より、もう少し後であれば、Collectd[6]で収集し、Graphite[7]に保存するといった選択肢もありました。

メトリックに限らず、当時Nagiosには使いやすいAPIがなく強引な手段で統合している実装をみると、Infrastructure As Codeを志向しようには世の中が追いついていなかった様が伺えます。

SaaSとしてのMackerelへ

2013年にMackerelの開発が始まり、2014年に正式リリースされました。 SaaSとして提供するにあたって、グラフツールではないため、メトリックに着目するより、「ホスト」に着目するといった主要な思想は、社内Mackerelから引き継がれました。他にも以下の要素が引き継がれました。

  • サービス・ロールの概念
  • ホストステータスと監視のon/offとの連動
  • 統合されたホスト管理とメトリックグラフビュー

特にサービス・ロールはMackerelの中心概念であり、社内Mackerelとほとんど同じものです。 私見ですが、サービス・ロールは、人間が決定しなければいけないラベル付けだと考えています。 インフラストラクチャに紐づくラベルは多種多様ですが、大きく分類すると、自動で付与できるものと、それ以外の人が判断して付与するものがあります。 自動で付与できるものは、OS、ハードウェア、ネットワーク、インストールされたミドルウェア、クラスタ内の役割などの情報です。 一方で、どのサービスのホストなのか、どのロールなのかといったことは、人間が最初に決めて付与しなければなりません。自動で決定できるとしても、人間が与えたルールにしたがい決定することが多いと思います。2 このラベルを用いて、リポジトリのディレクトリ構成を管理していることもあります。[8]

以上のように引き継がれた機能がある一方で、ラックや仮想ホストの管理など、クラウド時代にそぐわない機能は削ぎ落とされました。 逆に、死活監視やメトリック監視、外形監視機能は統合されました。

メトリックの収集と時系列グラフについては、SaaSとして提供するために、NAT超えを意識し、Pull型ではなく、Push型のアーキテクチャになっています。 ホストにインストールされたmackerel-agentのリクエストを[9][10]に書いた時系列データベースに格納します。

このように、Mackerelは監視機能をひと通り備えているため、サーバ「監視」サービスと銘打っていますが、その起源は、サーバ「管理」サービスです。 実際、サーバ「管理」ツールとしてのMackerelをうまく活用していただいている例[11]があります。 このブログでも、AnsibleのDynamic Inventoryとの連携[12]やServerspecとの連携[13]など、サーバ「管理」ツールとしてのMackerelをいくつか紹介しています。

その他のサーバ「管理」ツール

社内Mackerelと同じサーバ管理ツールは、他にもあり、ここでは、Collins、Yabitzなどを紹介します。

Collins

Collins[14]はTumblrで開発されているインフラストラクチャ管理ツールです。

Collinsの特徴は、Assetsベースのデータモデルです。Assetsはなんでもよく、事前設定されているものはServer Node、Rack、Switch、Router、Data Centerなどです。 さらに、このAssetsオブジェクトに対して、キーバリュー形式のTagsを付与できます。Tagsはハードウェア情報などユーザが管理できないものはManaged,外部自動化プロセスから動的に設定されるものはAutomated、ユーザによって設定されるものはUnmanagedというように3種類のタイプを持ちます。

このように、Collinsは抽象的なデータ表現により各種インフラストラクチャ要素を管理しており、具体的なデータ表現をもつ社内Mackerelとは対照的です。

Yabitz

Yabitz[15]は、旧ライブドアで開発されたホスト管理アプリケーションです。READMEでは、「ユーザ(多くの場合は企業)が保有するホスト、IPアドレス、データセンタラック、サーバハードウェア、OSなどの情報を管理するためのWebアプリケーション」と説明されています。 機能一覧を見るかぎり、社内Mackerelによく似ています。

いずれのソフトウェアも、死活監視機能やメトリック可視化機能は備えておらず、構成レジストリとしての役割に専念しているという印象です。 他にも、Zabbixのホストインベントリなどがサーバ管理機能にあたると思いますが、Zabbixはあまりに機能が多いので、よく知らずに言及することを控えました。

これからのサーバ「管理」ツール

昨今のインフラストラクチャの進化は激しく、従来のサーバ・ネットワーク機器といった管理単位よりも、さらに動的な要素やそもそもサーバとしての体裁をなしていないインフラストラクチャを管理していく必要があります。

コンテナやマネージドサービスの台頭により、管理の単位はもはやサーバだけではなく、プロセス、クラスタ、Functionなどに置き換わりつつあります。 実際、[9]のアーキテクチャの管理では、Lambda FunctionやDynamoDB Tableなどがホストとして登録されています。

サーバ構築と運用コストが激減し、インフラストラクチャ要素をデータのように扱えるようになってきた結果、マイクロサービスやサーバレスアーキテクチャのような要素同士の関係がより複雑になりやすいアーキテクチャが広まってきました。このようなアーキテクチャのもとでは、1つ1つの要素を管理するというより、関係そのものを管理する必要がでてくると考えます。 ネットワークグラフをTCPコネクションを追跡して可視化する実験[16]に既に取り組んでいました。

さらに、関係性の管理というとまず可視化が思い浮かびます。しかし、人がみて判断するその先として、関係性を表現したグラフ構造をAPIで操作することにより、システムの自動化につなげられないかを考えています。例えば、自動化が難しいレイヤとしてキャパシティプランニングやコストプランニングがあります。書籍「SRE サイトリライアビリティエンジニアリング」[17]の18章「SRE におけるソフトウェアエンジニアリング」にて、サービスの依存関係とパフォーマンスメトリックなどを入力として、キャパシティプランニング計画を自動生成するソフトウェアが紹介されています。 サービスの依存関係を管理するのが普通は大変で、書籍によると執筆時点では人間が設定を書いているように読めます。しかし、関係性のグラフ構造をプログラマブルに操作できるのであれば、プランニングを自動化しやすくなると思います。

参考文献


あとがき

先日、はてなWebオペレーションチームのテックリード - Hatena Developer Blog にて、Mackerelチームとの研究会をやるという話を書きました。 モニタリング研究会では、モニタリングの過去・現在・未来をテーマに、まず過去を知ることから始めており、その一環としてMackerelの源流をまとめました。

Mackerelのサーバ監視以外の「管理」の側面と「管理」と「監視」を統合し何ができるのかという点については、まだ十分に伝えられていないと思っています。 Mackerel本について、mattnさんに書いていただいた書評(Big Sky :: 「Mackerel サーバ監視[実践]入門」を読んだ。)に

これは本書で知ったのですが、どうやら はてな社は Mackerel をホスト管理としても使っている様で、数千いるサーバのロールをうまくラベリングして運用されているとの事でした。

とあり、このような運用手法を伝えていきたいですね。

このように、人の脳やExcelでは管理しきれない未知を明らかにするという観点で、「管理」や「監視」といった概念が組み合わさって、「観測」となり、その先にどのようなインフラストラクチャをつくっていくかをこれからやっていきます。


  1. mackerel2という社内Mackerelの改良版では、タグという概念で、複数のロールをさらに集約し、同じ設定で監視できるようになっています。これは、ロールはMySQL

  2. 例えば、サービスごとにネットワークレンジが決まっているなど。

コスト効率の悪いLambdaアプリケーションの性質に関する考察

概要

Lambdaは100msの実行時間単位でオンデマンドに課金されるため、立ち上げっぱなしのEC2インスタンスよりも、料金が安くなる可能性があることが一般に知られている。 しかし、以下の性質を満たすアプリケーションでは、EC2インスタンス上に構築したケースと比較して、Lambda上に構築したほうがコスト効率が悪くなるのではないかと考察してみた。

  • Lambda functionの実行時間のうち、ネットワークI/O時間が支配的である
  • Lambda functionの実行終了を同期的に待たなければならない
  • 複数のレコードをLambda functionの引数に渡すことができない

Lambdaの基本コスト構造

まず、Lambdaのコスト構造を把握する。 Lambdaの料金表[1]によると、「functionに対する合計リクエスト数」と「functionの合計実行時間」に応じて料金が発生する。

後者の合計実行時間は、静的に割り当てたメモリ量により100ミリ秒単位の価格が異なる。 したがって、実際にコストを試算するときは、前述の2つの料金項目に、「静的割り当てメモリ量」を加えた3つの料金項目があると考えるとよい。 LambdaのPricing Calculator[2]もそうなっている。

LambdaとEC2の時間あたりのランニングコスト比較

LambdaとEC2の時間あたりのランニングコストを比較する。 functionの平均実行時間を料金単位と同等の100msと仮定して、1時間分つまり18000リクエストしたあたりのコストを、代表的なメモリ量別に算出すると以下のようになる。

  • 128MB: $0.015/hour
  • 832MB: $0.056/hour
  • 1536MB: $0.097/hour

このうち、「functionに対する合計リクエスト数」料金については、$0.0072であり支配的ではない。

EC2 ap-northeast-1の料金表[3]から、Lambdaと同じ論理2コア[4]の代表的なインスタンスの1時間あたりのコストを以下のようになる。

  • t2.medium: $0.0608/hour
  • t2.large: $0.1216/hour
  • m4.large: $0.129/hour
  • c4.large: $0.126/hour

LambdaのFAQ[5]にあるように、メモリ割り当て量に対してCPU性能が比例することから、EC2インスタンスとの直接的な比較は難しいが、だいたい同程度かEC2が少々割高であることがわかる。

Lambdaに不向きなアプリケーション性質

アプリケーション例

前述のように、LambdaとEC2のランニングコストにそれほど差はないことを考えると、EC2よりも細かい粒度で消費コンピューティングリソースを制御できるLambdaのほうが有利なケースは、数多くあり得る。

一方で、例えば、次のようなアプリケーションをLambda functionとして実装することを考えてみる。 画像URLを渡すと、画像をフェッチし、特定の画像変換を実行するような画像変換プロキシを考える。 このプロキシに対して、多数のユーザがブラウザからリクエストするようなユースケースを想定する。 レスポンスは同期的に画像を返す必要があるため、以下の図のようにAPI GatewayをLambdaの前段に挟むことになる。 その場合、実際はさらに前段にCDNを挟むことになるだろう。

|-----------------------------------------------------------|
|                                      internet             |
| --> (CDN) --> API Gateway --> Lambda --------> image file |
|                                                           |
|-----------------------------------------------------------|

このようなアプリケーションは、以下の2点でコスト的に不利になる。 まず、Lambda function内でインターネット経由で画像をフェッチするため、function実行時間の料金は嵩みがちになる。 画像フェッチ処理と画像変換処理を並行実行し、実行時間を削減することも難しい。 次に、I/O多重化により、1回のfunction実行において、同時画像フェッチ数を増やし、同じ実行時間料金での処理効率を高めることも難しい。 なぜなら、ユースケース上、同じHTTPリクエストに複数の画像URLを含められず、functionの引数に複数の画像URLを渡せないためだ。

一方、EC2インスタンス上で同じようなアプリケーションを実装すると以下の図のようになる。

|----------------------------------------------------------|
|                                    internet              |
| --> (CDN) --> ALB(or NLB) --> EC2 ----------> image file |
|                                                          |
|----------------------------------------------------------|

EC2実装では、インスタンス上でWebサーバが動作する。Lambda実装とは異なり、複数のリクエストを1つのインスタンスで並行して受け付け、I/O多重化できる。 もちろん、1スレッドで動作するシリアルモデルなWebサーバであれば、同時に1つのリクエストしか受け付けられないが、古典的なWebサーバであっても並行モデルを採用していることがほとんどである。5 したがって、EC2実装(Webサーバ実装)では、同じランニングコストに対して、複数の画像をフェッチできることになり、Lambda実装と比較してコスト効率がよい。

アプリケーション例のコスト試算例

実際、コストがどの程度になるのかという感覚を掴むために、仮のパラメータで試算をしてみる。

平均実行時間 1000ms、平均リクエスト数 1000req/sとなるケースを考える。 一番小さいメモリ割り当て量である128MBのとき、月額は約$6000となる。 実行時間を小さくするために、メモリ割り当て量をN倍に増やしCPU性能を大きく設定すると、画像変換処理は速くなるが、ネットワークI/O部分の時間はかわらないため、実行時間は1/Nにはならない。 平均リクエスト数が10倍であれば、コストは10倍になり、平均リクエスト数が1/10であればコストも1/10になる。

この計算例を軸に、見覚えのあるアプリケーションの負荷パラメータをあてはめて、比例計算すると、だいたいのコスト感覚をつかめると思う。

考察

以上の例から一般化し、コスト観点でLambdaに不向きなアプリケーション性質は以下のようになるという考察をしてみた。

  • 1: Lambda functionの実行時間のうち、ネットワークI/O時間が支配的である
  • 2: Lambda functionの実行終了を同期的に待たなければならない
  • 3: 複数のレコードをLambda functionの引数に渡すことができない

3について、単にバルク処理できるエンドポイントを生やすだけで解決するのであれば、問題にならない。 1と3について、ネットワークI/O時間が支配的であっても、1レコードの処理をI/O多重化し、実行時間を小さくできるのであれば問題にならない。 さらに、2.と3.については、非同期でよいということであれば、Kinesis Streamsのようなキューを挟めば、Lambda function側でレコードを複数同時に処理し、I/O多重化できる。

しかし、コスト的に不利な性質のアプリケーションであっても、Lambdaの採用により初期構築の手間とその後の運用の手間を大幅に削減しやすいため、実際にコストを試算してみて採用可否を判断することが望ましい。 前述のLambda実装では、CDNのキャッシュヒット率を向上させることで解決できるなど、アーキテクチャ全体でコストを抑えれば問題ないケースもある。

LambdaのようなFaaSにおいて、実時間ではなく、CPU時間で課金するモードがあればおもしろいかもしれない。

今回の話は、Lambdaアーキテクチャの問題ではなく、Lambdaのコストモデルの問題といえる。 EC2上でLambdaのようなものを自前で運用したとして、同じインスタンス上に複数のfunctionコンテナが動作する状態と、マルチプロセスモデルのWebサーバが動作している状態とでは、ランニングコストあたりの集積度という観点ではさほど変わらない。

あとがき

このような性質をもつアプリケーションが現実にどれくらいあるかわからないが、実際、Lambdaでコスト試算してみると高くなったという話を社内で聞いたので、少し一般化してこのような考察をしてみた。 これを社内wikiで共有して人気だったので、社外共有することにした。 最近は、はてなWebオペレーションチームのテックリード - Hatena Developer Blog にあるようにアーキテクチャ相談をしているため、その活動の一環ということになる。

参考文献

RedisサーバのCPU負荷対策パターン

Redisは多彩なデータ構造をもつ1インメモリDBであり、昨今のWebアプリケーションのデータストアの一つとして、広く利用されている。 しかし、一方で、性能改善のための手法を体系的にまとめた資料が見当たらないと感じていた。 実際、最初にCPU負荷が問題になったときにどうしたものかと悩み、調査と試行錯誤を繰り返した。 そこで、この記事では、自分の経験を基に、RedisサーバのCPU負荷対策を「CPU負荷削減」「スケールアップ」「スケールアウト」に分類し、パターンとしてまとめる。

背景

Redisのハードウェアリソース使用の観点で重要なことは、Redisサーバはシングルスレッドで動作する(厳密には他にもスレッドあるがクエリ処理をするスレッドは1つ)ことと、Redisサーバ上のデータをメモリ容量を超えて保持できないことだ。 前者については、シングルスレッドで動作するということは、マルチコアスケールしないということであり、特定CPUコアのCPU使用率がボトルネックになりやすい。 後者については、Redisはディスクにデータを永続化2できるとはいっても、MySQLやPostgreSQLのようなメモリ上のバッファプールにデータがなければ、ディスク上のデータを参照するといったアーキテクチャではないため、基本的に全てのデータをメモリ容量以下に収めなければならない。

設定やクラスタ構成によっては、ディスクI/OやネットワークI/Oがボトルネックとなることもある。 例えば、Redisのデータの永続化方式[^2]としてRDBを利用していると、Redisサーバのメモリ使用量が大きいほど、ディスクにフラッシュするタイミングで、ディスクIOPSを消費する。 また、マスター・スレーブ構成をとる場合、スレーブのマスター昇格時の再同期処理でネットワーク帯域の使用がバーストすることがある。 さらに、Redisは低レイテンシで応答するため、1台あたりのスループットを高くしやすく、ホスト1台あたりのネットワーク帯域が大きくなることもある。

これらの中でも、特にCPU利用率と戦ってきた経験があり、Mackerelというサービスでは、以下のように、なぜか毎年CPU利用率と戦っていた。

f:id:y_uuki:20170910003050p:plainf:id:y_uuki:20170910003102p:plainf:id:y_uuki:20170910003054p:plain
issue

RedisのCPU負荷対策パターン

以下の図は、CPU負荷対策パターンをカテゴリごとにまとめたものになる。

RedisのCPU負荷対策のカテゴリとして、CPU負荷削減、スケールアップ、スケールアウトがある。 CPU負荷削減は、Redisサーバが実行する処理そのものを減らし、スケールアップはハードウェア性能そのものを向上させ、スケールアウトは複数のCPUコアやサーバに処理を分散させる。 実際には、各カテゴリの対策を組み合わせることが多いだろう。

CPU負荷削減

CPU負荷削減のためのテクニックとして、「multiコマンド」「Redisパイプライン」「Luaスクリプティング」「Redisモジュール」がある。

multiコマンド

MGETMSETMSETNXなどの複数のキーに対して操作するコマンドを使うことで、余分なCPU処理をせずにすむ。 これは、multiコマンドを使わずに複数回コマンドを発行することに比べて、リクエスト/レスポンスの往復回数が減り、Redisサーバ側でリクエスト受信とレスポンス送信のためのオーバヘッドを削減できるためだ。

Redisパイプライニング

Redisパイプライニング3は、クライアントがレスポンスを待たずにリクエストを投入しつづけることで、ラウンドトリップタイムを削減する。 ドキュメント[^4]には、ラウンドトリップタイムだけでなく、Redisサーバ上でのトータルでの処理量を削減できると書かれている。

ただし、全てのコマンド発行パターンにおいてパイプライニングが有効なわけではない。 Read After WriteとWrite after Readのような、レスポンスに含まれる結果を利用して次のコマンドを発行する場合には、レスポンスを待たないパイプライニングは有効ではない。 パイプライニングにおいて、クライアント側のコマンド実行順序は保証されるため、Write after Writeのパターンでは有効だ。 Read After WriteとWrite after Readのパターンで処理効率を向上させたい場合、後述するLuaスクリプティングを使うとよい。

一部のRedisクライアントは、pipelineという名前がついたインタフェースであっても、Redisプロトコルレベルでのパイプライニングではないことがある。 Redisプロトコルレベルでのパイプライニングを真のRedisパイプライニングと同僚と呼びあっている。 真のRedisパイプライニングかどうかは、tcpdumpでコマンド発行の度に同期的にQUEUD応答が返ってくるかどうかで確認できる。

パイプライニング自体は伝統的な技術であり、CPUプロセッサの命令パイプラインが有名だ。 ネットワークプロトコルの中では、例えばHTTPパイプライン4がある。Redisのドキュメント[^4]には、多くのPOP3実装でパイプライニングをサポートしていると書かれている。

Luaスクリプティング

Luaスクリプティング5は、Redisサーバに対してLuaコードを送り込んで実行できる機能だ。 具体的には、クライアントとサーバ間でコマンド発行と応答を往復させなければ一連の処理を、Luaで記述することでRedisサーバ上で処理を完結できる。

Redisパイプライニングのドキュメント[^4]の「Pipelining VS Scripting」の項を読むと、パイプライニングを利用できるような多くのユースケースにおいて、より効率的に処理を実行できると書かれている。 さらに、Redisパイプライニングでは対応できないコマンドパターンにおいて、ラウンドトリップタイムを改善できる。 Luaスクリプティングにより、例えば、HASH型に対するmultiコマンドは実装されていませんが、Luaで複数キーに対応したHMSET(MHMSET)のような処理をする関数を実装できる。

しかし、Luaコードの処理内容によっては、もともとクライアント側で計算していた処理をRedisサーバ側で実行するため、RedisサーバのCPU利用率が増加する可能性がある。

Redisモジュール(夢)

これまで、Redisのデータ構造として使えるのは、LIST、SET、HSET、ZSETなどの汎用のデータ構造だけだった。 しかし、Redis 4.0からRedisモジュール6が追加され、独自のコマンドとデータ構造をC拡張により追加できるようになった。 これを使えば、アプリケーションに最適なコマンドを実装することで、CPU利用率に限らず、その他の性能も大幅に向上させることが理屈上は可能である。

自分では使ったことも作ったこともないが、Redis Modules Hubには検索エンジンやJSONフォーマットをサポートするような拡張が公開されている。

スケールアップ

スケールアップは、CPUをのクロック周波数を高いもに変更するか、CPUアーキテクチャの世代を新しいものにするかといった選択肢がある。 経験上、CPUのクロック周波数に対してほぼ線形にRedisサーバのCPU使用率が変化する。

前述のようにRedisサーバはシングルスレッドで動作するため、CPUのコア数が大きいものを選んでも意味はあまりない。 ただし、ディスクの永続化方式にRDBを選択している場合、Redisサーバプロセスからforkされた子プロセスが、メモリ上のデータをディスクに書き出す処理をバックグラウンドで実行するため、最低でも2コアにしておくと安心だと思う。

ただし、EC2を利用する場合、EC2インスタンスのCPUコアは論理コアなので、RDB利用の場合、4コアのインスタンスを選ぶことをすすめる。7 物理CPUコア数は論理コア数(vCPU数)を2で割った値になる。 例えば、c4.2xlargeは表記上のコア数は8だが、物理コア数は4となる。

スケールアウト

Redisのスケールアウトのための手法として、「参照用スレーブ」「垂直分割」、「水平分割」、「Redis Clusterによる水平分割」がある。 「参照用スレーブ」「垂直分割」、「水平分割」は、Redisに限らずMySQLなどのRDBMSにおいても、一般的なスケールアウト手法として知られている。8 参照用スレーブは名前の通り、参照クエリのみ分散できる。 更新クエリを分散するならば、「垂直分割」または「水平分割」が必要になる。 垂直分割と水平分割は、CPU負荷の分散以外に、メモリ使用の分散にも利用できる。

参照用スレーブ

Redisはレプリケーションによりマスター・スレーブ構成のクラスタを作成できる。 はてなでは、主にスタンバイサーバの作成に利用しており、KeepalivedによりVIPベースで冗長化している。

スレーブは他にも用途があり、参照用スレーブは、アプリケーションから参照クエリを向けるためのスレーブだ。 スレーブに読み込みクエリを投げることにより、マスターの負荷をスレーブへ分散できる。

以下の図のように、クライアントから複数のスレーブに向けて参照クエリを投げます。複数のスレーブに向けてロードバランシングするための手段はいろいろあるが、TCPロードバランサを挟むか、DNSラウンドロビンによるロードバランシングが一般的だ。

|-------------------------------------|
|              |----> redis (slave)   |
|        read  |                      |
| client ------|----> redis (slave)   |
|        |     |                      |
|        |     |----> redis (slave)   |
|        |                            |
|        |----------> redis (master)  |
|        read/write                   |
|-------------------------------------|

何も考えずに、すべての参照クエリを参照用スレーブに向けるのは危険だ。 Redisのレプリケーションは基本的に非同期であり、レプリケーション遅延があるため、クライアントからは結果整合性をもつデータストアとして扱う必要がある。 具体的には、マスターにデータを書き込んだときにスレーブにデータが転送され書き込まれるのを待たずにクライアントへ応答を返す。 したがって、応答がクライアントへ返った後に、書き込んだキーの参照をスレーブへ向けても、該当データが書き込まれている保証がない。 参照用スレーブを使う場合は、アプリケーションロジックがデータの一貫性を要求しないものかをよく確認しておく必要がある。

垂直分割

垂直分割は、データの種類ごとに複数のRedisサーバを使い分ける手法だ。 下記の図のように、データの種類A、B、Cに必要なデータを異なるRedisサーバに書き込むことで、参照と更新クエリを機能ごとに分散できる。

|------------------------------------|
|              |----> redis (種類A)  |
|              |                     |
| client ------|----> redis (種類B)  |
|              |                     |
|              |----> redis (種類C)  |
|------------------------------------|

可用性のために、各Redisサーバは冗長化されている必要があり、1つのクラスタで処理を捌くより、無駄なサーバが増えやすいというデメリットがある。 また、特定種類のデータに対するクエリの負荷が大部分を占める場合、その種類のデータを別のRedisサーバに移しても、結局分散先のRedisサーバのCPU負荷が問題になることがある。

水平分割

水平分割は、ある特定の種類のデータをレコードごとに分割し、それぞれのレコードを別のRedisノードへ配置する手法だ。 水平分割のことをシャーディングやパーティショニングと呼ぶこともある。

レコード分割の手法は様々であり、後述するRedis Clusterのようなミドルウェア側で分割する機構がないもしくは利用しない場合は、データの性質を利用する。 例えば、RedisにユーザIDに紐付いたデータを格納するとして、ユーザのIDをノード数で割った剰余の値により、配置するノードをマッピングするといった方法がある。 下記の図はノード数が3の場合を示している。 他にはユーザ名の頭文字がaの場合はノード1、bの場合はノード2といった分割の方法もある。

|------------------------------------------|
|              |----> redis (id % 3 == 0)  |
|              |                           |
| client ------|----> redis (id % 3 == 1)  |
|              |                           |
|              |----> redis (id % 3 == 2)  |
|------------------------------------------|

水平分割は、垂直分割同様に、CPU負荷の分散以外に、メモリ使用の分散にも利用できる。

この手法のデメリットとして、ノードの負荷の偏りと、リシャーディングの困難さの2つがある。

ノードの負荷の偏りは、特定のユーザ(もしくはブログIDや記事IDなど)に関するコマンドは必ずマッピング先のノードに向けて発行されるため、特定のユーザの活動が非常に活発な場合、負荷が特定のノードに偏ってしまうという問題がある。

リシャーディングの困難さは、ノード数を増減させるオペレーション(リシャーディング)に非常に手間がかかることだ。 マッピングの手法にもよりますが、上記のような剰余ベースでマッピングしていると、リシャーディング時に既存のマッピングが変更される。 既存のマッピングが変更されると、すでに書き込み済みのデータを新しいマッピング先のノードに移動する必要がある。 最もナイーブな方法で移動するなら、新旧のマッピングで配置が変更されるレコードを抽出し、新マッピングにしたがってデータを移動するバッチスクリプトを実行するといった手段がある。 バッチスクリプトを流す時間だけサービス停止できればよいが、これをオンラインでやろうとすると大変だ。 例えば、新旧のマッピングを意識したアプリケーションに改修しつつ、裏で新マッピングへの移動スクリプトを流すといった泥臭い運用が待っている。

さらに、ノードの負荷の偏りを平滑化する(リバランス)となるとさらに困難なことになる。 このリシャーディングとリバランスの難しさから、できるだけ水平分割を選択しないようにしている。

しかし、Redisをキャッシュとして用いる場合は、リシャーディングはもうすこし簡単になる。 レコードにTTLを設定しておき、マッピングが変更された後に、たとえキャッシュミスしたとしても、キャッシュ元のなんらかのデータソースからデータを引く実装になっていれば、アプリケーションの動作としては問題ない。TTLにより、旧マッピングのデータがずっと残っているということもない。 キャッシュミスをできるだけ抑えたい場合は、consitent hashingを使うと、リシャーディング時にマッピングの変更を少なく抑えられる。

Redis Clusterによる水平分割

Redis Cluster9は、複数のRedisサーバに対してデータを「自動で」水平分割する。 前述の水平分割の場合は、アプリケーション開発者がレコードを分散する処理を書き、リシャーディング運用をしなければなかった。 Redis Clusterを使うと、自動でキーとノードのマッピングを作成され、アプリケーションからのコマンドは自動で分散される。 さらに、redis-tribというRedisが提供しているオペレーションスクリプトを使ってリシャーディングとリバランスができる。

しかし、Redis Clusterにはdatabaseと複数キーのコマンドの扱いに制限がある。

まずdatabaseについて、複数のdatabaseを使えない制限がある。databaseはRedisのキー空間を分ける機能で、キーの重複を考えなくてすむため、データの種類ごとにdatabaseを分割するといった使い方をする。すでにアプリケーションが複数のdatabaseを使っている場合、途中からRedis Clusterに移行するには手間が発生する。

次に複数キーコマンドについて、複数キーにまたがるコマンドは、全てのキーが同じノード上になければならないという制約がある。 そこで、hash tagsを使うと、キー文字列の中の部分文字列を中括弧で囲うことで、同じ部分文字列をもつキーであれば、同じノードにマッピングするようになる。 したがって、hash tagsにより、キーとノードのマッピングをある程度開発者がコントロールできる。 パイプライニングやLuaスクリプティングについても、基本的に操作対象の全てのキーが同じノード上にある必要がある。 ただし、一部のクライアント10では、同じノード上にキーがなくても、複数のノードにクエリを投げてマージするといった実装が入っていることがあるようだ。

これらの制限に加えて注意する点は、水平分割の項で説明したノードの負荷の偏りを、Redis Clusterを使ったからといって防げるわけではないということだ。 システムのワークロードによって、よくアクセスされるキーが存在すると、特定ノードへ負荷が偏ることは避けられない。 ただし、slotを別のノードへ移動させるコマンドにより、負荷の高いslotを移動させてマニュアルで負荷の偏りをある程度均すことは一応可能だ。

ちなみに、Redis Clusterは、Mackerelの時系列データベース11で利用している。 まだ投入して間もないため、運用ノウハウを積んでいくのはまだまだこれからというところだ。

その他

上記カテゴリのいずれにも該当しないチューニング例として、RPS12とCPU Affinityによるネットワーク割り込み負荷をRedisプロセスが処理するCPUコアとは別のコアへ分散がある。 この手法は、はてなのLinuxネットワークスタックパフォーマンス改善事例13の後半で紹介している。

スライド資料

あとがき

発表から少し時間が空いてしまいましたが、この記事の内容は、Kyoto.なんか #3での発表を加筆修正したものになります。

これらの内容は一人でやったものではなく、自分は主にアーキテクチャや作戦を考える係で、アプリケーションの実装は主に同僚のid:mechairoiさんとid:itchynyさんによるものです。 真のRedisパイプライニングについては、同じく同僚の id:ichirin2501 さんに教えてもらいました。

Redisは好きなミドルウェアで、[^11]でもお世話になっています。 オンディスクDBは、RDBMSならMySQLやPostgreSQL、分散データストアならHBase、Cassandra、Riak、Elasticsearchなどさまざまな選択肢がありますが、インメモリDBはウェブ業界の中では実績や使いやすさを考えるとRedis以外の選択肢をあまり思い付きません。

しかし、Redisがこれほど利用されているにもかかわらず、議論の土台となる運用をまとめた資料がないと感じていました。これは、単に自分が発見できていないだけかもしれません。 以前から、Webの技術を自分の中で体系化することに興味があり、過去の試みには 2015年Webサーバアーキテクチャ序論 - ゆううきブログWebシステムにおけるデータベース接続アーキテクチャ概論 - ゆううきブログ といった記事があります。 そこで、今回は、RedisのCPU負荷対策についてまとめまてみました。メモリやネットワークI/O全般について書ければよかったのですが、まだ知見が及ばないところも多いので、一旦CPUの部分のみにスコープを絞りました。

ところで、今年のはてなサマーインターン2017の大規模システムコースでは、Serfで使われているGossipプロトコルベースの自律分散監視や、グラフDBのNeo4jを用いた分散トレーシングといったとてもおもしろい成果がでています。

参考資料

時系列データベースという概念をクラウドの技で再構築する

サーバ監視サービスMackerelにおいて開発中の、高解像度・長期間のサーバメトリック収集を実現するための新しい時系列データベースDiamondを紹介します。具体的には、Amazon ElastiCache、Amazon DynamoDB、Amazon S3を組み合わせ、Amazon Kinesis StreamsとAWS Lambdaによりコンポーネント間を接続した、階層構造のデータストアアーキテクチャの設計と実装を解説します。

はじめに

先日開催されたAWS Summit Tokyo 2017にて、「時系列データベースという概念をクラウドの技で再構築する」というタイトルで登壇しました。 この記事では、講演した内容に加えて、時間の限られた講演では触れられなかった内容を併せて議論します。

タイトルに時系列データベースとあるので、時系列データベースの話をもちろん書きます。しかし、AWS Summitでの発表ベースの記事なので、「AWS」を用いたシステム設計という切り口で話を進めます。 具体的には、Write-Intensive Application(書き込み要求が支配的となるアプリケーション)をAWS上で設計するための議論の土台になればと考えています。 時系列データベースは比較的単純なデータモデルを扱うため、よいモデルケースになるのではないかと思います。 時系列データベースとしての機能比較や実装の詳細についてはまた別の機会に紹介します。

背景

Mackerelでは、成長しつづけるサービスのスケーラビリティの確保と、データ量とI/Oがひと桁またはふた桁増加するような機能実現が求められています。

Mackerelの重要な機能のひとつに、メトリックのグラフ化機能があります。 開発中の新時系列データベースは、このグラフ化機能について、より高解像度でより長期間のメトリック収集と保持を実現しようとしています。 さらに、1つのグラフに1000以上のメトリックが含まれている状況でも高速に表示できるようにするなどの性能向上や、それ以外に、メトリックの異常検知など将来的な機能追加に対するワークロードに対して対応できるようにしたいと考えています。

Mackerelにおけるメトリックの収集とグラフ化の仕組みの概要は、以下の図のようになっており、前述の機能要求にとって、時系列データベースというコンポーネントが重要になってきます。

時系列データベースとはそもそも何でしょうか。時系列データベースとは、時系列データを扱うことに特化したデータベースであり、サーバモニタリングやIoTの文脈でのセンサーデータの収集のために使われます。有名な実装として、OpenTSDBInfluxDBGraphite などがあります。地味にみえて、多くの実装が存在し、学術研究論文になっているものもあります。id:rrreeeyyy さんの 時系列データベースに関する基礎知識と時系列データの符号化方式について - クックパッド開発者ブログ [3]が詳しいので、一読することを薦めます。

Mackerelで運用している時系列データベースについては、Mackerelを支える時系列データベース技術 - ゆううきブログ [4]の記事にまとめています。2年前の記事ですが、現状でも概ねこの通りの構成で運用しています。1台あたりピーク時で150k write IOPSを叩き出しており、サーバモニタリング向け時系列データベースの探究 / The study of time-series database for server monitoring // Speaker Deck [5]で紹介したようにパッチをあててパフォーマンスを改善してきました。

ところが、MackerelのGraphite運用には、以下の4つの問題を抱えています。

まず、スケールアウトのための運用コストが大きいという問題です。書き込みI/Oのスケールアウト手法として、水平分割(sharding)が一般的です。Graphite自体にはconsistent-hashingにより、水平分割する仕組みがあります。しかし、シャードの増減により、ハッシュキーとノードのマッピングが変更されたときに、データを再配置するための仕組みはGraphite本体には実装されていないため、運用でカバーする必要があります。

次の問題は、データ保持期間を増やすと金銭的なコストが激増するというものです。前述したようにスケールアウトのための運用コストが大きいことから、書き込みI/Oをスケールアップするために、NANDフラッシュメモリを利用しています。非常に性能が高い一方で高価なハードウェアなので、容量単価が大きいという特徴があります。 したがって、データ保持期間を増やすと、ディスク上のデータ量が増加し、安価なハードウェアにのせることに比べてお金がかかることになります。 一般の解決策は、圧縮してディスクに格納するか、既に書き込まれたデータを低速で容量単価の小さいストレージに移動するような仕組みにすることです。 前者の圧縮については、[3]で紹介されている差分符号化やXOR符号化のテクニックをGraphiteに実装すれば、実現できそうではあります。しかし、これらのエンコーディングにより、ランダムアクセスできなくなるため、Graphiteのラウンドロビンデータベース構造とうまく噛み合うかがわからないところです。圧縮は基本的に、CPU負荷とトレードオフになるため、CPU負荷がボトルネックとなる可能性もあります。 後者のデータ移動については、Graphiteのラウンドロビンデータベースの、古いデータを新しいデータで上書きしていくという性質のため、別のストレージに効率よくデータを移動させることが難しく、データ構造を大きく変更する必要があります。

3つ目の問題は、データロスト耐性が低く、フル同期のための運用コストが大きいことです。アプリケーションから書き込みすると、Graphiteのメモリ上のキューにデータが非同期に書き込まれるため、サーバダウンによりキューの中身をロストしてしまいます。デュアルライトにより、別のサーバには書き込まれているので、そちらからデータを同期するなど、運用でカバーしなければなりません。これを解決するには、GraphiteにWAL(Write Ahead Log)を実装するなど、大きな変更を強いられます。

最後に、新規にメトリックが投稿されたときに、未来の領域をファイル作成時に確保するので、ディスク使用効率が低いという問題があります。これは2つめの問題と関連します。なぜこのようなデータ構造になっているかは http://graphite.readthedocs.io/en/latest/whisper.html?highlight=rollup#disk-space-efficiency に書かれています。このデータ構造は、コンテナのようなImmutable Infrastructureの概念と相性が悪く、非効率です。

これらの4つの現状の問題点と、冒頭に書いたような新しい機能要求を考慮すると、根本的なアーキテクチャの刷新が必要だと考えました。

既存の時系列データベース

まず、既存の時系列データベースの調査から始めました。Andreas Baderらのサーベイ論文[1]やOpen Source Time Series DB Comparison[2]にオープンソースの時系列データベース実装がまとめられています。

時系列データベースの概念

前述のサーベイ論文[^1]では、時系列データベースの定義は、以下のような性質を満たすDBMSのこととされています。

  • タイムスタンプ、値、属性(メトリック名など)で構成されるデータの行を格納できる
  • 時系列としてグループ化された複数の行を格納できる
  • データ行に対してクエリを発行できる
  • タイムスタンプまたは時間範囲をクエリに含められる

この定義を満たすものを実装するのはさほど難しくありません。しかし、実際には、分散/クラスタリング、Function、Rollup Aggregation(自動丸め)、タグ、複数のデータ解像度、クエリ言語などの時系列データベースにまつわる機能要求があります。 特に、今回のようなスケーラビリティを確保する目的であれば、分散/クラスタリングが重要になってきます。

時系列データベースの分類

時系列データベースのOSS実装は大きく2つの分類があると考えています。それは、他の汎用DBMS上に実装されたもの(TSDB on DBMS)と、時系列データベースに最適化したストレージエンジンをもつもの(TSDB standalone)です。 前者にあたるものは、例えば、OpenTSDBやKairosDBなどがあります。一方、後者にあたるものは、GraphiteやInfluxDB、Prometheusなどがあります。 前者のDBMSとして、HBase、Cassandra、Riak、ElasticsearchなどのいわゆるNoSQLが使用されます。もちろん、TimescaleDBのようにRDBMSであるPostgreSQLをベースにした実装も存在します。前述の時系列データベースとしての定義を満たすだけなら、ACID保証が必要ないので、NoSQLを利用するケースが多いようです。

時系列データベースを実装する側の視点でみると、TSDB on DBMSは、分散/クラスタリング機構を自前で実装せずにすむため、実装が楽というメリットがあります。一方で、TSDB standalineは分散/クラスタリング機構も実装しなければならないというデメリットがありますが、TSDB on DBMSと比較して時系列データベースに最適化できるというメリットがあります。

時系列データベースを運用する側の視点でみると、たいていのTSDB on DBMSはCassandraなどの分散システムとしての運用コストが高いといわれるDBMSを利用しており、一方で、TSDB standaloneは分散システムとしての信頼性を測るための実績が少ないといえます。実際には、製品ごとに細かくメリット・デメリットがありますが、大雑把にこのように認識しています。

さらに、Mackerelで採用するという視点でみると、既存の時系列データベースをそのまま採用するデメリットとして、Rollup Aggregationに対応していないものや、インタフェースが大きく変わるので変更コストがある、Mackerelの現在または将来のワークロードに対してスケールしない可能性があります。

新時系列データベースDiamondのアーキテクチャ

方針

調査の内容を踏まえて、既存の時系列データベースを採用せずに、Amazon DynamoDBなどのAWSマネージドサービスを採用し、マネージドサービス上に独自の時系列データベースアプリケーションを実装するという方針にしました。 マネージドサービスを採用したのは、時系列データベースの分散/クラスタリング機構に必ずついてまわるデータベースの検証・運用コストを削減するためです。はてなでは、特定ベンダーのロックインをなるべく避けるという技術選択方針がありますが、今回は、Mackerelの成長速度や将来の機能要求を考慮して、ある程度のロックインを受け入れることにしました。 独自に、TSDB on DBMSにあたるものを開発することにしたのは、ひとえにMackerelの要求を満たす実装がないか、要求を満たすかどうかの検証に時間がかかるためです。AWSマネージドサービス採用によりデータベースの検証/運用コストを削減できるので、その代わりの開発工数を確保できると考えました。

この大方針に沿って、Graphite運用の4つの問題点を解決できるか考えます。

まず、負荷分散の運用コストの解決です。前述したようにDynamoDBなどのフルマネージドサービスの利用により、運用コストを削減できます。水平分割のためのキーの設計を間違えなければ、お金を払った分だけI/Oスループットがスケールするので、検証も比較的楽になります。 ところが、DynamoDBのI/O課金が高いため、今のMackerelのI/Oを工夫せずにDynamoDBに向けるとインフラコストが大きく増加します。 したがって、個々のデータポイントを別々に書き込むのではなくまとめて書き込み、I/Oコストを下げる必要があります。 最終的には、前段にElastiCache(Redis)を配置し、ライトバックキャッシュ(講演ではバッファと呼んだ)として利用することにしました。

次に、データ保持期間についての解決です。DynamoDBのディスク容量課金は感覚的には安く感じましたが、1分解像度のデータを年単位で保持しようとすると、そこそこのコストがかかります。 S3のコストはスタンダードストレージでもDynamoDBの1/10程度に収まるので、ここでコストを浮かせておけば、他の機能拡充にコストをあてられるため、なるべくS3を使いたいと考えました。 とはいえ、S3のレイテンシはDynamoDBに比べて1桁以上大きいかつ、DynamoDBよりも内部的なパーティションあたりのスループットが低い可能性があります(要確認 リクエスト率およびリクエストパフォーマンスに関する留意事項 - Amazon Simple Storage Service)。 Mackerelの場合、古い高解像度データはほとんど参照されないという参照局所性があるので、参照回数が小さいデータの表示は多少遅くてもよいという性質があります。 そこで、この性質を利用し、ホットデータをDynamoDB、コールドデータをS3に配置することでコスト最適化することを考えました。

この時点で既に、階層型データストアアーキテクチャになることが決定しています。

3つ目に、データロスト耐性の向上です。これは簡単で、メモリ上のキューではなく、ディスク書き込みするメッセージキューを採用することです。OSSなら、KafkaやRabbitMQなどがこれにあたり、AWSでは、Amazon Kinesis Streamsになります。内部的な実装としてはキューではないと思いますが、キューのように利用できます。Kinesis Streamsは直近の24時間分のレコードをディスクに保存し、投稿したレコードをLambda functionの引数に渡すことも簡単です。Kafkaほどレイテンシが低いわけではないそうですが、どのみちMackerelのデータポイントはインターネットごしに投稿されるので、もともとユーザに反映されるまのでレイテンシが大きいので、無視できる程度のレイテンシの差だと考えました。キューというよりは、Using logs to build a solid data infrastructure (or: why dual writes are a bad idea) - Confluentに書かれているように、アプリケーションレベルのWAL(Write Ahead Log)を前段に配置していると捉えてもよいかもしれません。

最後に、ディスク使用効率の向上です。容量問題はS3の利用で解決できるので、ラウンドロビンデータベース構造にする必要がなく、未来の領域を確保することはしません。 圧縮や符号化については、RedisやDynamoDB上でレコード単位で圧縮しようとすると、レコードに対するデータポイントの更新時に、レコード内のデータを全て読み込んでからデータポイントを更新し、レコードの内容を差し替えるということをしなければならないため、余分なread I/Oとネットワーク帯域を圧迫する可能性があります。OSのファイルのようなseekできるストレージであれば、必要な分だけ読み出せるので、効率が良いと考えます。(しかし、これを書きながら、データサイズが小さくなっているのだから、ネットワーク帯域はさほど気にならない可能性もあるということに思いあたりました。)

さらに、時系列データを読み出すコンポーネントをMicroservicesとして開発しています。Graphite互換のインタフェースを実装し、新システム移行時のインタフェース変更コストを小さくしています。1つのクエリに1000以上のメトリックが含まれることがあるため、各メトリックのタスクを並行処理できるように、軽量スレッドでI/O多重化できるGo言語を実装言語として選択しました。

設計

前節の方針を元に、新時系列データベースのアーキテクチャの設計の概要を以下にまとめます。

  • 金銭コスト最適化のために参照局所性を利用する。データ参照頻度に応じて適切なデータストアへデータを移動させる
    • ホットデータをDynamoDB、コールドデータをS3に配置
    • ライトバックキャッシュのストレージとしてElastiCache(Redis)を利用
    • DynamoDBのTTL機能によりシームレスにデータ移動
  • Kinesis Streamsに対して先行ログ書き込みし、データロスト耐性と耐障害性の向上させる
  • Graphite互換インタフェースにより、各ストレージからデータ取得するWebアプリケーションを実装する

アーキテクチャ図を以下に示します。

このように複数のデータストアを組み合わせる場合、データをシンプルな手法で移動させるためのアイデアが必要です。 2017年2月にDynamoDBがTTLサポートされました。 TTLがexpireしたレコードをDynamoDB Triggers経由でLambda functionに渡すことができるため、簡単に古いデータをS3へ配置することができます。

この機能がリリースされたとき、ちょうどデータ移動の仕組みに困難さを感じていたところでした。当時は、タイムウィンドウごとにDynamoDBのテーブルを用意して、古いタイムウィンドウテーブルをテーブルごとDataPipelineによりS3にエクスポートすることを考えていました。いわゆる、MySQLで日時カラムに対してパーティションを切り、古いパーティションをパーティションごとDROPするという手法に近いと思います。 テーブルファイルがS3に配置されても、巨大なテーブルファイルに対してクエリするのは現実的ではないため、テーブルファイルをレコード単位に分割してS3に保存し直すような手間のかかる処理が必要でした。 テーブル単位による管理を強いられるため、アイテム単位で細かくデータ配置を制御したくなったときに、どうしようもなくなるという問題もありました。

古くなったデータを別のストレージに移動したいというのは一般的な要求なので、データベースのTTLとイベント通知というサーバーレスアーキテクチャらしい仕組みは、汎用的に利用できるアーキテクチャだと考えています。

実装

実装についての詳細は、スライドを参照してください。時系列データベース固有の話になります。Redis/DynamoDB上のスキーマ設計、Redisによるライトバックキャッシュ、Graphite互換アプリケーションのI/O多重化について簡単に紹介しています。

考察

Write-Intensive Applicationとして、例えばログ収集や、チャットメッセージングなどがあります。ログやチャットメッセージと違い、メトリックのデータポイントは固定長のデータなので、データ構造を最適化しやすいという特徴があります。したがって、可変なデータを書き込むアプリケーションの場合、時系列データベースでは考えなくてよかったようなデータ構造の工夫が必要かもしれません。

サービスの仕様変更により、データ構造を変更したくなったとき、3箇所にデータを保持していると、それぞれ変更を加えなければいけないというデメリットがあります。これについては、場合によっては、今あるデータ構造に追加の変更をするよりは、前段のKinesis Streamsから別のストレージに最適な構造で書き込むことも考えられます。

3層構造のデータストアアーキテクチャを提案しましたが、必ずしも3層にする必要はないと思います。ディスク使用が少なければElastiCacheとDynamoDB、書き込みI/Oが少なければ、DynamoDBとS3、readレイテンシを気にしなければ、RedisとS3というように、2層で対応できることもあるでしょう。 ちょうど手元のマシンでRAM+SSD+外部ストレージで大抵のことが事足りるように、3層あれば多くの状況でコスト最適化できると考えています。

DynamoDBにはDAXというライトスルーキャッシュが最近実装されました。もし、DAXにライトバックキャッシュが実装されれば、DiamondアーキテクチャのうちRedisの層は不要になるかもしれません。

その他、運用の観点で、スナップショットバックアップについては、ElastiCache自体にバックアップ機構があり、DynamoDBでは前述のDataPipelineによるS3への定期バックアップが可能であり、S3上のファイルは一旦作成されたら基本的に更新しないので、スナップショットバックアップはそれほど必要ないと考えています。

さらに、Lambda functionの実装にバグがあるとデータロストの危険性がないかについては、前段のKinesis Streamsに24時間(追加コストで1週間)データ保持されるので、原理上はそこからリカバリできるはずです。functionの実行がエラーになるケースでは、functionの実行がリトライされます。ただしこれには、functionの実行がべき等である必要があります。Diamondの場合、RedisとDynamoDBのデータ構造がタイムスタンプをキーとしたハッシュマップになっているので、データポイントの書き込みはべき等になっています。

Diamondアーキテクチャのデメリットとして、構成要素が多いことが挙げられます。Kinesis streamsもあわせると、4つのデータストアを併用することになります。一般に構成要素の数が増加すると運用コストも増加しますが、マネージドサービスの利用やCloudFormationのようなInfrastructure As Codeの利用により、運用・管理コストを抑えられると考えています。 しかし、Diamond自体のモニタリングは複雑になるため、モニタリング手法もあわせて提案する必要があると考えています。

資料

プレゼンテーション資料

参考資料

あとがき

「時系列データベースという概念をクラウドの技で再構築する」というタイトルは、少年ジャンプで連載中の食戟のソーマの第115話で、主人公 幸平創真の「親子丼という概念をフレンチの技で再構築する」という台詞から拝借したものです。 主人公は定食屋のせがれで、定食屋としての技術の限界を超えるため、フランス料理の技術を学び、日本の庶民の料理である親子丼を抽象化して、フレンチの技を用いて再構成するというお話になっています。 自分では、この話をとても気に入っていて、今回、ちょうど同じように、時系列データベースの構成要素を取り出し、マネージドサービスやサーバレスアーキテクチャなどのクラウドならではの技術を用いて再構築していると感じ、このタイトルをつけました。

今回話をしたかったのは、単に使っているOSSを列挙するようなものや込み入った実装の話ではなく、アーキテクチャの話です。 最近、アーキテクチャと実装の違いについて考えているのですが、まだあまりよい答えをもっていません。少なくともいえるのは、アーキテクチャには汎用性があり、実装するソフトウェアだけでなく他のソフトウェアにも通用するなにかがあるということです。 アーキテクチャの観点では、Webアプリケーションは参照局所性にあわせて複数のデータストアを併用するのは昨今では当たり前なので、今回のようなTTL + Lambda Functionのようにそこにもっとサーバレスというかイベント駆動な考え方を持ち込み、体系化できないかということを考えていたりします。

Mackerelでは、時系列データベース以外にも、おもしろいソフトウェアアーキテクチャを実現できるチャンスがまだまだたくさんあります。僕の場合は、最近は機械学習を支える効率のよいシステムやグラフDBのアーキテクチャに興味があります。 はてなでは、アーキテクチャを設計・実装し、本番投入し、運用まで持っていくことに興味があるエンジニアを募集しています。

また、今年のはてなインターンでは、Mackerelやはてなのインフラストラクチャを支えるシステムを開発できる、「大規模システムコース」と「クラウドサーバ管理システムコース」があります。 はてなサマーインターン2017

追記

2017/11/03のServerlessconf Tokyo 2017にて、リリース後の監視・運用の話を紹介しています。