Software Transactional Memo

STM関係のことをメモっていこうと思います。

正しいクラウドはある意味で遅い

TL;DR 正しく設計するとキャパシティは常にカツカツになる

これはpyspaアドベントカレンダーの8日目の記事です。前日はShibukawaさんです。

 

世はクラウド時代、ソフトウェアはひとたび作られたら何億回実行されても摩耗するものではないので、どんな間抜けなロジックであろうと動く以上は別のどこかで瑕疵が出てくるまで使い倒されるのは日常茶飯事である。

サービスを負荷の前提の上に定義する

クラウドより前の時代においてサービスを支えるマシンは「ロードアベレージが1.0を超えてなければとりあえずOK、超えたらマシンを増やして負荷を分散する」というノリのベストプラクティスがよく言われていたがそれはサーバ資源の確保にそれなりに時間がかかる時代の常識であって、クラウド時代でサーバは分単位で確保できるようになった。

クラウドの利点としてその即時的なスケーラビリティが常套句として使われて久しいが、これは裏から見れば必要がない時はあらん限り縮退して走ってて欲しいという前提と表裏一体である。極端なことを言うとサービスがまともに動く限りにおいてロードアベレージは高ければ高いほど良い、その方がクラウドに払うお金が安いから。そうなるとサービスが過負荷になっているかはロードアベレージの数字ではなく別の指標(i.e. SLO)で「まともに動く」を再定義した上で決定することになる。

古典的な分散システムあるあるな議論の典型の一つとして「負荷分散は忙しい奴がタスクを押し付ける(push)か、暇な奴がタスクを取っていく(pull)か」という設計判断があるが、クラウド時代において少なくとも前者はナンセンスである。なぜなら正しくスケーリングしているクラウドであれば自分以外の全てのワーカーは忙しくなければおかしいのであり、自サーバの忙しさは他サーバに負荷を押し付ける根拠ではないからである。当然他サーバもフルに負荷が掛かっている前提の上ではPushがあてになるケースは限られる。

全員等しく貧しい

何の気なしにClubhouseを聴いていたら興味深い話をしていた。とある人がアパート経営をしたくて物件を探していて、条件の割に妙に安い物件があったので話を聞きに行ってみたところ、近所に養豚場があって風下で常に臭いがくるので住み手が見つかりにくくて家賃が安く物件価値が低くなっていた。しかし多少の知識があった彼はその養豚場のキャパシティに対して豚がやけに少ない事に気づき、そのアパートを購入した。結果として養豚場は廃業し異臭は消えアパートの資産価値は大幅に向上した。曰く畜産は経済活動であるが為にまともにキャパシティいっぱいに育てていないとおかしいのに、それを埋めていないという事は廃業の意図があるとしか考えられない、との事であった。これ自体は不動産業界は情報格差を金に変えているんだなということをしみじみ感じさせる良いエピソードであったが、一般に経済活動とは利益の最大化を常に目指していなければ何かが変なのである。

サーバの余力とサービスレベルの相関は素人には分かりづらく、エンジニアの肌感覚で「これぐらいのサーバが要ります」と申請した内容で予算が通ればサーバが確保される感じで回してきた会社は多くあったのだろうと思う。しかし利益を最大化する観点でいうとその肌感覚は数値化されるべきであり実測と根拠に基づいて動的に決定される必要がある。

さて利益の最大化を目指すシステムで次に何が起きるかというと失敗時のリトライであり、それは当然輻輳を呼び、一週間ぐらい大丈夫と余剰を持ってキャパシティプランニングしたキューを秒速で使い切る破滅への片道切符である。

キューへの流入が流出を上回った時、物理学的であればその差分の速度で余剰がバッファーに蓄積していくものであるが、コンピュータの世界では失敗がリトライを呼び更なる負荷を追加し流入出差以上の速度でキューが埋まる。開発環境やステージング環境なんて特定の作業をしている時以外はCPU使用率はおよそ0%に張り付いているが、プロダクション環境のCPU使用率は常に100%であって、時々自分にも微かなCPU割当がスケジュールされているに過ぎないつもりで設計される必要がある。プロセスだって厳しい資本主義社会の中を生きなくてはならないのだ。そしてその中で無慈悲にも一番重要なプロセスがFull GCでStop The Worldする、そんな環境では実時間ベースのタイムアウトは使えない。

興味深いのが、結局必要となる処理の総量は変わらないのでマイクロサービス自身が自分のフロントエンドのロードバランサをクライアントとして叩いて自分のタスクのサブ問題を自マイクロサービスの別のマシンに押し付けるパターンである。サービスに対して建てるマシンの増減は自マシンではなくロードバランサから俯瞰したサービス全体の負荷によって決定されるので、外部から依頼されるタスクの粒度に差があるなら、自身のフロントを大きいタスクを受け取ったバックエンドから1クライアントとして叩く事でタスクを適当な粒度で早期からワークスティーリングデキューのようにクラスタ全体のタスクとして切り分け直せて負荷をより滑らかにクラスタ全体に行き渡らせる事ができる。これを実現するにはタスクの中にある意味で再帰的な構造を発見する必要があり、似たようなことをやったことがない人にはまるでピンとこない話かも知れない。

そのようにして滑らかにタスクが分割されたクラスタは基本的に限界ギリギリまでリソースを食い潰しているはずである。

回せステートマシン

人類の渇望は果てがないのでレイテンシがクリティカルでないが消えて欲しくないワークロードは一旦ストレージに置くのが順当であり

kumagi.hatenablog.com

にも書いたようにRDBMSをキューとして使うのは一つのパターンである。このブログを書いた頃と違って今はフルマネージドなRDBMSがリーズナブルな価格で利用できる。AuroraとかSpannerとかAlloyDBといった分散RDBMSは相当な負荷とサイズまで対応できる。もちろん、キューを置くこと自体は事態の解決になっていないが、話を簡潔にするのに大いに役立ってくれる。DBの1行を1ステートマシンとして複数のワーカーが状態を見ながら回していくのは悪くない戦略である、今風の言葉で言うとコレオグラフィパターンが近いだろうか。

余談であるが、経済活動と言えばやはり決済が避けられないが、世の中の会社の99%以上はクレジットカードを運営しているわけではないので顧客からクレジットカードで出される決済依頼を処理するにあたりクレジットカード会社との連携は不可欠であり、トランザクションが自社内で完結しないという頭痛の種でもある。人類はアプリケーションレイヤーでトランザクションを再実装する必要性からは逃れられないものなので、経済をちゃんとやっていくにはトランザクションの考え方が必要不可欠とも言える。と言ってもフルマネージドなRDBMSの上でステートマシンを回してロールバックとロールフォワードをやっていきましょう、なんだいつもの地獄か、である。

しかしステートマシンとは様々なところで役に立つ考え方である。思えばRDBMSリカバリプロトコルの金字塔であるARIESも俯瞰するとページそれぞれをステートマシンと見立てて、ログという一方向の冪等な情報を食わせて状態を回していく操作にLSNで横串を刺してトランザクションとして整合性を取っているという点においてただのいつもの泥臭いエンジニアリングであり、その正解が1992年に出版されているというのは大変な偉業である。

これは適当な想像だが、クラウドを支える仮想ネットワークと仮想マシンとスイッチのコンフィグの組み合わせも言ってしまえばそれぞれがステートマシンであり、例えば「一つ仮想マシンを建てて特定の仮想ネットワークを通して外と疎通させるようコンフィグする」というのは原理的にはARIES上で複数のページにまたがるトランザクションを実施するのと同様に物理マシンやスイッチをステートマシンと見立てて冪等なコンフィグ変更を流し込んでいく形に落ち着いているのではなかろうか。そうして全体をトランザクションであると定義すればその上で実施すべきundo/redoの整理や冪等化や物理論理ログやリカバリの設計は単体テストもしやすい形で整理できる。まぁもっと頭のいい人が更にスマートなやり方をとっくに実装したものが今のクラウドの裏側なのかも知れないけど。

こんなぐだぐだと長文を書いて何が言いたかったかと言うと、クラウドと聞くと落ちなくて速くてスイスイというイメージが押し出されているが、正しく設計されたクラウドは本業のサービス表面以外では基本的に過負荷状態かつ落ちまくりこそが常態でありそれを目指すのが経済的に正しく、そのために分散システムとトランザクションをやっていきましょうという話であった。