1年ほど経った頃、当時のアーキテクチャが少し不合理だったことが明らかになりました。特にハブは、その上に多くのタスクを引き受けすぎていました。ハブは新しい処理要求を受けなければならず、ビルドログを処理してドライブしなければならず、ユーザー情報をGithubに同期しなければならず、ビルドが成功したらユーザーに通知しなければなりませんでした。また、ビルドが成功したらユーザーに通知しなければなりません。
ハブは単一のプロセスとしてしか実行できないため、エラーの可能性が最も高い単一のポイントです。
Github APIは興味深い例です。Github APIsのヘビーユーザーは、これらのAPIsのビルドタスクの実行に依存しています。ビルド設定情報の取得、ビルドステータスの更新、ユーザーデータの同期など、これらのAPIは必要不可欠です。
振り返ってみると、これらのAPIのいずれかが利用できなくなると、hubはその日の作業を中断して次の作業に移ります。Github APIが利用できないと、多くのビルドが失敗します。
これらのAPIには多くの信頼が置かれていましたし、今もそうですが、結局のところ、これらはコントロールできないリソースなのです。これらのリソースそのものを管理するのではなく、別のチーム、別のネットワークシステム、独自の弱点を持つチームによって管理されています。
以前はそのようには考えられていませんでした。以前は、これらのリソースは常に信頼できる友人として扱われ、いつでもリクエストに応えてくれると思われていました。
Re.
ある年、これらのAPIは黙ってある機能に変更を加えました。この機能は文書化されていませんが、非常に頼りにされているものです。この機能があまりに無言で変更されたため、こちらで問題になりました。
その結果、システムは完全に混乱していました。理由は単純で、GithubのAPIを友達として扱い、それらのAPIがリクエストに応答するのを辛抱強く待っていたからです。新しいコミットがあるたびに、一度に数分という長い時間を待っていました。
タイムアウトの設定が緩すぎます。そのせいで、Github APIへのリクエストがタイムアウトになったとき、システムもエラーになっていました。その夜、この問題に長い時間を費やしました。
小さな問題でも、ある時点で重なれば、システムを破壊する可能性があります。
より短いタイムアウトを設定することで、これらのAPIリクエストを分離し始めました。Github側の中断によってビルドが失敗しないように、同じリトライ機構を追加しました。外部からの例外にうまく対処できるように、リトライのたびに有効期限を順番に延長するようにしました。
あなたのコントロールの及ばない外部APIは、いつ故障してもおかしくないという現実を受け入れるべきです。そのような障害を完全に切り離すことができないのであれば、その障害に対処するかどうかを検討する必要があります。
各シングルポイントエラーシナリオをどのように処理するかは、ビジネス上の考慮事項に基づきます。ビルドアウトの例外を許容できますか?もちろん、それが世界の終わりというわけではありません。外部システムの問題のために何百ものビルドが狂ってしまう余裕がありますか?いいえ、理由は何であれ、これらのビルド例外は顧客に影響を与えるのに十分だからです。
トラヴィスのCIは、最初は善意の塊でした。すべてが常に正しく機能するという楽観的なものでした。
残念ながら、それは真実ではありません。あらゆることが、ある瞬間にカオスにつながる可能性があるのに、コードはそれをまったく考慮していないのです。外部APIや内部システムの例外を処理するコードの能力を向上させるために、この状況を変えるために多くの努力がなされましたし、今もなされています。
システムに話を戻すと、ハブは異常が発生しやすいタスクを引き受けているため、多くの小さなアプリケーションに分割されています。それぞれの小さなアプリケーションには、それぞれの目的とタスクがあります。
システムをより簡単に拡張できるように、タスクをうまく分離してください。ほとんどのタスクは上から下へ直接実行されます。
新しいコミットの処理、ビルド通知の処理、ビルドログの処理です。
突然、新たな問題が発生しました。
#p#
アプリケーションは分割されていますが、これらはすべて travis-core と呼ばれるコアに依存しています。このコアには、Travis CIのすべての部分のための多くのビジネスロジックが含まれています。それは大きな泥のボールです。
コアに依存するということは、コアのコードを変更すると、すべてのアプリケーションに影響を及ぼす可能性があるということです。アプリケーションはそれぞれのタスクに従って分割されますが、コードはそうではありません。
今でも初期の建築設計の授業料を払っています。機能を追加したり、コードを修正したりすると、共通部分のわずかな変更が問題を引き起こす可能性があります。
すべてのアプリケーションでコードが確実に動作するように、travis-coreに変更が加えられた場合、それを検証するためにすべてのアプリケーションをデプロイする必要があります。
タスクは、コードの観点から分離するだけではありません。タスク自体も物理的に分離する必要があります。
複雑な依存関係はデプロイメントに影響し、同様に新しいコードや新機能を提供する能力にも影響します。
ゆっくりとコードの依存関係を小さくし、アプリケーション間の各タスクをコードから本当に分離します。幸運なことに、コード自体はすでに分離の度合いが高いので、このプロセスはずっと簡単に思えます。
スケーリングを行う上で最大の課題であるため、特に注意が必要なアプリケーションがあります。
ログの目的は2つあります。ログの元となるデータブロックがメッセージキューから送られてきたときにデータベースの対応する行を更新することと、リアルタイムでユーザーインターフェースを更新するためにそれをPusherにプッシュすることです。
ログブロックは異なるプロセスから同時にストリームとして入ってきて、1つのプロセスで処理されます。このプロセスは1秒間に最大100メッセージを処理できます。
一般的に、このようなログストリームの処理方法は非常に良いのですが、ある瞬間にログメッセージが急激に増加した場合に対処するのが難しいということでもあり、そのため、この単独処理はシステムの拡張にとって大きな障害となり得ます。
問題は、プロセスがメッセージキューに到着した順番にこれらのメッセージを処理し、Travis CI のすべてがメッセージキューに依存していることです。
データベースのログストリームを更新することは、すべてのログを含むデータの行を更新することを意味します。ユーザーインターフェースのログを更新することは、もちろん、DOMツリーに新しいノードを追加することを意味します。
この厄介な問題を解決するためには、多くのコード変更が必要でした。
その前に、何がより良いソリューションなのかを整理する必要があります。良いソリューションとは、プロセスのロギング部分を簡単に拡張できるものでなければなりません。
メッセージキューに入れられた順番に暗黙のうちに依存するのではなく、処理の順番をメッセージ自体のプロパティにすることにしました。
このアイデアは、1978年にレスリー・ランポートが発表した論文時間、時計、分散システムにおけるイベントの順序付け。)」に触発されたものです。
この論文でLamportは、分散システムでイベントが発生する順序を保持するためのインクリメンタルカウンタの使用について説明しています。メッセージが送信されると、送信者は受信者がメッセージを受信する際にカウンタの値をインクリメントします。
このシナリオでは、1つのログブロックは1つの送信者からしか得られないので、このアイデアは単純化できます。ログ収集が簡単になるように、カウンターの値を増やし続けることもできます。
あとは、カウンターの値に従ってログブロックを並べるだけです。
難しいのは、この設計は、小さなログブロックがデータベースに書き込まれ、対応するタスクの終了時にのみ完全なログに書き込まれることと同じであるということです。
しかし、これはユーザー・インターフェースに直接影響を与えます。整理されずに到着するメッセージに対処しなければなりません。この変更はより大きな領域に影響を与えますが、コードの多くの部分を単純化することになります。
一見すると、この変更は取るに足らないように見えます。しかし、頼る必要のないシーケンスに頼ることは、より複雑な可能性をもたらします。
今では、メッセージの送信方法に依存する必要はありません。
なぜなら、そのコードはどんなメッセージも順番に来るという前提に立っており、その前提は完全に間違っていたからです。分散システムでは、イベントはいつでもどのような順序で到着してもかまいません。ただ、その後に断片を再び組み立てることができるようにする必要があるだけです。
この問題についての詳しい説明はブログからご覧いただけます。
2013年までには、1日に45,000ビルドを生産するまでになりました。まだ初期の設計にお金を払っていますが、少しずつ改良しています。
今、別の問題が起きています。システムのすべてのコンポーネントは、やはり同じデータベースを共有しています。データベースに問題があれば、当然すべてのコンポーネントに問題が発生します。この不具合は先週一度あったばかりです。
繰り返しますが、これはログの書き込み数がAPIのパフォーマンスに影響することを意味し、ユーザーがユーザー・インターフェースをナビゲートする際に遅くなる可能性があります。
また、ビルド・タスクの数で考えた場合、次の課題はデータ容量の拡張をどうするかです。
Travis CIは500台のビルドサーバ上で稼働しており、もはや小規模な分散システムとは言えません。今取り組んでいる問題はまだかなり小さな次元で考えられていますが、その次元でも多くの興味深い課題に遭遇することができます。経験則として、シンプルで簡単なソリューションは、複雑なソリューションよりも常に優れています。





