Rails: 外部サービス接続障害に「サーキットブレーカー」で対応する(翻訳)
はじめに
明け方の4時にチームから「アプリが落ちてます」の緊急電話で叩き起こされ、コーヒーをがぶ飲みしながら調査を始めてみると、一連の障害が発見されました。「連携している支払いシステムが遅くなり始めた」→「チェックアウトリクエストがタイムアウトし始めた」→「ユーザーたちがあわてて画面を何度もリフレッシュし始めた」→「Railsアプリ全体が応答を停止」この手のカスケード障害は、思ったより起きやすいものです。
本記事では、Rubyで構築するサーキットブレーカーと、これを用いてカスケード障害を防ぐ方法について解説するとともに、実績のあるRubyソリューションであるstoplightについても紹介いたします。
かつては、カスケード障害を気にかけるのは、数十〜数百ものサービスを管理する大規模エンジニアリングチームぐらいでした。そうしたチームなら、十分な時間とリソースを注ぎ込んで、いわゆるサーキットブレーカーパターンなどのソリューションを検討し、必要ならツールを実装・調整することが可能です。
しかし現代ではAIが台頭してきたことで、立ち上げ段階のプロジェクトであっても、サーキットブレーカーを最初から導入するメリットのあるサービスがいくつも含まれることが増えてきています。
その場合の一般的なセットアップは、「Railsモノリス」と「OCRエンジンやLLMなどの機械学習モデルをホスティングする複数のサービス」で構成されます。そのようなモデルは応答が遅延したり予測が難しかったりする可能性があるため、サーキットブレーカーで保護すべき候補として理想的です。
ここで問題となるのは、サーキットブレーカーはほとんどの場合スタートアップで採用しにくいことです。監視機能やリアルタイム管理機能が不足していることが多く、設定項目も細かすぎて、すぐ使えるようなものではありません。
しかし解決策はあります。詳しくは後ほどご覧に入れます!
🔗 カスケード障害とは
最初に、サーキットブレーカーを導入するのが合理的である理由の背景を説明します。
ソフトウェア開発の世界におけるカスケード障害(cascading failure)はドミノ効果(domino effect)とも呼ばれ、あるコンポーネントで発生した障害が他のコンポーネントの障害を引き起こす連鎖反応のことです。
以下のような構成のシステムを考えてみましょう。
- モノリス(つまりメインのアプリケーション)
- 支払いサービス(スケーラビリティのためにモノリスから抽出された)
- 支払いプロセッサ(実際の支払いを処理する外部サービス)
外部の支払いプロセッサが処理不能に陥ったとしましょう。このときのユーザーの行動シナリオは以下のような感じになります。
- ユーザーは購入の完了を試み、モノリスはそのリクエストを支払いサービスに転送する。
- 支払いサービスは外部の支払いプロセッサに接続しようとするが、リクエストがタイムアウトする。
- 支払いサービスはモノリスに
408
(リクエストのタイムアウト)を返す。
ユーザーはこの状態からリトライする可能性が高いでしょう。リトライが1回だけなら大したことはありませんが、人気商品の発売日だったりセールの日だったりしたらどうなるでしょう?
このようなシナリオでは、リクエストが大量に積み上がり、その1つ1つが支払いプロセッサからの購入完了レスポンスを待ち続けることになります。スレッドはブロックされ、メモリは使いつくされ、最終的にはシステムが負荷に耐えきれずにダウンして、支払いサービスやモノリスに影響が生じるのみならず、アプリケーションで支払いと無関係な部分にまで影響が生じてしまいます。ここまで来ると、もう完全な停止です。
メモ: 多くのシステムでは重要なリクエストを自動リトライするようになっていますが、障害が発生すると障害発生中のサービスにすべてのクライアントのリトライが押し寄せてしまい(リトライストーム)、復旧がますます困難になります。
🔗 サーキットブレーカーは救いの神
ありがたいことに、カスケード障害は抑制可能です。Martin Fowlerの記事で広く知られている「サーキットブレーカー(Circut Braker)」パターンは、実績のある対処方法の1つです。Rubyに特化している本記事と合わせて、Martin Fowlerのオリジナル記事も読んでおく価値があります。
サーキットブレーカーパターンの基本を理解するために、アパートや住宅やあらゆるビルで使われている物理的なサーキットブレーカーについて考えてみましょう。家電製品の使いすぎやショートなどで電流が流れすぎると、「ブレーカーが落ちて」電力を遮断します。これによって、装置や配線の損傷、さらに火事や感電などの事態を防止します。
話をソフトウェアの世界に戻します。
こちらの世界のサーキットブレーカーが監視する対象は、電流ではなく、主に外部サービスへの呼び出しによるエラーです。エラーが増えすぎると、サーキットブレーカーが作動して、保護対象のサービスへの以後の呼び出しを一時的にブロックします。これにより、障害発生中のサービスからのレスポンスを待つ間に、システムがリソースを使い尽くしたり過負荷状態になったりすることを防止します。
上述の例で言うと、支払いプロセッサの速度低下によってRailアプリ全体が巻き添えを食らってカスケード障害に陥らないよう、サーキットブレーカーで支払いプロセッサの呼び出しを遮断します。
🔗 サーキットブレーカーの詳細
サーキットブレーカーが取れる2つのステートについて解説します(3つ目のステートについては後述)。
- 🟢 閉回路(Closed): すべての動作が正常で、リクエストも期待通りに流れている。
- 🔴 開回路(Opened): 以後の呼び出しをブロックする。エラーが多数発生したため、サーキットブレーカーが作動してシステムリソースを保護している。
訳注
電気回路においては、スイッチをオンにすること(回路を接続すること)を「回路を閉じる(close)」、スイッチをオフにすること(回路を切断すること)を「回路を開く(open)」とそれぞれ呼ぶ慣習があります。慣れていないと逆に思えがちなのでご注意ください。
余談ですが、電源スイッチに使われているや
という表示は、前者が閉回路、後者が開回路をそれぞれ表しており、国際規格であるIEC(国際電気標準会議)の60417で定められています。これらの表示がデジタルの1と0を表しているというのは俗説またはアナロジーです。
コード自身の観点では、サーキットブレーカーは保護したいメソッドをラップしてエラー数をカウントするオブジェクトです。エラー数が特定のエラーしきい値(失敗しきい値とも)に達すると、サーキットブレーカーが作動して🔴 開回路(Opened)に移行します。これにより、以後のメソッド呼び出しは外部サービスを呼び出す「前に」エラーを返すようになるため、リソース消費を大幅に削減できます。
ウィンドウサイズ(Window Size)は、直近のエラー数をトラッキングするのに使われる時間枠です。たとえば、アプリ起動時以後の全エラー数ではなく、直近60秒以内のエラー数だけをカウントしたい場合などに使います。
その後、一定時間(通常はクールオフ時間(Cool-Off Time)と呼ばれます)を経過すると、サーキットブレーカーは復元ステートに移行します。続いて外部サービスが利用可能な状態になったかどうかをチェックし、利用可能であれば🟢閉回路(Closed)ステートに移行します。これで、メソッド呼び出しと外部サービスへのアクセスが再び可能になります。
サーキットブレーカーのしくみを解説するため、シンプルなサーキットブレーカーを書いてみましょう。
# 🔴 開回路(Opened)と🟢 閉回路(Closed)だけを実装したシンプルなサーキットブレーカー
require 'net/http'
class CircuitBreaker
class CircuitBreakerOpened < StandardError; end
def initialize(error_threshold:, cool_off_time:)
# サーキットブレーカー作動前までに許容するエラー数
@error_threshold = error_threshold
# 外部サービス回復のチェック作業までに待つ時間
@cool_off_time = cool_off_time
# エラー数の初期値は0
@errors_count = 0
# サーキットブレーカーの初期ステートは`Closed`
@state = :closed
end
def run(&block)
# クールオフ時間が過ぎていたらサーキットブレーカーをリセットする【原文エラー修正】
if (@state == :opened) && (Time.now > @transition_time + @cool_off_time)
@errors_count = 0
@state = :closed
end
# リソース消費を回避するためにサーキットブレーカーが開回路になっていたらこちら側の接続を終了
raise CircuitBreakerOpened if @state == :opened
# ここで実際の接続が行われる
block.call
rescue StandardError => error
# エラー時にはステートを変更せずにスキップする
raise if error.is_a? CircuitBreakerOpened
# エラー数を1増やす
@errors_count += 1
# エラー数がサーキットブレーカー作動しきい値に達しているかチェック
if @errors_count >= @error_threshold
@transition_time = Time.now
# サーキットブレーカーを作動
@state = :opened
end
raise
end
end
url = URI('https://www.google.com')
# ここではネットワークエラー3件までを許容し、超えたらサーキットブレーカーを10秒間作動させる
circuit_breaker = CircuitBreaker.new(error_threshold: 3, cool_off_time: 10)
circuit_breaker.run { Net::HTTP.get(url) }
🔗 「🟡半開回路」ステートについて
ソフトウェア版のサーキットブレーカーでは、サービスが復旧したかどうかをテストするために中間の「🟡半開回路(Half-Opened)」ステートも必要です。クールオフ時間を過ぎたら、サーキットブレーカーは外部サービスが生きているかどうかを検査するために限定的なトラフィックのみを許可します。
- リクエストが成功した場合: サーキットブレーカーはリセットされて🟢 閉回路(Closed)になる
- リクエストがまだ失敗する場合: サーキットブレーカーは🔴 開回路(Opened)を返す
復旧フェーズ用にこの🟡半開回路ステートが用意されていることで、まだ不安定な外部サービスに対してトラフィックを無駄に全面復旧させることを防止できます。
# 🔴 開回路(Opened)、🟢 閉回路(Closed)、🟡半開回路(Half-Opened)を実装したシンプルなサーキットブレーカー
require 'net/http'
class CircuitBreaker
class CircuitBreakerOpened < StandardError; end
def initialize(error_threshold:, cool_off_time:)
# サーキットブレーカー作動前までに許容するエラー数
@error_threshold = error_threshold
# 外部サービス回復のチェック作業までに待つ時間
@cool_off_time = cool_off_time
# エラー数の初期値は0
@errors_count = 0
# サーキットブレーカーの初期ステートは`Closed`
@state = :closed
end
def run(&block)
# クールオフ時間を過ぎたらステートを🟡半開回路(Half-Opened)に切り替える
if (@state == :opened) && (Time.now > @transition_time + @cool_off_time)
@state = :half_opened
end
# リソース消費を回避するためにサーキットブレーカーが開回路になっていたらこちら側の接続を終了
raise CircuitBreakerOpened if @state == :opened
# ここで実際の接続が行われる
call_result = block.call
# リクエストが成功したらステートを🟢 閉回路(Closed)に切り替える
if @state == :half_opened
@state = :closed
@errors_count = 0
end
# 保護対象のコードの結果を返す
call_result
rescue StandardError => error
# エラー時にはステートを変更せずにスキップする
raise if error.is_a? CircuitBreakerOpened
# エラー数を1増やす
@errors_count += 1
# エラー数がサーキットブレーカー作動しきい値に達しているかチェック
if @errors_count >= @error_threshold
@transition_time = Time.now
# サーキットブレーカーを作動
@state = :opened
end
raise
end
end
url = URI('https://www.google.com')
# ここではネットワークエラー3件までを許容し、超えたらサーキットブレーカーを10秒間作動させる
circuit_breaker = CircuitBreaker.new(error_threshold: 3, cool_off_time: 10)
circuit_breaker.run { Net::HTTP.get(url) }
🔗 メモ: サーキットブレーカーはネットワーク呼び出し以外でも使える
ここまでの例ではネットワーク呼び出しの保護に重点を置いてきました。これはサーキットブレーカーの一般的な用途ですが、以下のように、不安定な操作やリソース消費の大きい操作をラップするのにも使えます。
# ネットワークと無関係なサーキットブレーカーの用法
circuit_breaker = CircuitBreaker.new(error_threshold: 5, cool_off_time: 3)
circuit_breaker.run { unstable_method_call }
ロジックが予想不可能な場合や、高負荷時にエラーになりやすい場合は、こうすることでシステムの他の部分を安定させられます。
🔗 Stoplight: シンプルさを追求したサーキットブレーカーgem
それでは、いよいよ本記事冒頭で予告していたソリューションについてお話いたします。Martin Fowloerが有名なサーキットブレーカー記事を公開してから、世界中のエンジニアが独自のサーキットブレーカーを実装し始めました。Rubyの世界で人気の高いソリューションの1つが、このStoplightです。
Stoplightはパフォーマンスとシンプルさを重視しており、エンジニアがサーキットブレーカーの設定に費やす時間を節約して、その分の時間をビジネスロジックの構築に回せるようになります。
StoplightのGUI管理パネルは、サーキットブレーカーをリアルタイムで監視・管理できる強力な制御インターフェイスです。Stoplightは、Railsに組み込むことも、スタンドアロンサービスとしてデプロイすることも可能で、どちらの場合もシステム内のあらゆるサーキットブレーカーをGUI上で完全に表示して、手動で設定を上書きできます。
ここからは、プロジェクトでStoplightを実装する方法について解説します。立ち上げ直後のスタートアップ企業や、自分たちのユースケースに合うと思える方はぜひお読みください。
Stoplightのセットアップは簡単です。まずは、GemfileにStoplightを追加します。
bundle add stoplight
続いて以下のインストールコマンドを実行して、Stoplightの設定ファイルが現在のRedis構成で動作するようにします。
rake stoplight:install
# config/initializers/stoplight.rb
require 'redis'
Stoplight.configure do |config|
# Redis.newを自分たちのRedis構成に変更する
config.data_store = Stoplight::DataStore::Redis.new(Redis.new)
end
以上で、以下のような形でネットワークリクエストを送信可能になります。
require 'net/http'
require 'stoplight'
light = Stoplight('Highway to Mars')
light.run do
Net::HTTP.get(URI('https://evilmartians-mars-base.com/'))
end
🔗 Stoplightでは「開閉」を信号灯の「点滅」に置き換えてわかりやすくした
伝統的なサーキットブレーカーでは、上述のようにステータスを電気回路の用語になぞらえた「🔴 開回路(Opened)」「🟢 閉回路(Closed)」「🟡半開回路(Half-Opened)」で表しています。これらの用語は直感に反することも少なくなく、混乱のもとになりがちです。たとえば、🟢 閉回路(Closed)は正常に動作中であることを表すのに、🔴 開回路(Opened)は完全に故障していることを表します。その延長で、🟡半開回路(Half-Opened)はリカバリーモードというより「バグ発生中」のように思えてしまいます。
そこで、Stoplightでは信号灯(light)になぞらえる形で抽象化を改善しました。
Stoplightでは、サーキットブレーカーパターンを信号灯(light)の点滅に置き換えて再設計し、そこに「信号色(color)」という誰にでもわかりやすいステータスを導入しました。
そのおかげで、🟡半開回路(Half-Opened)のようなあやふやな用語と取っ組み合いすることなく、ルーティングを直感的に監視して、状況を信号灯のように明確に伝え、カスケード障害を防ぎます。
個別のlight
(信号灯)は特定の交通「ルート(route)」を管理します(ここは地球から火星への飛行ルートで説明したいところですね)。
そして、進んでもよいかどうかをcolor
で表します。
🟢青信号(Green): 従来の🟢 閉回路(Closed)に相当し、すべてが正常に動作中であることを表す。Stoplightは通常通りの通行を許可し、全システムが動作中。
🔴 赤信号(Red): 従来の🔴 開回路(Opened)に相当し、リクエストの送信先で現在障害が発生中のため、Stloplightが外部への全リクエストをブロックすることで、リソースの浪費やシステムの過負荷を防ぐ。
🟡黄信号(Yellow): 従来の🟡半開回路(Half-Opened)に相当し、Stoplightが慎重に様子を見ながらルートを再開中であることを表す。これにより、外部サービスが復旧したかどうかをテストするために数件のリクエストの通行が許される。
- 接続テストがパスすると、
light
は🟢青信号(Green)に変わる - 接続テストが失敗すると、
light
は🔴赤信号(Red)に戻る
🔗 信号灯には適切な名前を付けよう
Stoplightでは、信号灯ごとに外部へのルートを制御するシステムを表すようになっているので、個別のルートに一意の名前を付ける必要があります。信号灯の名前は、エラーのトラッキング方法やトラフィックの管理方法1を決定するうえで重要です。
たとえば、2つの信号灯(light
)で同じ名前が共有されていると、エラー履歴やステートも共有されることになります。その場合、一方のルートで障害が発生すると、その名前に関連するすべてのトラフィックは、行き先が異なっていても障害とみなされてしまいます。
require 'net/http'
require 'stoplight'
light = Stoplight('Earth to Mars Mission corridor')
light.run do
Net::HTTP.get(URI('https://evilmartians-mars-base.com/'))
end
light = Stoplight('Earth to Mars Mission corridor') # 名前が同じだとエラートラッキングも共有される
light.run do
Net::HTTP.get(URI('https://evilmartians-moon-base.com/'))
end
上の例では、2つのルートに「Earth to Mars Mission corridor(地球→火星ミッション回廊)」という名前が付いているため、一方の信号灯が赤に変わると、サービスが異なっているにもかかわらず、どちらのルートも地上で足止めを食うことになります。
🔗 Stoplightのベストプラクティスとデフォルト設定
API呼び出しごとに新しい信号灯を設置する必要はありません(宇宙ネタでの説明を続行すると、実際の宇宙旅行サービスが、打ち上げごとに発射台を建設したりしないのと同じです)。
その代わり、信号灯のグループ化は、HTTPメソッド単位や関数単位ではなく、サービス単位で行うようにしてください。
たとえば、外部の支払いサービスを利用している場合、そのサービスへのリクエストにはすべて同じ名前を付けます(リクエストの種類がGET
やPOST
やDELETE
であっても関係ありません)。この信号灯で管理すべきなのは、送信される個別のコマンドではありません。そのルート全体を管理すべきです。
信号灯に名前を付けて適切なルートに設置すると、ネットワークリクエストが承認されて送信準備が整います。ありがたいことに、Stoplightにはすぐ使えるデフォルト設定が用意されているので、複雑な設定は不要です!
Stoplight gemをインストールしてRedisに接続するだけで、初日から運用開始できるよう調整済みの交通規制システムを装備できます。
- クールオフ時間: 60秒
- 信号灯が🔴 赤(Red)に変わると、トラフィックが60秒間ブロックされてから復帰モード🟡に移行する。この一時的な交通規制期間を置くことでシステムが安定し、テスト送信を試行する期間を確保できるようになる。
- エラーしきい値: 3回連続の失敗
- 3回連続で送信に失敗すると、信号灯が青🟢から赤🔴に変わる。このしきい値は、誤検出を避けて実際の問題に対応するのに十分。
- ウィンドウサイズ: 上限なし(
nil
) - デフォルトのStoplightは、起動後(少なくとも信号灯の作成後)のエラー数をすべてカウントする。このウィンドウ値を小さくしたい場合は後でカスタマイズ可能。
- トラッキングするエラー種別: すべての
StandardError
- Stoplightは、
Errno::ECONNREFUSED
などを含むRubyの標準エラーをすべて監視する。これにより、通信の切断をすぐに検出可能になる。
🔗 Stoplightをカスタマイズする
Stoplightのデフォルト設定はほとんどの用途について適切に調整済みですが、場合によっては微調整が必要になることもあります。
よくあるシナリオの1つに、エラートラッキングに関係ないエラー種別を除外したい場合があります。たとえば、データベースレコードが存在しない場合(ActiveRecord::RecordNotFound
が発生)がミッションの正常な振る舞いであれば、システム全体のロックダウンをトリガーする必要はありません。
それでは、Stoplightで使える設定のいくつかを見てみましょう。
🔗 1: 制御システムをグローバルに設定する
Stoplightをグローバルレベルで設定することで、すべての信号灯の振る舞いを調整できるようになります。これはイニシャライザ(config/initializers/stoplight.rb
)で行うのが普通です。
# config/initializers/stoplight.rb
require 'redis'
Stoplight.configure do |config|
config.data_store = Stoplight::DataStore::Redis.new(Redis.new)
config.skipped_errors = [ActiveRecord::RecordNotFound]
end
🔗 2: 個別の信号灯を調整する
特定のルートでのみ特殊なルールが必要な場合(火星行貨物レーンでは月面ミッションと異なる方法で障害処理が必要になるなど)、以下のように信号灯を個別に調整できます。
light = Stoplight(
"Mars freight lane",
skipped_errors: [ActiveRecord::RecordNotFound]
)
light.run do
Net::HTTP.get(URI('https://evilmartians-mars-base.com/'))
end
これで、システム全体に影響を与えることなくローカル例外を柔軟に定義可能になります。
🔗 3: エラー検出方法を調整する
どのエラーをトラッキングするかの指定は、信号灯の設定の一部にすぎません。個別の信号灯のエラー検出方法と対応方法を調整することで、個別のルートがどのエラーに応答するかを完全に制御することも可能です。
これらのミッション固有のパラメータは、不安定な状況で個別の信号灯の対応をどの程度慎重に、もしくは積極的に応答するかを定義できます。以下の項目は、Stoplightが提供する交通規制システムの項で既にご覧いただいたかと思いますが、エラー検出用の項目について改めて説明しておきます。
threshold
- 何回連続で失敗したら信号灯を赤🔴に切り替えるかを指定する。値が小さいほど、信号灯が早期に問題に応答する。
cool_off_time
- 障害発生後、黄信号🟡に切り替えて復旧を試みる前に赤信号🔴を維持する期間(秒)。
window_size
- 失敗をカウントする期間。これを設定することで、無関係な古いエラーによって振る舞いが影響を受けることを防げる。
これらの値は、以下のようにグローバルに設定することですべての信号灯に適用することも可能です。
# config/initializers/stoplight.rb
Stoplight.configure do |config|
config.cool_off_time = 30
config.threshold = 10
config.window_size = 60
end
あるいは、以下のように信号灯単位で定義して、ルートごとのニーズに合わせて振る舞いを調整することも可能です。
fast_light = Stoplight(
"Highway to Mars",
threshold: 10,
window_size: 60
)
slow_light = Stoplight(
"Country Road to Mars",
threshold: 5,
)
fast_light.run do
Net::HTTP.get(URI('https://evilmartians-mars-base.com/'))
end
slow_light.run do
Net::HTTP.get(URI('https://evilmartians-jupiter-base.com/'))
end
以上の制御によって、信号灯ごとにリスク許容度を管理できるようになります。これにより、ボリュームが大きくリスクの高いサービスでは迅速に対応し、低速または障害許容度の高いサービスではシャットダウン前にエラーを蓄積できます。
🔗 Stoplightの管理パネル
Stoplightの管理パネルは、あらゆる恒星間航行ミッションに必要な全システム、ルート、発射台の様子をリアルタイムで表示するコマンドデッキを提供します。
この管理パネルは、Railsアプリで以下のように直接マウントできます。
Rails.application.routes.draw do
# ...
mount Stoplight::Admin => '/stoplights'
# ...
end
または、Stoplightの公式Dockerイメージを用いてスタンドアロンの制御ステーションをデプロイすることも可能です。ただし、利用している信号灯と同じRedisインスタンスに接続すること。
Stoplight 5以降は、すべての信号灯を完全にGUIパネルで表示可能になり、最新の強力なミッションダッシュボードであらゆる信号灯を手動で制御できるミッションダッシュボードをシステムに装備可能になりました。
Stoplightの管理パネル -- 信号灯のミッション制御ダッシュボード
最後に、このダッシュボードの機能をざっと見ていきましょう。
🔗 1: 信号灯のライブステータス表示
ご想像の通り、システム上のすべての信号灯を監視して、現在のステータスを🟢、🟡、🔴の色で即座に確認できます。どのルートが正常で、どのルートが復旧中で、どのルートがロックダウン中をひと目で把握できます。
🔗 2: 手動での設定上書き
管理パネルでは、任意の信号灯を特定のステートにロックする形で、自動の振る舞いをバイパスできます。たとえば、テスト中や、開発中にすべてのトラフィックを一時的に有効にしたい場合は、青信号🟢にロックできます。逆に、赤信号🔴にロックすることで、しきい値に達する前であってもルートを手動で一時停止できます。
この機能は以下の場合に便利です。
- E2Eテスト環境
- ローカルデバッグで設定を上書きしたい場合
- 実際のインシデント対応
- システムの一部を安全にシャットダウンする
🔗 まとめ
本記事は朝の4時に電話で叩き起こされる場面から始まりました。適切な対策を講じていなければ、こうした障害が発生すると、サービス立ち上げが台無しになったり、売上が落ちたり、ひとときも無駄にしたくない貴重なエンジニアリング時間を浪費したりする可能性があります。
Stoplightは、「緊急招集」の瞬間を、まるで何の問題も起きていないかのような時間に変えてくれます。リトライが少々失敗してサーキットブレーカーが落ちても、システムの他の部分は問題修正中に正常な状態を維持できます。しかも、このStoplightは立ち上げ間もないスタートアップ企業でも手軽に導入できるのです。
障害の発生は避けられませんが、ダウンタイムを避ける方法はあります。行く手の空を明るく照らして、自信を持ってサービスを立ち上げられるようにしましょう!
関連記事
- "how traffic is managed"は、「道路交通の規制方法」にもかかっています。 ↩
概要
元サイトの許諾を得て翻訳・公開いたします。
日本語タイトルや見出しは内容に即したものにしました。